做毕业设计选CRM系统的人,十个里有八个会搜到一堆"XX管理系统源码",但下载下来能跑起来的少,能跑起来还能写进论文、能通过答辩的更少。我当年做这套贸易行业CRM系统的时候,主选型就是SpringBoot + Vue + MySQL,前后端分离,最后源码、数据库、论文、部署文档一体交付。现在回头看,这套系统之所以没烂尾,核心不在于代码多炫,而在于每个设计点都能说清楚"为什么这么干"。这篇就围绕这套系统,把从数据库建模、后端权限、前端联调到部署上线的完整链路讲透,全是实操里趟过来的经验。
1. 技术栈选型:为什么是SpringBoot+Vue+MySQL这个组合,而不是别的
1.1 前后端分离架构在毕业设计里的天然优势
很多同学在做选题时会纠结:到底是JSP+Servlet老一套,还是直接用若依之类的脚手架改一改?我当时的判断很简单——这套系统是要给贸易公司的销售团队用的,不是给自己交差用的,那就得按企业级标准来做。SpringBoot负责后端接口,Vue负责页面交互,MySQL存业务数据,这三者是目前中小型企业内部系统最主流的组合理。
前后端分离的好处体现在开发效率上:后端定义好接口文档,前端可以并行开发。我在实际操作中把接口路径全部按/api/customer、/api/order、/api/contact这种资源化方式命名,前端用axios封装统一请求入口。Vue这边用Vite初始化工程,相比Webpack那套,冷启动速度和热更新体验都好得多。这对毕设党来说很关键,因为调试时间越短,留给你写论文的时间越多。
1.2 为什么不用更"高级"的框架和中间件组合
我在做技术选型时主动砍掉了几样东西:一是微服务,二是Redis缓存,三是消息队列。不是说这些东西没用,而是贸易CRM这个场景根本不需要。一个中小型贸易公司,用户量可能就几十人,数据量最多几十万条,单体架构完全能扛住。硬上微服务,反而会让毕业设计变成一堆你驾驭不了的组件的拼凑。
但有两个组件我必须用上:MyBatis-Plus和Spring Security。MyBatis-Plus解决的是CRUD代码冗余问题,内置的分页插件和字段自动填充能大幅减少工作量;Spring Security负责登录认证和权限控制,这是"CRM系统"能不能称为"系统"的分水岭。贸易行业还有个特点——销售手里有客户资料、价格信息,权限必须分清楚,否则系统上线就是事故。
1.3 这套技术栈对找工作/考研复试的实际价值
选技术栈还有一个私心:SpringBoot和Vue在招聘市场是高频技能点。做完这套系统,IPO、AOP、自动配置原理、常见注解,这些都是后端面试的必问题。Vue的响应式原理、组件通信方式、路由守卫,前端面试也会问。MySQL的事务隔离级别、索引优化、慢查询排查,数据库面试更是跑不掉。一套毕设把这三块全部覆盖,面试时讲"我做过"和讲"我了解",说服力完全不一样。
2. 先从数据库设计说起:贸易行业CRM的7张核心表和关键字段
2.1 贸易业务场景的特殊性如何体现在表结构上
做贸易CRM系统,最忌讳的就是拿网上通用的"客户管理表"直接抄。贸易公司的客户有鲜明的业务特征:客户可能是海外买家,也可能是国内供应商;交易涉及币种、汇率、贸易术语(FOB、CIF、EXW这些);业务员跟进一笔订单往往要跨多个时区。所以我在设计表时重点考虑了这些字段:
客户主表(t_customer),除了公司名称、联系人、电话这些基础信息外,增加了customer_type(区分进口商/出口商/代理商)、country(国家/地区)、currency_type(默认结算币种)、payment_terms(付款条件,如T/T、L/C)、credit_line(授信额度)这几个贸易专属字段。
订单表(t_order),必填字段包括order_no(订单编号,用日期+流水号生成)、trade_term(贸易术语)、currency(成交币种)、exchange_rate(成交时汇率)、total_amount(按币种存储的原币金额)、total_amount_cny(换算成本币金额的冗余字段)。
这样设计的好处,我论文里花了整整一节来讲:按币种存储金额可以保留业务原貌,增加本币换算字段方便财务报表统计。在答辩时,评委问"为什么不做三张表关联换算",你能答出冗余存储的使用场景,这就是加分项。
2.2 从客户到订单:一对多关系的落地方式
贸易行业的业务链路通常是:销售录入客户 → 建立联系人 → 创建报价单 → 成交后转订单 → 后续做跟单记录。所以在数据库里我设计了一套完整的表关系:
- 客户表与联系人表:一对多,一个客户下挂多个联系人,联系人的
is_primary字段标记是否为默认联系人。 - 客户表与报价单表:一对多,报价单里有
version字段记录第几次修订报价。 - 报价单与订单表:一对一转化关系,报价单确认后可以一键生成订单草稿,
quote_id在订单表里做外键关联。 - 订单表与跟单记录表:一对多,每次与客户的沟通、发货、回款都追加一条记录。
这个结构在E-R图上非常清晰。我当时画E-R图用的工具是draw.io,导出矢量图放进论文里,答辩PPT里也直接用了这张图。别小看这种图的价值,论文评审老师拿到一篇带规范E-R图的论文和一篇纯文字表结构的论文,印象分完全是两个档次。
2.3 数据字典和状态字段的规范化处理
做数据库设计最容易忽略的是"状态字段该用什么类型"。我见过有人用status直接存"未开始/进行中/已完成"这种中文,查询没问题,但一旦业务调整、状态增多,程序里到处都是硬编码判断,维护极难。
我用的方案是:状态字段全部存数字(0/1/2...),程序里定义枚举类。比如订单状态:0-草稿,1-已确认,2-履约中,3-已完成,4-已取消。代码里写OrderStatusEnum.CONFIRMED.getValue()去设置和判断。数据库层面再配合TINYINT类型控制长度,索引空间小,查询也快。
另外,业务表基本都带create_by、create_time、update_by、update_time、deleted(逻辑删除)这五个字段。我用MyBatis-Plus的MetaObjectHandler做了字段自动填充,插入时自动填create_time和create_by,更新时自动填update_time,这样业务代码里就不用反复写这五行set了。
2.4 初始化SQL脚本里的数据分级:别一上来给全世界密码
一套合格的毕业设计源码,附带的SQL脚本应该分级分类。我在交付文档里放了三个脚本:schema.sql(建库建表)、data_dict.sql(数据字典表和数据初始化)、data_demo.sql(演示数据,含测试账号和模拟业务数据)。这里有个很实用的技巧:演示数据里的密码不能用明文,我用BCrypt加密后存入,并在部署文档里注明测试账号密码都是admin123。这样既保证了演示效果,又不会让使用者误把弱口令当成设计的一部分。
3. 后端落地:登录鉴权、通用CRUD和核心业务接口的完整实现思路
3.1 认证方案:自己写JWT拦截器还是用Spring Security
这是后端开发第一个分岔路口。我的做法是用Spring Security框架做底层过滤器链,配合JWT做无状态Token认证,但把大量配置简化掉了。为什么不自己写拦截器?因为手工实现认证逻辑有太多边角问题:密码加密方式(BCrypt)、Token过期刷新、接口白名单、匿名访问控制、CSRF防护……这些代码自己从头写,一周都未必能写完,而且容易出漏洞。Spring Security把这些处理成了一条清晰的过滤器链,你只需要定制自己的SecurityFilterChain里的规则就行。
核心配置逻辑大致是:
@Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/login", "/doc.html", "/webjars/**").permitAll() .requestMatchers("/api/admin/**").hasRole("ADMIN") .anyRequest().authenticated() ) .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint) .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); }JWT过滤器的工作逻辑是:从请求头Authorization里取Token → 解析出用户名 → 查数据库得到用户信息和权限列表 → 封装成Authentication对象放进SecurityContextHolder。这样Controller层只需要通过@AuthenticationPrincipal就能拿到当前登录用户,非常方便。
3.2 基于RBAC的权限控制:用户、角色、菜单、接口四个维度
贸易公司里角色区分很明确:老板要看全量数据,销售经理要看自己团队的数据,销售只能看自己的客户和订单,财务只能看回款和发票。我把权限控制分成三层:
第一层是接口路径权限,通过@PreAuthorize("hasAuthority('customer:add')")等注解控制。第二层是菜单显示权限,前端根据当前用户拥有的权限标识动态生成侧边栏菜单。第三层是数据范围权限,这是最容易被忽略的:同一张t_customer表,普通销售执行查询时只能返回create_by = 当前用户ID的数据;销售经理需要额外查下属的数据,我用MyBatis-Plus的DataPermissionInterceptor做数据权限拦截,自动拼接子查询条件。
在数据库层面,表结构是:t_user(用户)、t_role(角色)、t_user_role(用户角色关联)、t_menu(菜单/权限点)、t_role_menu(角色菜单关联)。这套RBAC模型是CRM类系统最标准的权限模型,论文里单开一节写完全够分量。
3.3 核心业务接口的设计模式:报价转订单的完整链路
CRM系统里最有业务含金量的功能就是"报价转订单",这个流程设计好了,论文的核心创新点就出来了。我实现的逻辑是:
- 销售在报价单页面点"转订单",前端携带
quoteId调用后端接口。 - 后端校验:报价单必须处于"已确认"状态、未过期(
valid_until晚于当天)、操作人拥有order:add权限。 - 通过后,系统从报价单表读取商品明细(报价单头+报价单明细两张表),拷贝生成订单头和订单明细草稿,把
quote_id写入订单表,同时把报价单状态更新为"已转订单"。 - 返回生成的
orderId,前端跳转到订单编辑页,销售补充实际成交信息后提交。
这整个过程我建议用@Transactional包裹,防止出现报价单已转订但订单生成失败的数据不一致问题。实际上我在开发中就踩过这个坑:第一次没加事务,测试时连续点了两次"转订单",结果生成了两个订单草稿。后来在Service层加了事务,并且在接口入口加了一个"重复提交校验"(Redis分布式锁,但为了简化学士论文,我改成数据库唯一索引:quote_id在t_order表里唯一,重复提交直接报唯一键冲突),问题就彻底解决了。
3.4 通用响应体和统一异常处理:告别堆if-else的接口
后端接口返回格式,我用一个统一结果类Result<T>来包装:
{ "code": 200, "message": "success", "data": { ... } }所有接口无论成功失败都返回这个结构,前端的axios拦截器统一处理。如果后端某个接口直接抛NullPointerException,全局异常处理器@RestControllerAdvice会捕获并返回code:500,前端Toast弹窗提示"系统异常,请联系管理员"。这样前端永远只需要关注code和data两个字段,逻辑简单很多。
全局异常处理里我细分了四种场景:业务异常(BusinessException,如"订单已取消,无法修改")、参数校验异常(@Valid触发)、认证授权异常(Token无效/无权限)、系统未知异常。这在异常信息上做到"可控、可解释",答辩时如果评委问"系统安全性怎么保证的",你就可以把异常处理链路拿出来讲。
4. 前端Vue工程:从登录页到动态路由,联调时最磨人的几个问题
4.1 工程初始化和目录结构:Vite + Vue3 + Element Plus + Pinia
前端的技术选型我直接用了Vue3的组合式API,没用Vue2的Options API。Vite创建工程后,我把目录拆成了api(请求封装)、assets(静态资源)、components(通用组件)、router(路由)、store(状态管理)、utils(工具函数)、views(页面)七大类。
状态管理用的Pinia。登录后把Token存在localStorage里,同时把用户信息和权限标识列表存到Pinia的userStore里。页面刷新时,Pinia数据会丢失,所以我在App.vue的onMounted里调了一次getUserInfo接口,从Token里找回用户信息,重新填充Pinia。这个"刷新后恢复登录态"的逻辑,就是毕设演示时最容易露怯的地方,提前处理好,演示时关掉页面重开都不会掉登录。
4.2 封装axios请求和响应拦截器,让所有接口都自动携带Token
axios封装这一步值得细写,因为它直接影响整个前端的开发体验。我在utils/request.js里做了一层封装,所有用到的HTTP方法都经由这个封装:
const service = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || '/api', timeout: 15000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code === 200) { return res.data } if (res.code === 401) { localStorage.removeItem('token') router.push('/login') } ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) }, error => { ElMessage.error(error.message || '网络异常,请检查后端服务') return Promise.reject(error) } )统一拦截的好处是每个业务页面调接口时只需要写request.get('/customer/list', { params }),完全不用关心Token和错误提示。这套封装让新增一个页面的成本降到了最低,我在实际开发中一天能写完两个业务页面的前后端联调。
4.3 动态路由和菜单权限:不同角色登录后看到的界面完全不同
动态路由是前端权限控制的核心。我初始化路由表只注册三个公共路由:/login、/404、/403。用户登录成功后,前端根据后端返回的权限标识(如customer:list、order:add)从前端预先定义好的完整路由映射表(constantRoutes)中过滤出当前用户可以访问的路由,再用router.addRoute()动态注册。这样每个用户登录后访问的菜单和页面URL都不一样。
这里有个很实用的经验:过滤逻辑一定在前端做还是后端做?我的方案是前端做菜单过滤,后端做接口权限校验。前端只管"显示什么",后端管理"能不能操作"。因为如果前端把能访问的接口都返回给用户,那就是把安全交到了客户端手里,这是初学者常犯的错误。正确做法是后端接口有独立的权限校验,前端动态路由只是提升体验、避免无关按钮出现,真正的安全防线永远在后端。
4.4 表格分页搜索、数据导出和ECharts看板:论文里最能出彩的页面
CRM系统核心页面一定包含客户列表和订单列表,这两个列表我统一实现了四个能力:条件搜索(关键字搜索+下拉筛选)、分页(后端分页,前端传递pageNum和pageSize)、排序(按创建时间倒序)、导出(后端用EasyExcel导出Excel)。导出功能别在论文里简单写"实现了导出",要写清楚实现方案:前端点击导出按钮,后端根据当前的搜索条件重新查询符合条件的数据,生成Excel后通过HTTP流式返回。这个方案能保证导出的数据和当前列表页筛选结果一致,而不是把全表数据导出。
数据看板是给管理层看的,我用了ECharts画了四个图表:客户地区分布环形图、月度成交金额折线图、销售业绩排名横向柱状图、订单状态占比饼图。数据来源是后端封装好的统计接口,SQL里用GROUP BY和聚合函数。ECharts这块在论文里的"系统实现"部分放两张截图,效果比一堆文字描述强得多。
4.5 联调阶段最坑的三个问题:跨域、404、路由回退
前后端联调时我遇到最多的问题就三个:
第一个是跨域。后端跑了8080端口,前端跑了5173端口。开发模式下,我在Vite的vite.config.js里配置了代理,把/api开头的请求转发到http://localhost:8080,这样浏览器看到的请求是同源的,跨域问题在开发环境就不存在了。部署时后端接口通过Nginx的location /api反向代理到内网服务的8080端口,生产环境也就没有跨域问题。前后端分离项目里,这套代理配置是标配,也是答辩时评委大概率会问的点。
第二个是刷新404。Vue是单页应用,路由用的是history模式,刷新页面时浏览器按路径请求服务器资源,如果Nginx没做回退配置,就会返回404。解决办法是在Nginx配置里加上:
location / { try_files $uri $uri/ /index.html; }第三个是路由回退时页面白屏。这是因为菜单里面写死了当前路由,但动态路由还没加载完就开始渲染。解决方法是加一个全局前置守卫,在router.beforeEach里判断当前用户是否存在,如果存在但没有动态路由,就先把路由加载完再放行next({...to, replace: true})。
5. 部署上线:源码能跑起来只是第一步,部署文档里必须交代清楚的五件事
5.1 本地部署的三要素检查:JDK版本、Node环境、MySQL版本
很多同学在交付毕业设计时会写一句"本地运行请参考部署文档",但部署文档写得太简略,老师拿到手根本跑不起来。我自己写部署文档时,第一条就明确列出环境要求:
- JDK:1.8及以上(我用的是JDK8,因为SpringBoot 2.x对JDK8支持最稳)
- Maven:3.6及以上
- Node.js:16及以上(Vite 4要求Node 16+)
- MySQL:5.7或8.0(我用的是8.0,注意MySQL 8.0的默认认证插件是caching_sha2_password,驱动要用
mysql-connector-java8.0+)
本地部署最省心的是用IDEA直接打开后端工程,等Maven把依赖拉完(第一次会比较久),然后修改application.yml里的数据库连接信息和Redis连接信息,启动CrmApplication类,前端在npm install之后运行npm run dev,浏览器打开http://localhost:5173就能看到登录页。
5.2 通过Maven打jar包,文件上传路径和日志路径的正确姿势
后端部署到服务器,最省事的方案是打成可执行jar包,用java -jar xxx.jar启动。但有几个配置必须提前想清楚:
一是文件上传路径。CRM系统里客户可能上传营业执照、合同附件,不能把文件存到jar包所在目录的/static下,因为jar包是只读的。我在application.yml里配置了一个动态上传路径:
file: upload-path: /data/crm/upload/代码里用一个FileConfig读取这个配置,拼接成绝对路径存储文件,同时在WebMvcConfig里加一个静态资源映射,把/upload/**映射到这个目录。这样jar包和文件存储完全解耦,升级系统时不会丢失历史附件。
二是日志配置。默认的SpringBoot日志只会输出到控制台,服务器重启后日志就没了。我在部署文档里建议使用logback-spring.xml做日志配置,按天滚动生成日志文件,保留30天。排查问题的时候,crm-info.log看业务日志,crm-error.log看异常堆栈,效率比翻控制台高得多。
5.3 前端打包后如何用Nginx托管,以及反代后端的完整配置
前端部署相对简单,npm run build之后会在dist目录生成静态文件,把整个dist目录上传到服务器的/data/crm/frontend/目录下,然后在Nginx站点配置里指向这个目录。
这里有一个非常关键的细节:Vite的base配置。默认情况下Vite构建出的资源路径是绝对路径/assets/xxx.js,如果你的项目部署在域名的根路径下没问题,但如果是放在子路径(如http://ip:8080/crm/)下,就必须在vite.config.js里设置base: '/crm/'。我在部署文档里默认让前端访问根路径,避免这个坑。
Nginx配置如下:
server { listen 80; server_name 你的服务器IP或域名; # 前端静态资源 location / { root /data/crm/frontend; index index.html; try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 文件上传访问 location /upload/ { alias /data/crm/upload/; } }5.4 MySQL初始化脚本导入的顺序问题和时区问题
部署文档里最容易忽略的一个步骤是数据库初始化。很多同学把SQL脚本一股脑塞给用户,但脚本里可能已经包含了建库命令,用户拿Navicat运行时如果没选目标库,就会报"Unknown database"错误。我交付文档里写得很清楚:先用CREATE DATABASE crm_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;建库,然后双击这个库再运行schema.sql,最后再按顺序运行data_dict.sql和data_demo.sql。
MySQL连接串上也有个大坑:时区问题。如果application.yml里写的是jdbc:mysql://localhost:3306/crm_system,启动项目后时间字段可能比本地时间少8小时。解决办法是连接串上明确指定时区:
url: jdbc:mysql://localhost:3306/crm_system?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai5.5 服务器部署的systemd服务配置:实现开机自启和进程守护
生产环境不建议用nohup java -jar这种裸奔方式管理后端进程。我一般在部署文档里附上systemd服务配置,把SpringBoot jar包注册成一个系统服务:
[Unit] Description=Crm System Server After=network.target mysql.service [Service] Type=simple User=root WorkingDirectory=/data/crm ExecStart=/usr/local/java/bin/java -jar /data/crm/crm-server.jar --spring.profiles.active=prod Restart=always RestartSec=10 [Install] WantedBy=multi-user.target配置完成后,systemctl daemon-reload,然后systemctl start crm启动,systemctl enable crm设置开机自启。这样做的好处是进程意外挂掉时systemd会自动拉起,服务器重启后服务也会自动运行。这段配置写进部署文档里,整篇文档的专业性立刻上一个台阶。
6. 论文写作和答辩演示:源码能跑通之后,怎么把工作量讲成亮点
6.1 论文结构怎么搭:从选题背景到系统测试的逻辑链条
写论文最容易犯的毛病是把系统代码照搬进文档,大段粘贴类名和方法名。正确的做法是围绕"业务需求→技术选型→系统设计→系统实现→系统测试"这条线展开。我当时论文的目录大致是:
- 绪论(背景、意义、国内外研究现状)
- 相关技术介绍(SpringBoot、Vue、MySQL、JWT)
- 系统需求分析(功能性需求、非功能性需求、可行性分析)
- 系统总体设计(系统架构图、功能模块图、数据库设计)
- 系统详细设计与实现(每个模块的时序图、核心代码、界面截图)
- 系统测试(功能测试用例表、性能测试结果)
功能模块图用Visio或draw.io画,数据库设计放E-R图和核心表结构,系统实现每个功能模块配1-2张截图+一段关键代码。测试章节要写具体的测试用例,比如"输入正确的账号密码→点击登录→跳转到首页",预期结果和实际结果对应起来。
6.2 答辩演示前必须过一遍的四条演示路径
答辩演示是最容易翻车的环节,我总结出四条必演的路径:
第一条是登录和权限演示:用管理员账号登录,展示功能菜单全貌;退出后用普通销售账号登录,展示菜单变少、只能看到自己的客户。
第二条是客户全流程演示:新增客户 → 新增联系人 → 创建报价单 → 报价转订单 → 提交订单 → 查看订单状态。
第三条是数据看板演示:展示首页ECharts图表,说明数据来源和SQL逻辑。
第四条是系统管理演示:新增一个角色、给角色分配菜单权限、创建一个新用户并绑定角色,然后用这个新用户登录验证权限是否生效。
这四条路径覆盖了系统80%的功能点,答辩时评委随机问任何一个环节你都能迅速演示出来。
6.3 评委常问的三个技术问题和参考回答
答辩时评委最喜欢问技术细节,我整理了三个高频问题:
问题一:"你这个系统安全性怎么保证?"回答思路:认证用的是JWT无状态Token,密码用BCrypt加密;权限控制用了RBAC模型,按角色分配菜单和接口权限;数据访问层做了数据权限隔离,普通用户只能操作自己创建的数据;SQL全部使用预编译,防SQL注入;前端提交的数据有参数校验。
问题二:"数据库为什么这么设计?"回答思路:先讲清楚业务背景,贸易公司数据有币种、贸易术语等特殊字段;再讲E-R图设计时如何分析实体关系;最后强调遵循了三大范式,部分字段的反范式存储(如冗余本币金额)是为了查询性能。这三步走完,评委基本就满意了。
问题三:"这个系统哪些地方你还能优化?"回答思路:可以提引入Redis做热点数据缓存,增加WebSocket做消息通知,引入工作流引擎(比如Flowable)优化审批流程,或者对接企业微信做客户提醒。这既展示了你的思考能力,也暴露了系统局限性的边界,比硬说"系统已经很完善了"好得多。
6.4 PPT展示的截图规范:背景统一、重点标注、连号编号
答辩PPT的截图有个小技巧:不要截大半个屏幕,而是只截核心功能区域,用红色框把重点操作区域圈出来。比如展示"报价转订单"功能时,截一张报价单列表页、一张订单创建成功后的提示信息,页面底部用文字注释说明流程逻辑。所有截图的尺寸尽量统一,窗口缩放比例调成一致,这样PPT会显得很整洁。
准备一个"查不到就准备"清单:登录页、客户列表、客户详情(含联系人)、报价单创建、报价转订单、订单列表、订单详情、打印订单、数据看板、用户管理、角色权限分配。截图时建议按模块连号命名(如l-login.png、customer-list.png),放进论文和PPT时引用路径清晰,不会出现插错图的情况。
7. 一些做完整套系统之后才想明白的事
整套系统从数据库建表到服务器部署,前前后后花了大概三周时间,白天写代码,晚上写论文,周末部署上线加录演示视频。做完之后最深的感受是:毕业设计真正考察的不是你会不会用某个框架,而是你有没有能力把一堆零散的技术组合成一个能解决实际问题的系统。
像权限控制这套逻辑,单独拎出Spring Security的用法,网上一搜一大把,但怎么结合贸易公司的角色分工去设计权限模型、怎么让前端菜单跟着后端权限变化、怎么把数据范围从"全部人"收敛到"仅本人",这些问题代码书里不会写,只有自己从需求出发一步步推演出来。
部署也是一样,java -jar能启动,但让它开机自启、崩溃自愈、日志可查、上传文件不丢,这些才是一个软件系统"可用"和"能用"的分界线。很多同学以为把源码压缩包发了就完事了,实际上部署文档里的Nginx配置、systemd服务脚本、环境变量说明,才是让老师能在自己电脑上把系统跑起来的关键。我后来还把这套系统稍作修改,把客户表格的字段调整成通用字段,又帮两个同学套了一版,说明核心架构的扩展性也经得起验证。
如果你也正在做类似题目的毕设,我给的最实在的建议就是:先把数据库表设计清楚,再动手写代码,写完业务模块后马上补权限和异常处理,最后再美化界面和处理部署细节。按照这个顺序走,每一步的产出都能对应到论文的某个章节,写论文的时候你就不会觉得是在硬凑字数了。遇到不会的功能不要慌,先去官方文档查,再去博客搜别人的踩坑记录,这两个动作能解决八成的问题。剩下的两成,就靠你自己一行一行调试了,而这个过程本身就比任何源码都值钱。