简介:这是一套面向计算机、软件工程等专业学生的智慧医疗HIS系统完整源码与数据库资源,采用SpringBoot框架开发,适合用作毕业设计、期末大作业或课程设计。项目经本地编译可运行,评审得分达98分,难度适中,涵盖患者信息管理、预约挂号、电子病历、药品库存、医生排班、财务管理等典型医疗信息化模块,并注重权限管理与数据安全。压缩包共1302个文件,约20.62MB,以972个Java源文件为核心,辅以129个XML配置、49个JavaScript脚本、37个HTML页面及16个Velocity模板,另有SQL脚本、YAML配置与样式资源,结构完整、层次清晰。目前已有183人学习下载。借助这套经过助教审定的项目,读者可快速理解SpringBoot在医疗信息系统中的落地方式,掌握模块化设计与数据库持久化思路,为毕业答辩与后续开发积累可复用的实践参考。
1. 智慧医疗HIS系统到底在做什么:从挂号到出药的一条数据链
很多人第一次接触医疗HIS系统,是被“毕业设计”四个字带进来的:想找一个难度适中、能跑起来、数据库结构完整、评审还能拿高分的题目。但真把源码拉下来跑一遍就会发现,HIS不是简单的增删改查堆叠,它是一条从患者建档、挂号分诊、医生开方、药房发药到收费结算的完整数据链,任何一环的表结构设计错了,后面全是连锁反应。
我见过太多同学把HIS做成“病人管理+订单管理”的换皮商城,答辩时被问一句“处方和医嘱怎么关联”就卡住。这篇笔记就围绕一套可本地编译运行的智慧医疗HIS系统源码和数据库,把它的领域模型、核心表设计、后端接口和部署路径拆开讲清楚。适合正在做医疗信息化方向毕业设计、期末大作业的开发者,也适合想理解业务系统数据库设计的后端新手。读完你应该能自己判断:这套东西值不值得投入时间改造成自己的作品。
2. 先立住领域模型:HIS的六张核心表和它们的关系
在动手跑代码之前,必须先把数据模型想明白。HIS系统的复杂度不在代码量,而在实体之间的关系。如果表设计阶段偷懒,后面写接口时会不断打补丁,最后数据库里全是冗余字段和孤儿记录。
2.1 患者、挂号、就诊、处方、处方明细、药品这六张表怎么串
一套典型的门诊HIS,最小可用模型包含以下核心表。我按数据流向排列,这样你能看清依赖顺序:
| 表名 | 作用 | 关键外键 | 生命周期 |
|---|---|---|---|
| patient | 患者基本信息 | 无 | 长期 |
| registration | 挂号记录 | patient_id | 单次就诊 |
| visit | 就诊记录(含诊断) | registration_id | 单次就诊 |
| prescription | 处方主表 | visit_id | 单次就诊 |
| prescription_item | 处方明细 | prescription_id, drug_id | 单次就诊 |
| drug | 药品目录 | 无 | 长期 |
数据流向是:患者先建档,然后挂号产生registration,医生接诊后创建visit并填写诊断,开方时生成prescription和若干条prescription_item,药房根据明细扣减库存。
这里有个容易翻车的点:挂号表和就诊表要不要合并?很多简化版HIS把两者合成一张表,字段里既有挂号费又有诊断结论。短期看省事,但一旦要做“退号”“复诊”“一次挂号多次就诊”就彻底崩了。我的建议是分开,挂号是财务行为,就诊是医疗行为,职责不同。
2.2 用SQL把核心表建出来:字段类型和约束的取舍
下面这段建表语句是我从常见HIS源码里提炼的简化版,保留了关键约束。你可以直接拿去用,也可以对照你手上的源码看它有没有做到这些。
-- 患者表:身份证号做唯一索引,避免重复建档 CREATE TABLE patient ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, id_card VARCHAR(18) NOT NULL, gender TINYINT DEFAULT 0 COMMENT '0未知 1男 2女', phone VARCHAR(20), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_id_card (id_card) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 挂号表:状态字段控制流转,避免物理删除 CREATE TABLE registration ( id BIGINT PRIMARY KEY AUTO_INCREMENT, patient_id BIGINT NOT NULL, dept_id BIGINT NOT NULL, doctor_id BIGINT, reg_fee DECIMAL(10,2) NOT NULL DEFAULT 0.00, status TINYINT DEFAULT 0 COMMENT '0待诊 1就诊中 2已完成 3已退号', reg_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_patient (patient_id), KEY idx_status_time (status, reg_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 处方明细:数量和单价分开存,金额不冗余 CREATE TABLE prescription_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, prescription_id BIGINT NOT NULL, drug_id BIGINT NOT NULL, quantity INT NOT NULL DEFAULT 1, unit_price DECIMAL(10,2) NOT NULL, usage_note VARCHAR(200) COMMENT '用法用量', KEY idx_prescription (prescription_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;逻辑说明:patient表的id_card加了唯一索引,这是医疗系统的基本要求,同一个患者不能建两次档。registration表用status字段做软状态流转,退号是改状态而不是删记录,否则财务对账时查不到历史。prescription_item里unit_price是下单时的快照价格,不能关联drug表实时取价,因为药品调价后历史处方金额会变,这是财务系统的大忌。
参数说明:字符集统一用utf8mb4,因为患者姓名可能有生僻字。金额字段用DECIMAL而不是FLOAT,浮点数算钱迟早出精度问题。状态字段用TINYINT配合注释,比用字符串省空间也更快。
2.3 挂号状态机:为什么不能只用一张表搞定
挂号状态从“待诊”到“就诊中”再到“已完成”,中间还可能插入“已退号”。如果不用状态机思维,代码里会出现大量if-else判断当前能不能退号、能不能开方。
常见做法是在Service层写一个状态流转检查:
// 挂号状态流转校验:只有待诊状态可以退号 public void cancelRegistration(Long regId) { Registration reg = registrationMapper.selectById(regId); if (reg == null) { throw new BizException("挂号记录不存在"); } // 已就诊或已完成的挂号不允许退号 if (reg.getStatus() != RegistrationStatus.WAITING.getCode()) { throw new BizException("当前状态不允许退号"); } reg.setStatus(RegistrationStatus.CANCELED.getCode()); registrationMapper.updateById(reg); // 退号后需要回滚挂号费,这里省略支付回滚逻辑 }这段代码的关键在于:状态判断放在业务层而不是数据库触发器里,方便调试和扩展。退号后还要处理费用回滚,真实系统里会对接支付网关,毕业设计里通常简化为改状态加一条退款记录。
3. 把源码跑起来:环境、配置和数据库初始化的完整路径
拿到一套HIS源码,最怕的是“编译报错但不知道从哪查”。这一章按实际部署顺序走一遍,从环境准备到接口验证,每一步都给出可复现的命令和排查方向。
3.1 技术栈确认与本地环境准备
常见的HIS毕业设计源码有两种技术栈组合:SpringBoot+MyBatis+MySQL,或者PHP+MySQL。热搜词里“php源码”和“基于vue3+springboot的毕业设计”都出现了,说明两种路线都有人走。这里以SpringBoot路线为例,因为它的分层结构更清晰,适合学习业务系统设计。
需要准备的环境:
- JDK 1.8或11(看源码pom.xml里的编译版本)
- Maven 3.6+
- MySQL 5.7或8.0
- Redis(可选,用于缓存科室和药品目录)
- Node.js 14+(如果前端是Vue)
先确认版本,不要盲目装最新版:
java -version mvn -v mysql --version如果源码pom.xml里写的是java.version=1.8,你本地用JDK 17大概率编译不过,因为部分老依赖不兼容模块化系统。这是血泪经验,别问我怎么知道的。
3.2 数据库初始化:建库、导表、造测试数据
数据库初始化是跑通HIS的第一步。通常源码包里会有一个sql目录,里面是建表语句和初始数据。操作顺序如下:
# 登录MySQL mysql -u root -p # 创建数据库,字符集必须和建表语句一致 CREATE DATABASE his_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 退出后用命令行导入,避免source路径问题 mysql -u root -p his_db < /path/to/schema.sql mysql -u root -p his_db < /path/to/data.sql导入完成后验证:
USE his_db; SHOW TABLES; SELECT COUNT(*) FROM drug; SELECT COUNT(*) FROM patient;如果drug表是空的,挂号开方时选不到药品,前端会报“药品列表为空”。这是常见问题,不是代码bug,是数据没导全。
参数说明:建库时COLLATE用utf8mb4_general_ci还是utf8mb4_unicode_ci?前者速度快,后者排序更准确。HIS系统里患者姓名排序不敏感,用general_ci就够了。
3.3 配置文件修改与启动:三个必须改的地方
源码里的application.yml或application.properties通常写的是作者本地的配置,你必须改三处:
spring: datasource: url: jdbc:mysql://localhost:3306/his_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 redis: host: localhost port: 6379 database: 0第一处是数据库连接串,serverTimezone必须加,否则MySQL 8.0会报时区错误。第二处是数据库账号密码。第三处是Redis,如果本地没装Redis,要么装一个,要么把相关依赖注释掉,否则启动时连接超时会卡住。
启动命令:
# 在项目根目录 mvn clean package -DskipTests java -jar target/his-system-1.0.jar看到“Started HisApplication in X seconds”就算启动成功。如果报端口占用,改server.port;如果报Bean创建失败,大概率是MyBatis的mapper扫描路径没配对。
3.4 用接口验证核心链路:挂号到开方走一遍
启动成功后,不要急着打开前端页面,先用接口把核心链路走一遍,这样出问题容易定位。
# 1. 创建患者 curl -X POST http://localhost:8080/api/patient \ -H "Content-Type: application/json" \ -d '{"name":"测试患者","idCard":"110101199001011234","gender":1,"phone":"13800000000"}' # 2. 挂号(假设科室ID为1,医生ID为1) curl -X POST http://localhost:8080/api/registration \ -H "Content-Type: application/json" \ -d '{"patientId":1,"deptId":1,"doctorId":1}' # 3. 查询挂号列表,确认状态为待诊 curl http://localhost:8080/api/registration/list?status=0如果第一步就报错,检查请求体字段名和实体类是否一致。如果第二步报“患者不存在”,检查patient表里是否真的有id=1的记录。接口验证的好处是每一步都有明确的输入输出,比在页面上点来点去更容易排查。
4. 避坑与排查:HIS源码跑不通时先看这五条
这一章记录的是我在跑这类项目时踩过的坑,按“现象→原因→解决”整理。如果你卡在某一步,先对照这里排查,大概率能省下几个小时。
4.1 启动报错“Table ‘his_db.xxx’ doesn‘t exist”
现象:项目能编译,启动时抛SQL异常,提示某张表不存在。
原因:schema.sql只导入了部分表,或者导入时中途报错但没注意。常见于sql文件里有外键约束,导入顺序不对导致建表失败。
解决:先执行SET FOREIGN_KEY_CHECKS=0,再重新导入全部sql,最后SET FOREIGN_KEY_CHECKS=1。导入过程中逐条看有没有ERROR输出,不要只看最后一行。
4.2 前端页面能打开但接口全部返回401
现象:登录页面正常,登录后跳转到主页,但所有数据接口都返回401未授权。
原因:Token没带上,或者后端JWT拦截器配置的放行路径不对。常见于前后端分离项目,前端axios拦截器没把token塞进请求头。
解决:打开浏览器开发者工具,看请求头里有没有Authorization字段。如果没有,检查前端request.js里的拦截器逻辑。如果有但还报401,检查后端JWT工具类的密钥是否和登录时签发的一致。
4.3 挂号时提示“医生排班不存在”
现象:选择科室和医生后点挂号,报排班相关错误。
原因:排班表(schedule)没有初始化数据,或者挂号接口在创建registration之前先校验了排班。
解决:往schedule表里插入几条测试数据,字段包括医生ID、科室ID、排班日期、时段、剩余号源。如果源码里没有排班模块,那可能是挂号接口里硬编码了校验逻辑,找到对应Service方法注释掉即可。
4.4 药品库存扣减后变成负数
现象:开方后药品库存出现负值。
原因:扣减库存时没有加库存充足性校验,或者并发情况下出现了超卖。
解决:在扣减SQL里加条件判断,用UPDATE drug SET stock = stock - ? WHERE id = ? AND stock >= ?,根据影响行数判断是否扣减成功。这是数据库层面最直接的防超卖手段,比在Java里先查再改可靠得多。
-- 安全的库存扣减:影响行数为0说明库存不足 UPDATE drug SET stock = stock - #{quantity} WHERE id = #{drugId} AND stock >= #{quantity};4.5 中文乱码:从数据库到页面的全链路排查
现象:患者姓名在数据库里正常,但页面上显示问号或乱码。
原因:字符集在某一环断了。可能是数据库连接串没加characterEncoding=utf8,可能是表字段用了latin1,也可能是前端页面meta标签没声明UTF-8。
解决:按“数据库→连接串→后端响应→前端页面”的顺序逐环检查。先用SELECT HEX(name) FROM patient确认数据库里存的是正确的UTF-8字节,再检查连接串,最后看前端。不要一上来就改前端,大概率不是前端的问题。
5. 从能跑到能答辩:二次开发与评分点的具体做法
把源码跑起来只是起点,毕业设计要拿高分,得有自己加的东西。评审老师看的不只是“能运行”,而是你有没有理解业务并做出合理扩展。这一章讲几个投入产出比高的改造方向。
5.1 加一个统计报表模块:用SQL聚合代替手写循环
HIS系统天然适合做统计,比如“今日各科室挂号量”“医生工作量排行”“药品消耗TOP10”。这些功能不需要复杂的前端图表库,先用SQL把数据查出来,再用ECharts渲染就行。
-- 今日各科室挂号量统计 SELECT d.dept_name, COUNT(r.id) AS reg_count FROM registration r JOIN department d ON r.dept_id = d.id WHERE DATE(r.reg_time) = CURDATE() AND r.status != 3 GROUP BY d.dept_name ORDER BY reg_count DESC;这条SQL的关键在于WHERE条件里排除了已退号的记录(status != 3),否则统计结果会虚高。GROUP BY配合COUNT是最基础的聚合,答辩时能说清楚“为什么排除退号”就是加分项。
5.2 用AOP记录操作日志:答辩时能讲清楚的可观测性
给系统加一个操作日志模块,记录谁在什么时候做了什么。技术上用Spring AOP拦截Controller方法,把操作人、操作类型、请求参数、耗时写进日志表。
@Aspect @Component public class OperationLogAspect { @Around("@annotation(operationLog)") public Object log(ProceedingJoinPoint joinPoint, OperationLog operationLog) throws Throwable { long start = System.currentTimeMillis(); Object result = joinPoint.proceed(); long cost = System.currentTimeMillis() - start; // 异步写入日志表,避免影响主流程 logService.save(operationLog.value(), cost); return result; } }这段代码的价值在于:它体现了你对系统可观测性的理解。答辩时老师问“怎么追踪问题”,你可以说“通过操作日志定位到具体请求和耗时”。注意日志写入要用异步,否则每个接口都会因为写日志变慢。
5.3 数据库索引优化:让挂号列表查询从2秒降到50毫秒
毕业设计的数据量通常不大,但你可以主动造数据来验证索引效果。往registration表里插入10万条记录,然后对比加索引前后的查询耗时。
-- 造测试数据(用存储过程批量插入) DELIMITER // CREATE PROCEDURE batch_insert_reg() BEGIN DECLARE i INT DEFAULT 0; WHILE i < 100000 DO INSERT INTO registration (patient_id, dept_id, doctor_id, reg_fee, status, reg_time) VALUES (FLOOR(1 + RAND() * 1000), FLOOR(1 + RAND() * 10), FLOOR(1 + RAND() * 50), 10.00, 0, NOW()); SET i = i + 1; END WHILE; END // DELIMITER ; CALL batch_insert_reg();然后对比:
-- 无索引时 EXPLAIN SELECT * FROM registration WHERE patient_id = 500 AND status = 0; -- 加联合索引 ALTER TABLE registration ADD INDEX idx_patient_status (patient_id, status); -- 再次EXPLAIN,看type从ALL变成ref这个实验做一遍,你对“最左前缀原则”的理解会比看书深十倍。答辩时把EXPLAIN的前后对比截图放上去,比说一堆理论有说服力。
5.4 答辩前必须自己走一遍的检查清单
最后给一个我每次交付前都会过的清单,按顺序检查:
| 检查项 | 验证方式 | 常见问题 |
|---|---|---|
| 数据库能否从零导入 | 删库重建,重新导入sql | 外键顺序、字符集 |
| 核心链路是否通 | 挂号→就诊→开方→发药 | 状态流转卡住 |
| 异常输入是否处理 | 空值、超长字符串、负数 | 500错误页 |
| 日志是否可查 | 触发一次操作,查日志表 | 异步未生效 |
| 前端是否适配 | 换分辨率、换浏览器 | 布局错乱 |
这份清单不复杂,但能帮你避免答辩现场翻车。我自己的习惯是:答辩前一天,把项目从零部署一遍,全程不参考任何笔记。如果哪一步卡住了,说明那里就是你还没真正掌握的地方。
希望帮到你。
本文还有配套的精品资源,点击获取