SpringBoot+Vue3+MyBatis实战:医院病历管理系统全栈设计与踩坑总结
做了几年的医疗信息化相关项目SpringBootVue3MyBatis这套组合在医院病历管理系统里算是很成熟的路子了。这次把整个项目的设计思路、表结构、前后端实现细节和踩过的坑完整梳理一遍给正在做类似系统或者准备拿这类项目练手的朋友做个参考。这套系统解决的核心问题很明确把医院里纸质病历电子化让医生能快速写病历、查历史、开处方让管理员能管科室、管账号、看数据。技术层面覆盖了RBAC权限控制、复杂查询分页、事务处理、前后端分离联调、文件上传这些高频场景学完这套东西市面上大部分管理系统的开发套路基本都能拿下来。适合看这篇内容的人有三类一是正在学Java后端、想完整跑通一个全栈项目的同学二是准备面试、要拿真实项目经验讲细节的求职者三是做医疗信息化周边产品、需要快速理解病历系统业务模型的产品和技术人员。1. 项目整体定位与技术选型为什么这套组合最适合病历管理1.1 病历管理系统到底在管什么先把业务边界理清楚。病历管理系统不是单纯的CRUD它围绕一次就诊这条主线展开患者先挂号建档医生接诊后书写病历主诉、现病史、既往史、体格检查、初步诊断然后开医嘱或处方最后形成一份完整的就诊记录。整个流程里涉及的数据有患者基本信息、医生信息、科室信息、病历文书、诊断结果、处方明细还有病历模板、附件影像等辅助数据。从角色权限看系统至少要分三类人管理员管全院的基础数据和账号、医生读写自己科室的病历、护士或门诊人员可能只读部分信息。权限模型直接决定了后台接口的设计方式前端菜单也要跟着角色动态渲染。这个业务模型非常有代表性它包含了主数据管理 核心业务流转 多角色权限 数据统计四大模块跟电商后台、教务系统、项目管理系统在架构上没有本质区别。所以做完这套病历系统你收获的不是医疗知识而是一整套企业级管理系统的方法论。1.2 技术栈选型的三个核心理由先说SpringBoot。病历系统这种业务表的数量在二三十张左右接口数量上百个SpringBoot的自动装配和Starter机制能极大减少配置工作量。更重要的是它的生态整合MyBatis、Spring Security或者JWT、文件上传、定时任务基本都有现成方案团队协作时上手成本也低。项目里我遇到过一次因为Jackson序列化配置不对导致日期格式全乱的场景SpringBoot里一行配置就能解决换成别的框架可能得折腾半周。再聊Vue3。前端这边的核心痛点是病历编辑和患者列表这种高频交互页面。Vue3的组合式API让逻辑复用变得非常舒服比如患者搜索的逻辑、分页逻辑、表单校验逻辑都可以抽成自定义hook多个页面共用。加上Element Plus的组件库非常全表格、表单、弹窗、上传组件都有开发效率比手写UI高一个量级。我实际开发里用的是Vite作为构建工具冷启动速度比Webpack快太多改完代码热更新几乎是秒级体验很好。最后说MyBatis。有人会问为什么不直接用MyBatis Plus或者JPA。病历系统有一个特点报表统计、病历检索、多条件组合查询特别多比如查询某科室某时间段内、某诊断类型的病历数量这类动态SQL用MyBatis的XML写if判断非常顺手SQL的可控性是最强的。MyBatis Plus虽然在单表CRUD上有优势但遇到复杂查询最后还是得写XML为了减少依赖我在这个项目里直接用原生MyBatis配合PageHelper做分页效果很好。1.3 前后端分离架构的收益和代价前后端分离现在几乎是新项目的默认选项。前后端团队或两个人分别开发可以并行推进后端定义好接口文档就能开工前端用Mock数据同步开发工期压缩很明显。部署上也灵活后端打包成Jar包前端构建成静态文件扔到Nginx里后端挂了前端页面照样能打开只是接口请求失败而已。但这套架构也有几个坑要提前想到。第一个是跨域问题前端跑在5173Vite默认端口后端跑在8080两者端口不同必然触发浏览器同源策略。我用的方案是前端Vite配置代理把/api开头的请求转发到http://localhost:8080这样浏览器看到的请求是同源的不在后端做CORS全局配置减少安全风险。第二个是鉴权前后端分离后Session不太好用我用JWTJSON Web Token方式登录成功后后端返回token前端存起来每次请求在Header里带着后端拦截器统一校验。第三个是接口文档的维护项目里我用了SwaggerSpringBoot 3.x对应的是springdoc-openapi自动根据注解生成接口文档前端直接对照文档联调省了大量口头沟通。2. 数据库核心设计与表模型拆解2.1 以患者主索引为中心的表关系设计病历系统的表设计有一条隐形主线患者主索引Patient Master Index。所有跟这个患者相关的记录都要通过患者ID关联起来。我设计的核心表结构大概是这样用户表sys_user存医生、管理员、护士等系统用户的账号、密码、姓名、科室ID。密码字段我用BCrypt加密存储绝不能明文。角色表sys_role和用户角色关联表sys_user_role管理用户的角色前端菜单和后端接口都按角色来控制。患者表patient存储患者的姓名、性别、出生日期、身份证号、联系电话、过敏史等基本信息。身份证号要有唯一约束这是患者主索引的关键。科室表dept科室名称、编码、负责人等信息。病历主表medical_record一次就诊一份主记录包含就诊时间、患者ID、医生ID、科室ID、主诉、现病史、既往史、体格检查、初步诊断、就诊状态等。这是最核心的表。诊断表diagnosis病历主表关联诊断明细因为一次就诊可能有多个诊断主要诊断和次要诊断所以单独拉一张表包含ICD编码、诊断名称、诊断类型。处方表prescription和处方明细表prescription_item处方头部存医嘱类型、开具时间、医生ID明细表存药品名称、规格、数量、用法用量。病历模板表medical_template存各类病历的书写模板比如门诊初诊模板、住院大病历模板方便医生写病历时直接套用。附件表medical_attachment存检查报告图片、影像文件的路径和关联的病历ID。这个设计的核心思想是一中心多张业务表。无论系统以后怎么扩展加住院模块、加检验模块只要患者ID不变所有的历史数据都能通过这个ID串联起来。实际落地的时候我特别建议画一张实体关系图ER图再建表。纸上谈兵五分钟后面省的是几天的返工。比如医生和病历的关系一开始我只设计了创建人ID后来发现一个病历可能需要多个医生协作经治医生和主任查房才在病历表里加了主治医生ID和记录医生ID两个字段。2.2 几个关键字段的设计权衡病历内容字段的类型选择。病历主表里的现病史和体格检查我最终用的是TEXT类型存富文本HTML。有些系统用JSON结构存比如把体格检查拆分成体温、血压、心率等结构化字段好处是便于统计但代价是灵活性差医生在临床上习惯按段落写一个文本框能表达的事拆成二十个字段会很痛苦。折中方案是主文本用TEXT存富文本同时把体温、血压这种高频统计字段单独拿出来存两条路都兼顾。逻辑删除还是物理删除。医疗数据有合规要求历史病历不能随便删。所以核心业务表病历表、处方表我都加了is_deleted字段默认0删除时更新为1查询时统一加where is_deleted 0条件。只有用户表、角色表这种管理类数据才考虑物理删除。时间字段的统一约定。我用的是create_time和update_time两个字段类型为datetime后端用MyBatis的自动填充功能insert时填create_time和update_timeupdate时只填update_time这样代码里不用手动维护时间。还有一个容易忽略的点所有时间字段都要约定好时区MySQL连接串上必须加serverTimezoneAsia/Shanghai否则查询出来的时间默认差8小时。这个坑我见太多了每次新同事遇到日期不对的问题基本都是这个原因。状态字段的设计。我用tinyint类型0和1是两个基础状态比如就诊状态0待诊1就诊中2已完成3已归档。状态字段最好用枚举类在后端做一层映射避免业务代码里到处是魔法数字。2.3 核心SQL示例多条件分页查询和时间线病历列表查询是系统里最核心、也是最容易写差的SQL之一。需求是按患者姓名、科室、诊断类型、就诊时间段组合查询分页返回还要带出患者姓名和医生姓名。先用PageHelper分页核心SQL用MyBatis的XML写动态条件大致结构如下select idselectRecordPage resultTypemap SELECT mr.id, mr.visit_time, p.name AS patient_name, u.name AS doctor_name, d.name AS dept_name, mr.primary_diagnosis, mr.status FROM medical_record mr LEFT JOIN patient p ON mr.patient_id p.id LEFT JOIN sys_user u ON mr.doctor_id u.id LEFT JOIN dept d ON mr.dept_id d.id where if testpatientName ! null and patientName ! AND p.name LIKE CONCAT(%, #{patientName}, %) /if if testdeptId ! null AND mr.dept_id #{deptId} /if if teststartTime ! null AND mr.visit_time gt; #{startTime} /if if testendTime ! null AND mr.visit_time lt; #{endTime} /if AND mr.is_deleted 0 /where ORDER BY mr.visit_time DESC /select这里有个实战细节动态SQL里用 做条件拼接时最后一个AND mr.is_deleted 0务必放在 标签内部这样如果前面没有条件MyBatis自动去掉开头的AND不会生成语法错误。另外LIKE查询用CONCAT拼接百分号不用$直接注入避免SQL注入风险。按患者查看历史就诊时间线也是一个高频功能其实就是一条SQLSELECT id, visit_time, primary_diagnosis, status FROM medical_record WHERE patient_id #{patientId} AND is_deleted 0 ORDER BY visit_time DESC前端把返回结果按时间倒序渲染成时间线组件患者每次就诊记录一目了然。这个功能虽然简单但在用户体验上非常加分。3. 后端SpringBoot关键模块的实现细节3.1 工程分层与统一返回结构整个后端工程我按经典的Controller-Service-Mapper三层结构组织包名按业务模块划分大体长这样com.example.medical ├── common // 通用类Result、全局异常、常量 ├── config // 配置类Swagger、MyBatis、拦截器注册 ├── controller // 接口层只做参数接收和结果返回 ├── service // 业务层写核心业务逻辑和事务 ├── mapper // 数据访问层接口XML ├── entity // 实体类 ├── dto // 接收参数的DTO对象 └── vo // 返回给前端的VO对象还有一个容易被忽视但非常影响开发体验的部分统一返回结构。我的Result类包含code、message、data三个字段成功返回code200业务异常code400未登录或token过期code401。这样做的直接好处是前端Axios响应拦截器可以统一处理所有接口的返回不用每个接口单独判断状态。全局异常处理我用RestControllerAdvice配合自定义业务异常类。Service里只要throw new BusinessException(患者不存在)异常处理器自动捕获并返回给前端标准格式。这个机制让Controller里的代码非常干净不用每个接口都写try-catch。3.2 登录认证与权限控制实现登录流程用的是JWT 拦截器方案。用户提交账号密码后端BCrypt验证密码登录时用PasswordEncoder.matches方法校验验证通过后用用户ID和角色生成token返回给前端。生成token我用的jjwt库密钥放在application.yml里为了安全项目里至少要换成32位以上的随机字符串用环境变量注入不要写死在代码里。拦截器是整个鉴权的核心。我在WebMvcConfig里注册一个LoginInterceptor对所有接口生效排除登录接口和Swagger文档接口。拦截器里做的事很简单取请求头里的Authorization字段去掉Bearer前缀解析token验证签名和过期时间通过后在ThreadLocal里存当前用户信息后续的Service层从ThreadLocal取值这样接口不需要在参数里来回传用户ID。权限控制这块我用了一个自定义注解RequireRole标注在Controller方法上比如管理员接口可以这样写RequireRole(ADMIN) GetMapping(/stats) public Result getStats() { // ... }拦截器里解析完token后拿当前用户的角色列表跟注解要求的角色比对不匹配直接返回403。这种做法比Spring Security简单直接对这类系统完全够用而且代码量少、容易理解。如果以后权限模型变复杂了再平滑迁移到更重的权限框架也不难。因为医疗数据极其敏感我特意对两个点做了加强一是登录错误不区分用户不存在和密码错误统一返回用户名或密码错误防止账号探测二是密码必须用BCrypt加密不能用MD5虽然MD5加盐也能用但BCrypt自带随机盐且计算速度可调安全性高得多。3.3 病历CRUD里的关键事务与数据权限新增病历是整个系统里事务最复杂的一个接口。医生保存一份完整病历后端要同时写三张表病历主表插入一条记录诊断表插入N条诊断处方表插入一条处方头部信息处方明细表插入M条药品明细。任何一步失败都应该整体回滚。我用Transactional注解标记在Service方法上默认遇到RuntimeException就会回滚这里必须注意一个问题自己catch住异常后不重新抛出事务是不会回滚的。正确的做法是catch里记录日志后继续抛或者干脆不catch让全局异常处理去兜底。数据权限是我在这个系统里特意做的需求普通医生只能看到自己科室的病历科主任能看全科管理员能看全院。实现方式不复杂在SQL查询条件里动态拼数据范围if (userRole.equals(DOCTOR)) { // 只查自己deptId当前用户的科室 query.setDeptId(currentUser.getDeptId()); } else if (userRole.equals(DEPT_MANAGER)) { // 也只查自己科室但要能看所有医生的病历 }控制前端是一次会话只能看一个科室后端SQL里永远要带权限条件。后期如果扩展到全院只要把deptId传 -1不拼接就行。我一直坚持后端永远不能信任前端传过来的参数所有权限判断都必须在后端做第二道校验。病历查询接口我实际开发时也加了同样的限制否则知道接口地址的人可以直接遍历患者数据。3.4 MyBatis使用心得XML与注解的选择项目里我的Mapper接口分层两种简单的单表操作直接用注解比如Select(select * from dept)这种复杂的动态SQL一律用XML。选择依据很简单SQL超过三行或者需要动态条件就交给XML。原因是注解里写动态SQL要把

