
如果你正在准备毕业设计或者课程设计SpringBootVue这种前后端分离的管理系统十有八九是你绕不开的选题。城乡居民基本医疗信息管理系统又是这类选题里非常典型的一个——业务上管的是居民参保、缴费、报销这条完整链路技术上覆盖了Java后端、Vue前端、MySQL数据库这三块主流内容复杂度不高不低正好适合作为毕设或课设项目。这篇文章把这套系统的业务结构、技术选型、环境准备、数据库设计、后端接口、前端联调、打包部署和二次开发方向拆开来讲。不管你是拿现成源码、还是打算自己从零实现一个都可以照着这个思路去理解项目、去回答答辩老师的提问。废话不多说我们直接从业务边界开始。1. 系统是干什么的先把这个毕设题目的业务边界摸清楚很多同学拿到项目第一反应是点运行但我觉得先弄清楚这套系统到底在管什么事比跑起来更重要。城乡居民基本医疗信息管理系统核心要解决的是基层医保管理中的三类问题谁参保了、交没交钱、报了多少钱。围绕这三件事整个系统的数据结构、页面菜单、接口设计就都好理解了。1.1 三个核心管理对象居民、参保缴费、报销记录城乡居民基本医疗保险和职工医保不太一样它面向的是没有固定工作单位的城乡居民、学生、老人这类群体通常按年度参保一年一缴保障的是基本门诊、住院和大病报销。那系统要管理的数据就很明确了。第一类是居民档案。一个居民从建档开始他的名字、身份证号、性别、出生日期、户籍类型、家庭住址、联系方式包括是否属于低保户、特困人员、重度残疾人这类特殊人群都要记录在案。为什么特殊人群重要因为这类人群通常享受个人缴费补助系统里需要有标记字段否则统计补助的时候无从下手。第二类是参保缴费记录。每一年度居民是否参保、缴费状态是已缴还是未缴、缴费金额多少、缴费日期是哪天都需要留痕。很多第一次做这类系统的同学会犯一个错误——在居民表上加一个是否缴费的字段今年缴了就更新成已缴费。这个设计看着简单但明年怎么办后年怎么办几年累计的缴费记录全丢了。正确做法是单独建一张参保记录表用年度字段区分每一条记录。第三类是报销记录。居民看病后门诊和住院分别按不同比例报销报销单要记录医疗机构、医疗类型、总费用、可报销费用、实际报销金额、审核状态。这里就有前后端交互的典型场景了居民提交报销申请经办人在系统里审核审核通过后打款。系统里必须体现待审核、已通过、已驳回这类状态流转。1.2 目录式模块拆解从一个页面菜单看系统全貌我习惯用菜单目录的方式去理解一个管理系统。这套城乡居民基本医疗信息管理系统按常规设计菜单结构大致是这样的系统管理用户管理、角色管理、菜单权限、操作日志居民档案管理居民列表、新增居民、档案修改、档案注销参保缴费管理年度参保登记、缴费记录查询、欠费提醒、缴费标准配置报销管理报销申请录入、报销审核、报销记录查询统计报表参保人数统计、缴费情况统计、报销金额统计这个目录结构本身就暗合了业务逻辑系统管理解决谁能用这个系统的问题居民档案解决给谁服务的问题参保缴费解决钱收了多少的问题报销管理解决钱支出了多少的问题统计报表解决领导怎么看数据的问题。有了这个菜单全景你再去看源码里的前端路由配置和后端Controller思路就清晰了。前端router里每一个路由对应一个vue页面后端Controller里每一个RequestMapping对应一类操作。前后端就是靠这些菜单页面的请求串联起来的。1.3 这套业务设计对答辩的价值为什么城乡居民基本医疗信息管理系统常年是毕设热门因为它是一个非常典型的CRUD里带一点业务状态的项目。你说它简单它确实没有复杂的算法但你说它水它又有真实的业务逻辑——年度缴费不能重复、报销要经过审核、统计要有据可查。这种看着不难但能讲出东西来的特性恰恰是答辩老师喜欢的。你想想如果做一个通用的学生管理系统老师问你的系统解决了什么实际问题你得憋半天。但换成医保场景你可以讲城乡居民医保的政策背景、按年度参保的业务规则、缴费与报销的状态流转甚至可以说一说特殊人群缴费补助的数据处理。这就是选题本身的优势。2. 技术选型为什么要这么搭SpringBoot、Vue、MySQL的角色分工技术选型是答辩必问的问题没有之一。你把这个问题想透了不仅做项目心里有底答辩的时候也能应对自如。这套系统的技术栈组合非常经典本质上是三层分工SpringBoot处理后端业务和数据交互Vue处理页面渲染和用户交互MySQL存所有的业务数据。2.1 后端用SpringBoot而不是SSH/SSM开发效率与生态先问一个问题为什么是SpringBoot而不是更早的SSH或者SSM如果你参考过旧项目你会发现早期的Java Web项目配置非常痛苦spring.xml、springmvc.xml、web.xml三个配置文件动不动几十行。SpringBoot把这一切简化了内嵌Tomcat打一个jar包就能跑不需要单独装Tomcat也不需要人工配置一堆bean。尤其是对毕设项目能快速跑起来比架构有多精巧更重要。SpringBoot的自动配置机制让你只需要在application.yml里写几行配置就能把数据源、事务、MyBatis这些都搞定。再加上spring-boot-starter-web这种起步依赖你写一个Controller就能直接对外提供HTTP接口。说句实在话用SpringBoot做一个这种规模的管理系统是有一些杀鸡用牛刀的意味的但毕设选题需要兼顾技术容量和完成难度SpringBoot的生态成熟、资料多、遇到问题搜索一下就有解决方案这个优势在赶毕设的时候是实实在在的。这套系统里SpringBoot的核心职责大概是这几块接收前端请求并做参数校验调用Service层处理业务逻辑通过MyBatis或JPA操作MySQL数据库返回统一的JSON格式给前端处理登录鉴权和权限控制。把这四件事做利索后端部分就完整了。2.2 前端用Vue而不是传统JSP前后端分离的合理性放在十年前这种管理系统的前端多半是JSPJava后端直接在页面里渲染数据。现在用Vue核心变化是前后端分离——前端只管页面展示和交互后端只管提供JSON数据接口。这么做的好处非常明显前端开发和后端开发可以并行页面效果更流畅数据交互更清晰。Vue本身是渐进式框架做管理后台最常用的搭配是Vue Vue Router Vuex/Pinia Element UI。Element UI提供了现成的表格、表单、弹窗、分页组件你不需要自己去写CSS布局一个页面半天就能搭出来。这就是为什么Vue能成为毕设管理系统的绝对主力。我也见过一些同学担心我不会Vue怎么办。其实管理后台的Vue写法套路极其固定data里放列表数据和查询条件created钩子里调用接口拉数据el-table负责展示el-dialog负责弹出表单el-form负责收集输入。你照着现有代码抄两个页面基本规律就能摸出来。Vue在毕设这种场景下的学习成本绝对比你想的低。2.3 数据库用MySQL的考量以及什么时候需要担心技术栈过于老旧MySQL在毕设管理系统里的地位几乎不需要解释——免费、轻量、资料多、JDBC驱动成熟而且还是SQL标准语法写起来和其他数据库差别不大。对这个项目来说数据量也就是几千条居民记录和几万条缴费记录MySQL绰绰有余。InnoDB引擎支持事务正好用在缴费登记、报销审核这类需要一致性的操作上。有同学会问现在企业里都在用Redis、微服务、分布式那一套我毕设只用SpringBootVueMySQL会不会显得老我的看法是一个课程设计和本科毕设最重要的是把业务逻辑和技术实现讲清楚而不是技术堆砌。如果你觉得有余力可以在Redis缓存、消息队列这些方向上加一点深度但前提是核心功能已经稳定跑通了。不要在连CRUD都还没跑明白的时候先去研究微服务拆分那是给自己找麻烦。3. 环境准备实操清单JDK、Maven、MySQL、Node.js的版本搭配与坑点大多数同学拿到源码的第一个动作是直接点运行然后被一堆异常拍在脸上。我见过太多次项目本身没问题纯粹是环境配置不对导致跑不起来的情况。这一章先别碰代码我们把基础环境从头捋一遍。3.1 JDK与IDEA版本不匹配是最常见的第一道坎先看项目的pom.xml里parent标签的SpringBoot版本再决定用哪个JDK。这里有个常见的对应关系SpringBoot版本推荐JDK版本说明SpringBoot 2.7.xJDK 8或JDK 11最稳的组合毕设项目最常见SpringBoot 3.0JDK 17及以上包名从javax迁移到jakarta旧代码要改SpringBoot 2.x配JDK 21容易出问题依赖兼容性不好不推荐硬配很多教程项目是基于SpringBoot 2.x的结果你用IDEA 2024新建项目或者导入时默认选了个JDK 21编译阶段就报错。这不是代码问题是版本组合问题。我的建议是源码如果写着JDK 8你就老老实实装一个JDK 8不要手痒升级。IDEA里导入Maven项目的步骤也要注意要用Open方式选择项目根目录下的pom.xml文件让IDEA识别为Maven项目。如果你是纯白板环境导入后第一次刷新Maven依赖会下载很久这时候要检查IDEA的Maven设置——不要用IDEA自带的Maven要配置成你自己下载好的Maven并且指定settings.xml路径。3.2 Maven配置镜像仓库、依赖下载慢、编译报错的处理Maven是Java后端构建工具这个项目所有的第三方依赖都是Maven从中央仓库拉下来的。国内网络环境直连中央仓库非常慢甚至超时所以第一步是配置国内镜像仓库。找到Maven安装目录下的conf/settings.xml在mirrors节点里加一个阿里云镜像mirror idaliyunmaven/id mirrorOf*/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror配置好镜像之后依赖下载速度会快一个量级。还有一个常见坑是IDEA里Maven的Runner设置JRE参数要选对否则编译时会报无效的源发行版错误。这个错误的意思是你的编译器JDK版本和项目要求的Java版本不一致解决方法是统一项目SDK、模块SDK、Maven Runner JRE三处的版本。3.3 MySQL 8.0的安装与连接字符集、时区、SSL报错一次说清MySQL版本建议用8.0但不要用最新的大版本因为驱动差异可能带来不必要的麻烦。安装完成后有几个配置必须确认。第一是字符集统一为utf8mb4。城乡居民的姓名、地址这些字段都可能有中文如果数据库默认字符集是latin1存中文就会乱码。创建数据库时直接指定CREATE DATABASE IF NOT EXISTS medical_insurance DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;第二是时区问题。MySQL 8.0默认时区是UTC你往里插入当前时间数据库存的和北京时间差8小时。连接串里加一个serverTimezone参数解决jdbc:mysql://localhost:3306/medical_insurance?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai第三就是很常见的SSL连接报错。MySQL 8.0默认开启SSL但本地开发环境通常不需要连接串里加useSSLfalse即可。如果你看到Communications link failure或者SSL握手相关的报错优先检查这个参数。3.4 Node.js与Vue环境版本选择、npm镜像、vue create流程前端Vue项目跑起来需要Node.js环境。这里有个版本匹配门道Vue 2项目建议Node 14或16Vue 3项目建议Node 16或18。如果你用Vue 2却装了Node 20运行npm run serve时大概率报OpenSSL的哈希错误这是Node版本太高导致的。npm是Node的包管理器国内直连下载依赖也很慢配置淘宝镜像的方式是npm config set registry https://registry.npmmirror.com配置之后执行npm install速度会快很多。依赖装完执行npm run serve开发服务器起来后默认端口是8080和后端接口端口可能会撞一般我们会在vue.config.js里改一下前端端口或者配置代理后面专门讲。还有一种情况是依赖装到一半报错node_modules目录残留了一堆不完整文件。稳妥的做法是删掉node_modules和package-lock.json重新npm install。这个操作我做过无数次比在报错堆里挣扎高效多了。4. 数据库设计思路从居民档案到医保报销的表结构全拆解数据库是整套系统的地基。表设计合理后面写接口顺畅表设计稀烂后面每个查询都在写复杂SQL。城乡居民基本医疗信息管理系统的主表说起来也就五六张但每一张的字段设计都得有讲究。4.1 核心表与字段设计我把这套系统的核心表整理成一张清单你看完基本就能在脑子里还原出数据流动的路径表名用途核心字段说明sys_user系统用户id, username, password, real_name, role_id, status登录账号和权限resident_info居民档案id, name, id_card, gender, birth_date, household_type, poverty_flag, address, phone最核心的基础数据insurance_record参保记录id, resident_id, year, insurance_type, pay_status, pay_amount, pay_date一人一年一条记录reimbursement_record报销记录id, resident_id, hospital_name, medical_type, total_amount, reimburse_amount, audit_status状态流转变动频繁policy_config政策参数id, config_type, config_value, remark缴费标准、报销比例可配置先说居民档案表的关键字段。身份证号必须是唯一的这就是业务的自然主键之一。但表设计时通常不把身份证号设为主键因为主键要稳定且常被外键引用用一个自增id当主键身份证号上建唯一索引就够了。poverty_flag这个字段非常重要它标记低保、特困等特殊人群缴费计算时要根据这个字段判断是否需要减免。再说明一下sys_user和resident_info的关系系统用户是操作人员比如乡镇医保经办人居民是服务对象两者之间没有从属关系。千万不要把居民直接当用户用来登录系统这是角色混乱的大忌。4.2 为什么要按年度存参保记录而不是只更新一个状态字段这是设计里最值得讲的一点也是答辩时容易出彩的地方。前面提到过居民医保按年度参保。如果只在居民表上放一个是否缴费的字段那问题来了2023年的状态被2024年的覆盖掉哪一年的数据你都说不清楚。正确的设计是insurance_record表每条记录包含resident_id和year两个业务字段。查今年谁没缴费就查year等于今年且pay_status为未缴的记录统计历年缴费趋势按year分组聚合即可。这种设计的本质是保留历史状态——对于周期性业务记录明细表永远比覆盖字段更合理。缴费记录的金额也要注意不要只存一个pay_amount最好同时存policy_config里读出来的标准金额和实际缴纳金额。因为可能存在减免政策比如个人缴350元特殊人群只需个人缴30元财政补助320元。你把这个减免逻辑写清楚数据才有说服力答辩的时候也能讲出业务深度。4.3 建库建表脚本的书写规范与常用查询建表脚本建议手写SQL而不是靠可视化工具反向导出这样你能真正理解每个字段的含义。推荐用下划线命名字段类型选对金额用DECIMAL(10,2)身份证号用VARCHAR(18)创建时间用DATETIME默认值CURRENT_TIMESTAMP。居民档案表在建表时加一个逻辑删除标记deleted字段用0和1表示这是后端管理系统的惯例——不直接物理删除用户数据避免误删和统计断档。常用的业务查询有这几类你可以提前练一练查询某年度未缴费居民列表__sql SELECT r.id, r.name, r.id_card FROM resident_info r LEFT JOIN insurance_record i ON i.resident_id r.id AND i.year 2025 WHERE i.id IS NULL__统计某年度总缴费金额__sql SELECT SUM(pay_amount) FROM insurance_record WHERE year 2025 AND pay_status PAID__查询报销金额超过某阈值的记录__sql SELECT * FROM reimbursement_record WHERE total_amount 5000 AND audit_status APPROVED__这些SQL写顺了后端Mapper里对应的就是最简单的单表查询和分组统计不需要任何复杂的子查询。5. 后端工程从零搭建SpringBoot项目的分层结构与核心接口实现后端工程的核心是分层清晰。你这套项目的包结构出去如果能让老师一眼看出哪些是接口层、哪些是业务层、哪些是数据访问层印象分就拿到了。我按常规模板给你拆一遍。5.1 工程目录结构与通用配置一个标准的SpringBoot后端项目包结构大致如下com.example.insurance ├── config // 配置类如CORS配置、WebMvc配置 ├── controller // Controller层接收前端请求 ├── service // Service接口 ├── service.impl // Service实现类业务逻辑 ├── mapper // Mapper接口如果用的是MyBatis ├── entity // 实体类对应数据库表 ├── dto // 传输对象接收前端参数 ├── vo // 视图对象返回前端数据 ├── common // 通用类统一返回体、异常处理、常量 └── util // 工具类JWT、密码加密等为什么必须分这么多层说白了是为了降低维护成本。Controller只负责参数接收和结果返回不写SQLService只处理业务逻辑不直接用JDBCMapper只做数据库操作不判断业务条件。三层各司其职出了问题你知道去哪个层找。答辩时老师问分层设计的好处你回答单一职责、便于测试、便于替换实现这是标准答案。application.yml里的配置有几个要注意的。数据源配置里连接串必须带上时区和SSL参数前面已经写过。MyBatis配置里mapper-locations要指向你的XML文件目录驼峰映射map-underscore-to-camel-case建议设为true这样数据库的下划线字段能自动映射到Java的驼峰属性。5.2 统一返回格式、全局异常处理与登录鉴权前后端分离的核心约定是数据格式。我强烈建议所有接口统一返回一种结构前端才能做统一处理。最常见的格式是{ code: 200, message: success, data: {} }Java里对应一个泛型类Result成功时调用Result.success(data)失败时调用Result.error(message)。前端的axios拦截器只看这个code——是200就取data里的内容不是200就弹出message里的错误信息。这个约定定型之后写后面所有接口的速度都会快很多。全局异常处理用RestControllerAdvice注解配合ExceptionHandler把业务异常和系统异常分别处理。业务异常比如该居民本年度已缴费可以直接抛出BusinessException由全局处理器统一包装成错误JSON返回。系统异常比如SQL执行失败返回给前端时只提示系统繁忙不要把堆栈信息暴露给用户。这一层能写的代码不多但答辩时拿出来讲是很好的加分点。登录鉴权方面如果你用的JWT方案流程是用户登录成功后后端生成一个带过期时间的token返回给前端前端把token存到localStorage每次请求在Header里带Authorization字段后端用一个拦截器校验token校验通过才放行。这种方案的优点是无状态服务器不需要存session适合前后端分离架构。5.3 两个核心接口的代码实现分页查询居民、缴费登记光讲概念不够我给你拆两个最实际的接口代码。第一个是居民列表分页查询。为什么用分页因为居民档案可能几千几万条一次性全查出来前端渲染都卡顿。分页参数pageNum和pageSize是通用约定前端el-table的页码组件就传这两个值。RestController RequestMapping(/resident) public class ResidentController { Resource private ResidentService residentService; GetMapping(/list) public ResultPageResultResidentVO list( RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String keyword) { return Result.success(residentService.pageQuery(pageNum, pageSize, keyword)); } }Service里做的事其实就是用MyBatis的分页插件PageHelper或者手写LIMIT语句条件查询里加一个keyword对姓名和身份证号做模糊匹配。这部分的实现代码很简单但你要能讲清楚为什么返回给前端的是PageResult里面包含total总数和records列表因为前端的页码组件要显示总条数不能只有当前页数据。第二个是缴费登记。这个接口里就带着业务逻辑了——不能重复缴费。Override Transactional(rollbackFor Exception.class) public void pay(InsurancePayDTO dto) { // 1. 查本年度是否已经有缴费记录 InsuranceRecord record insuranceRecordMapper.findByResidentIdAndYear(dto.getResidentId(), dto.getYear()); if (record ! null PAID.equals(record.getPayStatus())) { throw new BusinessException(该居民本年度已缴费请勿重复操作); } // 2. 查居民信息判断特殊人群是否享受减免 ResidentInfo resident residentInfoMapper.selectById(dto.getResidentId()); if (resident null) { throw new BusinessException(居民档案不存在); } BigDecimal actualAmount calculatePayAmount(resident, dto.getYear()); // 3. 插入缴费记录并更新参保状态 InsuranceRecord newRecord new InsuranceRecord(); newRecord.setResidentId(dto.getResidentId()); newRecord.setYear(dto.getYear()); newRecord.setPayStatus(PAID); newRecord.setPayAmount(actualAmount); newRecord.setPayDate(new Date()); insuranceRecordMapper.insert(newRecord); }这个接口有三个技巧。第一是Transactional事务注解缴费插入和状态更新要么都成功、要么都失败不能出现钱扣了记录没插上的情况。第二是查重逻辑放在插入前面防止重复操作。第三是减免计算单独抽一个方法保持业务清晰。你把这个例子吃透后面类似的新增、修改、审核接口就都通畅了。6. 前端Vue工程与前后端联调路由、Axios封装和跨域处理后端接口写好了前端要能把数据拿到并渲染成页面。这一章讲的是Vue项目的组织方式和联调中的常见障碍。6.1 Vue项目结构与页面开发顺序建议一个典型Vue后台项目的src目录结构src ├── api // 接口请求模块按业务模块拆文件 ├── assets // 静态资源 ├── components // 公共组件 ├── router // 路由配置 ├── store // 状态管理 ├── views // 页面组件 ├── utils // 工具类比如request.js └── App.vue页面开发顺序我建议按照登录页 → 布局页 → 居民列表 → 居民新增/编辑 → 缴费登记 → 报销审核这个路线走。登录页最独立单独一个路由布局页包含侧边菜单和顶部导航居民列表是第一个会走完请求数据—渲染表格—分页全流程的页面后面依次增加表单弹窗、状态流转这些交互。把居民管理做出手感剩下所有页面都是复制的套路。路由配置走的是懒加载方式好处是首屏加载更快。const routes [ { path: /login, component: () import(/views/Login.vue) }, { path: /, component: () import(/layout/Index.vue), redirect: /resident, children: [ { path: resident, component: () import(/views/ResidentList.vue) }, { path: insurance, component: () import(/views/InsuranceList.vue) }, { path: reimbursement, component: () import(/views/ReimbursementList.vue) } ] } ]有精力的同学可以做动态路由登录后根据用户角色从后端拉取他有权限的菜单再用router.addRoute动态添加到路由表。这个功能很加分但属于进阶方向先保证固定路由跑通再说。6.2 axios封装、请求拦截、401跳转前端所有的HTTP请求都建议走同一个封装我用axios创建独立的request实例统一配置baseURL和超时时间。关键是拦截器请求拦截器负责把token挂上响应拦截器负责处理业务错误和登录过期。import axios from axios import { Message } from element-ui import router from /router const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer token } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 200) { Message.error(res.message) return Promise.reject(new Error(res.message)) } return res.data }, error { if (error.response error.response.status 401) { Message.error(登录状态已过期请重新登录) router.push(/login) } return Promise.reject(error) } ) export default request这段代码的收益是以后每写一个接口只需要调用request.get(/resident/list)不需要管token怎么拼、错误怎么弹。细节注意response拦截器成功分支里返回的是res.data而不是res这样调用方拿到的直接就是后端Result里的data字段少一层嵌套。6.3 联调阶段最容易出问题的三个点前后端联调我总结三个高频坑。第一个是接口404。前端请求/api/resident/list后端明明有这个接口为什么404大概率是baseURL用的绝对路径不对或者后端启动端口不是8080。联调之前先用浏览器或者Postman直接访问一下后端接口地址确认接口能通再去看前端代理配置。第二个是跨域CORS报错。浏览器控制台出现Access-Control-Allow-Origin字样说明前端地址和后端地址的域名或端口不一致。开发环境最稳妥的解决方案是通过vue.config.js配置代理把前端所有/api开头的请求转发到后端地址// vue.config.js module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }这样前端的请求地址写/api/...浏览器看到的还是同源请求绕开了跨域。第三个是token没生效。前端明明登录成功了后端却一直返回401或未登录。排查思路是先在浏览器开发者工具的Network面板里看请求头有没有Authorization字段再去看后端的拦截器有没有正确解析这个字段。很多时候问题出在校验逻辑写错位置拦截器放到了静态资源放行之前把登录接口也给拦截了。7. 打包部署Vue构建产物如何塞进SpringBoot以及部署中的版本坑项目开发完要能交付毕设通常要求打成一个可运行的jar包。这也是Vue和SpringBoot整合时最常出问题的一环。7.1 两种常见打包方式对比我见过两种主流做法各有优劣直接列个表对比打包方式操作路径优点缺点方式一合并进staticnpm run build生成dist复制到SpringBoot的src/main/resources/static目录再maven package打jar简单粗暴一个jar包搞定适合毕设每改一次前端要重新拷贝方式二Maven多模块前端和后端拆成不同模块用frontend-maven-plugin在Maven构建时自动执行npm构建一体化构建工程化程度高配置复杂首次构建下载Node依赖容易卡毕设阶段我个人推荐方式一因为它的心智负担最低。执行npm run build前端项目会生成一个dist目录里面是index.html和一堆静态资源把它们全部复制到SpringBoot的static目录下然后重新打jar包。启动jar访问http://localhost:8080后端服务会直接把index.html返回给浏览器静态资源和接口完全同域连跨域问题都不存在。有人会问两个项目各管各的不整合行不行也行前端部署到nginx、后端打jar但这对于毕设来说多了一套服务器环境的复杂度。一个jar包跑通全部功能演示的时候也更不容易出意外。7.2 前端路由刷新404的解决思路这是部署阶段最经典的问题。你用Vue Router的history模式本地npm run serve一切正常但打成jar包后访问首页正常一刷新子页面就404。原因是history模式的路由是前端模拟的真实服务器上不存在这个URL路径刷新时后端找不到对应资源就返回404。解决方案有两个。一个是后端加转发规则凡是匹配到前端路由路径的请求统一转发到index.html。在SpringBoot里可以加一个Controller用RequestMapping匹配常见路径再forward。这个方案比较繁琐前端新增路由要同步改后端。另一个更省事把Vue Router的模式改成hash模式。hash模式的URL长这样http://localhost:8080/#/resident刷新时服务器只认#前面的部分也就是根路径天然不会404。缺点是URL样式不够美观但毕设演示完全够用。如果你在答辩时能解释清楚这两种模式的区别老师反而会觉得你深入了解了前端路由机制。7.3 SpringBoot 2.x与3.x的兼容性问题实操提醒这一节想专门提醒版本问题。现在网络上大量课程和源码模板还是SpringBoot 2.x的写法如果新建项目时手滑创建了SpringBoot 3.x会遇到一系列兼容性问题。最典型的是javax包名变成了jakarta。SpringBoot 2的Controller和Servlet相关代码用的是javax.servletSpringBoot 3改成了jakarta.servlet。如果你在3.x项目里粘贴旧代码直接用javax开头编译直接报错。解决办法不是逐个改import而是确认pom里的SpringBoot版本如果项目是2.x的就别去升级parent。另一个版本坑在MySQL驱动。SpringBoot 2用的JDBC驱动是mysql-connector-java或者com.mysql.cj.jdbc.DriverSpringBoot 3里驱动坐标变了连数据库也可能报Unable to load authentication plugin。遇到这种问题检查数据库驱动版本是否和SpringBoot版本匹配不要盲目升级。所以我的建议很明确拿到一套源码第一件事是看pom.xml确定SpringBoot版本、JDK版本、驱动版本然后让本地环境去对齐这个组合而不是让代码来适配新版本。版本配对理顺了部署阶段能避开三分之二的坑。如果你想把项目放到云服务器上给评审老师看数据库连接配置不要写死明文密码用application.yml里的配置项管理启动时通过环境变量覆盖。服务器上只开放必要端口MySQL不要直接暴露到外网。这套项目在答辩演示场景够用也不必上更复杂的容器化方案。8. 如果这是你的毕设从这套源码二次开发出差异化的三个方向大部分同学拿到的源码是同一套大家做的系统都一样答辩时老师一看就觉得又是这个。怎么做出差异化让老师觉得你动了脑子我给你三个可行的方向都不需要推翻重来而是在现有基础上加功能。8.1 加数据可视化ECharts统计页面的落地方式第一个方向是做统计报表可视化。原来的系统数据都是表格展示你可以在首页加一个Dashboard用ECharts画几类图历年参保人数柱状图、缴费金额趋势折线图、报销类型占比饼图。这些图表的数据来源就是后端聚合接口——按年份分组统计参保记录、按医疗类型分组统计报销金额。SQL都是简单的GROUP BY后端新增一两个Controller接口前端用ECharts的init方法渲染一个页面两天能做完。这个方向的答辩价值在于你可以讲出分组聚合的SQL优化思路比如加索引、避免全表扫描。虽然数据量不大但想法是完整的。8.2 加报表导出EasyExcel批量处理居民和缴费数据第二个方向是Excel导入导出。管理系统的经办人经常需要把居民名单导出成表格或者把纸质信息批量导入系统。引入EasyExcel提供居民列表导出接口、缴费记录导出接口、Excel批量导入新居民接口。前端的操作就是一个导出按钮调用下载接口或者上传excel文件调用导入接口。这里面的技术点是Excel模板的表头定义、导入时的校验逻辑比如身份证号格式、必填项校验、出错时返回具体行号错误信息。这个功能是真实业务里高频使用的老师会觉得你的系统接地气。8.3 答辩前必须能自己讲清楚的五个问题最后我列五个答辩大概率被问的问题你提前想清楚为什么采用前后端分离架构好处是什么数据库为什么拆这么多张表居民表和参保记录表是什么关系如果两个经办人同时给同一个居民缴费会不会产生重复缴费代码怎么保证权限控制是怎么实现的不同角色登录看到的菜单是否不同如果数据量达到十万级现有的分页查询和统计SQL会不会变慢怎么优化第五个问题回答思路是给查询字段加索引比如insurance_record表的resident_id和year字段联合建索引分页不要用深分页的LIMIT offset写法可以用游标或者索引条件下推。你不需要真的优化但能说出方案就是加分项。最后再分享一点个人体会。实操这类项目真正值钱的部分不是那几十张页面而是你亲手把一个请求从浏览器按钮一路跟到SQL语句再回到页面的全过程。我建议无论你最终是直接用源码还是自己敲都留出一个完整的下午把这条链路自己断点走一遍登录、建档、缴费、报销审核、退出。中间遇到任何报错都自己去读堆栈、去改配置而不是立刻求助于别人。你会发现这一遍下来答辩时老师问什么你都能聊上几句。这个项目我前后带人跑过很多次每次都有人卡在不同地方但凡是肯自己动过一次断点的人最后都稳稳过了答辩。