资讯详情

医院HIS源码解析:从表结构到门诊流程的完整落地指南

📅 2026/10/12 18:01:55 | 华诺云谱 👁 阅读
医院HIS源码解析:从表结构到门诊流程的完整落地指南
简介这是一套基于SpringBoot、Jpa与Thymeleaf开发的Java医院信息管理系统源码面向中小型医疗机构信息化建设需求也适合希望深入理解主流Java Web技术栈实战应用的开发者与学习者。系统整合患者管理、医生排班、科室协作、药品库存、财务收支、预约挂号、住院管理、检查报告、权限控制与系统设置等核心业务模块覆盖医院日常运营的主要流程。压缩包共3973个文件约15.37MB以svg图标、scss与css样式、class编译文件、js脚本、java源码及html模板为主另含yml配置、sql脚本与少量字体、图片资源目录结构完整便于按模块检索与二次开发。目前已有2525人学习下载。通过这套源码读者可参考实体与业务分层设计、数据库映射方式及页面渲染实现对照自身项目进行功能扩展或定制改造也可作为课程设计、毕业设计的技术选型参考。1. 医院信息管理系统源码从一堆表结构到能跑起来的门诊流程很多开发者第一次拿到“java医院信息管理系统源码”时会以为这是一套开箱即用的成品结果解压后看到几百张表、上千个 Java 文件直接懵了。医院HIS源码的核心价值不在于代码量而在于它把挂号、就诊、收费、发药、住院这条业务链用数据流串了起来。你真正要搞清楚的是这套系统怎么把“一个患者从进医院到离开”拆成可落地的模块。适合谁看想接医疗信息化私活的后端、需要做课程设计的学生、以及准备把 HIS 作为中台底座的技术负责人。下面我按实际落地顺序把选型、跑通、踩坑、进阶一次讲透。2. 先看懂医院HIS源码的模块拆分与数据流2.1 门诊、住院、药房、收费四条主线的边界一套能用的 HIS 源码模块划分基本逃不出这四块门诊管理挂号、分诊、医生站、住院管理入院登记、医嘱、床位、药房药库库存、发药、退药、收费结算划价、支付、发票。很多新手一上来就扎进代码里找 Controller结果被各种 Service 绕晕。我的习惯是先看数据库表名前缀比如outp_开头的基本是门诊inp_是住院drug_是药品fee_是费用。表结构清楚了业务边界自然就出来了。这四块之间靠什么串靠患者主索引和就诊流水号。一个患者一次就诊生成一个visit_id挂号、开单、收费、发药全挂在这个 ID 下。你如果发现源码里到处用patient_id直接关联而没有中间的就诊表那这套代码的扩展性基本到头了后面加个复诊逻辑就会翻车。2.2 表结构里藏着的业务约束看源码别急着跑先导出建表语句。重点看三类字段状态位、金额、时间戳。状态位决定流程能不能往下走比如挂号表的status从 0待就诊到 1已就诊到 2已退号你改错一个值整个队列就乱了。金额字段必须用decimal用double的源码直接放弃收费对不上账是迟早的事。时间戳要看是datetime还是timestamp涉及跨天结算的逻辑时区处理不好就是一笔糊涂账。-- 典型的挂号表核心字段注意状态位和金额类型 CREATE TABLE outp_register ( id BIGINT PRIMARY KEY AUTO_INCREMENT, patient_id BIGINT NOT NULL COMMENT 患者主索引, visit_id VARCHAR(32) NOT NULL COMMENT 就诊流水号, dept_id INT NOT NULL COMMENT 科室, doctor_id INT COMMENT 医生, reg_level TINYINT COMMENT 挂号级别, reg_fee DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 挂号费, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待就诊 1已就诊 2已退号, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_visit (visit_id) ) COMMENT门诊挂号;上面这段建表语句里visit_id加了唯一索引这是防止重复挂号的第一道防线。reg_fee用DECIMAL(10,2)而不是FLOAT因为金额计算不能有精度丢失。status字段没有用枚举类型是为了方便后续扩展状态而不用改表结构。你拿到任何一套 HIS 源码先检查这三处基本能判断代码质量的下限。2.3 从挂号到发药的一次完整调用链以最简门诊流程为例患者挂号 → 医生站开处方 → 收费处划价收费 → 药房发药。对应到代码里通常是RegisterController调RegisterService写挂号表然后DoctorController调PrescriptionService写处方主表和明细表接着FeeController调ChargeService更新处方状态并写收费记录最后PharmacyController调DispenseService扣库存、写发药记录。每一步都依赖上一步的状态位不能跳步。你调试的时候可以在每个 Service 方法入口打日志把visit_id和status打出来这样一眼就能看出卡在哪。常见做法是用 AOP 切面统一记录但源码里如果没带手动加几行log.info也够用。3. 把源码跑起来环境、依赖与最小启动命令3.1 JDK、数据库、中间件的版本对齐医院HIS源码常见的技术栈是 Spring Boot MyBatis MySQL Redis。JDK 版本看pom.xml里的java.version1.8 和 11 居多17 的也有但少。MySQL 建议 5.7 或 8.0注意驱动包版本要匹配mysql-connector-java8.0 以上用com.mysql.cj.jdbc.Driver。Redis 一般用来存会话和挂号队列没有的话先把相关配置注释掉能启动再说。# 先确认本地环境 java -version mysql --version redis-server --version # 导入数据库注意字符集用 utf8mb4 mysql -uroot -p -e CREATE DATABASE his_db DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; mysql -uroot -p his_db doc/sql/his_init.sql导入 SQL 的时候如果报Unknown collation: utf8mb4_0900_ai_ci说明你的 MySQL 是 5.7 而脚本是 8.0 导出的把脚本里的utf8mb4_0900_ai_ci全部替换成utf8mb4_general_ci即可。这个坑我踩过不止一次血泪经验就是先看 SQL 文件头部的版本注释。3.2 配置文件里必须改的四个参数源码里的application.yml或application.properties拿到手先改这四处数据库连接、Redis 地址、文件上传路径、日志级别。数据库连接里url要加useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai不然中文乱码和时间差 8 小时会同时出现。文件上传路径如果写的是 Linux 绝对路径在 Windows 上跑必然报错改成相对路径或临时目录。spring: datasource: url: jdbc:mysql://127.0.0.1:3306/his_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver redis: host: 127.0.0.1 port: 6379 database: 0 servlet: multipart: location: /tmp/his_upload logging: level: com.his: debugserverTimezoneAsia/Shanghai这个参数不加挂号时间可能变成前一天收费对账直接对不上。logging.level.com.his改成 debug 是为了看 MyBatis 打印的 SQL排查数据问题时比任何工具都快。上传路径用/tmp在 Linux 下没问题Windows 下改成D:/his_upload并确保目录存在。3.3 启动类与前端资源的挂载方式后端启动类一般叫HisApplication直接mvn spring-boot:run或打 jar 包java -jar his.jar。前端如果是 Vue 或 React看package.json里的scriptsnpm run dev起开发服务器npm run build出静态文件。有些源码把前端打包后放在src/main/resources/static下这种直接访问http://localhost:8080就能看到页面。如果前后端分离注意跨域配置源码里通常有个CorsConfig类没有的话自己加一个CrossOrigin注解在 Controller 上。Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowCredentials(true) .maxAge(3600); } }这段跨域配置用了allowedOriginPatterns而不是allowedOrigins因为后者在allowCredentialstrue时不能写*。很多源码在这里翻车前端一直报 CORS 错误其实就是这个细节。加上之后重启前端就能正常调接口了。4. 二次开发前必须搞清的权限与业务校验4.1 基于角色的菜单与按钮级控制HIS 源码的权限模型通常是 RBAC用户关联角色角色关联菜单和按钮。菜单控制页面能不能进按钮控制操作能不能点。你看sys_user、sys_role、sys_menu、sys_role_menu这几张表就够了。关键点在拦截器或过滤器里判断当前用户有没有某个权限编码。常见做法是用 Shiro 或 Spring Security源码里如果用了自定义注解加 AOP那就看注解的value值跟菜单表里的perms字段是否对应。// 自定义权限注解配合 AOP 使用 Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface RequiresPermission { String value(); } // AOP 切面里校验 Around(annotation(requiresPermission)) public Object checkPermission(ProceedingJoinPoint joinPoint, RequiresPermission requiresPermission) throws Throwable { String perm requiresPermission.value(); if (!SecurityUtils.hasPermission(perm)) { throw new BusinessException(无权限操作); } return joinPoint.proceed(); }这段代码里SecurityUtils.hasPermission需要你自己实现从当前会话里拿用户 ID查角色权限列表判断是否包含传入的perm。注意权限编码要跟菜单表里的perms完全一致大小写敏感。我见过有人把outp:register:add写成outp:register:save结果按钮一直灰着排查半天。4.2 挂号、收费、发药的状态机校验状态机是 HIS 里最容易出 bug 的地方。挂号状态从待就诊到已就诊收费状态从待收费到已收费发药状态从待发药到已发药。每个操作前必须校验当前状态是否允许。比如退号只能对“待就诊”的挂号操作已经就诊的不能退。收费只能对“已开单未收费”的处方操作。发药只能对“已收费”的处方操作。这些校验如果源码里没写全你二次开发时一定要补上否则数据一乱整个科室的队列都受影响。public void cancelRegister(Long registerId) { OutpRegister reg registerMapper.selectById(registerId); if (reg null) { throw new BusinessException(挂号记录不存在); } if (reg.getStatus() ! 0) { throw new BusinessException(只有待就诊状态才能退号); } reg.setStatus(2); registerMapper.updateById(reg); // 退号后释放号源更新排班剩余数量 scheduleMapper.increaseRemain(reg.getDoctorId(), reg.getRegLevel()); }这段退号逻辑里先查记录再判状态最后更新并释放号源。increaseRemain是自定义的更新语句把医生排班的剩余号加回去。如果你只改状态不释放号源后面的人就挂不上这个医生的号了这是典型的业务漏洞。4.3 药品库存扣减的并发处理药房发药时要扣库存多个窗口同时发药库存扣减必须加锁。源码里常见两种做法数据库乐观锁版本号或 Redis 分布式锁。乐观锁简单在drug_stock表加version字段更新时where id? and version?影响行数为 0 就重试。Redis 锁适合并发量大的场景但要注意锁过期时间和误删问题。// 乐观锁扣库存 public boolean reduceStock(Long drugId, Integer quantity) { int retry 3; while (retry-- 0) { DrugStock stock stockMapper.selectById(drugId); if (stock.getQuantity() quantity) { throw new BusinessException(库存不足); } int rows stockMapper.reduceStock(drugId, quantity, stock.getVersion()); if (rows 0) { return true; } } throw new BusinessException(库存扣减失败请重试); }对应的 SQL 是update drug_stock set quantityquantity-#{quantity}, versionversion1 where id#{drugId} and version#{version} and quantity#{quantity}。注意quantity#{quantity}这个条件不能少否则并发下可能扣成负数。重试次数设 3 次够用再多说明并发冲突太频繁该考虑换方案了。5. 避坑与排查源码跑不通时先看这五处5.1 启动报错“找不到符号”或“程序包不存在”现象mvn clean install时报cannot find symbol或者 IDEA 里一片红。原因通常是依赖没下全或者 JDK 版本不匹配。解决先看pom.xml里的java.version改成你本地的 JDK 版本。然后mvn clean install -U强制更新依赖。如果还不行删掉本地仓库里对应的com/his目录再重新拉。IDEA 里记得 File → Invalidate Caches → Restart。5.2 登录后菜单空白或接口 403现象能登录但左侧菜单不显示或者点任何按钮都提示无权限。原因一般是权限数据没初始化或者角色菜单关联表是空的。解决查sys_role_menu表有没有数据没有的话手动插入几条把管理员角色关联到所有菜单。另外检查登录接口返回的用户信息里有没有permissions字段前端是根据这个字段渲染按钮的。5.3 挂号时间比实际时间少 8 小时现象挂号记录里的create_time比当前时间早 8 小时。原因数据库连接串没加serverTimezoneAsia/Shanghai或者 JVM 时区不对。解决改连接串同时在启动参数加-Duser.timezoneAsia/Shanghai。如果用的是 Docker检查容器时区是不是 UTC是的话挂载/etc/localtime进去。5.4 收费金额出现 0.01 的误差现象收费明细加起来和总金额差一分钱。原因用了double或float存金额或者计算时先除后乘。解决把所有金额字段改成DECIMALJava 里用BigDecimal并且setScale(2, RoundingMode.HALF_UP)。除法运算一定要先乘后除比如(a * 100) / b而不是a / b * 100。5.5 发药后库存没变或变成负数现象发药成功但库存没扣或者扣成了负数。原因没加乐观锁或锁失效或者扣减 SQL 的条件写错了。解决按 4.3 节的方案加版本号SQL 里必须带quantity#{quantity}。如果用了 Redis 锁检查锁的 key 是不是按药品 ID 维度加的锁过期时间是不是太短导致业务没执行完就释放了。6. 从能跑到好用HIS 源码的进阶改造与验证6.1 用接口幂等保证挂号不重复挂号接口如果被重复提交会生成两条挂号记录。进阶做法是加幂等控制前端传一个requestId后端用 Redis 的setnx判断是否已处理。setnx成功才执行业务执行完设置过期时间。这样即使网络抖动导致重试也只会有一条记录。public RegisterResult register(RegisterRequest req) { String key reg:idempotent: req.getRequestId(); Boolean ok redisTemplate.opsForValue().setIfAbsent(key, 1, 30, TimeUnit.SECONDS); if (Boolean.FALSE.equals(ok)) { throw new BusinessException(请勿重复提交); } try { // 原有挂号逻辑 return doRegister(req); } catch (Exception e) { redisTemplate.delete(key); // 失败时删除允许重试 throw e; } }注意失败时要删 key否则用户重试会被拦住。过期时间设 30 秒够覆盖一次正常请求。这个方案对挂号、收费、发药都适用是投入产出比最高的改造之一。6.2 用对账脚本验证收费数据一致性HIS 最怕收费对不上。我一般会写一个对账脚本每天跑一次比对处方明细金额之和与收费记录金额是否一致。不一致的记录下来人工核查。脚本用 SQL 就能写不用写 Java。-- 找出处方明细总额与收费金额不一致的记录 SELECT p.visit_id, p.total_amount AS pres_amount, c.charge_amount, (p.total_amount - c.charge_amount) AS diff FROM prescription p JOIN charge_record c ON p.visit_id c.visit_id WHERE ABS(p.total_amount - c.charge_amount) 0.00;这个查询跑出来如果有数据说明要么有退费没同步要么有折扣没算对。每天定时跑比出了事再查强得多。我习惯把这个脚本放在运维目录用 cron 每天凌晨执行结果发到工作群。6.3 一个具体技巧用日志追踪一次就诊的全链路最后分享一个我常用的排查技巧。在visit_id生成的地方打一条日志格式固定为[VISIT_TRACE] visitIdxxx, stepregister然后在每个关键节点开单、收费、发药都打同样格式的日志。出问题时用grep VISIT_TRACE | grep visitId就能把一次就诊的所有操作按时间顺序拉出来比看数据库快十倍。这个习惯我保持了五年每次线上问题都是靠它定位。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