相关新闻

WebStorm前端工程化实战:从页面开发到交付的标准化工作流

WebStorm前端工程化实战:从页面开发到交付的标准化工作流

1. 项目概述&#xff1a;这不是“装个IDE就完事”的前端开发&#xff0c;而是用WebStorm把页面从草图变成可交付产品的完整链路你打开WebStorm&#xff0c;新建一个HTML文件&#xff0c;敲下<h1>Hello World</h1>&#xff0c;浏览器一刷——成了。但真正的前端页面…

2026/10/13 7:28:03 阅读更多 →
OpenCV 3.2.0 + MinGW + Qt 5.9.6 编译配置:避开ABI陷阱的完整指南

OpenCV 3.2.0 + MinGW + Qt 5.9.6 编译配置:避开ABI陷阱的完整指南

简介&#xff1a;面向Windows 10 64位系统上的Qt 5.9.6视觉项目开发&#xff0c;这份编译好的OpenCV 3.2.0 MinGW版免去了自行配置CMake、检查依赖和交叉编译的繁琐工作&#xff0c;可直接在Qt Creator中使用。包内含245个文件&#xff0c;核心是145个hpp与57个h头文件&#xf…

2026/10/13 7:28:03 阅读更多 →
C盘爆红不用慌:用Codex定位AppData 87.8GB占用并安全清理

C盘爆红不用慌:用Codex定位AppData 87.8GB占用并安全清理

C盘爆红是个老话题了&#xff0c;但每次有人遇到还是会慌&#xff0c;尤其是系统提示“磁盘空间不足”的那一刻&#xff0c;电脑卡顿、软件闪退、保存文件失败一起涌过来&#xff0c;非常糟心。我这次遇到的更离谱——C盘只剩不到2GB可用空间&#xff0c;整个系统处于随时可能罢…

2026/10/13 7:27:03 阅读更多 →

最新新闻

阿里云ECS IPv6配置全链路排障指南

阿里云ECS IPv6配置全链路排障指南

1. 为什么现在还值得花时间配通ECS的IPv6——不是赶时髦&#xff0c;是绕不开的现实需求 “阿里云ECS开启IPv6支持”这个标题看起来像一份过时的操作指南。毕竟IPv6喊了十几年&#xff0c;很多人印象里还是“实验室技术”“过渡方案”“等运营商推”。但去年底我帮某高校做一套…

2026/10/13 8:19:36 阅读更多 →
JDK编译报错“不支持发行版本6”:根因分析与Maven配置修复指南

JDK编译报错“不支持发行版本6”:根因分析与Maven配置修复指南

最近帮一位同事收拾旧项目&#xff0c;报错很典型&#xff1a;本地装的是JDK 11&#xff0c;代码从仓库拉下来&#xff0c;在IDE里一点编译&#xff0c;控制台直接打出java: 错误: 不支持发行版本 6。第一反应是Maven没配置好&#xff0c;又怀疑依赖版本冲突&#xff0c;折腾了…

