每次有同学拿医院信息系统的选题来找我,我先问一句:你是把它当一个“管理后台”做,还是当一个“医疗业务系统”做?这个回答基本决定了作品最后能拿多少分。Spring Boot 电子病历管理系统(毕业设计源码编号23540)这类题目常年热门,表面看就是一套增删改查,实际上却暗含数据库建模、状态流转、权限控制、事务一致性这些硬功夫。这篇文章我把平时带项目积累的经验揉进去,拆一拆这套系统怎么做才像样,也把从技术选型到答辩展示的整条思路梳理给准备用它做毕业设计的同学参考。1. 先搞清楚:电子病历到底管理的是什么,这题就不能只写增删改查很多同学拿到题目第一反应是“患者表、医生表、病历表,加几个页面”。这么想不算错,但做完基本也就是个演示级作品。电子病历系统的业务核心不是“存病历”,而是“记录一次医疗行为发生的完整过程”。你写的每一张表、每一个状态,本质上都在回答一个问题:这个病人在什么时间、被谁、按什么流程、做了哪些诊疗操作。1.1 一次就诊背后的数据闭环我把这套系统要覆盖的业务链拉直了看,大概是这样的:患者建档是入口,紧接着是挂号或分诊,然后医生接诊。接诊之后产生核心诊疗数据:主诉、现病史、既往史、体格检查、初步诊断。诊断之后是处理意见,可能是开药,可能开检查单、检验单,也可能直接建议住院。再往后,药房执行发药,护士录入体征、执行医嘱,费用模块记录每一笔收费。最后是复诊、病程记录、出院归档。这一串流程里,病历不是孤立的一张大表,而是围绕“一次就诊记录”展开的一组数据。患者可能会来很多次,所以患者和就诊是父子关系;一次就诊可能产生多份病历文书,所以就诊和病历又是一对多。医嘱挂在就诊下面,检查、检验报告也挂在就诊下面,费用又和医嘱关联。这个关系理顺了,数据库设计才不会变成所有表都去关联患者表的大平铺。1.2 从角色反推功能:四类人眼里是完全不同的四个系统做系统之前先列角色,这是老生常谈,但医疗系统的角色差异比大多数管理系统都大。我至少会划分出四类角色:管理员:只管用户、角色、菜单、数据字典、系统参数,基本不参与诊疗流程。医生:核心使用者,需要患者列表、接诊工作台、病历编辑器、诊断录入、医嘱开立、检查检验单申请。护士:需要体征记录、医嘱执行、护理记录、输液/注射执行单,关注的是“该做什么、做没做”。药房:需要待审核处方、发药退药、药品库存预警、有效期管理。同一个系统,四类角色打开首页后看到的菜单完全不同,这叫功能级权限;同一个患者列表,医生只能看自己接诊过的患者,这叫数据级权限。很多毕设只做了前者,没做后者。而“医生只能看自己负责科室的数据”恰恰是医疗系统里最现实、也最容易被评委追问的点。1.3 状态流转比几十个接口更能体现设计能力一个用心做的电子病历系统,肯定有明确的状态机。就诊状态要经历:待接诊、接诊中、检查中、治疗中、待出院、已出院、已归档。病历状态要经历:草稿、待提交、已提交、已审核、已归档。医嘱状态要经历:已开立、已审核、已执行、已停止。这些状态不是想改就改的,得有流向约束。比如草稿才能编辑,已归档的不能再动;已发药的医嘱不能直接把库存加回来。实现上我建议用一个统一的业务状态流转方法,集中处理每一个合法迁移,而不是在十几个接口里各写各的 if else。状态迁好了,后面统计每个阶段耗时、查流程卡在哪,都是顺手的事。这一章看明白了,你就会发现这套系统不只是“电子病历”,它是“诊疗流程的数字化影子”。带着这个认知去做表格设计和接口设计,项目的高度完全不一样。2. 技术选型:Spring Boot 生态里最稳妥的毕设组合技术选型的核心逻辑是:在有限时间内最大化体现个人能力,同时保证项目能稳定跑起来、答辩能讲清楚。我见过太多同学在选型上追求“新”,最后被框架本身的问题拖死。下面这套组合我实测过很多次,是性价比最高的方案。2.1 后端版本与持久层框架:为什么我总推 2.7.x + MyBatis-PlusSpring Boot 版本建议选 2.7.x(比如 2.7.18),而不是 3.x。原因很现实:3.x 强制 JDK17,且部分老依赖不兼容,有的同学本机还是 JDK8 或者学校机房环境较老,光是环境就够折腾两天。2.7.x 配 JDK8 或 JDK11 都非常稳,网上可参考的资料最多,遇到问题一搜就有答案。持久层我推荐 MyBatis-Plus 3.5.x,理由有三条:单表 CRUD 几乎不用写 SQL,节省大量时间;分页插件 PageHelper 一条语句就能分页,写列表页效率极高;内置代码生成器,可以从表结构直接生成实体、Mapper、Service、Controller 四层代码。但这不代表你可以不懂 SQL。答辩时评委很喜欢问“你这条分页怎么实现的”“连表查询写在哪”,你至少能解释 MyBatis-Plus 的分页底层是拦截器改写 SQL 生成 LIMIT,关联查询还是要手写 XML 里的 ResultMap。为什么不用原生 MyBatis?不是不能用,而是纯手写 CRUD 太耗时,毕设时间宝贵,把时间花在业务设计上更值。为什么不用 JPA/Hibernate?Hibernate 自动建表、自动管理状态确实省事,但对字段和 SQL 的掌控感弱,一旦出现“为什么多查了一条”“为什么更新了整行”这类问题,新手很难排查。MyBatis-Plus 是半自动,SQL 和映射尽在掌握,更适合讲解。2.2 认证与权限:Sa-Token 还是 Spring Security,我站前者说到登录和权限,很多人下意识想到 Spring Security。它确实是行业标准,但学习曲线放在那里,Security 的过滤器链和认证流程对于新手来说过于抽象。而 Sa-Token 最直观的优势就是 API 简单:登录后一句StpUtil.login(userId),校验登录一句StpUtil.checkLogin(),做角色校验一句@SaCheckRole("doctor")。底层帮你封装好了 Token 的生成、存储、过期和拦截,业务代码里几乎不需要关心过滤器链。我给的组合是:Sa-Token 负责认证授权 + 自定义注解和数据权限校验,实现 RBAC 模型。如果你非要用 Spring Security,也不是不行,但请留出至少一周的时间熟悉它,而且答辩准备时间要加倍。相比之下,把精力花在业务功能上,收益明显更高。2.3 前端呈现方式:Vue3 前后端分离还是 Thymeleaf 服务端渲染这是另一个关键决策。如果时间充裕(两个月以上),我强烈推荐 Vue3 + Vite + Element Plus + Axios 做前后端分离。理由是:页面组件化以后写病历编辑、医嘱选择这类复杂交互方便得多;接口调用和 JSON 数据结构也能体现现代开发模式;答辩展示时前端项目和后端项目各跑一个进程,观感上更专业。如果时间真的紧,或者你 JS 基础比较弱,那 Thymeleaf + Bootstrap 服务端渲染也可以。Thymeleaf 的一个明显好处是数据直接在后端 Model 里,不需要处理跨域、Token 缓存等前端联动问题,一个 Spring Boot 工程跑起来就完事。它的问题是复杂交互写起来痛苦,但毕设的功能复杂度和真实产品比有限,用模板渲染完全能应付。我的建议是:除非你对自己写前端很有信心,否则第一版先用 Thymeleaf 把业务跑通,剩下的时间再考虑要不要迁移到 Vue 重构首页。不要上来就 Vue,一大把时间砸在环境、构建、路由上,回头业务没做透,那才是真正的翻车。2.4 中间件:加还是不加,要看你能不能讲清楚不少同学一看别人项目用了 Redis、用了 RabbitMQ,自己也想加。我的态度是:加了中间件,你就要为它负责。答辩时评委的一句“为什么这段逻辑要引入 Redis”就够你解释半天。这套系统里,Redis 可以用在三个非常合理的场景:存储登录 Token 的会话状态(Sa-Token 本身就支持 Redis 集成)、缓存数据字典和科室列表等低频变动数据、存图形验证码。这三个场景既真实,又容易讲清楚,能明显提升响应速度。至于 RabbitMQ,建议先不加。电子病历系统在单体毕业设计阶段没有跨系统异步通信的刚需,硬上消息队列只会暴露你对分布式架构理解的薄弱。MinIO 或其他对象存储可以看情况,如果要做病历附件上传,用它比把文件塞进数据库强得多。3. 数据库设计:医疗表结构里的关键约束与常见坑数据库设计是这类系统真正的分水岭。逻辑建得好,后端代码写起来行云流水;建得乱,后面每个接口都在为表结构补洞。我直接把核心表结构和容易踩的坑摊开讲。3.1 核心表结构与数据关系:从 ER 图到物理表先画大关系:用户表和角色菜单表负责权限;患者表独立存在,身份证号做唯一约束;就诊表是业务中转站,几乎一切都围着它转;病历表挂就诊;医嘱表挂就诊;药品表、检查表、检验表作为基础资料;收费表关联医嘱或检查单;操作日志表记录每一个关键动作。就诊表是这个系统的“总线”。它至少要有这些字段:患者ID、接诊医生ID、科室ID、就诊类型(门诊/急诊/住院)、就诊状态、主诉、初步诊断、收费状态、创建时间。这样一张表建好,后面统计科室接诊量、医生工作量、患者就诊次数都直接从它出发。再看病历表,它不能简单叫“medical_record”然后一个 content 字段打完收工。我建议表结构至少包含:就诊ID、病历类型(初诊/复诊/病程记录/出院小结)、标题、病历正文(富文本)、诊断结果JSON、状态、创建人、创建时间、提交时间、审核人、审核时间。其中“病历类型”特别重要,同一份就诊下可能有“首诊记录”和“病程记录”两种文书,用 type 字段区分比拆成两张表灵活得多。这里的唯一索引可以这样设计:同一个就诊下,相同类型的病历如果已经到了归档状态,就不允许再新建一份草稿,防止医生重复录入。当然,不同类型的病历可以并存。3.2 病历正文:富文本与结构化数据并存病历正文是这个系统最核心的内容载体。很多同学的方案是存一个 HTML 富文本字符串,其实可以但不够。纯 HTML 能解决“像病历”的问题,却解决不了“按诊断类型统计”“按症状筛选”这些结构化查询需求。我给你一个折中方案:主表里用 LONGTEXT 存富文本正文,保证展示效果和打印效果;另设一个结构化子表或 JSON 字段,存主诉、现病史、既往史、体格检查、诊断ICD码这类键值对。这样展示和统计两边都占住。如果不想建子表,MySQL 5.7 以上支持 JSON 字段,诊断信息直接塞JSON列也够用。这里我记得一个细节:JSON 字段虽然查询时可以用 JSON_EXTRACT 取值,但写 SQL 会复杂一点,不建议在 JSON 上建索引做核心查询。你还是拿它做低频统计和校验展示,高频查询放独立列里。还有一个小坑:富文本编辑器很容易引入不安全的 HTML,比如<script>。如果这个病历内容会被别的浏览器打开,XSS 漏洞就来了。做法是后端接收富文本后做白名单过滤,只保留 p、br、strong、em、span 这类常用标签,Strip 掉 script、iframe、link 等危险节点。前端不能完全信任,一定要在后端再过滤一遍。有些做毕设的同学不知道这个,答辩时被问到安全问题容易卡壳,提前注意很加分。3.3 时间字段、状态字段和逻辑删除:防御性设计三件套医疗系统对时间极其敏感。谁来建的就诊记录、什么时候提交的病历、什么时候审核的,全部要能追溯。MyBatis-Plus 的MetaObjectHandler可以统一处理createTime和updateTime的自动填充,避免每个 Service 里手动 set,既省事又不会漏。状态字段我强烈建议用tinyint加代码里的枚举类,不要直接存字符串。字符串“已提交”“审核中”看着直观,但一旦有人填了“已提价”,数据就废了。用数字枚举,通过注释和枚举类明确含义,既节省空间又方便做状态流转控制。PostgreSQL 同学可能喜欢用VARCHAR + CHECK约束,MySQL 里用 tinyint 就够了。逻辑删除是医疗数据的硬性要求,因为真实业务里病历是不能物理删除的,只能作废并留痕。MyBatis-Plus 提供了@TableLogic注解,删除时自动转 UPDATE,查询时自动加 WHERE deleted=0。这个特性一定要用,答辩时可以说这是为了满足医疗数据留痕要求。注意逻辑删除字段别加到唯一索引里去,不然逻辑删除后无法重建同身份证号的患者——这个问题我见过不止一次。3.4 医嘱、药品和收费的挂接:中间表别建薄了医嘱表和药品之间是多对多关系。一张医嘱可能开了三种药,一种药也可能在多次医嘱里出现,所以要建doctor_order_item中间表。这个表要承载的不仅仅是“哪个药品挂在哪个医嘱下”,还包括单次剂量、频次、数量、给药途径、用法说明、状态、已发药数量。很多同学把中间表建成了只有“医嘱ID+药品ID”,完全无法支撑发药、退药、费用统计。这个细节直接决定了药房模块能不能做出来。收费表建议挂接到医嘱明细,而不是只挂到医嘱头。因为费用往往按药品项计算,一次订单费用等于所有明细费用之和。这样退一盒药时,费用只调整那一项。否则只记总费用,退药就永远算不清楚账。建模阶段多想一步,后面写退药接口能省两小时。4. 核心功能实现:我从 0 到 1 绕过的几个坎技术选型和表结构定好,系统骨架就成型了。真正把系统填起来的时候,有几个功能点容易写崩,我逐个讲一下我的实现方案和踩坑记录。4.1 病历模板与动态表单:别再为每个科室硬编码一个页面电子病历最烦人的地方在于,内科病历和外科病历的字段不一样,门诊病历和住院病程记录也不一样。如果每张表单都写一个 Vue 组件或 Thymeleaf 模板去匹配,工作量巨大,而且后续加模板就得改代码,极难维护。我用的是 JSON 模板思路,类似动态表单设计。在数据库里建一张record_template表,一个模板是一段 JSON 配置。比如:{ "title": "门诊初诊病历", "sections": [ { "key": "chiefComplaint", "label": "主诉", "type": "textarea", "required": true, "maxLength": 500 }, { "key": "presentIllness", "label": "现病史", "type": "textarea", "required": true, "maxLength": 2000 }, { "key": "physicalExam", "label": "体格检查", "type": "textarea", "required": false, "maxLength": 1000 }, { "key": "diagnosis", "label": "初步诊断", "type": "select", "required": true, "optionsSource": "icdDict" } ] }前端拿到这个 JSON 后动态渲染表单,用户填完提交的也是 JSON。后端用统一的校验器根据 required 和 maxLength 规则完成参数校验,然后把数据同时写入结构化字段和富文本正文。以后要增加“出院小结模板”,只需要在后台配置新 JSON,不用动代码。这个设计在答辩时非常抢眼,因为它体现了抽象能力,属于典型的“低成本高表现”功能。4.2 医嘱开立与药品库存扣减:事务边界和并发扣减怎么处理开医嘱不是简单地往 order_item 里 insert 一行,还要同步扣减药品表里的库存。这两个动作必须在一个事务里,要么都成功,要么都失败。Spring 的@Transactional可以包住这个方法,但要记住事务只对RuntimeException回滚,自定义业务异常最好继承RuntimeException,或者显式配置rollbackFor。光有事务还不行,并发扣减是经典考点。我用的是乐观锁写法,不搞悲观锁,避免锁表拖慢正常业务。扣减 SQL 类似这样:UPDATE drug_stock SET stock = stock - #{quantity}, version = version + 1 WHERE drug_id = #{drugId} AND stock >= #{quantity}这条 SQL 的含义是:库存足够才更新库存,行数影响为 0 说明当前并发条件下数量不足,抛出异常让事务回滚,前端提示“库存不足”。它不需要显式SELECT ... FOR UPDATE,性能更好,代码也短。答辩时你可以说这模仿了秒杀系统的库存防超卖方案,虽然医疗场景并发量没那么大,但这个防御性设计很专业。还有一个很容易漏掉的:退药逻辑。病人没来取药或者医生停医嘱时,要把库存还回去。还多少?要注意区分“已开立未发药”“已发药未执行”“已执行”几种状态。我建议order_item里维护prescribedQuantity(开立数量)和dispensedQuantity(已发药数量)。退药时只能退dispensedQuantity剩余部分,且要同步更新费用表和库存表。这些都是真实业务里的细节,做进去之后系统一下就有了“产品感”。4.3 会话、鉴权与操作审计:医疗系统的底线逻辑登录会话方案我用 Sa-Token 后,拦截器配置非常简单。后端写一个配置类,注册 SaInterceptor,对/api/**路径开启鉴权,再按方法注解控制角色。比如医生接口上加@SaCheckRole("doctor"),管理员接口上加@SaCheckRole("admin"),未登录的请求在拦截器层面直接被挡掉。操作审计是医疗系统绕不开的。我用 AOP 做了一个@OperationLog注解,标在关键 Service 方法上,通过环绕通知把操作人、操作时间、操作类型、方法耗时、操作结果统一写入日志表。比如“医生提交病历”“药房发药”“管理员重置密码”这几个操作全部埋点。实现很轻量,但效果很重——答辩时评委若问“这套系统的安全性体现在哪里”,你可以直接展示这张审计表,再强调业务规范要求可追溯,安全性的回答就落地了。数据脱敏也得做。患者手机号、身份证号、住址属于敏感信息,列表接口不应该返回明文。我习惯在 VO 层统一处理:手机号保留前3后4,中间四位置星;身份证号保留前1后1;地址精确到小区或楼栋级别就停。不要等到前端再遮,后端口径统一才是真正安全的。4.4 文件上传与附件管理:格式、路径、下载三个细节病历系统通常还要支持上传检验报告图片、检查影像文件、出院小结 PDF。Spring Boot 里实现很简单,但有三处必须处理好:第一,文件类型白名单。不要用前端传的扩展名校验,别人改一下上传请求就能绕过。后端应该读取文件的 MIME Type 或判断文件头魔数,只允许 JPG、PNG、PDF 这几个常见类型,大小限制建议单文件 10MB 以内。第二,磁盘路径不可预测。保存文件时不要拼用户输入,统一用 UUID 重命名后放到按日期分层的目录里,比如/data/upload/2025/06/01/uuid.pdf。文件名存数据库时也存这个相对路径,绝对地址每次请求按配置拼接。千万别把上传文件名当作实际存储名,更不要直接拿文件名做下载路径,否则路径穿越漏洞很容易爆。第三,下载接口要做鉴权。不要直接走静态资源映射暴露整个上传目录,而是要写一个 Controller 接口,先校验登录状态和权限,再从数据库取出文件相对路径,用ResourceHttpMessageConverter把文件流返回给前端。这样所有文件访问都在权限保护之下。5. 从“能运行”到“能答辩”:演示路径与高频追问应对方案很多同学系统做完了,到答辩现场不知道先点哪个按钮。为了演示效果更好,我建议设计一条3分钟的业务闭环链路,按这条线走完,评委基本就能理解系统的全貌和亮点。5.1 一条 3 分钟的最佳演示链路我的推荐顺序是:先用管理员账号登录系统,演示图形验证码和登录成功后的菜单变化,顺带展示首页统计卡片;切到医生账号,打开“我的患者”,新建一位患者完成建档;给这位患者新建一次就诊,选择门诊类型;在接诊工作台里选择“门诊初诊病历”模板,演示模板自动带入表单的过程,填写主诉和初步诊断,提交病历;然后开一组医嘱,选择两种药品,提交后观察药品库存同步扣减;切到药房账号,看到待审核处方,执行发药,再切回医生视角确认医嘱状态变为已发药;最后切到管理员后台,查看操作审计日志里刚才那几步的操作记录。这条链路的妙处在于:它把四类角色、两大模块(病历+医嘱)、一条事务、一个安全机制全部串进了一次演示。不用刻意讲功能列表,业务自然就讲完了。5.2 评委最爱问的问题及回答思路答辩里常见问题其实相当一致,我梳理了一份高频问题清单:问题建议回答思路为什么用乐观锁而不是悲观锁?避免长事务锁表;库存充足场景下乐观锁失败概率低,遇到失败就回滚重试,符合医疗场景并发量不高的实际病历正文怎么防止 HTML 注入?后端白名单过滤危险标签;只保留安全富文本标签;在存储和展示两侧都做清洗如果两个医生同时编辑同一份病历怎么办?方案一是乐观锁版本号,更新时校验 version,冲突提示重新加载;方案二是操作前锁定病历,编辑期间其他人只读不可写数据库里的病历能不能删除?医疗数据必须留痕,物理删除一律禁止;逻辑删除必须由管理员操作并记录审计日志数据权限怎么控制的?医生查询患者列表时,SQL 自动追加“接诊医生=当前用户”或“科室=当前用户科室”的条件,通过 MyBatis 拦截器统一注入,避免在业务代码里重复写为什么用 Sa-Token 而不是 Shiro?四年前 Shiro 用得比较多,但 Sa-Token 对 Token 无状态鉴权、会话管理、踢人下线的集成更简洁,毕设场景下更直观易懂建议把每个问题都准备一个1分钟左右的答案,讲清“背景-方案-效果”就行,不要绕。5.3 答辩加分项:工程化细节比功能多更值钱几个很便宜的加分项,强烈建议做进去:统一返回格式。所有接口返回{ code, message, data }结构,失败返回错误码。这个改动本身不大,但在答辩时展示接口文档,层次感立刻不一样。全局异常处理器捕获业务异常和系统异常,统一转成标准返回体,页面不会弹裸的500错误。用 Knife4j 生成在线 API 文档,启动后打开/doc.html就能看到每个接口的说明和参数,这是最直观的工程化证据。多环境配置文件拆成 dev 和 prod 两套,演示时用 dev,说明你懂环境隔离。项目启动初始化脚本里创建好管理员账号和基础数据,让评委自己点两下就能上手,观感会好很多。6. 源码 23540 的 2.0 方向:这套系统还能往哪里长毕设做完不代表这套系统的生命力就结束了。如果时间有富余,或者后续想把它写成更完整的作品,下面几个方向是最自然的延伸路径。6.1 向医疗标准靠拢:结构化文档与归档导出目前病历内容虽然是 JSON 存储,但严格来说还不算标准化。进阶方向是引入临床文档架构(CDA)的简化版定义。简单理解:就是把病历的文档头、文档体、章节、条目拆成标准结构,让不同系统之间能够交换病历数据。实现不复杂:写一个导出服务,把数据库里的病历 JSON 渲染成标准 XML 或 PDF 文件,文件名按“患者ID-就诊日期-病历类型”规则生成,落在归档目录。这样打印、病案室归档、第三方调阅都有据可查。虽然毕业设计不一定需要走到这一步,但答辩时展示“病历数据可以按规范归档导出”明显更有临床场景的味道。6.2 患者端延伸:院外报告查询与消息通知给系统增加一个患者端的简化版门户,比如 H5 页面或小程序,提供的功能不需要多:登录、查看自己的就诊记录、查看检查报告 PDF、在线预约复诊。接口层已有完整的鉴权和数据模型,扩展患者端几乎是顺理成章,主要新增一套查询接口和页面。加上一条简单的消息通知机制——比如检查报告出结果后给患者推送一条待查看提醒,就能让系统从“医生端的工具”变成一个“闭合的服务链路”。这里要提醒一句:患者端和医生端的数据权限要重新过一遍,确保患者只能查自己名下的数据,防越权测试那种安全意识对医疗场景尤其重要。6.3 给正在肝毕设的同学:三条能省两周时间的实操习惯最后分享几条开发节奏上的经验。第一,拿到这套源码后,绝对不能直接拿去交差。我的建议是把核心模块重写一遍:数据库表自己建,登录权限自己配,病历模块自己写。重写不仅让你熟悉每一行代码,更重要的是答辩时心里有底。直接背别人的项目,评委随便追问一个关联查询就会措手不及。第二,每天推进后立即提交 Git 记录,至少每天一次,commit message 写清楚今天干了什么。这个习惯除了防误删外,还能在答辩时展示你的开发周期和分工,属于低投入高回报。第三,任何时间点都要保证项目能一键启动运行。毕设导师可能随时要看系统演示,如果演示时环境配置依赖过多、启动失败,印象分会掉得很快。尽量精简启动步骤,把初始化 SQL 和默认配置做到位,启动文档单独写清楚。我带的很多同学都是答辩前两周才意识到“能跑”本身其实就是一个巨大的加分项。电子病历这类题目之所以被反复选作毕设,就是因为它在复杂度、可实现性和展示效果上平衡得很好。按上面的思路把业务模型做透,把事务、权限、审计几个关键点讲明白,这套系统不仅是代码量的堆砌,更是一份拿得出手的作品。
每次有同学拿医院信息系统的选题来找我,我先问一句:你是把它当一个“管理后台”做,还是当一个“医疗业务系统”做?这个回答基本决定了作品最后能拿多少分。Spring Boot 电子病历管理系统(毕业设计源码编号23540)这类题目常年热门,表面看就是一套增删改查,实际上却暗含数据库建模、状态流转、权限控制、事务一致性这些硬功夫。这篇文章我把平时带项目积累的经验揉进去,拆一拆这套系统怎么做才像样,也把从技术选型到答辩展示的整条思路梳理给准备用它做毕业设计的同学参考。1. 先搞清楚:电子病历到底管理的是什么,这题就不能只写增删改查很多同学拿到题目第一反应是“患者表、医生表、病历表,加几个页面”。这么想不算错,但做完基本也就是个演示级作品。电子病历系统的业务核心不是“存病历”,而是“记录一次医疗行为发生的完整过程”。你写的每一张表、每一个状态,本质上都在回答一个问题:这个病人在什么时间、被谁、按什么流程、做了哪些诊疗操作。1.1 一次就诊背后的数据闭环我把这套系统要覆盖的业务链拉直了看,大概是这样的:患者建档是入口,紧接着是挂号或分诊,然后医生接诊。接诊之后产生核心诊疗数据:主诉、现病史、既往史、体格检查、初步诊断。诊断之后是处理意见,可能是开药,可能开检查单、检验单,也可能直接建议住院。再往后,药房执行发药,护士录入体征、执行医嘱,费用模块记录每一笔收费。最后是复诊、病程记录、出院归档。这一串流程里,病历不是孤立的一张大表,而是围绕“一次就诊记录”展开的一组数据。患者可能会来很多次,所以患者和就诊是父子关系;一次就诊可能产生多份病历文书,所以就诊和病历又是一对多。医嘱挂在就诊下面,检查、检验报告也挂在就诊下面,费用又和医嘱关联。这个关系理顺了,数据库设计才不会变成所有表都去关联患者表的大平铺。1.2 从角色反推功能:四类人眼里是完全不同的四个系统做系统之前先列角色,这是老生常谈,但医疗系统的角色差异比大多数管理系统都大。我至少会划分出四类角色:管理员:只管用户、角色、菜单、数据字典、系统参数,基本不参与诊疗流程。医生:核心使用者,需要患者列表、接诊工作台、病历编辑器、诊断录入、医嘱开立、检查检验单申请。护士:需要体征记录、医嘱执行、护理记录、输液/注射执行单,关注的是“该做什么、做没做”。药房:需要待审核处方、发药退药、药品库存预警、有效期管理。同一个系统,四类角色打开首页后看到的菜单完全不同,这叫功能级权限;同一个患者列表,医生只能看自己接诊过的患者,这叫数据级权限。很多毕设只做了前者,没做后者。而“医生只能看自己负责科室的数据”恰恰是医疗系统里最现实、也最容易被评委追问的点。1.3 状态流转比几十个接口更能体现设计能力一个用心做的电子病历系统,肯定有明确的状态机。就诊状态要经历:待接诊、接诊中、检查中、治疗中、待出院、已出院、已归档。病历状态要经历:草稿、待提交、已提交、已审核、已归档。医嘱状态要经历:已开立、已审核、已执行、已停止。这些状态不是想改就改的,得有流向约束。比如草稿才能编辑,已归档的不能再动;已发药的医嘱不能直接把库存加回来。实现上我建议用一个统一的业务状态流转方法,集中处理每一个合法迁移,而不是在十几个接口里各写各的 if else。状态迁好了,后面统计每个阶段耗时、查流程卡在哪,都是顺手的事。这一章看明白了,你就会发现这套系统不只是“电子病历”,它是“诊疗流程的数字化影子”。带着这个认知去做表格设计和接口设计,项目的高度完全不一样。2. 技术选型:Spring Boot 生态里最稳妥的毕设组合技术选型的核心逻辑是:在有限时间内最大化体现个人能力,同时保证项目能稳定跑起来、答辩能讲清楚。我见过太多同学在选型上追求“新”,最后被框架本身的问题拖死。下面这套组合我实测过很多次,是性价比最高的方案。2.1 后端版本与持久层框架:为什么我总推 2.7.x + MyBatis-PlusSpring Boot 版本建议选 2.7.x(比如 2.7.18),而不是 3.x。原因很现实:3.x 强制 JDK17,且部分老依赖不兼容,有的同学本机还是 JDK8 或者学校机房环境较老,光是环境就够折腾两天。2.7.x 配 JDK8 或 JDK11 都非常稳,网上可参考的资料最多,遇到问题一搜就有答案。持久层我推荐 MyBatis-Plus 3.5.x,理由有三条:单表 CRUD 几乎不用写 SQL,节省大量时间;分页插件 PageHelper 一条语句就能分页,写列表页效率极高;内置代码生成器,可以从表结构直接生成实体、Mapper、Service、Controller 四层代码。但这不代表你可以不懂 SQL。答辩时评委很喜欢问“你这条分页怎么实现的”“连表查询写在哪”,你至少能解释 MyBatis-Plus 的分页底层是拦截器改写 SQL 生成 LIMIT,关联查询还是要手写 XML 里的 ResultMap。为什么不用原生 MyBatis?不是不能用,而是纯手写 CRUD 太耗时,毕设时间宝贵,把时间花在业务设计上更值。为什么不用 JPA/Hibernate?Hibernate 自动建表、自动管理状态确实省事,但对字段和 SQL 的掌控感弱,一旦出现“为什么多查了一条”“为什么更新了整行”这类问题,新手很难排查。MyBatis-Plus 是半自动,SQL 和映射尽在掌握,更适合讲解。2.2 认证与权限:Sa-Token 还是 Spring Security,我站前者说到登录和权限,很多人下意识想到 Spring Security。它确实是行业标准,但学习曲线放在那里,Security 的过滤器链和认证流程对于新手来说过于抽象。而 Sa-Token 最直观的优势就是 API 简单:登录后一句StpUtil.login(userId),校验登录一句StpUtil.checkLogin(),做角色校验一句@SaCheckRole("doctor")。底层帮你封装好了 Token 的生成、存储、过期和拦截,业务代码里几乎不需要关心过滤器链。我给的组合是:Sa-Token 负责认证授权 + 自定义注解和数据权限校验,实现 RBAC 模型。如果你非要用 Spring Security,也不是不行,但请留出至少一周的时间熟悉它,而且答辩准备时间要加倍。相比之下,把精力花在业务功能上,收益明显更高。2.3 前端呈现方式:Vue3 前后端分离还是 Thymeleaf 服务端渲染这是另一个关键决策。如果时间充裕(两个月以上),我强烈推荐 Vue3 + Vite + Element Plus + Axios 做前后端分离。理由是:页面组件化以后写病历编辑、医嘱选择这类复杂交互方便得多;接口调用和 JSON 数据结构也能体现现代开发模式;答辩展示时前端项目和后端项目各跑一个进程,观感上更专业。如果时间真的紧,或者你 JS 基础比较弱,那 Thymeleaf + Bootstrap 服务端渲染也可以。Thymeleaf 的一个明显好处是数据直接在后端 Model 里,不需要处理跨域、Token 缓存等前端联动问题,一个 Spring Boot 工程跑起来就完事。它的问题是复杂交互写起来痛苦,但毕设的功能复杂度和真实产品比有限,用模板渲染完全能应付。我的建议是:除非你对自己写前端很有信心,否则第一版先用 Thymeleaf 把业务跑通,剩下的时间再考虑要不要迁移到 Vue 重构首页。不要上来就 Vue,一大把时间砸在环境、构建、路由上,回头业务没做透,那才是真正的翻车。2.4 中间件:加还是不加,要看你能不能讲清楚不少同学一看别人项目用了 Redis、用了 RabbitMQ,自己也想加。我的态度是:加了中间件,你就要为它负责。答辩时评委的一句“为什么这段逻辑要引入 Redis”就够你解释半天。这套系统里,Redis 可以用在三个非常合理的场景:存储登录 Token 的会话状态(Sa-Token 本身就支持 Redis 集成)、缓存数据字典和科室列表等低频变动数据、存图形验证码。这三个场景既真实,又容易讲清楚,能明显提升响应速度。至于 RabbitMQ,建议先不加。电子病历系统在单体毕业设计阶段没有跨系统异步通信的刚需,硬上消息队列只会暴露你对分布式架构理解的薄弱。MinIO 或其他对象存储可以看情况,如果要做病历附件上传,用它比把文件塞进数据库强得多。3. 数据库设计:医疗表结构里的关键约束与常见坑数据库设计是这类系统真正的分水岭。逻辑建得好,后端代码写起来行云流水;建得乱,后面每个接口都在为表结构补洞。我直接把核心表结构和容易踩的坑摊开讲。3.1 核心表结构与数据关系:从 ER 图到物理表先画大关系:用户表和角色菜单表负责权限;患者表独立存在,身份证号做唯一约束;就诊表是业务中转站,几乎一切都围着它转;病历表挂就诊;医嘱表挂就诊;药品表、检查表、检验表作为基础资料;收费表关联医嘱或检查单;操作日志表记录每一个关键动作。就诊表是这个系统的“总线”。它至少要有这些字段:患者ID、接诊医生ID、科室ID、就诊类型(门诊/急诊/住院)、就诊状态、主诉、初步诊断、收费状态、创建时间。这样一张表建好,后面统计科室接诊量、医生工作量、患者就诊次数都直接从它出发。再看病历表,它不能简单叫“medical_record”然后一个 content 字段打完收工。我建议表结构至少包含:就诊ID、病历类型(初诊/复诊/病程记录/出院小结)、标题、病历正文(富文本)、诊断结果JSON、状态、创建人、创建时间、提交时间、审核人、审核时间。其中“病历类型”特别重要,同一份就诊下可能有“首诊记录”和“病程记录”两种文书,用 type 字段区分比拆成两张表灵活得多。这里的唯一索引可以这样设计:同一个就诊下,相同类型的病历如果已经到了归档状态,就不允许再新建一份草稿,防止医生重复录入。当然,不同类型的病历可以并存。3.2 病历正文:富文本与结构化数据并存病历正文是这个系统最核心的内容载体。很多同学的方案是存一个 HTML 富文本字符串,其实可以但不够。纯 HTML 能解决“像病历”的问题,却解决不了“按诊断类型统计”“按症状筛选”这些结构化查询需求。我给你一个折中方案:主表里用 LONGTEXT 存富文本正文,保证展示效果和打印效果;另设一个结构化子表或 JSON 字段,存主诉、现病史、既往史、体格检查、诊断ICD码这类键值对。这样展示和统计两边都占住。如果不想建子表,MySQL 5.7 以上支持 JSON 字段,诊断信息直接塞JSON列也够用。这里我记得一个细节:JSON 字段虽然查询时可以用 JSON_EXTRACT 取值,但写 SQL 会复杂一点,不建议在 JSON 上建索引做核心查询。你还是拿它做低频统计和校验展示,高频查询放独立列里。还有一个小坑:富文本编辑器很容易引入不安全的 HTML,比如<script>。如果这个病历内容会被别的浏览器打开,XSS 漏洞就来了。做法是后端接收富文本后做白名单过滤,只保留 p、br、strong、em、span 这类常用标签,Strip 掉 script、iframe、link 等危险节点。前端不能完全信任,一定要在后端再过滤一遍。有些做毕设的同学不知道这个,答辩时被问到安全问题容易卡壳,提前注意很加分。3.3 时间字段、状态字段和逻辑删除:防御性设计三件套医疗系统对时间极其敏感。谁来建的就诊记录、什么时候提交的病历、什么时候审核的,全部要能追溯。MyBatis-Plus 的MetaObjectHandler可以统一处理createTime和updateTime的自动填充,避免每个 Service 里手动 set,既省事又不会漏。状态字段我强烈建议用tinyint加代码里的枚举类,不要直接存字符串。字符串“已提交”“审核中”看着直观,但一旦有人填了“已提价”,数据就废了。用数字枚举,通过注释和枚举类明确含义,既节省空间又方便做状态流转控制。PostgreSQL 同学可能喜欢用VARCHAR + CHECK约束,MySQL 里用 tinyint 就够了。逻辑删除是医疗数据的硬性要求,因为真实业务里病历是不能物理删除的,只能作废并留痕。MyBatis-Plus 提供了@TableLogic注解,删除时自动转 UPDATE,查询时自动加 WHERE deleted=0。这个特性一定要用,答辩时可以说这是为了满足医疗数据留痕要求。注意逻辑删除字段别加到唯一索引里去,不然逻辑删除后无法重建同身份证号的患者——这个问题我见过不止一次。3.4 医嘱、药品和收费的挂接:中间表别建薄了医嘱表和药品之间是多对多关系。一张医嘱可能开了三种药,一种药也可能在多次医嘱里出现,所以要建doctor_order_item中间表。这个表要承载的不仅仅是“哪个药品挂在哪个医嘱下”,还包括单次剂量、频次、数量、给药途径、用法说明、状态、已发药数量。很多同学把中间表建成了只有“医嘱ID+药品ID”,完全无法支撑发药、退药、费用统计。这个细节直接决定了药房模块能不能做出来。收费表建议挂接到医嘱明细,而不是只挂到医嘱头。因为费用往往按药品项计算,一次订单费用等于所有明细费用之和。这样退一盒药时,费用只调整那一项。否则只记总费用,退药就永远算不清楚账。建模阶段多想一步,后面写退药接口能省两小时。4. 核心功能实现:我从 0 到 1 绕过的几个坎技术选型和表结构定好,系统骨架就成型了。真正把系统填起来的时候,有几个功能点容易写崩,我逐个讲一下我的实现方案和踩坑记录。4.1 病历模板与动态表单:别再为每个科室硬编码一个页面电子病历最烦人的地方在于,内科病历和外科病历的字段不一样,门诊病历和住院病程记录也不一样。如果每张表单都写一个 Vue 组件或 Thymeleaf 模板去匹配,工作量巨大,而且后续加模板就得改代码,极难维护。我用的是 JSON 模板思路,类似动态表单设计。在数据库里建一张record_template表,一个模板是一段 JSON 配置。比如:{ "title": "门诊初诊病历", "sections": [ { "key": "chiefComplaint", "label": "主诉", "type": "textarea", "required": true, "maxLength": 500 }, { "key": "presentIllness", "label": "现病史", "type": "textarea", "required": true, "maxLength": 2000 }, { "key": "physicalExam", "label": "体格检查", "type": "textarea", "required": false, "maxLength": 1000 }, { "key": "diagnosis", "label": "初步诊断", "type": "select", "required": true, "optionsSource": "icdDict" } ] }前端拿到这个 JSON 后动态渲染表单,用户填完提交的也是 JSON。后端用统一的校验器根据 required 和 maxLength 规则完成参数校验,然后把数据同时写入结构化字段和富文本正文。以后要增加“出院小结模板”,只需要在后台配置新 JSON,不用动代码。这个设计在答辩时非常抢眼,因为它体现了抽象能力,属于典型的“低成本高表现”功能。4.2 医嘱开立与药品库存扣减:事务边界和并发扣减怎么处理开医嘱不是简单地往 order_item 里 insert 一行,还要同步扣减药品表里的库存。这两个动作必须在一个事务里,要么都成功,要么都失败。Spring 的@Transactional可以包住这个方法,但要记住事务只对RuntimeException回滚,自定义业务异常最好继承RuntimeException,或者显式配置rollbackFor。光有事务还不行,并发扣减是经典考点。我用的是乐观锁写法,不搞悲观锁,避免锁表拖慢正常业务。扣减 SQL 类似这样:UPDATE drug_stock SET stock = stock - #{quantity}, version = version + 1 WHERE drug_id = #{drugId} AND stock >= #{quantity}这条 SQL 的含义是:库存足够才更新库存,行数影响为 0 说明当前并发条件下数量不足,抛出异常让事务回滚,前端提示“库存不足”。它不需要显式SELECT ... FOR UPDATE,性能更好,代码也短。答辩时你可以说这模仿了秒杀系统的库存防超卖方案,虽然医疗场景并发量没那么大,但这个防御性设计很专业。还有一个很容易漏掉的:退药逻辑。病人没来取药或者医生停医嘱时,要把库存还回去。还多少?要注意区分“已开立未发药”“已发药未执行”“已执行”几种状态。我建议order_item里维护prescribedQuantity(开立数量)和dispensedQuantity(已发药数量)。退药时只能退dispensedQuantity剩余部分,且要同步更新费用表和库存表。这些都是真实业务里的细节,做进去之后系统一下就有了“产品感”。4.3 会话、鉴权与操作审计:医疗系统的底线逻辑登录会话方案我用 Sa-Token 后,拦截器配置非常简单。后端写一个配置类,注册 SaInterceptor,对/api/**路径开启鉴权,再按方法注解控制角色。比如医生接口上加@SaCheckRole("doctor"),管理员接口上加@SaCheckRole("admin"),未登录的请求在拦截器层面直接被挡掉。操作审计是医疗系统绕不开的。我用 AOP 做了一个@OperationLog注解,标在关键 Service 方法上,通过环绕通知把操作人、操作时间、操作类型、方法耗时、操作结果统一写入日志表。比如“医生提交病历”“药房发药”“管理员重置密码”这几个操作全部埋点。实现很轻量,但效果很重——答辩时评委若问“这套系统的安全性体现在哪里”,你可以直接展示这张审计表,再强调业务规范要求可追溯,安全性的回答就落地了。数据脱敏也得做。患者手机号、身份证号、住址属于敏感信息,列表接口不应该返回明文。我习惯在 VO 层统一处理:手机号保留前3后4,中间四位置星;身份证号保留前1后1;地址精确到小区或楼栋级别就停。不要等到前端再遮,后端口径统一才是真正安全的。4.4 文件上传与附件管理:格式、路径、下载三个细节病历系统通常还要支持上传检验报告图片、检查影像文件、出院小结 PDF。Spring Boot 里实现很简单,但有三处必须处理好:第一,文件类型白名单。不要用前端传的扩展名校验,别人改一下上传请求就能绕过。后端应该读取文件的 MIME Type 或判断文件头魔数,只允许 JPG、PNG、PDF 这几个常见类型,大小限制建议单文件 10MB 以内。第二,磁盘路径不可预测。保存文件时不要拼用户输入,统一用 UUID 重命名后放到按日期分层的目录里,比如/data/upload/2025/06/01/uuid.pdf。文件名存数据库时也存这个相对路径,绝对地址每次请求按配置拼接。千万别把上传文件名当作实际存储名,更不要直接拿文件名做下载路径,否则路径穿越漏洞很容易爆。第三,下载接口要做鉴权。不要直接走静态资源映射暴露整个上传目录,而是要写一个 Controller 接口,先校验登录状态和权限,再从数据库取出文件相对路径,用ResourceHttpMessageConverter把文件流返回给前端。这样所有文件访问都在权限保护之下。5. 从“能运行”到“能答辩”:演示路径与高频追问应对方案很多同学系统做完了,到答辩现场不知道先点哪个按钮。为了演示效果更好,我建议设计一条3分钟的业务闭环链路,按这条线走完,评委基本就能理解系统的全貌和亮点。5.1 一条 3 分钟的最佳演示链路我的推荐顺序是:先用管理员账号登录系统,演示图形验证码和登录成功后的菜单变化,顺带展示首页统计卡片;切到医生账号,打开“我的患者”,新建一位患者完成建档;给这位患者新建一次就诊,选择门诊类型;在接诊工作台里选择“门诊初诊病历”模板,演示模板自动带入表单的过程,填写主诉和初步诊断,提交病历;然后开一组医嘱,选择两种药品,提交后观察药品库存同步扣减;切到药房账号,看到待审核处方,执行发药,再切回医生视角确认医嘱状态变为已发药;最后切到管理员后台,查看操作审计日志里刚才那几步的操作记录。这条链路的妙处在于:它把四类角色、两大模块(病历+医嘱)、一条事务、一个安全机制全部串进了一次演示。不用刻意讲功能列表,业务自然就讲完了。5.2 评委最爱问的问题及回答思路答辩里常见问题其实相当一致,我梳理了一份高频问题清单:问题建议回答思路为什么用乐观锁而不是悲观锁?避免长事务锁表;库存充足场景下乐观锁失败概率低,遇到失败就回滚重试,符合医疗场景并发量不高的实际病历正文怎么防止 HTML 注入?后端白名单过滤危险标签;只保留安全富文本标签;在存储和展示两侧都做清洗如果两个医生同时编辑同一份病历怎么办?方案一是乐观锁版本号,更新时校验 version,冲突提示重新加载;方案二是操作前锁定病历,编辑期间其他人只读不可写数据库里的病历能不能删除?医疗数据必须留痕,物理删除一律禁止;逻辑删除必须由管理员操作并记录审计日志数据权限怎么控制的?医生查询患者列表时,SQL 自动追加“接诊医生=当前用户”或“科室=当前用户科室”的条件,通过 MyBatis 拦截器统一注入,避免在业务代码里重复写为什么用 Sa-Token 而不是 Shiro?四年前 Shiro 用得比较多,但 Sa-Token 对 Token 无状态鉴权、会话管理、踢人下线的集成更简洁,毕设场景下更直观易懂建议把每个问题都准备一个1分钟左右的答案,讲清“背景-方案-效果”就行,不要绕。5.3 答辩加分项:工程化细节比功能多更值钱几个很便宜的加分项,强烈建议做进去:统一返回格式。所有接口返回{ code, message, data }结构,失败返回错误码。这个改动本身不大,但在答辩时展示接口文档,层次感立刻不一样。全局异常处理器捕获业务异常和系统异常,统一转成标准返回体,页面不会弹裸的500错误。用 Knife4j 生成在线 API 文档,启动后打开/doc.html就能看到每个接口的说明和参数,这是最直观的工程化证据。多环境配置文件拆成 dev 和 prod 两套,演示时用 dev,说明你懂环境隔离。项目启动初始化脚本里创建好管理员账号和基础数据,让评委自己点两下就能上手,观感会好很多。6. 源码 23540 的 2.0 方向:这套系统还能往哪里长毕设做完不代表这套系统的生命力就结束了。如果时间有富余,或者后续想把它写成更完整的作品,下面几个方向是最自然的延伸路径。6.1 向医疗标准靠拢:结构化文档与归档导出目前病历内容虽然是 JSON 存储,但严格来说还不算标准化。进阶方向是引入临床文档架构(CDA)的简化版定义。简单理解:就是把病历的文档头、文档体、章节、条目拆成标准结构,让不同系统之间能够交换病历数据。实现不复杂:写一个导出服务,把数据库里的病历 JSON 渲染成标准 XML 或 PDF 文件,文件名按“患者ID-就诊日期-病历类型”规则生成,落在归档目录。这样打印、病案室归档、第三方调阅都有据可查。虽然毕业设计不一定需要走到这一步,但答辩时展示“病历数据可以按规范归档导出”明显更有临床场景的味道。6.2 患者端延伸:院外报告查询与消息通知给系统增加一个患者端的简化版门户,比如 H5 页面或小程序,提供的功能不需要多:登录、查看自己的就诊记录、查看检查报告 PDF、在线预约复诊。接口层已有完整的鉴权和数据模型,扩展患者端几乎是顺理成章,主要新增一套查询接口和页面。加上一条简单的消息通知机制——比如检查报告出结果后给患者推送一条待查看提醒,就能让系统从“医生端的工具”变成一个“闭合的服务链路”。这里要提醒一句:患者端和医生端的数据权限要重新过一遍,确保患者只能查自己名下的数据,防越权测试那种安全意识对医疗场景尤其重要。6.3 给正在肝毕设的同学:三条能省两周时间的实操习惯最后分享几条开发节奏上的经验。第一,拿到这套源码后,绝对不能直接拿去交差。我的建议是把核心模块重写一遍:数据库表自己建,登录权限自己配,病历模块自己写。重写不仅让你熟悉每一行代码,更重要的是答辩时心里有底。直接背别人的项目,评委随便追问一个关联查询就会措手不及。第二,每天推进后立即提交 Git 记录,至少每天一次,commit message 写清楚今天干了什么。这个习惯除了防误删外,还能在答辩时展示你的开发周期和分工,属于低投入高回报。第三,任何时间点都要保证项目能一键启动运行。毕设导师可能随时要看系统演示,如果演示时环境配置依赖过多、启动失败,印象分会掉得很快。尽量精简启动步骤,把初始化 SQL 和默认配置做到位,启动文档单独写清楚。我带的很多同学都是答辩前两周才意识到“能跑”本身其实就是一个巨大的加分项。电子病历这类题目之所以被反复选作毕设,就是因为它在复杂度、可实现性和展示效果上平衡得很好。按上面的思路把业务模型做透,把事务、权限、审计几个关键点讲明白,这套系统不仅是代码量的堆砌,更是一份拿得出手的作品。