ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Spring Boot 3的电子健康记录管理系统毕业设计全解析

基于Spring Boot 3的电子健康记录管理系统毕业设计全解析 网上搜“springboot毕设”出来一大半是图书管理、学生选课、个人博客这类老面孔看多了确实容易审美疲劳。我当时选定“基于Spring Boot 3的电子健康记录管理系统”这个题目一方面是医疗信息化方向在毕业设计里不算烂大街写进论文有东西可挖另一方面Spring Boot 3本身变化不小从Java 17基线到Jakarta命名空间迁移再到Spring Security 6的写法调整光这些技术点就够凑出一整章“关键技术介绍”。整套系统从前到后做下来大概三周中间踩了不少坑尤其是Spring Boot 3和网上老教程对不上的那些地方。这篇文章就把我完整的设计思路、数据库表结构、后端实现细节、踩坑记录和答辩准备全部梳理一遍项目源码在文末有说明需要的可以直接拿去参考。1. 需求分析与整体设计思路1.1 这个毕设要解决的核心问题电子健康记录EHR在真实场景里是一个非常庞大的概念医院里的HIS系统、体检中心的健康档案、社区医疗的随访记录都归属于这个范畴。但作为毕业设计不可能也不需要对标医院级系统关键是把“电子化健康档案管理”这条主线做完整。我当时把核心需求拆成四块第一不同类型的用户管理员、医生、患者要有不同的权限和操作界面第二患者的健康档案要能记录基本信息、既往病史、过敏史、家族病史这些静态数据第三动态数据要跟上包括每次就诊的诊断记录、检查结果、用药处方第四要有基础的数据展示能力比如患者血压趋势、体重变化这类可视化图表。把这四条线走通整个系统的骨架就立起来了既覆盖了增删改查这个毕设基础盘又比单纯的管理系统多了一点医疗业务的味道。1.2 技术选型为什么死磕Spring Boot 3选Spring Boot 3不是拍脑袋决定的主要原因有三点。版本新有话题性。Spring Boot 3.0在2022年11月正式发布到现在已经非常成熟。它把基线提升到Java 17全面拥抱Jakarta EE 9的命名空间原来代码里的javax.servlet全部要变成jakarta.servlet。这个变化对做过SSM或者Spring Boot 2项目的人来说是一个必须重新适应的点写进毕设的“难点与解决方案”部分很加分。生态兼容性已经到位。早几年确实有第三方库不兼容Spring Boot 3的情况但现在主流的MyBatis-Plus、Sa-Token、Hutool、EasyExcel这些库都出了对应的Spring Boot 3版本做毕设完全够用。我实测下来唯一要注意的是部分老教程里的依赖坐标写法已经变了后面我会详细说。安全框架的写法和以前不一样了。Spring Security 6把之前WebSecurityConfigurerAdapter继承式写法彻底废弃改成组件注册加SecurityFilterChain的Lambda风格。这个改动对旧教程依赖症患者来说是最大的坑但也正好是论文里可以展开写的“技术难点”。整个系统我采用的是前后端分离结构后端Spring Boot 3提供RESTful API前端用Vue 3加Element Plus搭建管理界面。这里有个经验可以分享给正在做毕设的同学如果你们学校没有明确要求前后端不分离强烈建议用前后端分离方案。Spring Boot自带Thymeleaf那套模板渲染写起来是快但答辩时演示的效果非常朴素而Vue加Element Plus的表格、表单、对话框组件开箱即用界面质感完全不在一个级别。1.3 系统总体架构设计架构上我保持了清晰的分层思想Controller层只负责参数接收和响应封装Service层写业务逻辑Mapper层用MyBatis-Plus做数据库交互实体类用MyBatis-Plus的注解映射表结构不让SQL散落在业务代码里。这样分层的好处是答辩被问到“为什么这么设计”时可以很自然地回答职责单一、可维护性强、后续扩展方便。接口设计上采用RESTful风格统一返回结构用ResultT封装里面包含code、message、data三个字段。前端通过axios拦截器统一处理返回码登录失效时自动跳转到登录页用起来比较省心。所有需要登录的接口除登录接口外都要求请求头携带Authorization字段值就是JWT令牌。考虑到毕设的使用场景我把角色权限设计成三端管理员负责用户管理、数据统计和系统配置医生可以查看患者列表、维护就诊记录、开具用药处方患者能看到自己的完整档案、就诊历史和健康趋势图。权限控制用Sa-Token来做比起手写拦截器它对注解式鉴权支持更好一个SaCheckRole(admin)就能搞定接口级权限省下不少重复代码。2. 数据库设计与核心表结构2.1 实体关系拆解数据库设计是这类管理系统毕设的重中之重答辩时老师大概率会围绕表结构提问。我先梳理一下实体关系画不出E-R图的话也可以先在纸上列出来一个用户User可以扮演患者或医生一个患者拥有一份健康档案HealthRecord这是一对一关系一份档案关联多条就诊记录MedicalRecord这是一对多关系一次就诊可能开出多个药品所以用药记录MedicationRecord挂在就诊记录下面。体检数据PhysicalExam也挂在患者维度按时间记录一条条数据方便后续做趋势图。2.2 核心表结构说明用户表我设计得比较精简字段包括id、username、passwordBCrypt加密后存储、real_name、role、phone、status。密码加密这一点必须做我见过不少毕设源码直接把明文密码存数据库答辩被老师问一句“真实项目里密码不能存明文你怎么办”就很被动。用了Spring Security自带的BCryptPasswordEncoder加密既能体现安全意识代码也简单。健康档案表是整个系统的核心字段包括id、patient_id外键关联用户表、gender、birth_date、height、weight、blood_type、allergy_history、past_history、family_history、emergency_contact、emergency_phone。这里有个小设计考量为什么单独建一张档案表而不是直接塞在用户表里理由是这样可以把登录账号信息和医疗业务信息解耦以后想给档案加字段不用动用户表这在真实项目里很有意义。就诊记录表字段包括id、patient_id、doctor_id、visit_date、chief_complaint、diagnosis、treatment_plan、status。status字段我用数字表示0是待就诊、1是已完成、2是已取消。这个字段看着不起眼但它是做统计报表的基础比如按月份查就诊量、按医生查接诊量都靠它。用药记录表字段包括id、medical_record_id、drug_name、dosage、frequency、duration、remark。有人觉得用药记录放在就诊记录里作为JSON数组存就行了但单独建表的优势是后续可以按药品名统计使用频次甚至可以做用药提醒功能扩展性好得多。体检数据表字段包括id、patient_id、exam_date、height、weight、systolic_pressure、diastolic_pressure、heart_rate、blood_sugar、note。这张表是后面做折线图的数据来源所以设计了主要体征指标字段。特别注意要保留exam_date这个时间字段画时间序列图表时这是X轴的主力。2.3 演示数据的准备心得造数据是实操环节最耗时但最容易被忽略的一步。很多毕设系统看功能没毛病一演示就露馅原因就是演示数据太假。我花了一个晚上专门造了一套符合真实情况的演示数据一个管理员账号、三个医生账号、八个患者账号。每个患者有两年以上的体检记录按每三个月一条的频率生成血压血糖数据在正常范围内有波动不是死板的递增递减直线。就诊记录也造了十几次主诉、诊断、用药都写得像真人案例比如“高血压3年规律服药近一周头晕加重”这种。这套数据在答辩演示时非常加分老师看到的不再是空表格而是能讲出故事的数据。3. 后端核心功能实现3.1 登录鉴权与JWT接入登录鉴权我选的是JWT加Sa-Token的组合方案。使用流程是用户提交用户名密码后端校验通过后生成一个token返回给前端前端存在localStorage里每次请求时放在请求头satoken字段后端通过Sa-Token拦截器校验token有效性。具体配置分三步。第一步引入依赖dependency groupIdcn.dev33/groupId artifactIdsa-token-spring-boot3-starter/artifactId version1.37.0/version /dependency注意这个依赖坐标里的spring-boot3旧版本的sa-token-spring-boot-starter在Spring Boot 3下会直接启动报错这是网上教程最容易过期的地方。第二步在application.yml里配置token过期时间sa-token: token-name: satoken timeout: 86400 active-timeout: 1800 is-concurrent: true is-share: false is-log: false第三步写登录逻辑核心代码大致是这样public String login(String username, String password) { User user userMapper.selectOne(new LambdaQueryWrapperUser() .eq(User::getUsername, username)); if (user null) { throw new ServiceException(用户不存在); } if (!BCryptPasswordEncoder.matches(password, user.getPassword())) { throw new ServiceException(密码错误); } if (user.getStatus() 0) { throw new ServiceException(账号已被禁用); } StpUtil.login(user.getId()); return StpUtil.getTokenValue(); }登录逻辑里有几个细节值得在答辩时讲用户不存在和密码错误返回同样的提示避免泄露用户是否存在登录成功后把用户ID存入Sa-Token会话后续通过StpUtil.getLoginIdAsLong()就能拿到当前操作人ID账号状态在登录时就要校验防止被禁用的账号继续使用系统。3.2 健康档案管理的增删改查实现健康档案管理是核心业务逻辑上分为患者端和管理端两条线。患者端登录后只能查看和修改自己的档案医生端可以查看患者列表并进入某个患者的档案详情管理员拥有全部操作权限。权限控制在Controller层用注解实现比如医生端的接口标注SaCheckRole(doctor)管理端的接口标注SaCheckRole(admin)。档案的分页查询我用的是MyBatis-Plus的Page对象配合LambdaQueryWrapper做条件拼接public PageHealthRecordVO getHealthRecordPage(int pageNum, int pageSize, String keyword) { PageHealthRecord page new Page(pageNum, pageSize); LambdaQueryWrapperHealthRecord wrapper new LambdaQueryWrapper(); wrapper.like(StringUtils.hasText(keyword), HealthRecord::getPatientName, keyword) .orderByDesc(HealthRecord::getCreateTime); return healthRecordMapper.selectPage(page, wrapper); }这里有个自测时发现的坑MyBatis-Plus的物理分页需要额外的PaginationInnerInterceptor配置官方文档叫分页插件。不配置的话selectPage查出来的数据其实是全量只是内存里切片数据一多前端翻页就是几百毫秒的卡顿。配置方式是在MybatisPlusConfig里注册一个MybatisPlusInterceptor加上PaginationInnerInterceptor数据库类型设成MySQL这个问题就解决了。档案和用户是一对一关系查询档案详情时还需要把关联表的数据拼进来。这个场景我用了MyBatis-Plus的selectVoById配合TableField(exist false)标记非表字段查询出来后手动组装VO对象。有些教程推荐直接用TableName(autoResultMap true)加TableField(typeHandler ...)来做类型转换但那一套对毕设来说过度设计了反而增加理解成本。3.3 附件上传与静态资源映射健康档案支持上传检查报告图片或PDF附件这是系统里比较出效果的模块。上传接口我用的是Spring MVC自带的MultipartFile没有引入专门的OSSSDK因为毕设部署环境一般就是本地或者学生服务器对象存储服务没必要用。保存路径放在项目根目录下的upload/文件夹按日期分目录存储String datePath LocalDate.now().toString(); String uploadDir System.getProperty(user.dir) /upload/ datePath; File dir new File(uploadDir); if (!dir.exists()) { dir.mkdirs(); } String originalFilename file.getOriginalFilename(); String suffix originalFilename.substring(originalFilename.lastIndexOf(.)); String newFilename UUID.randomUUID().toString().replace(-, ) suffix; file.transferTo(new File(uploadDir, newFilename));文件名一定要用UUID重命名这个我踩过坑。学生交上来的PDF经常叫“新建文档.pdf”这种名字如果多个患者上传同名文件后上传的会把先上传的覆盖掉。改成UUID后彻底杜绝了这个问题。另外上传大小限制也要改一下Spring Boot默认单文件最大1MB检查报告扫描件很容易超。在配置类里注册MultipartConfigElement把maxFileSize设置成10MBmaxRequestSize设置成20MB就够用了。附件上传后还需要配置静态资源映射才能通过URL访问。在Spring Boot 3里自定义静态资源映射是重写WebMvcConfigurer的addResourceHandlers方法Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: System.getProperty(user.dir) /upload/); } }配置好后前端拿到附件地址直接拼上/upload/20240415/xxx.pdf就能在浏览器打开预览。3.4 数据统计与图表接口设计数据统计模块是我整套系统最后加上的也是答辩时被问得最多的模块。刚开始我觉得图表只是锦上添花做完之后发现它其实是区分“管理系统”和“毕业设计亮点”的分界线。我做了三个统计接口按月份统计近六个月的就诊数量按科室统计各科室接诊量占比查询指定患者的血压和体重历史数据。按月份统计的SQL用了MySQL的DATE_FORMAT函数SELECT DATE_FORMAT(visit_date, %Y-%m) AS month, COUNT(*) AS count FROM medical_record WHERE visit_date DATE_SUB(CURDATE(), INTERVAL 6 MONTH) GROUP BY month ORDER BY monthMyBatis-Plus里可以直接用Select注解写这个方法返回一个Map列表。这里建议不要图省事返回ListString保留month和count两个字段前端直接绑定ECharts的数据格式省去二次加工的麻烦。患者血压趋势图的数据接口会同时返回收缩压和舒张压两组数据前端用ECharts的双Y轴图表展示。这个图表做出来效果很直观导师看了基本都会点头。4. 前端页面与联调中的关键细节4.1 前端工程搭建前端我用Vue 3加Vite加Element Plus的组合用npm create vuelatest初始化工程。这里要提醒一句Vite对Node版本有要求必须16以上我一开始在旧电脑上装了Node 14启动直接就报错升级Node后一切正常。工程里用到的核心依赖有axios、vue-router、pinia状态管理、echarts、dayjs。dayjs这个库强烈建议引入处理日期格式比手写格式化函数省太多事比如接口返回的2024-04-15T10:30:00前端用dayjs(value).format(YYYY-MM-DD)一行搞定。路由守卫是前端鉴权的关键。在src/router/index.js里配置好所有路由后通过router.beforeEach做判断如果访问的页面需要登录且本地没有token就跳转到登录页如果已经登录但访问的是登录页就跳转到首页。Element Plus的菜单组件和路由联动也比较方便侧边栏菜单根据当前路由的meta.title动态渲染。4.2 接口对接的常见问题前后端联调阶段最大的坑是跨域。我在后端写了跨域配置类实现WebMvcConfigurer接口的addCorsMappings方法允许所有来源访问/**路径。不过要注意的是配置文件里前端地址是http://localhost:5173后端是http://localhost:8080只有开放跨域才能正常请求。更规范的方案是使用Nginx反向代理把前后端放在同域下但毕设阶段直接在后端开放CORS已经足够。另一个问题是时间字段格式不一致。后端返回的LocalDateTime默认序列化成2024-04-15T10:30:00这种ISO格式而前端表格显示需要2024-04-15 10:30:00。我统一在配置里加了Jackson的序列化配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8全局生效所有接口传回来的时间格式都一样省得每个VO都加JsonFormat注解。5. 常见问题与排查技巧实录5.1 Spring Boot 3的依赖兼容性问题这是所有用Spring Boot 3做毕设的同学最需要关注的问题。网上的教程大量停留在Spring Boot 2.x时代很多依赖坐标拿过来直接用会报错。我归纳了一份针对Spring Boot 3的常见依赖对照清单组件Spring Boot 2时代写法Spring Boot 3正确写法MyBatis-Plusmybatis-plus-boot-startermybatis-plus-spring-boot3-starterSa-Tokensa-token-spring-boot-startersa-token-spring-boot3-starter数据库驱动mysql-connector-javacom.mysql:mysql-connector-j命名空间javax.*jakarta.*除了依赖坐标配置项也有一些变化比如Spring Boot 3的application.yml里spring.redis.host改成了spring.data.redis.host。如果同时使用Redis做缓存这个改动很容易被忽略。5.2 MyBatis-Plus分页查询总页数为0这个坑让我排查了整整一个下午。现象是调用分页接口返回的数据正常但total字段一直是0。后来发现是MyBatis-Plus的PaginationInnerInterceptor没有正确注册。Spring Boot 3的自动配置对MyBatis-Plus插件的扫描机制和Boot 2略有不同除了配置Bean的MybatisPlusInterceptor还要确保Configuration类能被主启动类的SpringBootApplication扫描到。如果放在子包外面配置类就不会生效分页静默降级成内存分页。5.3 JPA或MyBatis查询结果转换异常使用MyBatis-Plus时如果实体类里添加了非表字段比如patientName、doctorName查询时MyBatis会把它们当作表字段拼接进SQL导致报错“Unknown column”。解决办法是在非表字段上加TableField(exist false)注解。这个知识点属于MyBatis-Plus的经典坑几乎每个用它的毕设都会踩到。5.4 文件上传到本地路径后浏览器无法访问上传成功后我把文件路径存进了数据库前端拿着路径去请求图片却404。排查下来是因为Spring Boot默认只把classpath:/static/作为静态资源目录上传到项目根目录upload/的文件不在默认映射范围内。需要重写addResourceHandlers把/upload/**映射到磁盘路径具体代码前面已经贴过。这个坑很隐蔽因为本地IDE跑的时候文件确实存在不过浏览器就是访问不到非常容易被误判成前端问题。5.5 答辩前必须准备的问题清单答辩是毕设的最后一步系统做得再好讲不清楚也白搭。我结合亲身体验整理了一份高频问题清单基本覆盖老师最关心的维度为什么选择Spring Boot 3而不是Spring Boot 2答新版本技术栈更先进性能更好安全性更强通过使用新版本可以体现对新技术的掌握能力。密码加密是怎么做的答使用BCrypt算法进行加密BCrypt是单向哈希算法即使数据库泄露也无法反推出明文密码。权限控制是怎么实现的答使用Sa-Token框架的注解式鉴权通过SaCheckRole在Controller层对接口进行权限控制。表之间的关联关系答用户与档案一对一档案与就诊记录一对多就诊记录与用药记录一对多。系统有哪些改进空间答可以引入消息队列做预约提醒引入分库分表应对大数据量引入Redis缓存热点数据提升性能。这些问题想清楚之后答辩会从容很多。尤其是前两个问题几乎是必问的提前准备等于提前送分。6. 项目扩展想法与源码说明这套系统做完之后我自己最大的感触是它的可扩展性比想象中强。如果想把项目再往上拔一个档次可以考虑往这几个方向加功能接入Redis缓存热点用户数据和验证码引入Spring Security OAuth2实现第三方登录用WebSocket做在线问诊的实时消息推送把统计报表接上定时任务每天凌晨自动生成汇总数据。每一个方向都可以作为项目亮点写进简历也能在论文的“未来展望”章节里凑篇幅。最后说回源码。这套系统的完整源码包括前端Vue工程、后端Java工程、数据库SQL脚本和演示数据我已经整理好放在网盘里。代码里的注释做了详细标注核心模块写了README说明跟着数据库脚本导入数据后直接就能跑起来。有需要的同学可以在评论区留言获取也欢迎在文章下面交流Spring Boot 3踩坑心得。作为一个过来人我的建议是源码可以用来参考和学习但一定要自己动手把代码敲一遍尤其是遇到问题、排查问题、解决问题的过程那才是毕业设计真正的收获。
返回列表