2026/10/13 8:19:36 阅读更多 →
LLM代理成为一等优化手段:算子优化范式从手写汇编到代理驱动的转变

LLM代理成为一等优化手段:算子优化范式从手写汇编到代理驱动的转变

1. 从"手写汇编"到"代理驱动"&#xff1a;算子优化范式正在发生什么变化2026年过半&#xff0c;如果你还在用纯手工的方式去调一个LLM推理场景下的算子kernel&#xff0c;大概率会在效率上吃大亏。这不是危言耸听&#xff0c;而是我这半年在几个实际项目里…

2026/10/13 8:19:36 阅读更多 →
开源可自托管代码评审工作流:从流程负担到质量抓手的工程实践

开源可自托管代码评审工作流:从流程负担到质量抓手的工程实践

代码评审这件事&#xff0c;做得好是团队效率的放大器&#xff0c;做得不好就是每天下班前的一场互相折磨。我在一线写了十几年代码&#xff0c;见过太多评审流于形式的团队——评论里全是“LGTM”&#xff0c;合入之后bug频发&#xff1b;也有评审严格到让人不敢提PR的团队&am…

2026/10/13 8:19:36 阅读更多 →
鸿蒙校园通开发实战:分布式数据同步与后台任务调度避坑指南

鸿蒙校园通开发实战:分布式数据同步与后台任务调度避坑指南

