尧图精选

Java毕设智慧医疗平台:从源码到部署答辩的全链路拆解

🕒 发布时间:2026/10/2 18:22:21 📁 来源:尧图网络
简介基于Java的智慧医疗服务平台完整毕业设计源码面向计算机、电子信息工程、数学等专业需要完成毕设、课程设计或期末大作业的学生也适合需要项目实战练习的开发者参考学习。项目采用Java后端与Vue前端相结合的技术栈代码结构清晰覆盖常见业务模块经严格调试运行稳定被评为98分毕设项目。压缩包共包含732个文件以Java源文件203个和Vue组件142个为核心辅以SVG图标、JS脚本、CSS样式、XML配置及图片素材另有构建与启动脚本包体仅19.3MB便于下载和部署。已有257人学习浏览适合作为系统源码和设计参考。压缩包内附带完整前端页面组件、后端接口代码、配置信息以及一键运行脚本可帮助读者快速启动项目、理解前后端交互逻辑配合清晰的目录结构和构建启动脚本适合需要完整项目方案和代码实现思路的毕业生进行二次开发与学习。1. 为什么智慧医疗服务平台是Java毕设里的高分常青树每年毕设季我收到最多的选题咨询之一就是Java方向能不能做“智慧医疗服务平台”。这个题目听起来很大拆开其实就是挂号、病历、报告三条主流程加一个后台管理Spring Boot MyBatis Plus 完全撑得住。很多人从网上下了一份毕业设计源码然后卡在第一步项目打不开、数据库脚本报错、跑起来又不知道改哪里。这篇就按我拿到这类源码后实际会做的事讲一遍——先拆业务模块再做表结构和核心代码最后落到部署、避坑和答辩演示。适合准备Java毕设、想把一套现成源码改成自己东西的人。这里不聊泛泛的医疗信息化概念只讲能落地的那部分。2. 把智慧医疗拆成四件事挂号、病历、报告与主数据毕设能落地的边界在哪2.1 三端怎么分患者端、医生端、管理端的权限边界与菜单差异智慧医疗服务平台在毕设里最常见的形态是一个前后端分离的Web应用少数会套一个H5或小程序壳。无论前端长什么样后端都逃不开三个端患者端、医生端、管理端。这三个端不是三个独立项目而是一套Spring Boot工程里按角色区分的接口分组。角色划分清楚了后端的权限模型、数据隔离和菜单设计才有的放矢。端核心业务数据范围典型接口患者端注册、挂号、缴费、查报告只看自己的数据/api/patient/appointment医生端排班、接诊、写病历、开检查看自己科室和名下患者/api/doctor/record管理端科室维护、医生审核、号源配置全库可读可写/api/admin/department权限落地的常见做法是用Spring Boot拦截器或Spring Security写一个角色过滤器解析JWT里的role字段然后对URL前缀做匹配。比如/api/admin/**只允许ROLE_ADMIN访问/api/doctor/**只允许ROLE_DOCTOR访问。我不建议在毕设阶段上太重的权限框架一个拦截器加一个注解就够答辩展示了。提示管理端不要混在患者端里做“隐藏菜单”。答辩时老师一定会问“怎么防止普通用户调管理接口”如果你答不上来前面演示做得再好也会扣分。至少用拦截器把三个前缀隔开。这套源码拿到手第一步不是看代码而是看pom.xml和application.yml先确认这个项目是不是前后端分离、用的什么鉴权方式。如果前端有独立的vue目录后端src/main/resources下没有static静态资源那十有八九是前后端分离项目如果所有页面都在templates或static里就是传统单体JSP/Thymeleaf项目。两种项目的启动和打包方式差别很大分清这一点比读业务代码更重要。2.2 门诊挂号与号源池毕设里的号码预扣和释放机制挂号是智慧医疗平台的命脉也是答辩老师最爱追问的模块。它的业务实质是“库存扣减”——号源就是库存挂号就是下单。这里有两套典型做法复杂度差出一个量级。第一套是直接扣减患者提交挂号请求UPDATE schedule SET remain remain - 1 WHERE id ? AND remain 0更新成功就生成挂号单失败就返回“号源已满”。这套逻辑占90%的毕设源码代码短、好解释缺点是抗不住并发同一个号源被两个线程同时读到remain1时会出现超卖。第二套是预扣加超时释放挂号时先把号源状态改成“锁定”给一个10分钟的支付/确认窗口超时未确认就释放回池子。这是我更推荐在答辩里讲的方案虽然代码量多一点但它能引出“定时任务”“Redis过期键”“事务补偿”三个加分点每一个都是java面试里真的会问的东西。落地时我会用一个枚举管号源状态而不是用一堆散落的魔法数字public enum SlotStatus { AVAILABLE(0, 可预约), LOCKED(1, 待确认), BOOKED(2, 已占用); private final int value; private final String desc; SlotStatus(int value, String desc) { this.value value; this.desc desc; } public int getValue() { return value; } }参数说明这个枚举在预约挂号的Service层贯穿全程AVAILABLE是初始状态LOCKED表示已经被某个患者锁定但还没缴完费BOOKED是最终占用。枚举的好处是让状态流转在代码里可见答辩画状态图时直接从枚举拿不用再去翻数据库字典。预扣的时序是进入挂号接口 → 用乐观锁把号源从AVAILABLE改成LOCKED→ 创建挂号单并生成一个10分钟倒计时 → 到点没支付就把状态改回AVAILABLE并把挂号单标记为“已过期”。如果你用的是RedisSETEX命令天然适合做这个倒计时不用Redis也能做起一个Scheduled定时任务每分钟扫一次LOCKED状态且lock_time超过10分钟的号源强制释放。两种方式在答辩里都很出彩我一般会做定时任务版本因为它不依赖额外中间件别人在你的机器上演示时少一个Redis也是一个减分项。2.3 电子病历的状态机别用布尔值管理就诊流程很多毕设源码的病历模块只有一个is_finished字段接了诊置1看报告置0导致业务流转完全失控。智慧医疗的就诊流程是线性的已挂号 → 已接诊 → 诊疗中 → 已开单 → 报告已出 → 已完成。这个流程用状态机管理比用三个布尔字段清晰得多。我在改造源码时会定义一个VisitStatus枚举把流转动作作为枚举方法写进去public enum VisitStatus { REGISTERED(0, 已挂号) { Override public VisitStatus next() { return CONSULTING; } }, CONSULTING(1, 接诊中) { Override public VisitStatus next() { return TREATING; } }, TREATING(2, 诊疗中) { Override public VisitStatus next() { return COMPLETED; } }, COMPLETED(3, 已完成) { Override public VisitStatus next() { return null; } }; private final int value; private final String desc; VisitStatus(int value, String desc) { this.value value; this.desc desc; } public abstract VisitStatus next(); }这段代码的巧妙之处在于把“下一个状态”的规则内聚到了枚举本身Service层只需要调current.next()不用在业务逻辑里写一长串if-else判断当前能跳到哪。参数说明value是入库的值desc是给前端展示的中文名next()是状态流转规则。答辩时老师问你“病历状态怎么防止跳级”你指着这个枚举说法律在编译期就保证了下一次只能从合法路径走这个回答比“我在Service里做了校验”有说服力得多。实际业务里还有一个分支诊疗中可能开出检查单检查报告回传后状态要从“待报告”回到“诊疗中”而不是直接“已完成”。这种非线性的旁路我建议不要硬塞进next()枚举而是单独写一个returnTo(Long recordId)方法做状态回退只允许TREATING接REPORT_READY分支。毕设源码里这种旁路经常写得一团乱你理清这条线就已经比原源码作者想得深一层了。3. 核心表结构与预约挂号最小代码从建表SQL到扣号事务3.1 五张核心表科室、排班、挂号单、病历、用户的字段设计智慧医疗平台的表通常有二十张上下但你不需要全部重写。拿到源码先找到这五张能串起主流程的表读懂了它们平台的骨架就在你脑子里了sys_user用户、department科室、schedule排班、registration_order挂号单、medical_record病历。这五张表的关联关系是用户挂排班的号生成挂号单医生凭挂号单写病历。表职责关键字段sys_user患者与医生统一账号role、name、phone、id_carddepartment科室主数据dept_name、doctor_countschedule号源排班doctor_id、dept_id、date、slot、remain、versionregistration_order挂号与缴费状态patient_id、schedule_id、status、pay_statusmedical_record病历与主诉patient_id、doctor_id、conclusion、create_time我见过很多毕设源码把用户表和医生表拆成两张物理表结果登录逻辑写得又长又啰嗦。毕设阶段用一张sys_user加role字段区分身份完全够用医生信息职称、科室放doctor_profile扩展表病人信息医保卡号、既往病史放patient_profile扩展表。扩展表用user_id关联主表查询时做一个连表即可。这种“主表存账号、附表存资料”的做法比拆成多套账号体系更符合Spring Security的习惯。下面是建表SQL里的精华部分含乐观锁字段和索引设计CREATE TABLE schedule ( id bigint(20) NOT NULL AUTO_INCREMENT, doctor_id bigint(20) NOT NULL COMMENT 医生用户ID, dept_id bigint(20) NOT NULL COMMENT 科室ID, schedule_date date NOT NULL COMMENT 出诊日期, slot varchar(16) NOT NULL COMMENT 时段MORNING/AFTERNOON, total_count int(11) NOT NULL DEFAULT 20 COMMENT 总号源数, remain_count int(11) NOT NULL DEFAULT 20 COMMENT 剩余号源数, version int(11) NOT NULL DEFAULT 0 COMMENT 乐观锁版本号, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0停诊 1出诊, PRIMARY KEY (id), KEY idx_doctor_date (doctor_id, schedule_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT医生排班号源表; CREATE TABLE registration_order ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 挂号单号, patient_id bigint(20) NOT NULL COMMENT 患者用户ID, schedule_id bigint(20) NOT NULL COMMENT 排班ID, visit_status tinyint(4) NOT NULL DEFAULT 0 COMMENT 就诊状态机, pay_status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0未支付 1已支付 2已退款, lock_time datetime DEFAULT NULL COMMENT 锁号时间, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_patient (patient_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT挂号订单表;字段说明schedule表里的version是乐观锁的关键所有“扣号源”的更新语句都必须带上它否则并发下一定超卖remain_count是每次患者真正抢号时扣减的目标字段。registration_order的order_no用唯一索引后续支付回调幂等就靠它我在代码里生成它的方式是yyyyMMddHHmmss 用户ID 4位随机数毕设里不用上雪花算法但要让老师看到你有“唯一单号”的意识。实际操作时把原源码里的建表脚本全部删掉用上面这套重新建一遍库你会发现业务功能反而更稳。原脚本里满眼是varchar(255)没有注释没有索引那是加分题变成了减分题。索引只加两条查询链路必须的按医生查排班、按患者查挂号单其他地方不要乱加索引否则答辩时数据库优化问题的解释成本会很高。3.2 预约挂号的最小代码路径Controller、Service、Mapper怎么协作从一次挂号请求到数据库落库源码里经过的路径是Controller → Service → Mapper。我把这条路径写成最小可复现版本你对照自己手里的源码看一般能立刻发现它在哪一步逻辑掉了链子。先看Controller层它只负责收参、调服务、返回结果不写任何业务RestController RequestMapping(/api/patient) public class AppointmentController { Autowired private AppointmentService appointmentService; PostMapping(/appointment) public Result? createAppointment(RequestBody AppointmentRequest request) { // 只做两件事参数翻译成业务对象调Service Long patientId JwtUtil.getCurrentUserId(); Long scheduleId request.getScheduleId(); return appointmentService.book(patientId, scheduleId); } }逻辑说明JwtUtil.getCurrentUserId()从当前登录态的Token里解析患者ID这个ID在后面的事务里要用来写registration_order。参数说明AppointmentRequest是一个只含scheduleId的DTO不要图省事直接传Map带了RequestBody之后前端传什么字段就由这个DTO约束多传的字段会被Spring忽略少传的会因为NotNull校验直接返回400。这也是规范的体现。Service层是事务和并发控制的中心扣号源和生成订单必须在一个事务里Transactional(rollbackFor Exception.class) public Result? book(Long patientId, Long scheduleId) { // 1. 查排班判断号源是否充足 Schedule schedule scheduleMapper.selectById(scheduleId); if (schedule null || schedule.getRemainCount() 0) { return Result.error(号源已满); } // 2. 乐观锁扣减号源防止并发超卖 int rows scheduleMapper.deductCount(scheduleId, schedule.getRemainCount(), schedule.getVersion()); if (rows 0) { return Result.error(号源已被抢完请刷新重试); } // 3. 生成挂号单初始状态都是0/0 RegistrationOrder order new RegistrationOrder(); order.setOrderNo(buildOrderNo(patientId)); order.setPatientId(patientId); order.setScheduleId(scheduleId); order.setVisitStatus(VisitStatus.REGISTERED.getValue()); order.setPayStatus(0); order.setLockTime(new Date()); registrationOrderMapper.insert(order); return Result.success(order.getOrderNo()); }逻辑说明第2步是整段代码的灵魂。deductCount做的不是“查出来减一再写回去”而是一条原子SQL——UPDATE schedule SET remain_count remain_count - 1, version version 1 WHERE id ? AND remain_count 0 AND version ?。两个条件缺一不可remain_count 0避免负号源version ?保证只有一个线程能更新成功。rows 0说明更新影响行数为0即并发下被别人抢走了直接返回失败。Mapper层写下对应的更新方法Mapper public interface ScheduleMapper extends BaseMapperSchedule { Update(UPDATE schedule SET remain_count remain_count - 1, version version 1 WHERE id #{id} AND remain_count 0 AND version #{version}) int deductCount(Param(id) Long id, Param(remainCount) Integer remainCount, Param(version) Integer version); }参数说明这里有个很容易被坑到的细节Param(remainCount)并不参与实际扣减计算它只是作为条件里的一个“当前版本视图”。很多毕设源码在这里抄错把remain_count写成了remain_count #{remainCount} - 1这在低并发下看起来正常压测一跑就超卖。正确的扣减是让数据库自己执行remain_count remain_count - 1不让Java变量参与库存值的计算否则你减的可能是一个过期的数目。3.3 分页、事务与数据一致性答辩追问和java面试题都爱考这几个参数这一节直接关系到你能不能招架住答辩老师的连环问。挂号单列表、病历查询都要分页MyBatis Plus的分页插件写法在源码里随处可见但真正要理解的参数是这几个current当前页码、size每页条数、total总条数。我会告诉你在源码里把分页插件配好并且强调一个参数——size必须设上限。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(50L); // 单页最大50条防止恶意大页码拖垮数据库 interceptor.addInnerInterceptor(pagination); return interceptor; } }参数说明setMaxLimit(50L)这个配置我在大多数毕设源码里都没见过但它极能体现工程素养。不设上限时前端传size10000你也会照查MySQL的LIMIT 10000不算致命但配合大表就会慢查询。答辩时老师问“接口怎么防慢查询”你把这一行配置和userId索引拿出来答案就完整了。事务是另一个重头戏。之前第3.2节的Transactional(rollbackFor Exception.class)三个部分各有讲究rollbackFor Exception.class表示所有异常都回滚默认情况下Spring只对RuntimeException回滚受检异常不会回滚很多源码在这里写Transactional不带参数导致抛SQLException时数据已经写了一半——这是数据一致性的大坑也是java面试题里“事务失效场景”的原题。提示如果你发现源码里所有Service方法都做了事务或者锁代码写在事务外面最好自己动手理一遍。事务的范围应该刚好包住“扣库存建订单”这两个写操作多余的操作比如发短信通知不应该混在这个事务里否则事务长时间持有连接数据库连接池会被占满。数据一致性这块毕设阶段真正要掌握的只有两个手段乐观锁和唯一索引。乐观锁处理了“库存扣减”层面的并发order_no唯一索引处理了“重复提交”层面的幂等。如果患者连续点了两次挂号按钮后端收到两个相同内容但order_no不同的请求唯一索引拦不住要靠前端按钮置灰或者后端做幂等表。源码里通常没有幂等设计你自己加一个request_id字段或者用Redis的SETNX都能讲出花来这是改造源码时最容易出彩的地方。4. 本地跑通智慧医疗源码JDK、数据库、Maven打包与启动排错4.1 环境匹配JDK版本、MySQL8密码规则和Redis哪些必须装拿到源码第一件事不是兴奋地双击start.sh而是匹配环境。这个环节最多的问题来自JDK和MySQL的版本错位老源码配新环境翻车是常态。环境项推荐版本新版本会踩什么坑JDK1.8对应Spring Boot 2.x源码用JDK8语法拿JDK17跑要么编译失败要么启动报错MySQL5.7 或 8.08.0默认认证插件改成了caching_sha2_password老JDBC驱动连不上Maven3.6.x3.9也没事但仓库镜像要配好否则依赖下载卡死Redis有就用3.x/4.x/5.x如果源码里没用到Redis不要自己硬加增加演示风险先说java环境变量配置这个基础项。下载JDK8后要设置JAVA_HOME并把它放到PATH首位。很多人的机器上装了不止一个JDKjava -version显示的版本和mvn -version用的版本不一致于是编译时用一套、运行时用另一套出现了只在特定机器上出现的怪问题。我的习惯是在命令行里分别跑一次java -version和mvn -v确认两个输出里的Java版本一致再往下走。MySQL那边如果是8.0版本且源码里的pom.xml用的是com.mysql.jdbc.Driver老驱动名启动必报Unable to load authentication plugin。解决方法是驱动升级成com.mysql.cj.jdbc.Driverpom.xml里MySQL Connector版本升到8.0.x连接串加上serverTimezoneAsia/Shanghai。如果是5.7基本不用折腾默认配置就能跑。Redis要不要装我的建议是看源码里有没有spring-boot-starter-data-redis依赖。有就老老实实装一个没有就千万别为了“显得高级”硬塞进去。毕设演示那天Redis服务没启动整个应用都会起不来为一个非核心模块增加一个崩溃点是得不偿失的。市面上很多智慧医疗源码的Redis只是用来存验证码和Token黑名单去掉它换成内存缓存功能也不受影响。4.2 从源码到运行初始化SQL、Maven打包和启动参数环境配好后的标准操作序列我固定是五步每一步都有明确产出出错也知道去看哪里# 1. 初始化数据库用第3章那份建表SQL再执行源码里的data.sql如果有 mysql -uroot -p123456 -e CREATE DATABASE smart_medical DEFAULT CHARSET utf8mb4; mysql -uroot -p123456 smart_medical docs/sql/schema.sql # 2. 检查配置文件确保数据源指向本机 vim src/main/resources/application.yml # 3. 打jar包跳过测试减少不必要的等待 mvn clean package -DskipTests # 4. 启动应用指定环境为本地 java -jar target/smart-medical-1.0.0.jar --spring.profiles.activedev # 5. 验证启动结果看端口和健康检查 curl http://localhost:8080/api/health逻辑说明第1步里的docs/sql/schema.sql是源码自带的脚本如果它报错直接用它所在目录里的其他SQL文件覆盖或者干脆用我之前给的表结构重建——源码的SQL脚本一般是最先烂掉的部分。第4步里的--spring.profiles.activedev是Spring Boot的外部化配置参数它能把application-dev.yml里的覆盖值启用起来比直接改application.yml要安全答辩演示和生产环境切换时不需要再改代码。启动失败的排查顺序也固定先看控制台第一行有没有Spring Boot的ASCII字样没有说明JVM没起来那查JAVA_HOME有字样但报数据库连接错误那查MySQL服务有没有启动、账号密码对不对报端口占用用netstat -ano | findstr 8080找出占用进程报NoClassDefFoundError那是依赖没下全把mvn -U clean package再执行一遍强制更新快照依赖。启动成功后会有一行Started Application in X.XXX seconds看到它不是终点还要确认Swagger或Knife4j能访问、登录接口能返回Token。很多源码启动日志正常但第一次登录就500问题一般出在application.yml里的mybatis-plus.mapper-locations路径写错Mapper XML没被加载于是所有Mapper方法执行时报Invalid bound statement。这个错误我把它的排查方法固定在第一步打开编译产物target/classes/mapper/看有没有.xml文件没有就是打包配置漏了资源目录往pom.xml的resources里加一行家目录即可。4.3 演示加分三项Mock支付回调、过期号源清理、日志链路到这一步源码已经能跑了但“能跑”和“能演示出亮点”之间还差三件小事。这三件事代码量不大每一件都能在答辩时多讲两分钟。第一件是Mock支付回调。智慧医疗平台的支付功能在毕设里大多接的是支付宝/微信的沙箱接口但沙箱有有效期限制很容易在演示当天掉链子。我的做法是在源码里加一个/api/admin/mockPay接口入参是orderNo后端直接把这个挂号单的pay_status置为1模拟支付成功回调。演示流程里“挂号→支付→医生端看到新患者”这个闭环就永远不会断不用依赖第三方沙箱的状态。PostMapping(/mockPay) public Result? mockPay(RequestParam String orderNo) { // 模拟第三方支付回调按订单号更新支付状态 RegistrationOrder order orderMapper.selectByOrderNo(orderNo); if (order null) { return Result.error(订单不存在); } order.setPayStatus(1); // 已支付 order.setVisitStatus(VisitStatus.REGISTERED.getValue()); // 已挂号 orderMapper.updateById(order); return Result.success(支付成功医生端可看到该患者); }逻辑说明这个接口要在管理端权限下开放不能在患者端暴露否则就绕过了真实支付流程。参数说明orderNo是挂号单号它承载了幂等性——支付回调天然有“可能重复通知”的特性所以这个接口内部不要做先查再改以外的多余操作多次调用同一orderNo时置为已支付是幂等的不会产生重复扣费。第二件是过期号源清理。如果源码里没有定时任务我自己加一个Scheduled方法每5分钟执行一次把registration_order里pay_status0且lock_time超过10分钟的挂号单状态改为“已取消”同时把对应schedule的remain_count加回去、version加一。这里的关键是释放号源时也要走乐观锁否则并发释放会把别人刚抢到的号源覆盖掉。第三件是日志链路。在application.yml里把日志级别调成DEBUG并输出到文件然后在挂号接口的入口和扣号SQL执行处各加一行日志打印订单号和影响行数。演示的时候如果出了岔子你能当场上终端看日志定位这个临场反应比PPT里任何截图都有用。我见过太多人演示失败后愣在原地而一句“我查一下日志”就能把事故现场变成技术能力展示。5. 智慧医疗毕设源码的五处踩坑现象、原因、解决一次说清5.1 现象挂号成功但医生端看不到新号源这是最常见也最隐蔽的bug。患者端明明显示“挂号成功”医生端刷列表却什么都没有。原因通常是两个一是挂号事务和医生端查询不在同一个数据库连接可见性里比如医生端查的是Redis缓存而扣号成功后没有删缓存二是visit_status没有按状态机的初始值写入医生端列表的查询条件默认查“已挂号状态”的患者而挂号单的该字段写成了NULL被SQL的WHERE visit_status 0过滤掉了。解决方式是双管齐下先查库里这条挂号单的visit_status是不是0如果不是修正Service里创建订单时的初始值如果是再看医生端是不是读缓存在扣号事务的afterCommit里删一次缓存键。我自己的习惯是毕设阶段不引入缓存列表数据库直查虽然慢一点但省一类查不到新数据的坑。5.2 现象前端反复跳登录Token明明没过期智慧医疗平台的移动端和H5页面里这个现象特别频繁。原因是跨域请求的预检OPTIONS被拦截器拦截了JWT还没解析完就被拦在门外前端拿到401就跳登录页而真实请求根本没有发出去。解决方式是在拦截器里加一行判断if (OPTIONS.equals(request.getMethod())) { return true; }。同时确认后端的CORS配置里设置了allowedHeaders和allowedMethods否则预检通过了正式请求也会被浏览器拦下。这个坑我建议改完代码后用浏览器开发者工具看Network面板凡是Preflight开头的请求返回401都往这个方向查。5.3 现象数据库时间比本地多了八小时挂号时间显示成凌晨两点报告时间比实际晚八小时这是MySQL连接串时区问题的典型表现。如果application.yml里的url是jdbc:mysql://localhost:3306/smart_medical没有serverTimezone参数MySQL驱动会拿服务器的默认时区来解析datetime字段而Linux服务器多半配置的是UTC于是差了八个小时。解决方式是把连接串改成jdbc:mysql://localhost:3306/smart_medical?serverTimezoneAsia/ShanghaiuseUnicodetruecharacterEncodingutf8。同时确认Java进程所在机器的时区也是东八区在启动命令里加-Duser.timezoneAsia/Shanghai。这个坑的隐蔽性在于它只在部署到服务器后出现本地Windows开发机怎么跑都对属于那种不提前踩过就很难想到的玄学问题。5.4 现象打包成jar后图片和页面全404在IDE里点运行一切正常前端页面、上传的用户头像都能显示但mvn package后用jar -jar启动所有静态资源找不到。原因是Maven打包时没有把前端构建产物或者上传目录包含进去Spring Boot的jar里只打包classpath上的文件src/main/resources/static如果被resources配置排除了页面就在包里不存在。解决方式是在pom.xml的build里补一段资源配置把src/main/resources包含进去并且确认application.yml里配置的上传目录路径是一个磁盘绝对路径而不是项目相对路径。如果你发现静态资源在jar包里存在但依然404那检查有没有配置spring.mvc.static-path-pattern默认是/**被改成了/static/**后图片的URL路径就要对应调整。这个坑对毕设的影响是灾难性的因为演示时你一定会打jar包而不是在IDE里点绿按钮。5.5 现象“你的高并发怎么验证”答辩现场被问住的死角挂号这个场景天然有并发属性老师几乎必问“你测试过并发吗TPS多少”源码跑得再好这个问题答不上来项目的工程可信度直接打折。可悲的是大多数毕设源码在开发时没做过任何压测你临时被问到只能说“理论上有乐观锁保证”。解决方式是提前做一个小型压测用JMeter开50个线程同时打挂号接口然后把结果截图放进答辩PPT。具体步骤是JMeter新建线程组并发数设50循环1次HTTP请求指向/api/patient/appointment给请求头加一个提前生成好的Token。跑完后看“异常率”和“响应时间”两个指标如果异常率是0你就有了实测数据。如果出现超卖回到第3章的deductCount检查SQL有没有写对。我还会额外做一个对比实验把乐观锁SQL临时改成“先查后写”的伪代码压测并记录异常率答辩时两张图放一起说“这个对比说明乐观锁在50并发下有实际效果”这是整场答辩最有技术分量的三分钟。提示压测时注意不要把size调成一个大到让应用崩溃的值那不是并发问题而是配置问题。另外JMeter跑完必须清零测试数据把测试产生的挂号单手动确认掉或清表否则体检馆看到一堆脏数据反而扣分。6. 答辩前把代码过成自己的演示验收清单与最该展示的一段SQL到这一步源码基本被整理成了你熟悉的东西。最后给自己过一遍验收清单按这个顺序演示逻辑最顺顺序演示项验证点对应模块1患者注册登录Token下发与拦截器放行患者端2科室列表与医生排班数据查询链路主数据3预约挂号并支付乐观锁扣号与Mock支付核心4医生端接诊与写病历状态机从0到1医生端5查看报告与结束流程状态机到终结态报告6管理端号源统计管理员权限隔离管理端过完清单挑一段你最熟悉的代码现场讲不用多一段就够。我自己的习惯是讲deductCount这条SQL因为它是整个项目里唯一一处用三行SQL解决并发问题的设计UPDATE schedule SET remain_count remain_count - 1, version version 1 WHERE id #{id} AND remain_count 0 AND version #{version}讲的时候别背按这个思路说为什么库存字段要在数据库里自减而不是在Java里算好再写为什么version条件能防超卖为什么影响行数为0时要让用户重试而不是重查。把这三点说清楚老师基本不会再追其他并发问题。我做毕设辅导这些年最深的教训是一个不要演示自己不理解的代码。哪怕是再简单的功能只要是你从源码包里抄过来但没追过一遍逻辑的答辩现场一旦被追问就漏洞百出反过来一段你亲手改过、跑过、甚至改出过bug又修好的代码即使简单也能讲出故事感。智慧医疗这个题目的上限很高但拿高分从来不需要你实现一个真正的医院信息系统而是把挂号、病历、报告这条主流程用正确的方式做完并讲透。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →