Spring Boot电子病历管理系统实战:架构、鉴权与数据库设计全解析

发布时间:2026/10/21 5:36:13
Spring Boot电子病历管理系统实战:架构、鉴权与数据库设计全解析
1. 项目全貌与设计思路不只一个CRUD那么简单电子病历管理系统在医疗信息化领域是个标准得不能再标准的业务场景但真正动手做过的人都知道它远不是搭个框架、写几个增删改查就能交差的。这套基于Spring Boot的电子病历管理系统从标题里的“源码23540”就能看出来是一个完整度相当高的教学级项目适合正在学Java后端、想找一份能写进简历的实战项目的开发者也适合刚接手医疗类系统开发的新人。先说结论这个项目的核心价值不在“电子病历”这四个字的业务噱头而在于它把Spring Boot生态里最常用的一套组合拳——Spring Boot MyBatis-Plus JWT Vue或Thymeleaf取决于源码具体实现——揉进了一个真实业务场景里。你跟着源码走一遍等于把后端开发的常规套路完整过了一遍实体建模、数据访问层封装、业务层事务控制、鉴权拦截、异常处理、枚举状态流转再加上前后端联调。当时我拿到这套源码第一反应是先扒目录结构。一个合格的教学项目目录就是它的讲课提纲Controller、Service、Mapper、Entity、Config、Common这些包各司其职看一眼就能猜出作者的设计思路。整个系统大致可以拆成两大块一块是患者诊疗数据的生命周期管理从建档、门诊就诊、病历书写、诊断开具到处方录入另一块是系统支撑体系包括医生账号权限、科室维护、字典数据管理。这两块凑在一起才构成一个能跑通的闭环。我特别想强调的一点是电子病历系统和普通的表单管理系统有一个本质区别——它的数据是有“连续性”和“法律效力”的。一次就诊产生的病历、诊断、处方、检查申请必须能串成一条完整的时间线任何一次修改都得留下痕迹这在数据模型设计上就直接决定了你不能只设计一张“病历表”草草了事。这套项目的表结构设计恰恰是值得反复咀嚼的地方。2. 核心需求与功能模块拆解系统到底做了什么2.1 患者档案与病历主索引所有业务的起点这个模块别看不起眼但它是整个系统的地基。患者主索引Patient Master Index解决的是“同一个患者在不同就诊时间、不同科室下怎么唯一识别”的问题。源码里对应的核心表一般叫patient_info或patient除了姓名、性别、出生日期、身份证号、联系电话这些常规字段之外还往往设计了medical_record_no病历号、allergy_history过敏史这类医疗专属字段。设计上的一个关键细节是以“门诊号/病历号”作为业务主键来串联就诊记录。很多新手在这里会犯一个错误直接用数据库自增id来关联业务数据表面上没问题但一旦涉及系统对接、数据迁移、多院区部署自增id就会成为灾难。源码中如果把case_number或medical_record_no作为独立业务编号字段单独管理说明作者是考虑过这个问题的。从前后端交互的角度看患者列表页一般会提供模糊搜索、分页加载、快速建档这几个功能。搜索条件通常包括姓名、病历号、手机号、身份证号这背后对应的是一个带条件的动态SQL——如果用了MyBatis-Plus就是QueryWrapper的组合条件构造。2.2 门诊病历与电子病历文书结构化数据的落地门诊病历是整个系统的核心业务对象。一次门诊就诊涉及的环节包括主诉、现病史、既往史、体格检查、初步诊断、治疗意见、医师签名、开具处方。这些内容不能全塞在一个超长文本字段里否则后续想做统计分析、按诊断编码检索、区域卫生信息平台上报全都无从谈起。源码里的medical_record表一般会拆出多个核心字段chief_complaint主诉、present_illness现病史、past_history既往史、physical_exam体格检查、diagnosis诊断结果、treatment_advice治疗意见。这种“半结构化”设计是在可维护性和实现成本之间最常见的折中方案——每个字段独立存储又保留了必要的灵活性。更讲究一点的实现会引入diagnosis_dict诊断字典表和prescription处方表。诊断字典表通常关联标准的ICD-10编码处方表则记录药品名称、剂量、频次、用法。这套源码如果在这些表上有完整实现那含金量会比单纯一张病历表高出不少因为处方和诊断本身就是电子病历不可或缺的两个伴生数据。2.3 系统管理与权限控制多角色下的操作边界病历系统的使用者至少有三种角色医生、护士、系统管理员如果再细化还有药剂师、科室主任、院级管理员。不同角色能看到的菜单、能执行的操作、能维护的数据范围完全不一样。源码里常见的角色表sys_role、用户表sys_user、菜单权限表sys_menu体现的就是一套基于RBAC基于角色的访问控制的经典设计。Spring Boot侧实现权限拦截的常规做法是登录接口签发JWT令牌前端在请求头里带Authorization: Bearer token后端通过拦截器或Spring Security过滤器链校验token有效性再从token里解析出用户id和角色进而控制接口访问。这套源码如果用拦截器方式实现核心逻辑通常写在一个JwtInterceptor或AuthInterceptor里先校验token是否存在、是否过期再通过预加载的权限列表判断当前用户是否拥有目标接口的访问权限。这种方式相比Spring Security学习门槛更低执行逻辑也更直观特别适合教学项目演示。3. 关键技术实现数据库设计、后端分层与鉴权链路3.1 数据库表设计思路从ER图到字段落地整套系统的表结构一般来说会围绕三个中心展开患者中心、诊疗中心、系统中心。患者中心的数据表存储的是静态档案类数据——患者基本信息、过敏史、既往史等变化频率低但查询频率高。诊疗中心的数据表是动态产生的流水数据——每次就诊产生的病历记录、处方记录、检查记录这些表通常是持续累加的增长速度快是系统设计的重点。系统中心的数据表则服务于支撑系统运行——用户表、角色表、菜单表、字典表、日志表这部分是几乎所有管理系统的标配。我在源码里反推它的建表语句时比较看重这几个方面主键策略是否统一是否为雪花算法或自增id是否有明确的业务唯一键。是否包含create_time、update_time、creator、operator这类审计字段——这在实际医疗系统里是合规要求不是可选项。关联关系是通过逻辑外键还是物理外键表达。教学项目通常用逻辑外键也就是不加外键约束靠代码层面维护关联这样对开发友好但需要开发者有足够自律。3.2 Spring Boot后端分层架构Controller-Service-Mapper的协作基于Spring Boot项目的分层架构核心是这样的Controller层接收前端请求参数做基础的参数校验调用Service层方法封装统一返回结果。Service层业务逻辑的核心承载者。事务控制、业务规则判断、跨表数据组装全在这一层。Mapper层数据库访问对应MyBatis或MyBatis-Plus的接口定义。Entity层与数据库表一一对应的实体类。这样的分层有几个好处职责边界清晰出了问题能快速定位Service层不依赖具体的HTTP协议细节便于做单元测试和复用当数据库从MySQL切换到其他数据库时只需调整Mapper层的方言配置业务逻辑层完全不用动。3.3 认证与鉴权链路从JWT签发到接口放行这套系统如果采用无状态鉴权那条链路大致是用户提交用户名密码后端校验通过后用密钥生成一个JWT token返回给前端。前端把token存在本地存储或内存中后续每次请求在请求头中携带。后端通过拦截器统一拦截校验token的合法性再放行或拒绝。有几个容易踩坑的细节值得单独拎出来说token过期时间的设置太短则用户频繁重新登录体验差太长则存在安全风险。比较合理的做法是access token设30分钟到2小时refresh token设7天到30天。密钥管理硬编码在application.yml里是教育项目的常见做法但生产环境必须通过环境变量或配置中心注入防止泄露。白名单路径登录接口、验证码接口、静态资源都需要在拦截器里配置为放行状态。新手经常在这里翻车怎么调都登不进去结果发现是拦截器把登录接口也拦了。4. 实操过程与核心环节实现把项目跑起来的完整记录4.1 环境准备JDK、Maven、MySQL、Node.js在复现源码并成功运行之前环境准备是第一步。根据这套项目的技术栈本地环境大致需要JDK 8或JDK 11Spring Boot 2.x时代用8较多如果用Spring Boot 3.x则需要JDK 17Maven 3.6用于依赖管理以及项目编译打包MySQL 5.7或8.0用于业务数据的存储Node.js 14如果前端部分基于Vue开发则需要Node环境这些工具的安装和配置网上资料很多这里我只提醒几个细节Maven的settings.xml里需要配置阿里云镜像源国内用户如果不配置下载依赖的速度会让人崩溃。MySQL建库时需要注意字符集务必选择utf8mb4这个字符集能完整支持手机号、特殊符号以及患者在备注里填的各种生僻字。4.2 源码导入与数据库初始化从SQL脚本到数据可查这是整个复现过程中最关键的一步。拿到源码后第一件事是找到sql目录下的初始化脚本通常叫init.sql、medical_record.sql或类似的名字。导入数据库的流程是先用Navicat或命令行工具创建一个空的数据库然后在Navicat中执行SQL脚本或者直接在命令行执行mysql -u root -p init.sql。执行完第一步先检查表清单通常会有十几张表包括用户表、角色表、患者表、病历表、处方表、字典表等确认核心表都存在再用几条简单的查询语句验证数据是否正常填充。有一个容易踩的坑有的SQL脚本在执行前需要设置数据库名如果脚本里写死了USE medical_db;而你的库名不一致会出现导入失败的情况。解决办法是打开SQL文件全局替换库名或者手动在目标库内执行。导入数据库后记得修改后端项目里的数据库连接配置。打开application.yml或application.properties将url、username、password改为本地的实际值。4.3 后端启动与接口验证从控制台日志到Swagger页面当数据库配置就绪我们就可以启动后端服务了。在项目根目录执行mvn spring-boot:run或者在IDE中直接运行主类。首次启动时Maven会下载大量依赖包耐心等待是一件需要做的事情。启动成功的标志是控制台出现类似Tomcat started on port(s): 8080的日志。紧接着可以打开浏览器访问http://localhost:8080/doc.html或http://localhost:8080/swagger-ui/index.html尝试在Swagger页面调用登录接口获取token再带着token调用患者列表接口验证整个链路是否通畅。第一次启动时最常见的失败是端口被占用。解决方案是检查application.yml里的server.port配置如果8080端口被其他进程占用就改成8081或其他空闲端口。4.4 前端联调与整体跑通从登录页到病历录入全流程如果这套源码包含Vue前端最后一步就是启动前端服务与后端联调。前端项目通常独立存在于frontend或web目录下先执行npm install安装依赖再执行npm run dev启动开发服务器。前端默认端口大概率是localhost:8081或localhost:3000这就要求在接口请求封装中配置跨域代理或直接在前端代码里将baseURL指向后端地址。这里有一个高频困扰新手的点前后端联调接口不通时优先通过浏览器开发者工具中的Network面板查看请求和响应状态再根据报错情况排查——是404路径不对、401没有token或token过期、403没有权限还是500后端代码报错。定位出问题所在后再去执行对应的操作效率会高很多。5. 核心代码片段解析从登录到病历创建的关键链路5.1 登录接口与JWT工具类用户登录是系统业务链路的起点它在后端代码里通常长这样PostMapping(/login) public Result login(RequestBody LoginRequest loginRequest) { String username loginRequest.getUsername(); String password loginRequest.getPassword(); // 校验用户名密码是否匹配 SysUser user sysUserService.login(username, MD5Util.MD5(password)); if (user null) { return Result.error(用户名或密码错误); } // 登录成功签发JWT令牌并返回 String token JwtUtil.createToken(user.getId(), user.getUsername(), user.getRole()); return Result.success(token); }JwtUtil的核心是一个生成token和解析token的工具类public class JwtUtil { private static final String SECRET your-256-bit-secret; private static final long EXPIRE 1000 * 60 * 60 * 2; // 2小时过期 public static String createToken(Long userId, String username, String role) { return Jwts.builder() .setSubject(username) .claim(userId, userId) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() EXPIRE)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser() .setSigningKey(SECRET) .parseClaimsJws(token) .getBody(); } }这里token的载荷里放的是用户名、用户ID和角色这些数据后续在拦截器和Controller中都能被再次解析出来用于记录“谁在什么时间操作了什么数据”。5.2 拦截器统一鉴权拦截与放行后端接口不能每个方法都手动校验token那样代码会非常冗余。源码里一般会定义一个拦截器统一处理public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行预检请求 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } String token request.getHeader(Authorization); if (StringUtils.isBlank(token) || token.startsWith(Bearer )) { // 解析实际token token token.substring(7); try { Claims claims JwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } catch (Exception e) { response.setStatus(401); return false; } } response.setStatus(401); return false; } }在Web配置类里完成拦截器注册与放行路径配置Configuration public class WebConfig implements WebMvcConfigurer { Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) .addPathPatterns(/**) .excludePathPatterns(/login, /captcha, /doc.html, /swagger-ui/**, /webjars/**); } }一个值得留意的细节是过滤器和拦截器的顺序。过滤器在Servlet容器层面执行拦截器在Spring MVC层面执行。如果一个请求先被过滤器拦住了token校验逻辑就不会走到拦截器这也是不少人在集成过程中遇到的问题根源。5.3 患者创建与病历写入业务链患者建档是核心流程之一对应的Service层逻辑可能长这样public boolean createPatient(Patient patient) { String medicalNo generateMedicalNo(); // 生成病历号 patient.setMedicalRecordNo(medicalNo); patient.setCreateTime(new Date()); return patientMapper.insert(patient) 0; }病历创建的逻辑会相对复杂因为它涉及多张表的联合写入Transactional(rollbackFor Exception.class) public boolean createMedicalRecord(MedicalRecordDTO dto) { // 1. 写入病历主表 MedicalRecord record new MedicalRecord(); BeanUtils.copyProperties(dto, record); medicalRecordMapper.insert(record); // 2. 写入诊断关联 if (dto.getDiagnosisList() ! null) { for (String diagnosis : dto.getDiagnosisList()) { Diagnosis diag new Diagnosis(); diag.setRecordId(record.getId()); diag.setDiagnosisName(diagnosis); diagnosisMapper.insert(diag); } } // 3. 写入处方数据 if (dto.getPrescriptionList() ! null) { for (PrescriptionDTO p : dto.getPrescriptionList()) { Prescription pre new Prescription(); pre.setRecordId(record.getId()); pre.setDrugName(p.getDrugName()); pre.setDosage(p.getDosage()); pre.setFrequency(p.getFrequency()); prescriptionMapper.insert(pre); } } return true; }这段代码最大的看点是Transactional注解——它保证了要么所有数据都写入成功要么全部回滚。在实际的医疗系统里这种原子性要求非常重要病历主体都写了但诊断失败了最终留下的是一条不完整的数据会带来很大的数据质量问题。6. 常见问题与排查技巧实录实战中踩过的坑在把整个系统从源码到部署运行的过程完整走了一遍之后我总结了一些出现频率极高的实际问题把它们整理成下面这个速查表希望能对后来者有所帮助。问题现象可能原因排查思路与解决方案数据库连接失败用户名/密码错误、数据库未创建核对application.yml配置检查MySQL服务是否启动确认库名一致启动报端口被占用8080端口已被其他进程占用使用netstat -ano | findstr 8080查看占用的进程杀掉进程或改项目端口登录接口返回401Token校验失败或请求头未携带token使用Swagger调用登录接口获取token再检查请求头Authorization字段格式前端页面请求跨域报错前端端口与后端端口不一致CORS未配置在后端增加CorsFilter全局跨域配置或使用Vue CLI的proxy代理转发中文乱码数据库字符集与连接字符集不一致建库时使用utf8mb4连接串中追加characterEncodingutf8mb4SQL执行报“字段不存在”导入的SQL脚本版本与实体类版本不一致对比源码中的实体类字段与数据库表结构手动补齐或重新导入最新SQL上传文件或图片后访问404上传目录与静态资源映射未配置在配置类中重写addResourceHandlers方法将上传目录映射为静态资源路径除了上面的表格还有几个需要啰嗦两句的经验点。第一个是关于跨域的。Vue开发服务器默认端口通常是8081而Spring Boot是8080只要前后端端口不一致就会出现跨域问题。开发阶段最省事的方案是在前端vue.config.js里配置代理module.exports { devServer: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }这样前端代码里所有请求都写成/api/xxx代理会把请求转发到后端8080端口从浏览器的视角看请求是同源的就不会触发跨域拦截。第二个是关于MyBatis-Plus的。如果你的项目用的是MyBatis-Plus实体类字段映射有一个经典坑数据库字段是下划线命名create_time实体类是驼峰命名createTime如果没开启下划线转驼峰配置所有时间字段都查不出值。MyBatis-Plus默认是开启这个转换的但如果是原生MyBatis就需要在application.yml里手动配置mybatis: configuration: map-underscore-to-camel-case: true第三个是关于Lombok的。代码里大量使用了Data、NoArgsConstructor这类注解如果IDE没装Lombok插件编译会直接报找不到getter/setter方法。刚接触项目的新手被这个问题卡住的概率很高而且报错信息迷惑性很强会让人误以为是代码本身写错了。第四个是JWT密钥长度的问题。如果你使用的JDK版本较新并且用了HS256加密密钥长度必须不少于256位也就是32个字节否则启动或调用时会抛异常。源码中理论上会内置一个足够长的密钥但如果你自己改成短字符串就会踩到这个坑。7. 学习路径与扩展建议这套源码还能怎么用如果你是以学习为目的拿到这套源码我的建议是别停留在“能跑起来”这个层面那只是刚开始。真正有价值的是接下来的三步进阶。第一步是先读代码再改需求。静下心来花一两天时间把这套系统的表结构、接口清单、核心Service实现通读一遍对整体架构就会形成一个相对完整的认知。在此基础上找到一个薄弱环节尝试改造比如给病历模块加上一个“模板选择”功能或者给患者档案模块扩展一个“就诊历史时间线”通过改需求倒逼自己真正理解代码。第二步是往工程化方向靠拢。这套源码虽然完整但考虑到教学场景它在日志埋点、异常处理、监控指标、生产环境配置等维度上大概率还有提升空间。你可以尝试接入统一的日志体系给关键业务操作加上操作日志记录把配置文件改成多环境Profile结构这些改造项比再写一个Demo对职业成长的帮助更显著。第三步是往微服务方向演进。电子病历系统天然适合拆分成患者服务、病历服务、处方服务、系统管理服务等独立模块。你可以尝试把单体架构中的患者模块和病历模块拆成两个Spring Boot应用通过OpenFeign进行远程调用借此熟悉微服务架构中最核心的服务拆分方法。循序渐进地做完这一步你对分布式系统下数据一致性、服务调用链路追踪等问题的理解会和只做单体项目的人拉开明显差距。拿我自己来说我最初复现这套系统时光是在环境配置和数据库导入上就耗了大半天一度怀疑是不是自己的姿势不对。后来摸清套路之后发现大部分时间都花在“小问题”上——字符集不对、端口冲突、依赖下载失败、路径写死而后端代码本身反而是整条链路中出错概率最低的环节。所以如果你在复现过程中被某个环境问题卡住了切换一下排查视角——先确认网络和端口通不通、数据库连不连得上、配置文件改得对不对往往比盯着代码本身更高效。这套源码用到的技术栈非常主流你把它整个流程打通一次积累下来的调试能力和全局思维对之后接触任何Spring Boot项目都有实实在在的助力。