简介:本资源是一套完整的Java毕业设计项目——医院电子病历管理系统源码,面向计算机专业本科生及Java初学者,解决医疗信息化场景下病历管理、预约挂号与医生排班等核心业务建模与实现问题。压缩包共2000个文件,含143个Java业务逻辑类、360个JSP前端页面、274个CSS样式文件、708个JS交互脚本及4个SQL建表与初始化脚本,辅以XML配置、properties参数文件和少量图片资源,整体63.27MB,结构清晰,模块边界明确,便于分层学习与调试。已有195人下载学习,适合课程设计实践、SSM框架综合训练及毕业设计参考。读者可直接导入Eclipse或IDEA运行,完整复现个人中心、科室/医生/出诊管理、预约挂号、门诊病历录入与就诊评价等11个功能模块,并通过Navicat快速初始化MySQL 5.7+数据库,配套说明文档与LW(论文)素材进一步降低二次开发门槛。
1. 为什么医院电子病历系统毕业设计总卡在「能跑通」和「真可用」之间?
你下载了这个标着【java毕业设计】医院电子病历管理系统源码(ssm+mysql+说明文档+LW).zip,解压后看到pom.xml里写着 Spring 4.3.28、MyBatis 3.4.6、SpringMVC、MySQL 5.7 —— 看似标准的 SSM 三件套,连毕业论文(LW)都配好了。但一运行,Tomcat 启动报ClassNotFoundException: org.springframework.web.context.ContextLoaderListener;改完依赖又卡在登录页 404;好不容易进去了,患者列表空着,数据库里patient表明明有数据,却查不出来;导出 PDF 报NoClassDefFoundError: com.itextpdf.text.Document;更别说权限控制形同虚设,护士能删医生的诊断记录……这不是代码写得烂,而是SSM 架构在真实医疗业务场景下暴露的典型断层:它把「技术栈能搭起来」和「业务逻辑能闭环」当成一回事。本篇不讲 Spring 是什么、MyBatis 怎么配置 XML,而是带你用真实医院科室排班、门诊号段管理、诊断编码映射(ICD-10)、病历结构化录入这四个硬骨头,把这套毕业设计源码从「演示级 demo」拉回「可调试、可验证、可延展」的工程基线。适合正在赶毕设 deadline 的 Java 本科生,也适合想快速复用 SSM 模板做医疗信息化轻量原型的初级工程师——关键不是抄代码,是知道哪几行必须改、哪张表字段不能少、哪个 SQL 语句漏了事务隔离。
2. 用 SSM 搭建电子病历系统:从 pom.xml 到 Controller 层的最小闭环
SSM 不是三个框架简单堆砌,而是一条数据流:前端请求 → SpringMVC DispatcherServlet → Service 层业务编排 → MyBatis 执行 SQL → MySQL 返回结果 → SpringMVC 封装响应。毕业设计源码常把这整条链路写成“能过编译”,但没打通“业务语义”。我们以「新增门诊患者」为例,重建这条链路。
2.1 依赖版本对齐:别信 zip 包里的 pom.xml,先校验三处硬伤
毕业设计源码的pom.xml常存在三类致命版本冲突:
- Spring 4.x 与 MyBatis 3.4.x 兼容,但若引入
mybatis-spring版本错配(如用了 2.0.0+),会导致SqlSessionFactoryBean初始化失败; - MySQL Connector/J 驱动版本低于 5.1.47 时,
serverTimezone=UTC参数会被忽略,导致时间字段存入乱码; jstl和standard标签库版本不匹配,JSP 页面<c:forEach>直接报 500。
提示:直接替换为经实测兼容的组合(Spring 4.3.28.RELEASE + MyBatis 3.4.6 + mybatis-spring 1.3.2 + mysql-connector-java 5.1.47)
<!-- pom.xml 关键依赖段 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.28.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency>为什么必须锁死这些版本?
Spring 4.3.x 的@Transactional注解底层依赖TransactionSynchronizationManager的resourcesMap 结构,MyBatis-Spring 1.3.2 正好适配该结构;高版本会因ThreadLocal清理时机差异导致事务不生效——这是毕业设计里「增删改没反应」的根源之一。
2.2 数据库初始化:用 MySQL 5.7 建库建表,绕开字符集玄学
毕业设计文档常写“执行 sql 文件即可”,但hospital.sql里若没显式声明CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci,MySQL 5.7 默认用latin1,中文字段存进去全是问号。更隐蔽的是datetime字段没设DEFAULT CURRENT_TIMESTAMP,导致插入时create_time为空引发 NPE。
-- hospital.sql 必须包含的建库语句(直接复制执行) CREATE DATABASE IF NOT EXISTS `hospital_db` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; USE `hospital_db`; CREATE TABLE `patient` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '患者姓名', `id_card` varchar(18) NOT NULL COMMENT '身份证号', `gender` tinyint(1) NOT NULL DEFAULT '0' COMMENT '性别:0-男,1-女', `birth_date` date DEFAULT NULL COMMENT '出生日期', `phone` varchar(11) DEFAULT NULL COMMENT '联系电话', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_id_card` (`id_card`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='患者基本信息表';参数说明:
utf8mb4:支持 emoji 和四字节 UTF-8 字符(如某些生僻汉字),避免病历中出现「」;CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP:让 MySQL 自动维护时间戳,省去 Java 层手动 set;UNIQUE KEY uk_id_card:身份证号唯一索引,防止重复挂号——这是医疗系统底线,不是可选项。
2.3 Controller 层落地:一个@RequestMapping背后的三层校验
毕业设计常把 Controller 写成“接收参数→调 Service→返回 JSON”,但真实电子病历要求:
①前端传参校验(如身份证号格式、手机号正则);
②业务规则校验(如同一患者 24 小时内不可重复挂号);
③数据一致性校验(如科室 ID 存在、医生 ID 在该科室排班中)。
// PatientController.java @RequestMapping(value = "/add", method = RequestMethod.POST) @ResponseBody public Result addPatient(@RequestBody Patient patient) { // ① 前端传参校验(用 Hibernate Validator 更规范,此处简化) if (StringUtils.isBlank(patient.getName()) || !IdCardUtil.isValidIdCard(patient.getIdCard())) { return Result.fail("姓名或身份证号格式错误"); } // ② 业务规则校验:24小时内不可重复挂号 Date now = new Date(); Date yesterday = DateUtil.addHours(now, -24); int count = patientService.countByCardAndTime(patient.getIdCard(), yesterday, now); if (count > 0) { return Result.fail("该患者24小时内已挂号,请勿重复操作"); } // ③ 数据一致性校验:科室ID必须存在 Department dept = departmentService.getById(patient.getDeptId()); if (dept == null) { return Result.fail("科室不存在,请检查科室ID"); } // 执行插入 boolean success = patientService.insert(patient); return success ? Result.success() : Result.fail("挂号失败,请重试"); }逻辑说明:
IdCardUtil.isValidIdCard()是自研身份证校验工具类(非正则,含校验码算法),避免前端随便输个 110101199003072XXX 就通过;countByCardAndTime()对应 SQL 使用WHERE id_card = ? AND create_time BETWEEN ? AND ?,注意BETWEEN包含边界,且create_time类型必须是datetime;departmentService.getById()查询科室表,若返回 null 则说明前端传了假科室 ID——这是防止恶意构造请求的关键拦截点。
3. MyBatis 映射与业务逻辑:让病历数据真正“活”起来的三张核心表
SSM 毕业设计最常翻车的地方,是 MyBatis 的Mapper.xml只写了单表 CRUD,但电子病历本质是多实体强关联的树状结构:一个门诊记录(outpatient_record)关联患者(patient)、医生(doctor)、科室(department)、诊断(diagnosis)、处方(prescription)。毕业设计源码往往只实现“查患者列表”,却没处理“查某医生今日所有门诊记录及对应诊断详情”。
3.1 门诊记录表(outpatient_record):设计必须带“号源”和“状态机”
医院挂号不是插入一条记录就完事,它要管理号源池(如某医生上午限号 20 个)、预约状态(已预约/已就诊/已取消)、就诊时段(08:00-08:30)。毕业设计源码常把outpatient_record设计成扁平表,缺失关键字段:
CREATE TABLE `outpatient_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `patient_id` bigint(20) NOT NULL COMMENT '患者ID', `doctor_id` bigint(20) NOT NULL COMMENT '医生ID', `dept_id` bigint(20) NOT NULL COMMENT '科室ID', `reg_no` varchar(20) NOT NULL COMMENT '挂号序号,如20240520001', `reg_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '挂号时间', `visit_date` date NOT NULL COMMENT '就诊日期', `visit_period` tinyint(2) NOT NULL COMMENT '就诊时段:1-上午,2-下午,3-晚上', `status` tinyint(1) NOT NULL DEFAULT '0' COMMENT '状态:0-已预约,1-已就诊,2-已取消,3-爽约', `queue_number` int(5) DEFAULT NULL COMMENT '叫号序号,如第5位', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_reg_no` (`reg_no`), KEY `idx_patient_id` (`patient_id`), KEY `idx_doctor_id` (`doctor_id`), KEY `idx_visit_date` (`visit_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='门诊挂号记录表';为什么reg_no必须唯一且带日期前缀?
20240520001表示 2024年5月20日第1号,方便按日统计号源使用率;queue_number用于叫号屏显示,避免人工喊号出错;status是状态机起点,后续所有业务(如医生接诊、收费、发药)都基于此字段流转。
3.2 诊断表(diagnosis)与 ICD-10 编码映射:别再用字符串存诊断名称
毕业设计源码常见错误:diagnosis表里disease_name是varchar(100),直接存“高血压病3级 很高危”。这导致无法统计“高血压病”总人次(因写法不统一)、无法对接医保结算(医保要求 ICD-10 编码)。正确做法是诊断主表存编码,辅以字典表映射名称:
-- 诊断主表(关联门诊记录) CREATE TABLE `diagnosis` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `record_id` bigint(20) NOT NULL COMMENT '门诊记录ID', `icd_code` varchar(10) NOT NULL COMMENT 'ICD-10编码,如I10', `icd_name` varchar(200) NOT NULL COMMENT 'ICD-10名称,如原发性高血压', `is_primary` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否主诊断:0-否,1-是', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_record_id` (`record_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ICD-10 字典表(供前端下拉选择) CREATE TABLE `icd10_dict` ( `code` varchar(10) NOT NULL COMMENT 'ICD-10编码', `name` varchar(200) NOT NULL COMMENT '疾病名称', `parent_code` varchar(10) DEFAULT NULL COMMENT '父编码,支持树形结构', `level` tinyint(1) NOT NULL DEFAULT '1' COMMENT '层级:1-大类,2-亚类,3-细类', PRIMARY KEY (`code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;MyBatis 映射技巧:
在DiagnosisMapper.xml中,用collection标签嵌套查询诊断详情,避免 N+1 查询:
<!-- 查询门诊记录及关联诊断 --> <select id="selectRecordWithDiagnosis" resultType="OutpatientRecord"> SELECT r.*, d.icd_code, d.icd_name, d.is_primary FROM outpatient_record r LEFT JOIN diagnosis d ON r.id = d.record_id WHERE r.id = #{id} </select>参数说明:
LEFT JOIN确保无诊断记录的门诊也能查出(如刚挂号未就诊);resultType="OutpatientRecord"要求OutpatientRecord类中定义List<Diagnosis> diagnosisList字段,并在ResultMap中配置collection;- 若需分页查“某医生今日所有门诊及诊断”,SQL 中
GROUP BY r.id并用GROUP_CONCAT(d.icd_name)拼接诊断名称,但注意长度限制(默认 1024)。
3.3 处方表(prescription)与药品库存联动:事务必须跨三张表
毕业设计常把开处方写成“插入处方头 + 插入处方明细”,但真实场景要求:
① 插入处方头(prescription_head);
② 插入处方明细(prescription_detail);
③ 更新药品库存(drug_stock);
④ 若库存不足,整个事务回滚。
// PrescriptionService.java @Transactional(rollbackFor = Exception.class) public boolean createPrescription(PrescriptionHead head, List<PrescriptionDetail> details) { // ① 插入处方头 prescriptionHeadMapper.insert(head); // ② 插入处方明细(批量插入提升性能) if (!details.isEmpty()) { prescriptionDetailMapper.insertBatch(details); } // ③ 扣减库存:遍历明细,检查并更新 drug_stock for (PrescriptionDetail detail : details) { // 先查当前库存 DrugStock stock = drugStockMapper.selectByDrugId(detail.getDrugId()); if (stock == null || stock.getStockNum() < detail.getQuantity()) { throw new RuntimeException("药品【" + detail.getDrugName() + "】库存不足"); } // 扣减库存(乐观锁:where stock_num >= ?) int updated = drugStockMapper.updateStock( detail.getDrugId(), detail.getQuantity(), stock.getVersion() ); if (updated == 0) { throw new RuntimeException("库存扣减失败,请重试"); } } return true; }关键点解析:
@Transactional保证原子性,任何一步失败自动回滚;drugStockMapper.updateStock()的 SQL 必须带AND version = #{version}实现乐观锁,避免并发超卖;insertBatch()比循环insert()快 5~10 倍,MyBatis 3.4.0+ 支持<foreach>批量插入。
4. 避坑指南:SSM 医疗系统上线前必踩的 5 个深坑及血泪解法
毕业设计源码最大的陷阱,是它“看起来能跑”,但一模拟真实业务就崩。以下是我在医院信息科驻场时,帮学生 debug 过的 5 个高频致命坑,每一条都附带现象、根因、解法,拒绝玄学。
4.1 现象:Tomcat 启动成功,但访问/login返回 404,控制台无任何错误日志
原因:web.xml中DispatcherServlet的url-pattern配置为/,但spring-mvc.xml里<mvc:annotation-driven />缺失,导致@Controller注解不被扫描。
解法:
① 检查web.xml,确认DispatcherServlet的url-pattern是/(不是*.do);
② 在spring-mvc.xml开头添加<mvc:annotation-driven />;
③ 确保spring-mvc.xml被ContextLoaderListener加载(contextConfigLocation指向正确路径);
④ 若用 IDEA,右键项目 →Add Framework Support→ 勾选Web Application,避免web.xml被忽略。
4.2 现象:登录成功后跳转到首页,但导航栏显示“欢迎,null”,session.getAttribute("user")为 null
原因:LoginController中request.getSession().setAttribute("user", user)执行了,但web.xml里<session-config>的cookie-http-only为 true,且前端 AJAX 请求未设withCredentials: true,导致 session cookie 未携带。
解法:
①web.xml中注释掉<http-only>true</http-only>或设为 false;
② 前端登录请求加xhrFields: { withCredentials: true };
③ 后端LoginController返回 JSON 前,显式设置response.setHeader("Access-Control-Allow-Credentials", "true");
④ 浏览器地址栏输入http://localhost:8080/hospital/(带上下文路径),不要直接开 HTML 文件。
4.3 现象:患者列表分页正常,但点击“导出 Excel”按钮,后台报java.lang.NoClassDefFoundError: org/apache/poi/ss/usermodel/Workbook
原因:毕业设计源码的pom.xml里poi依赖版本过低(如 3.9),与 JDK 8+ 的java.timeAPI 冲突;或poi-ooxml未引入,导致读写.xlsx失败。
解法:
① 统一升级 POI 到 4.1.2(兼容 JDK 8):
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.1.2</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.1.2</version> </dependency>② 导出方法中,用XSSFWorkbook替代HSSFWorkbook(支持 .xlsx);
③ 若导出大量数据(>10万行),改用SXSSFWorkbook避免内存溢出。
4.4 现象:医生修改诊断后,历史诊断记录被覆盖,无法追溯修改痕迹
原因:diagnosis表设计为“单条记录更新”,而非“每次修改插入新记录”。毕业设计源码的updateDiagnosis()方法直接UPDATE diagnosis SET ... WHERE record_id = ?。
解法:
① 修改表结构,删除record_id唯一约束,允许同一门诊记录有多条诊断;
②updateDiagnosis()改为insertDiagnosis(),插入新记录并标记is_current = 1,同时将旧记录is_current = 0;
③ 查询时WHERE record_id = ? AND is_current = 1获取最新诊断。
4.5 现象:MySQL 本地能连,部署到服务器后报Communications link failure
原因:MySQL 5.7 默认skip-networking=ON或bind-address=127.0.0.1,仅允许本地连接;或防火墙未开放 3306 端口。
解法:
① 修改my.cnf:
[mysqld] # 注释掉 skip-networking # skip-networking # bind-address 改为 0.0.0.0 或注释 bind-address = 0.0.0.0② 执行mysql -u root -p,运行:
GRANT ALL PRIVILEGES ON *.* TO 'hospital_user'@'%' IDENTIFIED BY 'your_password'; FLUSH PRIVILEGES;③ 服务器执行sudo ufw allow 3306(Ubuntu)或firewall-cmd --permanent --add-port=3306/tcp(CentOS)。
5. 权限控制与安全加固:用 Shiro 替换毕业设计里的“假权限”
毕业设计源码的权限控制,90% 是“登录后跳转不同页面”,但真实电子病历必须做到:
- 护士只能查看自己科室的患者,不能看其他科室;
- 实习医生只能录入诊断,不能修改已提交的处方;
- 系统管理员可重置密码,但不能查看患者身份证号明文。
SSM 原生没权限框架,强行用if(role.equals("nurse"))写满 Controller,既难维护又易绕过。Shiro 是轻量级、可插拔、与 SSM 无缝集成的方案。
5.1 Shiro 集成:三步接入,不改一行业务代码
Shiro 的核心是Subject(当前用户)、SecurityManager(安全管理器)、Realm(数据源)。毕业设计只需改三处:
①添加依赖(pom.xml):
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.7.1</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.7.1</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.7.1</version> </dependency>②配置 ShiroFilterFactoryBean(spring-shiro.xml):
<!-- 定义 Realm --> <bean id="authRealm" class="com.hospital.shiro.HospitalRealm"> <property name="credentialsMatcher" ref="credentialsMatcher"/> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="authRealm"/> </bean> <!-- Shiro Filter --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.jsp"/> <property name="successUrl" value="/index.jsp"/> <property name="unauthorizedUrl" value="/403.jsp"/> <property name="filterChainDefinitionMap" ref="chainDefinitionMap"/> </bean> <!-- URL 权限规则 --> <bean id="chainDefinitionMap" class="java.util.LinkedHashMap"> <constructor-arg> <map> <entry key="/login.jsp" value="anon"/> <entry key="/login" value="anon"/> <entry key="/static/**" value="anon"/> <entry key="/logout" value="logout"/> <entry key="/**" value="authc,roles[admin,doctor,nurse]"/> </map> </constructor-arg> </bean>③自定义 Realm(HospitalRealm.java):
public class HospitalRealm extends AuthorizingRealm { @Autowired private UserService userService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 查询用户角色(如 doctor, nurse) List<String> roles = userService.findRolesByUsername(username); info.addRoles(new HashSet<>(roles)); // 查询用户权限(如 patient:read, diagnosis:write) List<String> permissions = userService.findPermissionsByUsername(username); info.addStringPermissions(new HashSet<>(permissions)); return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); User user = userService.findByUsername(username); if (user == null) { throw new UnknownAccountException("用户名不存在"); } // 密码比对由 Shiro 自动完成 return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName()); } }参数说明:
anon:无需认证,如登录页、静态资源;authc:必须认证;roles[admin,doctor]:必须拥有 admin 或 doctor 角色;perms["patient:read"]:需有 patient:read 权限(可在 Controller 方法上用@RequiresPermissions("patient:read")注解)。
5.2 前端权限控制:JSP 标签与 JS 双保险
Shiro 提供 JSP 标签库,避免前端“按钮可见但点击报 403”:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <!-- 只有 doctor 角色可见 --> <shiro:hasRole name="doctor"> <button onclick="openDiagnosisDialog()">录入诊断</button> </shiro:hasRole> <!-- 只有拥有 patient:delete 权限才显示删除按钮 --> <shiro:hasPermission name="patient:delete"> <a href="javascript:void(0)" onclick="deletePatient(${p.id})">删除</a> </shiro:hasPermission>JS 端二次校验(防右键审查元素删掉标签):
// 获取当前用户角色 function getCurrentUserRoles() { // 从 sessionStorage 或全局变量读取(登录后存入) return JSON.parse(sessionStorage.getItem('userRoles') || '[]'); } // 检查是否有某权限 function hasPermission(permission) { const perms = JSON.parse(sessionStorage.getItem('userPerms') || '[]'); return perms.includes(permission); } // 调用前校验 if (hasPermission('diagnosis:write')) { openDiagnosisDialog(); } else { alert('您没有录入诊断的权限'); }5.3 敏感数据脱敏:身份证、手机号、病历内容加密存储
毕业设计源码常把id_card、phone明文存 MySQL,违反《个人信息保护法》。Shiro 可集成加解密,但更推荐在 MyBatis 层透明处理:
①定义 TypeHandler(IdCardTypeHandler.java):
public class IdCardTypeHandler implements TypeHandler<String> { @Override public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { // 存储时 AES 加密 String encrypted = AesUtil.encrypt(parameter, "hospital_key_2024"); ps.setString(i, encrypted); } @Override public String getResult(ResultSet rs, String columnName) throws SQLException { // 查询时 AES 解密 String encrypted = rs.getString(columnName); return encrypted != null ? AesUtil.decrypt(encrypted, "hospital_key_2024") : null; } // ... 其他重载方法 }②在patient表的id_card字段映射中指定 TypeHandler:
<result column="id_card" property="idCard" jdbcType="VARCHAR" typeHandler="com.hospital.handler.IdCardTypeHandler"/>关键点:
AesUtil使用 AES/CBC/PKCS5Padding,密钥"hospital_key_2024"存于配置文件,禁止硬编码;- 加密后字符串长度固定(如 AES-128 加密 18 位身份证得 32 字符),
id_card字段类型改为VARCHAR(64); - 前端展示时,用
****遮盖中间 8 位(110101********1234),数据库仍存完整密文。
6. 毕业答辩前最后一周:用这 3 个动作让评委眼前一亮
答辩不是讲“我用了 SSM”,而是证明“我理解医疗业务,且代码能支撑它”。我带过的 12 届毕设学生里,90% 卡在“功能演示顺利,但被问‘如果并发挂号怎么办’就懵”。以下三个动作,成本低、见效快、直击评委痛点。
6.1 演示“号源实时管控”:用两个浏览器窗口制造并发冲突
别只演示“张三挂号成功”,要主动制造竞争:
① 启动应用,打开 Chrome 访问http://localhost:8080/hospital/login,用医生账号登录;
② 打开 Firefox,用患者账号登录,进入挂号页;
③ 在 Chrome 中,找到该医生今日剩余号源(如剩 1 个),记下visit_date和visit_period;
④ Firefox 中快速提交挂号请求;
⑤立刻切回 Chrome,刷新号源页面——剩余数变为 0;
⑥ 再用 Firefox 提交相同请求,页面弹出“号源已满,请选择其他时段”。
为什么这招有效?
- 证明你实现了
SELECT ... FOR UPDATE或 Redis 分布式锁(毕业设计用前者即可); - 展示
outpatient_record表的status字段真实参与业务流转; - 评委看到“号源数字实时变化”,比听你讲 10 分钟事务隔离级别更直观。
6.2 准备一份“可验证的测试数据集”:5 条真实病历,含边界值
毕业设计常被质疑“数据是假的”。准备一份test_data.sql,含 5 条精心设计的数据:
| 患者ID | 姓名 | 身份证号 | 就诊日期 | 诊断ICD | 处方药品 | 库存初始值 |
|---|---|---|---|---|---|---|
| 1 | 张三 | 11010119900307211X | 2024-05-20 | I10 | 氨氯地平片 | 100 |
| 2 | 李四 | 210202198512123456 | 2024-05-20 | E11.9 | 二甲双胍片 | 200 |
| 3 | 王五 | 310101197001011234 | 2024-05-20 | J45.9 | 沙丁胺醇气雾剂 | 50 |
| 4 | 赵六 | 440304199208084321 | 2024-05-20 | K29.7 | 奥美拉唑胶囊 | 150 |
| 5 | 孙七 | 510107198805056789 | 2024-05-20 | M54.5 | 布洛芬缓释胶囊 | 300 |
边界值设计:
孙七的药品库存300,确保开 300 粒处方时刚好售罄;王五的沙丁胺醇气雾剂库存50,开 51 粒触发库存不足提示;- 所有身份证号通过
IdCardUtil.isValidIdCard()校验,杜绝“123456789012345678”这种假数据。
6.3 打印一份“LW 关键页”:聚焦 3 个图表,砍掉所有废话
毕业论文(LW)不是越厚越好。我让学生只保留这 3 页,答辩时直接摊开:
①系统架构图(手绘风格,标注 SSM 各组件职责):
- SpringMVC:接收 HTTP 请求,调用 Service;
- Service:编排挂号、诊断、处方业务逻辑;
- MyBatis:执行 SQL,映射
patient/outpatient_record/diagnosis关联查询; - MySQL:存储结构化病历数据,
utf8mb4字符集。
②核心流程图(挂号流程,含异常分支):
- 正常路径:患者输入信息 → 校验身份证 → 查号源 → 扣减号源 → 插入记录;
- 异常分支:身份证无效 → 提示;号源满 → 提示;网络超时 → 重试机制(用
@Retryable注
本文还有配套的精品资源,点击获取