每年三四月份,找我聊毕业设计选题的人就明显多起来。如果你正在SpringBoot、Vue、Java、MySQL这几个词之间反复纠结,说实话,SpringBoot + Vue 前后端分离架构,配合 Java + MySQL 做一个城乡居民基本医疗信息管理系统管理平台,是一个很经典也很稳妥的选项。
这个选题为什么值得做?因为它的业务边界清晰,需求不是凭空编造的,而是贴近真实场景:居民信息维护、参保登记、缴费记录、报销审核、门诊住院记录查询,这些模块有明确的数据流和状态流转。对毕设和课设来说,既能体现完整的后端业务设计能力,又能展示前端交互与工程化水平,而且答辩时“为什么这样设计”特别容易讲清楚——因为每一个设计决策都可以对应到真实业务问题。
这篇文章我不打算只讲“这是个好项目”,而是把这类系统从需求拆解、数据库设计、后端核心模块到前端配合、部署落地的完整链路拆开,结合我自己做项目和带人踩坑的经验,把能直接用的思路和代码逻辑写出来。你可以把它当作一份“拿到源码之后怎么理解、怎么改动、怎么讲出深度”的参考。
1. 为什么是“城乡居民基本医疗信息管理系统”:选题价值与业务拆解
1.1 这个题目背后到底在做什么业务
很多同学拿到一个项目源码,第一反应是“把数据库导入,run 起来看看页面长什么样”。但如果你只是停留在这一步,答辩的时候很容易被问倒。所以我建议先想清楚一个问题:城乡居民基本医疗信息管理系统,它管的到底是什么?
“城乡居民基本医疗”这八个字指的不是某个单体系统,而是一套围绕居民参保和费用报销的持续业务流程。简单概括,核心生命周期是这样一条线:
- 居民信息建档:管理辖区内居民的姓名、身份证号、户籍地址、联系方式等基础档案。
- 参保登记与缴费记录:居民参保是有时间周期的,每年度需要登记、续保、缴费,系统要能记录每一年度的参保状态。
- 就医记录登记:居民在定点医疗机构发生门诊或住院行为后,相关记录需要录入系统,作为后续报销的依据。
- 报销申请与审核:居民提交医疗费用报销申请,业务人员审核材料,审批通过后计算报销金额并生成支付记录。
这套业务逻辑的妙处在于:它天然包含一对多关系(一个居民有多条参保记录、多条报销记录)、状态机概念(参保状态从正常到暂停/终止,报销申请从待审核到通过/驳回),还有金额计算逻辑(报销比例、报销金额)。这些全都在考察你的数据库设计和后端编码能力,正是毕设评分最看重的部分。
1.2 模块边界怎么划:三个角色三种视角
业务理解了,接下来是系统模块怎么拆。看源码的时候你会发现,几乎所有同类型的系统都绕不开“管理员 + 业务人员 + 居民”这三类角色。但真正好的项目不会把功能堆成一个扁平菜单,而是按角色的职责边界划分:
| 角色 | 核心职责 | 典型功能范围 |
|---|---|---|
| 系统管理员 | 基础数据与权限维护 | 用户管理、角色管理、菜单权限配置、人员信息管理 |
| 业务经办人员 | 日常业务办理 | 居民档案录入与修改、参保登记审核、缴费记录登记、报销审批 |
| 普通居民 | 查询与申请 | 查看个人信息、参保状态、缴费历史、提交报销申请、查看审核进度 |
我之前看过很多毕设项目,最大的问题在于权限等于摆设:所有角色进了系统看到的页面一模一样,居民也能去后台删别人的档案。这样的项目即使功能齐全,答辩时也经不起追问。反之,只要你在路由或者菜单层面做了角色过滤,哪怕逻辑再简单,也能体现出“设计意识”——这在评分标准里属于系统设计部分的加分项。
所以拿到这个主题的源码后,第一步不要急着读代码,先把系统的角色和功能树画出来,然后去代码里找“角色对应的功能入口在哪里被控制的”,这是你理解整个项目最快的方式。
2. 技术选型不是碰运气:SpringBoot + Vue + MySQL 这套组合的取舍
2.1 后端版本坑:SpringBoot 2.7 与 3.x 怎么选
现在网上的源码下载下来,依赖版本五花八门。如果你刚接触这套技术栈,一定会遇到搜索词里那个高频问题:“springboot版本太高”。这确实是个坑,因为 SpringBoot 3.x 相比 2.x 有破坏性升级,最核心的一点是:SpringBoot 3 强制要求 JDK 17+,而很多机构教学和毕设演示环境都还在 JDK 8。
做这个项目的选型逻辑应该是:
- 如果你的 JDK 是 1.8,那就用 SpringBoot 2.7.x,这是 2.x 的最后一个稳定版本,生命周期也持续到 2025 年后,社区资料最丰富。
- 如果你的环境是 JDK 17 及以上,可以直接用 SpringBoot 3.x,但要注意部分老教程里的依赖写法需要调整,比如
javax.*包名变成了jakarta.*。
我自己做演示的时候,默认都是用 JDK8 + SpringBoot 2.7.18,因为兼容性最稳,网上搜得到的报错和解决方案也最多。毕设阶段最大的敌人不是技术老旧,而是报错查到天亮也没人踩过同一条坑。
2.2 前端 Vue 与组件库的配套选择
前端部分的选型也一样。Vue 2 已经进入维护状态,Vue 3 是当前主流,但对于毕设源码来说,你经常会看到 Vue 2 + Element UI 的组合,原因是这俩人搭配最成熟,中文资料、案例数不胜数。
更合理的建议是:
- 如果你是从零开始写,推荐 Vue 3 + Vite + Element Plus,更现代,组件库维护也更积极。
- 如果你拿到的是现成源码,它是 Vue 2 + Element UI,除非你有把握系统迁移,否则不要强行升级。“vue安装及环境配置”“Vue入门”这些都是绕不开的环节,先把工程跑起来再谈重构。
组件库这块,选择 Element 系列几乎是没有悬念的,因为这类管理平台的核心页面全是表格+表单+弹窗+树形控件,Element 的 table、form、dialog、tree 几个组件就能解决 90% 的界面需求。像“城乡居民医保”这种业务信息管理平台,界面要求是清晰、规整、操作路径短,而不是花哨的视觉创意。
2.3 持久层框架为什么选 MyBatis-Plus
现在网上主流的 JavaWeb 毕设后端,持久层基本就是 MyBatis-Plus 一家独大。很多人会问,为什么不用 JPA、Hibernate?原因也很实际:
- MyBatis 和 MyBatis-Plus 的 SQL 控制力更强。医疗信息管理系统里有很多复杂查询——按时间区间、按状态、按身份证号模糊搜索、多表关联统计,SQL 看得见摸得着,调试时直接把日志里的 SQL 复制出来就能跑。
- MyBatis-Plus 解决了单表 CRUD 的重复劳动。它内置
BaseMapper和LambdaQueryWrapper,不用写 XML 就能完成简单的条件查询、分页查询,这对以业务逻辑为主的毕设项目非常友好。 - 学习曲线低。你只需要搞懂实体类上的注解(
@TableName、@TableId、@TableField)以及 wrapper 构造器,就基本掌握了 80% 的数据访问写法。
但这不意味着你不需要 SQL 能力。恰恰相反,如果你能在项目里写出一两个“稍微有点复杂”的多表关联查询 SQL,比如统计某年度某街道居民的缴费汇总,答辩时是可以主动拿出来讲的。
3. 数据库设计是这类项目的命门:核心表结构与关系
3.1 六张核心表的职责
一个合格的城乡居民基本医疗信息管理系统,数据库至少要包含下面这些核心表。我按业务重要程度排一下:
- 居民信息表(resident_info):存居民基础档案,核心字段包括姓名、身份证号、性别、出生日期、户籍地址、联系电话、参保状态等。这里“身份证号”必须设唯一索引,这是整个系统的业务主键。
- 参保缴费表(insurance_record):存居民每年的参保登记和缴费情况,核心字段包括居民ID、年度、参保类型、缴费金额、缴费日期、参保状态(正常/暂停/终止)。
- 报销申请表(reimburse_apply):存居民的报销申请,核心字段包括居民ID、就诊类型(门诊/住院)、总费用、申请金额、诊断说明、申请时间、审核状态(待审核/通过/驳回)、审核人、审核意见。
- 门诊/住院记录表(medical_record):存就医明细,包括就诊医院、科室、诊断结果、医疗总费用、开始和结束时间。这张表和报销申请是上下游关系。
- 系统用户表(sys_user):登录账号,包含用户名、密码(加密切勿明文)、真实姓名、角色ID、状态。
- 角色表(sys_role):角色定义,管理员、经办人员、普通居民这三类,至少要有基础的数据初始化。
这些表不是凭空定的,而是从业务生命周期里自然推出来的。你每设计一张表,都能对应回“居民参保→看病→报销→审核”这条链路中的某一个环节。
3.2 表关系怎么设计:从业务推导外键
数据库设计最怕的是“按页面来建表”——你看到一个页面展示什么字段,就建一张表,完全不管表之间的业务关联。正确的做法是顺着业务关系推导外键:
- 一个居民可以有多个年度的参保缴费记录,所以
insurance_record里要有resident_id外键指向居民表。 - 一个居民可以有多次门诊/住院记录,所以
medical_record里要有resident_id。 - 一次就医记录对应一次报销申请,报销申请表里通常会冗余
medical_record_id,或者至少保留诊断摘要和费用总额,方便审核人对照。 - 一个用户对应一个角色,角色表独立出来,为后续做菜单权限控制留空间。
外键约束在毕设里建不建物理外键,我个人的经验是:可以只建索引不建物理外键。因为 MyBatis-Plus 这类主流组件并不是靠数据库外键驱动业务,而是靠 Java 代码维护关联。物理外键在删除数据时会带来很多约束麻烦,对演示项目不友好。但你要在文档里写清楚“逻辑外键的含义与关联方式”,这样就不算设计缺陷。
3.3 几个容易踩坑的字段设计细节
我在检查别人数据库的时候,几乎每次都会发现同几个问题,这里单独列出来,你们对照自己的表检查一遍:
- 金额字段必须用 decimal,不能用 float 或者 double。医疗费用和报销金额涉及精确计算,浮点数在累计、比较时会出现精度丢失。字段建议
decimal(10,2),能覆盖千万级以内的金额。 - 状态字段用 int 或 varchar,加注释说明含义。比如报销审核状态:0=待审核,1=通过,2=驳回。不要真的一长串字符串存进去,后期改需求时你根本没法统计。
- 时间和日期字段建议用 datetime 而不是 date。像缴费时间、申请时间都可能精确到时分秒,将来做“按时间段查询最近三个月报销记录”时才不会丢数据。
- 身份证号、手机号这类字段建议 varchar 而不是 int。这个错误很低级但经常出现,身份证号超过整数范围后要么报错,要么丢失精度,更别提中间还可能含字母 X。
这三条设计完成后,你可以顺手做一个特别加分的动作:给每个表加create_time和update_time两个通用字段。MyBatis-Plus 还支持自动填充,这样你所有新增和修改操作自动维护时间戳,答辩时提一句“通过自动填充减少重复代码”,这是很务实的设计。
4. 后端核心实现:从登录鉴权到报销审核的链路
4.1 统一返回结果与全局异常处理
后端代码拿到手,第一件事找com.xxx.common之类的工具包,看它的统一返回结构。一个成熟的管理平台,接口返回值不会直接返回 Map 或者裸的 List,而是会包装成一个统一对象。常见设计是:
public class Result<T> { private Integer code; private String msg; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMsg("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String msg) { Result<T> result = new Result<>(); result.setCode(500); result.setMsg(msg); return result; } }对应的前端 axios 响应拦截器,拿到data.code判断业务是否成功。这里面试官或者答辩老师经常会追问的一个点是“为什么不直接返回Map”,你要能回答:统一包装的好处是前端可以集中处理错误码、提示语和登录态过期等通用逻辑,而不是每个接口单独判断。
同时要有全局异常处理器,用@RestControllerAdvice捕获BusinessException和系统异常,转换成统一返回结构。这一步的意义是:离用户越近的地方越不应该把异常堆栈直接暴露出来,而是给一个“参数校验失败”或者“操作失败”的友好提示,同时后端日志记录完整堆栈。这是一个极具工程感的细节。
4.2 基于 JWT 的登录态管理
城乡居民基本医疗信息管理系统里有“居民查自己报销记录”这种需求,所以权限必须能区分到用户级。当前主流、也是这套源码里最常见的技术方案是 JWT。
核心流程是怎样的:
- 用户传用户名密码调用
/login接口。 - 后端校验账号密码,生成一个 token,里面可以包含用户 ID、用户名、角色 ID 等非敏感信息,签名后返回给前端。
- 前端把 token 存在 localStorage 或 Pinia/Vuex 里,以后每次请求在请求头里带
Authorization: Bearer token。 - 后端写一个拦截器或过滤器,校验 token 合法性和有效期,再从 token 里解析出当前用户信息,放入
ThreadLocal或直接作为参数传给控制器。
实现的时候推荐使用io.jsonwebtoken:jjwt依赖,关键代码如下:
// 生成 token,有效期设置为 24 小时 String token = Jwts.builder() .setSubject(user.getUsername()) .claim("userId", user.getId()) .claim("roleId", user.getRoleId()) .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact();这里有个特别值得注意的细节:用户修改密码、被禁用之后,旧 token 还没过期怎么办?很多毕设源码不会处理这件事,只校验 token 本身。你可以捎带手加一个“用户状态字段比对”的步骤:鉴权时除了解析 token,还要根据用户 ID 去数据库查一下当前状态,如果被禁用则直接拒绝。代码量不大,但是答辩时讲“我不仅做了 token 校验,还每次查询用户最新状态”会让老师觉得你考虑得比一般学生周全。
4.3 分页查询与模糊搜索:MyBatis-Plus 的标准范式
业务管理系统的后端接口,几乎是清一色的“分页列表 + 条件筛选”。老旧写法是手动算 limit 偏移量,再用 PageHelper 插件;而基于 MyBatis-Plus 的源码里,通常会这样写:
// 配置分页拦截器 @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }在 Service 层配合 LambdaQueryWrapper 构造查询条件,配合 Page 对象完成分页:
Page<ResidentInfo> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<ResidentInfo> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.hasText(name), ResidentInfo::getName, name) .eq(StringUtils.hasText(idCard), ResidentInfo::getIdCard, idCard) .eq(residentInfo.getStatus() != null, ResidentInfo::getStatus, residentInfo.getStatus()) .orderByDesc(ResidentInfo::getCreateTime); residentInfoService.page(page, wrapper);这里大家最容易忽略的一点是:分页查询的返回值不要直接return page,因为 MyBatis-Plus 的Page对象里有些字段前端不需要,而且不同接口返回结构要稳定。更规范的做法是返回一个自定义 VO(视图对象)或统一的分页结构,包含total、records、pageNum、pageSize四个字段。很多源码偷懒直接返回 Page,功能上没毛病,但重构时你就会发现到处都是 MyBatis-Plus 的痕迹,想换成别的框架时全部要改。至少你应该在 Controller 层做一次数据组装。
4.4 事务处理:报销审核为什么会同时改三张表
这类系统里最值得留意的事务场景,是报销审核流程。业务人员点一下“审核通过”,后端要同时做什么?
- 更新报销申请表的审核状态为“通过”,写入审核人、审核时间。
- 根据费用金额和报销比例计算并写入报销金额。
- 如果设计了资金台账表,还要新增一条支付记录。
这三个操作必须在一个事务里完成。如果只把申请状态改了,金额写入失败,用户就“审核通过了,但钱没算出来”,业务数据就烂了。Spring 里最直观的做法是在 Service 方法上打@Transactional(rollbackFor = Exception.class)。
但这里有个非常隐蔽且常见的问题:@Transactional走的是 Spring 代理,如果你在同一个类的内部 self-invocation(this.xxx())调用事务方法,事务是不会生效的。毕设里很多同学把 Controller 直接写业务逻辑、或者 Service 内部方法互相调用,就会踩到这个坑。排查方法也很简单:看方法报错后数据有没有回滚,或者看日志里有没有TransactionInterceptor的调用记录。
另外我再建议一个小优化:把事务拆细。比如“登记缴费记录”和“发送短信通知”就不应该在同一个事务里,短信服务不稳定,一旦失败会把正常业务也回滚掉。事务的边界是写库的关键业务数据,通知、日志、外部调用这些尽量放到事务外面。这个意识能让你在答辩时从“会使用注解”升级到“会设计事务边界”。
5. 前端 Vue 的配合要点:路由、请求封装与权限控制
5.1 动态路由还是静态路由
管理平台的菜单权限怎么落地?这是我看见的另一个高频困惑。最简单的做法是:在路由表里把管理员、经办人员、居民的功能页全部注册为静态路由,然后根据登录用户角色,在前端根据菜单配置去 v-if 隐藏入口。这种方法代码简单,但这同样意味着用户知道 URL 就能直接访问未授权页面。
稍微进阶一点的做法是动态路由:用户登录后,后端返回当前角色可访问的路由列表,前端通过router.addRoute()动态注册。这就解决了“页面隐藏但路由可直达”的问题。
它的核心流程:
- 登录后拿到用户角色和菜单权限列表。
- 前端根据权限列表过滤动态路由表,再
router.addRoute添加。 - 路由守卫里判断当前访问路径是否在权限列表中,不在则 403 或跳转到第一个可用页面。
动态路由的代码实现会复杂不少,但它带来最大的好处是:权限控制的源头集中在一个地方,新增角色只需要配菜单,前端不需要改代码。你在答辩时能讲清楚“为什么用动态路由”,就比只会写页面循环的选手高出一截。
5.2 axios 封装:token 携带与错误码统一处理
前端工程必然有一个utils/request.js文件,对 axios 进行二次封装。你在理解源码时,重点看三处:
第一,请求拦截器里自动携带 token:
service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }, error => Promise.reject(error))第二,响应拦截器里统一处理业务码和 HTTP 状态码:
service.interceptors.response.use(response => { const res = response.data if (res.code !== 200) { Message.error(res.msg || '系统错误') if (res.code === 401) { // 登录过期,清除 token 并跳转登录页 router.push('/login') } return Promise.reject(new Error(res.msg)) } return res }, error => { Message.error(error.message) return Promise.reject(error) })第三,API 接口不要散落在各个组件里。合理的做法是按模块建src/api/resident.js、src/api/insurance.js、src/api/reimburse.js等文件,每个文件导出一个函数,组件里只调用函数。这样将来后端接口调整时,你只需要改 API 文件,不用全局搜。这也是一个可以讲给老师听的“前端工程化”点。
5.3 跨域问题的解决:Vue CLI/Vite 代理配置
前端开发时调用后端接口,最烦的就是跨域。“vue打包放进springboot中”这个词出现的频率极高,就是因为大家把前后端部署方式搞混了。开发阶段最常见的解决方案是配置代理:
如果你用 Vue CLI 创建的项目,在vue.config.js里:
module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } }如果你用 Vite,则在vite.config.js里配置server.proxy。代理的原理不复杂:前端开发服务器接收/api开头的请求,转发给后端地址,这样浏览器看到的是同源请求,就不存在跨域了。需要注意后端接口如果本身没有/api前缀,代理配置里通常会再加一段pathRewrite: { '^/api': '' },把前缀去掉再转发。
很多同学会问,为什么不在后端加@CrossOrigin或者配置全局 CORS?答案是可以用,但这只解决开发环境的问题。而且如果将来前端和后端部署在同一个域下,代理和 CORS 都可以不配。这个问题在部署章节里继续展开。
5.4 表格、表单与弹窗的重复劳动怎么减少
医疗信息管理系统的页面,本质上就是三种模式的循环:列表页、新增/编辑弹窗、详情页。遇到“居民列表”“缴费记录列表”“报销列表”,它们长得高度相似。如果你只是照着写,工作量会非常大,而且代码冗余严重。
我建议至少做两件事:
第一,封装一个通用的分页查询组件,把“搜索条件区 + 表格区 + 分页器”整合成一个带插槽的组件。数据请求逻辑在组件内部统一处理,页面只需传入接口函数和列配置。
第二,抽一个表单弹窗的基础组件,包含v-model控制显隐、标题、确定/取消按钮、加载状态。每个具体的新增或编辑弹窗只需要往插槽里填表单项即可。
这样的封装能让你在写“居民信息管理”和“业务人员管理”两个看似完全不同的页面时,直接复用八成的基础代码。这也是项目里“代码量”和“工程质量”中最容易拉开差距的部分——评阅老师看你源码时会特别在意重复代码多不多。
6. 从源码到跑起来:环境搭建与部署全流程
6.1 后端环境:JDK、Maven、MySQL 配置
拿到源码第一件事不是打开 IDE,而是先把环境对齐了。这个项目的后端基础环境建议是:JDK 8 或 17、Maven 3.6+、MySQL 8.0。MySQL 安装是程序员的老朋友了,我补充几个和高频搜索词“mysql安装教程”“mysql安装配置教程”“mysql安装教程8.0”有关的实操细节:
- MySQL 8.0 默认加密规则是
caching_sha2_password,某些老版本 JDBC 驱动连接会报错,解决办法是换最新驱动,或者把用户加密规则改回mysql_native_password。 - 连接字符串一定要带
serverTimezone=Asia/Shanghai和useSSL=false,否则容易报时区和 SSL 错误。这也是搜索词“mysql ssl连接错误”的来源。 - 导入 .sql 文件前,先创建数据库并指定字符集:
CREATE DATABASE medical_system DEFAULT CHARACTER SET utf8mb4;。记得是utf8mb4,不是utf8,这样才能正常显示生僻字和表情符号。
application.yml里典型的配置如下:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/medical_system?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这行建议保留,控制台会打印每个 SQL。你排查“为什么查不到数据”“为什么 SQL 条件不对”时,看这里是最快的。
另外还有个毕设阶段很常见的问题:Maven 依赖 download 特别慢,或者下载失败。这跟网络有关,建议把阿里云 Maven 镜像配进settings.xml。如果还卡,优先检查是不是公司或者学校网络把镜像源禁了。
6.2 前端环境:Node 版本与 npm install
前端子目录一般是一个独立的工程,结构类似frontend或者vue-web。进入目录后执行:
npm install这一步通常是最劝退的环节。常见问题有两个:
第一,Node 版本和依赖不匹配。Vue 2 项目用 Node 16/18 基本没问题;Vue 3 + Vite 项目则建议 Node 18+。如果你用的是 Node 22,某些老依赖在编译时会报错。我的经验是备一个 nvm(Node 版本管理工具),随时切换版本,比反复重装 Node 高效得多。
第二,npm install 卡在某个依赖上。可以先删除node_modules和package-lock.json再重新执行。如果是网络问题,设置淘宝镜像源:
npm config set registry https://registry.npmmirror.com启动命令通常是npm run serve(Vue CLI)或npm run dev(Vite)。启动后访问http://localhost:8081,如果页面能出来,就说明前端工程本身没问题,接下来看接口联调。
6.3 前后端联调的关键配置
前后端各自跑起来后,如果前端页面接口全部请求失败,99% 是两个原因:
一是代理没配对。检查vue.config.js或vite.config.js里的代理 target 是否指向后端实际端口。后端如果改过server.port,前端代理也要同步改。
二是跨域虽然代理配了,但后端的 ContextPath 对不上。比如后端spring.mvc.servlet.path=/api,前端代理路径却又加了一层/api,结果请求变成/api/api/user/login,自然 404。解决方式是统一约定接口前缀,要么后端加,要么前端加,不要两边各加一次。
验证联调是否成功有一个笨但有效的办法:打开浏览器开发者工具,看网络请求的 URL 路径和响应状态。如果是 200,再看数据结构是否符合期望;如果是 404,说明路径不对;如果是 405,多半是方法类型不匹配(比如后端是 POST 前端用了 GET)。
6.4 打包部署:vue 打包放进 SpringBoot 的两种做法
这是“vue打包放进springboot中”被反复搜索的原因所在。毕设阶段不需要上复杂的 Nginx 集群,两种做法非常实用:
做法一:经典前后端分离部署。前端npm run build生成dist目录,用 Nginx 托管静态文件,并配置反向代理把/api请求转发到后端 SpringBoot 服务。这种方式最贴近企业真实环境,答辩如果不是跑本地而是上云服务器,建议用这种方式。
做法二:前端构建产物放进后端。把dist里的静态文件复制到 SpringBoot 的src/main/resources/static目录下,然后重新打 jar 包。这样启动一个 Java 进程就能同时提供页面和接口。简化了部署,但不太符合真实的前后端分工逻辑,适合老师要求“一个 jar 跑起来”的场景。
如果选了做法二,需要注意一个坑:Vue Router 使用 history 模式时,刷新某个子路由会出现 404,因为后端没有对应的静态资源路径。解决办法是加一个路由回退配置,把未匹配的路径转发到index.html。如果你用的是 hash 模式,就没有这个问题。毕设演示我一般建议直接用 hash 模式,省心可靠。
打包之后还有一步值得做:检查application.yml里数据库账号密码是否还是本地测试用的,如果别人在自己电脑上跑你的 jar,他需要先改配置才能连接到自己的 MySQL。这个细节在源码分享里很体现职业素养。
7. 答辩和展示时怎么把项目讲出深度
7.1 主动准备好的两个技术亮点
等到把项目跑通,只是完成了最基本的任务。如果你想让答辩老师觉得这个项目“不是抄的”,建议准备两个可以主动展开的技术点。
第一个是登录鉴权链路。从“为什么用 JWT 而不是 Session”开始,到“token 过期怎么处理”“用户被禁用后 token 怎么立即失效”“密码为什么要加密存储”,这一整条链路,每讲一层都有代码支撑。Session 和 JWT 的区别是最容易展开的话题:Session 存在服务端,天然支持主动失效,但不利于水平扩展;JWT 自包含、无状态,适合前后端分离,但被动失效难。你完全可以在项目里同时保留两者:用 JWT 做无状态认证,在用户每次请求时查一次状态字段弥补被动失效问题。这种“两全”的思考方式在答辩中非常有说服力。
第二个是报销审核的事务边界。前面提到审核时同时更新申请表、计算金额、新增流水。你可以用“如果不开事务会出现什么情况”来反向说明:用户报销申请状态显示通过,但支付流水缺失,对账就出问题了。然后说明 Spring 事务机制、事务失效的常见原因,以及你如何确保了事务真正生效。这类“业务 + 技术”结合的问题,是评委最常追问的方向。
7.2 被问到“哪里还可以改进”时的回答思路
答辩快结束的时候,老师几乎必问“你觉得项目还有哪些不足”。这是个送分题,但很多同学答成“我的项目很完美”,或者“我对系统还不熟”。这两种都不好。最好的回答是给出合理的、基于真实技术的改进方向,比如:
- “目前报销比例的配置是写死在代码/配置里的,后续可以做成独立参数表,由业务人员动态维护不同类别的报销比例。”
- “目前上传的发票/病历还是以附件形式保存,后续接入对象存储做统一管理,并在审核页面做图片预览。”
- “日志方面目前主打控制台输出,后续希望基于注解切面做操作日志审计,记录谁在什么时候对哪些敏感数据做了修改。”
这三个改进方向分别对应了系统参数化、文件存储、操作审计,都是很典型的真实业务需求。你如果能把“改进方向”讲成一个具体的设计,而不仅仅是两句口号,老师会明显感受到你理解系统的深度。
我个人做了一轮又一轮类似项目后,最大的体会是:这种管理信息系统,技术上没有特别高深的内容,功夫全在业务建模的完整度、事务边界的清晰度、权限设计的严谨度、以及前后端异常处理的一致性上面。你把这四个点做扎实,整个项目的可信度和完成度立刻就不一样了。
如果你正在拿这套 SpringBoot + Vue + Java + MySQL 的源码做课设或者毕设,我给一个具体的行动顺序:先按要求把项目跑起来,再画业务流程图和表结构图,然后找一个模块(比如报销审核)把它的前后端完整链路看完,最后在这个模块上做一个自己的小改进。这样走完一遍,无论答辩问题怎么刁钻,你都接得住。