城乡居民医疗信息管理系统:SpringBoot+Vue毕设全栈开发实战
发布时间:2026/9/28 11:16:23 作者:尧图编辑部 阅读量:1,286

做毕设最痛苦的事不是代码写不出来而是项目名字起好了脑子还是一片空白。“SpringBootVue web城乡居民基本医疗信息管理系统”光看这个标题你会猜想它是个多庞大的政务级系统。真把源码打开一看落到技术上就是一个标准到不能再标准的 Java 全栈信息管理系统后端 SpringBoot 负责接口和业务规则前端 Vue 负责页面交互MySQL 存储数据。功能上绕不开增删改查、分页搜索、登录权限、统计报表这老几样。但它的价值恰恰就在这里——结构清晰、技术主流、流程完整既能当毕设拿得出手也能当课设交得上去还能作为一个入门前后端分离开发的最佳练手项目。这篇文章会把“城乡居民基本医疗信息管理系统”从立项、建表、写接口、调前端、打包部署到答辩展示完整拆开揉碎给你看。我会重点讲清楚哪些设计是同类系统通用的套路哪些坑是新手必踩的雷还有答辩时怎么把话说得漂亮。无论你是打算拿这份源码直接二开还是自己从零手写一遍读完都能少走不少弯路。1. 项目全貌拆解这其实是个“标准政务业务系统”1.1 功能地图与业务闭环先说结论城乡居民基本医疗信息管理系统本质上是社保类业务系统的“缩略版”真实世界里对应的东西就是城镇居民医保、新农合这些业务的后台管理系统。既然是“医保管理系统”它的核心业务就是围绕“人”和“钱”两条线展开。人这条线管的是居民的基础档案——姓名、身份证号、户籍地址、参保状态。钱这条线管的是缴费记录和报销记录——居民交了多少钱生病住院花了多少钱医保报了多少个人自费多少。再往上一层就是给管理员看的数据汇总比如参保人数统计、各年度缴费金额汇总、报销支出统计。所以功能模块通常就长这样系统登录管理员账号登录、退出可能区分超级管理员和普通操作员居民信息管理居民档案的增删改查、身份证号唯一性校验、条件筛选和分页参保缴费管理按年度登记参保记录处理续保、停保记录缴费档次和金额报销审核管理录入就诊报销单、核算报销金额、审核状态流转慢病管理可选登记特殊慢性病患者的备案信息统计看板参保人数、缴费总额、报销总额等可视化报表系统管理用户管理、角色分配、操作日志、数据字典这个功能地图一列出来你会发现它和电商系统、OA系统的后端逻辑几乎没有本质区别。凡是别人说“我会 CRUD”指的就是这套东西。但医疗保险业务的特点在于它有一个非常严格的“业务状态流”——居民从参保、缴费、就诊到报销每一步都有明确的状态字段约束这比单纯堆 CRUD 更接近真实企业项目。1.2 为什么 SpringBoot Vue 成了毕设第一梯队十几年前做毕设主流组合是 JSP Servlet MySQL写出来的东西是前后端不分的“单体 JSP 页面”看着就老。后来流行 SSMSpring SpringMVC MyBatis做后端搭配 EasyUI 或 Layui 这类 jQuery 前端库也算常见。而现在打开任何招聘软件Java 后端岗位的技能要求里几乎都写着 SpringBoot前端岗位里 Vue 或 React 至少会一个。所以 SpringBoot Vue 等于踩在了当前毕业设计的“最大公约数”上。对它个人学习者来说最大的好处是前后端分离之后职责非常清楚。后端写接口、处理业务逻辑前端只管渲染页面、调接口、展示数据通过 JSON 交互。调试的时候一旦数据不对用浏览器 F12 看 Network 面板接口是 200 还是 500返回体里报什么错一眼就能定位。这是 JSP 时代敢都不敢想的体验。还有一个隐藏价值是技术栈的“可解释性”。答辩时你可以说后端用 SpringBoot 的约定优于配置简化了项目搭建前端用 Vue 的组件化开发实现了页面复用数据库用 MySQL通过外键和索引保证数据一致性。这三句话一出来评委就知道你是真的做过而不是照着网上的源码跑通就算完。1.3 三类人应该怎么用这份源码毕业设计毕设。你的任务是“在别人代码基础上做出自己的东西”。我建议先完整跑通然后至少替换一个业务模块比如给居民信息模块加上 Excel 批量导入导出或者把统计报表从表格换成 ECharts 图表然后写进论文的创新点里。课程设计课设。课设的时间一般只有两三周核心是“能跑、能演示、有文档”。这种情况下不需要做太多二次开发把项目的业务逻辑、表结构、接口文档梳理清楚再准备一份流畅的演示流程就够了。纯粹学习学习。如果你是正处于 Java 入门到进阶阶段的新手我的建议恰恰相反——不要直接改代码。先把表结构搞懂再跟着代码走一遍从 Controller 到 Service 再到 Mapper 的数据流最后关闭源码自己从空项目重新写一遍哪怕只写一个模块。这个过程的价值是任何教学视频都替代不了的。2. 系统设计与数据库建模决定项目上限的关键环节2.1 后端分层架构与项目结构大多数这类系统源码都是标准的“四层结构”Controller 层接收前端请求Service 层处理业务逻辑MapperDAO层负责数据库操作Entity 实体类对应数据表。额外的会有 Config 配置类、Common 公共类、DTO/VO 数据传输对象还有一个放工具类JWT、密码加密、日期处理的 Utils 包。项目目录结构一般这样命名src ├── main │ ├── java │ │ └── com.xxx.medical │ │ ├── controller # 接口入口 │ │ ├── service # 业务逻辑层 │ │ ├── mapper # MyBatis 数据访问层 │ │ ├── entity # 实体类 │ │ ├── dto / vo # 请求/响应对象 │ │ ├── config # 配置跨域、拦截器、Swagger │ │ ├── common # 统一返回体、异常处理 │ │ └── utils # 工具类 │ └── resources │ ├── mapper # MyBatis XML 文件 │ └── application.yml # 核心配置这里有一个非常关键的经验永远不要把 Entity 直接返回给前端。原因很简单前端不需要看到数据库字段的全部比如密码的哈希值、逻辑删除标记 deleted还有一些你不想暴露的内部字段。正确做法是建一个 VOView Object只封装前端需要的字段。很多新手写项目不区分 Entity 和 VO图省事直接返回实体类这在课堂作业里能跑但放到实际项目里会被老同事骂死。因此你会看到优秀的源码里总会有一个统一返回体常见命名是 Result 或 R结构类似public class ResultT { private Integer code; // 200成功500失败 private String message; // 提示信息 private T data; // 业务数据 }所有接口返回的都包一层 Result。这样做最大的好处是前端 axios 拦截器可以统一处理错误码而不是每个页面单独判断返回结构。这也是为什么你能从“它有没有统一返回体”判断一份源码到底写得好不好。2.2 核心表结构设计思路数据库设计是这类系统的灵魂。我见过的“烂代码项目”居民信息、缴费记录、报销记录全部塞在一张表里字段多得吓人查询效率低下、逻辑混乱。而好的设计一定是“主表 子表”的多表结构。核心表通常包括这五张sys_user系统用户表resident居民信息表insurance_record参保缴费记录表medical_record就诊报销记录表chronic_disease慢病登记表可选下面是两张表的字段设计示例你可以对照自己手里的源码看是不是这个套路。居民信息表 resident字段名类型说明idbigint主键自增id_cardvarchar(18)身份证号必须唯一namevarchar(50)姓名gendertinyint性别1男 2女birth_datedate出生日期household_typevarchar(20)户籍类型城镇/农村addressvarchar(255)家庭地址phonevarchar(11)联系电话insurance_statustinyint参保状态0未参保 1正常 2停保create_timedatetime创建时间update_timedatetime更新时间deletedtinyint逻辑删除标记attention逻辑删除字段 deleted 是这套设计的重头戏。真实业务里的居民档案是不能物理删除的因为参保和报销记录都关联着居民一旦删掉历史数据就断了。所以设计上统一用 0 表示有效、1 表示已删除查询时强制带条件WHERE deleted 0。这也是答辩时一个很好的加分点。参保缴费记录表 insurance_record字段名类型说明idbigint主键resident_idbigint关联居民id外键yearvarchar(10)参保年度如2025pay_levelvarchar(20)缴费档次pay_amountdecimal(10,2)缴费金额pay_statustinyint缴费状态0未缴 1已缴operator_idbigint操作人idcreate_timedatetime创建时间为什么缴费记录是单独一张表而不是在居民表里加一个“缴费状态”字段因为医保是按年度缴费的一个居民会有 2024、2025、2026 多条缴费记录。如果把它塞进居民表表结构会爆炸也没法按年度统计缴费总额。这就是数据库第二范式在真实业务里的体现。医疗报销表 medical_record 核心字段通常有resident_id、hospital_name、visit_date、total_amount总费用、medical_expense医保报销、self_expense自费、reimburse_status报销状态其中 reimburse_status 一般用整型枚举例如 0 待审核、1 已通过、2 已驳回。2.3 权限与安全设计医保系统最不能丢的一环医疗数据属于个人敏感信息哪怕只是个毕设权限和安全的“骨架”也得摆出来。基础做法是三张表用户表 sys_user、角色表 sys_role、中间表 sys_user_role。如果不做角色多对多最少也要在用户表里加一个 role 字段区分管理员和普通操作员。登录校验方面主流方案是 JWT用户登录成功后签发一个 token前端把 token 存在 localStorage每次请求在请求头带上Authorization: Bearer token后端通过拦截器验证 JWT 是否有效然后从 token 里解析出用户信息。Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 if (request.getRequestURI().contains(/login)) { return true; } String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { throw new BusinessException(401, 未登录或登录已过期); } // 解析 token校验签名和过期时间 Claims claims JwtUtil.parseToken(token.replace(Bearer , )); request.setAttribute(userId, claims.get(userId)); return true; } }再说密码存储。我见过特别多毕设项目的用户表密码是明文比如 123456 直接存。真做了医保系统你会发现明文存储一旦数据库泄露所有账号都是裸奔状态。正确的做法是用 BCrypt 或 MD5 加盐。虽然 BCrypt 对新手来说要引入额外依赖但在答辩时你可以明确地说“为了防止拖库导致密码泄露我对敏感字段做了加密处理”这在医疗类项目里是非常对口的加分点。还有一个细节就是 XSS 防护。最近有个热搜词是“SpringBoot 项目全局过滤器处理上传 PDF 文件时 XSS 攻击”说明很多人在文件上传和富文本过滤这块吃过亏。如果你的系统里有居民备注、审核意见这类可输入文本的字段一定要在前端做输入校验或后端统一加过滤器防止有人提交script标签。哪怕只是简单的字符串替换也说明你有安全意识。搜索引擎热词里反复出现这类问题恰恰说明它确实是这个领域的高频坑。3. 核心功能模块的落地实现从接口到页面的完整链路3.1 登录认证与用户权限的落地登录模块是所有管理系统的入口也是代码量不大但细节极多的模块。流程上前端引入 axios 后在请求拦截器里统一注入 tokenaxios.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer token } return config })后端方面登录接口接收用户名密码校验密码成功就生成 JWT 返回。这里有一个特别推荐的细节——登录时顺便查一下用户的角色把角色信息放进 token 或返回体里前端拿到之后控制菜单显示。这样就能实现“管理员能看到系统管理菜单普通操作员只能看到业务菜单”的差异化。在这个环节上很多初学者会纠结 Vue 路由的权限控制。实际上 Vue Router 有 beforeEach 路由守卫可以每次跳转前判断有没有 token没有就重定向到登录页。热搜词里“vue路由参数”和“vue安装及环境配置”说明大家经常卡在这一块我建议你先不要过度设计动态路由把“登录拦截、无权限跳转、退出清理 localStorage”这三件事做好就够了。3.2 居民档案管理的分页与条件检索居民信息管理是系统里使用频率最高的功能核心需求就是“分页 多条件查询”。常见查询条件组合有姓名模糊匹配、身份证号精确匹配、参保状态下拉筛选。后端代码最常用的组合是 MyBatis 的 PageHelper 分页插件。用法三步走// 1. 引入依赖 mybatis-pagehelper // 2. 在 Service 中设置分页参数 PageHelper.startPage(pageNum, pageSize); // 3. 紧跟其后的第一条查询就是分页查询 ListResidentVo list residentMapper.selectByCondition(query); PageInfoResidentVo pageInfo new PageInfo(list);这个插件在网上讨论得非常热烈原因是它太方便了但也容易误用。最重要的经验是PageHelper.startPage后面必须紧跟一条 Mapper 查询中间不能夹带其它 SQL 操作否则分页会失效或者作用到错误的查询上。我在项目里看见过有人在一个方法里先 query 了一次菜单列表再 query 居民列表结果第一段查询也被拦截分页Bug 就悄悄出现了。分页接口的返回结构一般包含 total总条数、list当前页数据、pageNum页码、pageSize每页条数。前端用 Element UI 的 el-table 配合 el-pagination 组件页码变化时重新请求接口。这个模式在前后端分离项目里是“肌肉记忆级”的操作你一定要亲手敲一遍。顺便说一个条件查询容易出的错身份证号、手机号这类字段查询接口不要把等值查询写成了 like 查询。既是精确匹配又是模糊匹配用户把身份证号输入完整了反而查不到——因为数据库字段是 varchar(18)你用了like %部分号码%看似没问题但如果业务要求严格甚至会有两个不同身份证号因包含关系被同时查出来。我建议身份证号一律用等值姓名才用模糊匹配。3.3 参保缴费和报销审核业务规则的“状态机”设计如果说增删改查是管理系统的标配那参保登记和报销审核就是医保系统的“差异点”。这两块能看出一个开发者有没有真正想过业务闭环。参保登记的流程大概是这样的选择居民 - 选择参保年度 - 选择缴费档次 - 生成缴费记录。这里就有业务规则同一个居民同一年度不能重复参保。比如居民“张三”2025 年已经参保了操作员再点一次参保系统必须给出“该居民本年度已参保”的提示。这个校验在 Service 层做不要依赖数据库唯一索引兜底。再比如报销审核。一张报销单从提交到结束要经历待审核 - 审核通过 / 审核驳回。已通过的记录在统计报表中计入医保支出被驳回的记录有驳回原因字段。这就是一个典型的状态机流程。我强烈建议你在代码中写一个枚举类来定义这些状态public enum ReimburseStatus { PENDING(0, 待审核), APPROVED(1, 审核通过), REJECTED(2, 已驳回); private final int code; private final String desc; }用枚举而不是魔法数字好处是代码里不会出现if (status 1)这样看不懂的硬编码。过两周你自己回来看代码看到ReimburseStatus.APPROVED.getCode()立刻知道这个 1 是什么意思。这就是可读性也是答辩时能讲出深度的点。热搜词里“java基础”和“java面试题”老是出现其实很多面试题考的就是你有没有这种代码洁癖。再补充一个业务细节报销金额核算。居民医保的报销规则一般不是“全额按比例报”而是有起付线和封顶线的比如“超过 500 元以上的部分按 60% 报销最高报销 2000 元”。这样一个简单的报销核算逻辑用代码实现起来就是一个纯函数public BigDecimal calculateReimbursement(BigDecimal totalAmount) { // 起付线500元 BigDecimal threshold new BigDecimal(500); BigDecimal ratio new BigDecimal(0.6); BigDecimal maxAmount new BigDecimal(2000); if (totalAmount.compareTo(threshold) 0) { return BigDecimal.ZERO; } BigDecimal eligible totalAmount.subtract(threshold).multiply(ratio); if (eligible.compareTo(maxAmount) 0) { return maxAmount; } return eligible.setScale(2, RoundingMode.HALF_UP); }这个逻辑放到 Service 层比让前端传一个计算好的报销金额回来要可靠得多。同时也说明你不只是“会 CRUD”而是真的理解了业务规则由后端收敛这条企业开发原则。3.4 统计报表让数据说话的最后一步一个管理系统如果没有统计模块总觉得差了最后一口气。医保系统的统计报表一般包括以下几类按年度统计参保缴费总人数、缴费总金额按月份统计报销笔数、报销支出总额按户籍类型统计参保覆盖率城镇 vs 农村慢病病种分布统计后端实现思路非常简单就是“SQL 聚合”。比如统计历年缴费总额SELECT year, SUM(pay_amount) AS total_amount, COUNT(*) AS person_count FROM insurance_record WHERE pay_status 1 GROUP BY year ORDER BY year DESC;做统计报表最需要注意的问题是金额精度。MySQL 的 decimal 类型传到 Java 后会变成 BigDecimal前端拿到后如果是 JSON 序列化BigDecimal 会被转成数字一般没问题。但如果你用了 double 来接收金额字段就可能出现 0.1 0.2 ! 0.3 这种精度丢失问题。做医疗类管理系统凡是涉及钱的字段一律用 BigDecimal别碰 double。前端可视化方面最简单的是用 ECharts 的柱状图、饼图展示。Vue 项目里安装 echarts 后用this.$echarts.init()渲染通过后端接口拿到聚合数组再 setOption 赋值。这一步视觉效果拔群答辩演示时一打开看板评委的注意力立刻就被抓住了。4. 前后端联调、部署与环境配置4.1 本地联调必做的两个配置前后端分离项目第一次联调时新手最常见的困扰就是跨域报错。前端跑在http://localhost:5173Vite 默认端口后端跑在http://localhost:8080两个端口不同浏览器默认会拦截跨域请求。你要做的不是在后端配置全局允许跨域也不是在前端改什么神秘参数而是用代理解决。如果是 Vue CLI 创建的项目在vue.config.js里配置module.exports { devServer: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }意思就是前端发出的/api/login请求会被代理到http://localhost:8080/api/login浏览器看到的还是同源请求。这是企业项目里非常标准的联调方式比后端加CrossOrigin或者全局 CorsFilter 要优雅得多。当然后端也常配一个跨域过滤器兜底两个都配也不冲突。后端方面强烈建议集成 Swaggerspringfox 或 springdoc。访问http://localhost:8080/swagger-ui.html所有接口一目了然还能直接在页面上测试。答辩时打开 Swagger 页面给评委看接口文档又是一个稳赚好感分的小细节。4.2 打包部署从开发环境到“能演示”的最后一公里很多同学项目在本地跑得飞起一到要交给老师验收或者部署到云服务器就露怯。其实部署流程是固定的。后端先打包成可执行 Jar。在 IDEA 里执行 Maven 的package命令或者命令行mvn clean package -DskipTests然后在target目录下生成.jar文件通过java -jar 项目名.jar启动。如果你想让项目存活在服务器上而不是关掉终端就死就用 nohupnohup java -jar medical-system.jar --server.port8080 app.log 21 前端就简单了执行npm run build生成一个dist目录。然后有两种部署方式。一种是直接把 dist 目录扔到 Nginx 的 html 目录下在nginx.conf里配置将/api路径反向代理到后端 8080 端口。另一种是把前端打包好的静态资源复制到后端项目的src/main/resources/static目录下这样后端 Jar 会同时提供接口和页面访问起来相对省事但不够“前后端分离”所以我不太推荐第二种。Nginx 配置中最容易踩的坑是 Vue Router 的 history 模式刷新 404。因为前端路由是前端自己控制的后端服务器不知道/resident这个地址刷新时会直接报 404。解决方案是在 Nginx 里加一行try_files $uri $uri/ /index.html;让所有不存在的路径都回到 index.html由前端路由接管。这个坑几乎每个部署 Vue 项目的人都遇过写进博客绝对能帮一批人。4.3 环境配置常见报错速查这部分我直接整理成表格都是我亲眼见过同学栽进去的“高频事故”。症状原因解决方案启动报Access denied for user rootlocalhost数据库账号密码或权限不对检查 application.yml 中的账号密码或执行授权 SQL接口查询报错Table doesnt exist数据库只导入了结构没导入数据用 Navicat 导入完整的.sql文件数据表和测试数据一起导能登录但查询中文全是乱码数据库字符集不是 utf8mb4创建库时指定DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci连接 URL 加characterEncodingutf8连接 MySQL 8.x 报Public Key Retrieval is not allowed连接 URL 缺少允许公钥检索参数在 JDBC URL 末尾加allowPublicKeyRetrievaltrue后端端口被占用上次启动没停干净或其它程序占用Windows 用 netstat -anonpm install 卡死或慢网络问题或镜像源问题换成淘宝镜像源npm config set registry https://registry.npmmirror.com前端请求接口一直 404代理没配置或接口路径拼接错误检查代理配置是否生效统一在 axios 中配置 baseURL这里单提一下 MySQL 8 的时间时区问题。连接 URL 里经常要写serverTimezoneAsia/Shanghai否则会报The server time zone value йʱ is unrecognized。一劳永逸的办法是在 MySQL 命令行里执行set global time_zone 8:00或者建库时直接指定字符集和排序规则。这些都是老生常谈的问题但每年毕设季都有大量人卡在这一步可见搜索引擎里 “mysql安装配置教程”、“mysql设置默认值为0” 这类热搜词为什么一直居高不下。5. 答辩展示与源码二开的正确姿势5.1 答辩演示只讲一条业务主线很多同学答辩时习惯按菜单顺序逐个功能演示——登录一下、点点居民信息、点点缴费、点点报销像逛淘宝一样没重点。评委听下来只觉得“哦是个系统”但不知道你到底做了什么。我的建议是“业务主线演示法”。开场就说清楚一句话“本项目围绕城乡居民医保的核心业务闭环展开即居民参保 - 缴费 - 就诊 - 报销。” 然后顺着这条线往下走第一步登录系统展示如何区分管理员与普通操作员权限第二步新增一个居民信息强调身份证唯一性校验和逻辑删除设计第三步为该居民办理年度参保缴费展示重复参保校验第四步录入一条就诊报销记录走审核流程展示状态流转第五步打开统计看板说明刚才的操作如何反映在缴费总额或报销支出里五个步骤串起来就是完整的数据闭环。评委看一眼就会觉得“这个学生懂业务”而不是只会点按钮。评委常见的几个提问也要提前准备一下“分页是怎么实现的” 答PageHelper 插件startPage 后由插件拦截 SQL 自动拼接 limit。“为什么用 JWT 而不用 Session” 答Session 依赖服务器端存储分布式扩展时有问题JWT 无状态服务端不用存会话适合前后端分离。“两个用户同时操作同一张报销单怎么办” 答可以加乐观锁字段 version更新时校验版本号。哪怕没实现能说出这个方案也很加分。“数据库删除数据会不会导致关联记录丢失” 答采用逻辑删除业务数据以留痕为准。5.2 拿到一份源码后第一步该做什么最后聊聊源码消化的问题。不知道为什么很多人拿到源码第一反应是“先跑起来”跑完就扔在一边。但如果你真的想把这个项目变成自己的东西我建议按照这个顺序来先别急着运行。用 Navicat 打开数据库把每张表看一遍手动画出 ER 图搞清楚表与表之间的外键关系。这一步能筛掉一半走马观花的人。然后是全局搜索“TODO”看看作者留了什么没做完的活这些地方往往是你下手改代码的最佳切入点。再往后才是运行项目打断点从登录接口跟到数据库返回把这个数据链路彻底走一遍。我自己遇到过很多来问“我把源码改了但报错了”的同学一查原因连表结构都没看就直接改代码字段名对不上当然报错。所以我的个人原则是拿到一份新项目源码不是在 IDEA 里打开就完事先画 ER 图再谈改代码。按这个顺序你能把一份毕设源码学出至少三倍的价值。这几年我调试过的毕设项目没有一百也有几十个最大的感觉是信息管理系统这类项目看起来简单但它对企业级开发的核心要素其实都有涉及分层思想、表关系设计、权限控制、状态流转、前后端交互、部署上线。把这个项目真正吃透SpringBoot 和 Vue 的基础框架就在你的脑子里了。以后再做任何管理系统无非是换业务表、换状态流程而已。最后再分享一个实际操作中的小技巧本地开发时连数据库别用 root 账号单独建一个普通用户授权业务库权限这样既安全又避免误删系统库生产环境部署时关闭 Swagger 和 Actuator 的敏感端点细节做到位项目才算真正收尾。