简介&#xff1a;这份资源是面向鸿蒙应用开发初学者与在校学生的实战项目源码包&#xff0c;以华为HarmonyOS为技术底座&#xff0c;围绕校园生活场景构建了一款功能完整的「校园通」软件。项目覆盖课程表管理、食堂菜谱、图书馆查询、校园导航、景点导览、号码百事通、校区平面…

2026/10/13 8:19:36 阅读更多 →
JMeter If Controller深度解析:逻辑调度与条件控制最佳实践

JMeter If Controller深度解析:逻辑调度与条件控制最佳实践

1. 项目概述&#xff1a;If Controller不是“条件开关”&#xff0c;而是JMeter里最被低估的逻辑调度中枢 如果你在用Jmeter做接口压测时&#xff0c;还只是把所有请求堆在同一个线程组里、靠“固定定时器”硬控节奏&#xff0c;或者一遇到“登录成功才跑下单”“状态为process…

2026/10/13 8:18:35 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目&#xff0c;我最大的感受是&#xff0c;大部分坑不是出在技术前沿&#xff0c;而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节&#xff0c;最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了&#xff0c;很难不碰到舵机控制这道坎。做智能小车要转方向&#xff0c;做机械臂要抬关节&#xff0c;做云台要偏航俯仰&#xff0c;背后都是同一个东西在干活&#xff1a;舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”&#xff1f;——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台&#xff1f;或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜&#xff1f;又或者&#xff0c;在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介&#xff1a;基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码&#xff0c;面向计算机相关专业课程设计与期末大作业学生&#xff0c;以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程&#xff0c;…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程&#xff1a;键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化&#xff0c;十个新手有八个栽在"往输入框里填东西"这件事上&#xff1a;要么填不进去&#xff0c;要么填了一半&#xff0c;要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程&#xff1a;阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀&#xff1a;什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面&#xff0c;跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →