简介这份资源是一套轻量级医院信息系统HIS源码包面向小型诊所、基层医疗机构以及希望快速了解医疗信息化架构的开发者与学习者用于搭建和试用基础的患者管理、挂号、药品库存、收费结算与统计报表等核心业务模块。压缩包共451个文件约7.05MB以C#源码136个cs和ASP.NET页面49个aspx为主体配合JavaScript脚本、CSS样式、ashx一般处理程序及少量DLL组件构成一套可运行的Web端HIS工程另有PNG、JPG界面素材、doc/docx说明文档与xlsx示例数据便于理解系统结构与演示数据。资源还包含按日期归档的日志文件可辅助排查运行过程。目前已有284人学习下载适合作为医疗信息化入门实践、课程设计或小型系统二次开发的参考起点帮助读者快速掌握HIS各模块的代码组织与业务逻辑。1. 拆开一个「超级简单版本商业HIS系统」压缩包它到底能跑通哪些真实业务如果你在医疗信息化行业待过大概率听过这句话HIS 是医院信息系统的地基但也是最难啃的骨头。门诊挂号、医生站开方、药房发药、收费结算、住院登记、医嘱执行、医保对接——随便拎一个模块出来背后都是几十张表、上百个字段、一堆状态机。所以当我第一次看到「超级简单版本商业HIS系统」这个压缩包时第一反应是怀疑简单版本能简单到什么程度是只有登录页的壳子还是真能把门诊一条线跑通拆开之后我的判断是它属于后者但「简单」体现在架构选型和功能裁剪上而不是糊弄。它把商业 HIS 里最核心的门诊业务闭环保留了下来——患者建档、挂号、医生接诊开处方、收费、药房发药这条主线是通的。住院、手术麻醉、LIS/PACS 对接、医保实时结算这些重资产模块被砍掉了换来的是部署门槛极低、代码可读性高。适合谁适合刚入行医疗信息化的开发者拿来理解 HIS 的数据流和状态流转适合小诊所或社区门诊做二次开发的基础骨架也适合作为课程设计或技术预研的起点。但如果你指望它直接上线三甲医院那还是趁早换方向。2. 环境搭建与数据库初始化从解压到第一个登录页2.1 技术栈选型与运行环境确认这个压缩包里的技术栈是典型的「轻量级 Java Web 组合」后端 Spring Boot MyBatis-Plus前端 Thymeleaf 或者简单的 Vue 静态页数据库 MySQL 5.7/8.0构建工具 Maven。为什么用这套因为 HIS 系统的核心难点不在技术框架而在业务逻辑的复杂度。用 Spring Boot 能快速把 Controller-Service-Mapper 三层搭起来MyBatis-Plus 省掉大量单表 CRUD 的 XML 配置让开发者把精力放在业务状态流转上。运行环境建议JDK 1.8 或 11MySQL 5.7 以上Maven 3.6IDE 用 IntelliJ IDEA 或 Eclipse 都行。内存给到 2GB 以上因为 MySQL 和 Java 进程同时跑1GB 的云主机容易在启动阶段就 OOM。我一般会在本地用 Docker 起一个 MySQL避免污染宿主机环境命令如下docker run -d --name his-mysql \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDhis123456 \ -e MYSQL_DATABASEhis_db \ mysql:5.7 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_unicode_ci这段命令做了三件事拉取 MySQL 5.7 镜像并后台运行把容器 3306 映射到宿主机创建名为his_db的数据库同时设置字符集为 utf8mb4。字符集这一步很关键HIS 里患者姓名、诊断描述、药品名称都可能出现生僻字或特殊符号utf8 在三字节以上字符会翻车必须用 utf8mb4。2.2 导入 SQL 脚本与配置文件修改解压后的目录里一般会有sql/his_db.sql和src/main/resources/application.yml两个关键文件。先导入 SQLmysql -h 127.0.0.1 -P 3306 -u root -p his_db sql/his_db.sql导入完成后用SHOW TABLES;检查是否生成了患者表、挂号表、处方表、收费记录表、药品库存表等核心表。常见做法是至少 15 到 25 张表如果少于 10 张说明这个「商业版」可能被裁剪得太狠业务闭环跑不通。接着改application.ymlspring: datasource: url: jdbc:mysql://127.0.0.1:3306/his_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: his123456 driver-class-name: com.mysql.cj.jdbc.Driver thymeleaf: cache: false server: port: 8080参数说明serverTimezoneAsia/Shanghai不加的话MySQL 8.0 驱动会报时区错误characterEncodingutf8mb4要和数据库字符集一致thymeleaf.cachefalse在开发阶段关闭模板缓存改完页面不用重启。改完配置后在项目根目录执行mvn clean package -DskipTests然后java -jar target/*.jar启动。看到控制台输出Started Application in x.x seconds就算成功浏览器访问http://localhost:8080应该能看到登录页。提示如果启动时报Table his_db.xxx doesnt exist八成是 SQL 导入时用了错误的数据库或者脚本里有CREATE DATABASE语句但没执行。手动建库再导入一次即可。3. 门诊业务闭环实战挂号、开方、收费、发药四步走3.1 患者建档与挂号模块的数据流HIS 的第一条数据永远是患者。这个系统里患者建档的入口在「门诊管理」菜单下字段包括姓名、性别、出生日期、身份证号、联系电话、医保类型。身份证号是主键级别的唯一标识但实际业务里会有没带身份证的患者所以系统一般会生成一个内部患者编号作为主键身份证号只做唯一索引且允许为空。挂号的核心逻辑是选择患者 → 选择科室 → 选择医生 → 选择号别普通/专家/急诊→ 生成挂号记录 → 同时生成一条待收费记录。这里的状态机是重点挂号记录的状态从「已挂号」到「已收费」再到「已接诊」最后到「已完成」。如果患者退号状态回滚到「已退号」同时触发退费流程。我一般会先手动在数据库里插一条测试患者避免每次都要走前端表单INSERT INTO patient (patient_no, name, gender, id_card, phone, create_time) VALUES (P20250101001, 测试患者, 男, 110101199001011234, 13800000000, NOW());然后在前端走一遍挂号流程观察registration表和charge表的数据变化。如果挂号后charge表没有生成待收费记录说明挂号服务的业务逻辑里漏了「生成收费单」这一步这是新手二次开发时最容易踩的坑。3.2 医生站开处方与药品库存扣减医生站是整个 HIS 里逻辑最密集的地方。这个简化版里医生接诊后可以看到患者的基本信息和历史就诊记录然后开具处方。处方分西药、中药、检查检验三类每类对应不同的处方模板。开方时系统会实时查询药品库存如果库存不足会弹窗提示。处方保存的核心逻辑是处方主表存患者、医生、诊断、开方时间处方明细表存药品、规格、数量、用法用量、频次。保存成功后处方状态为「待收费」同时药品库存表执行预扣减——注意是预扣减不是实际出库。实际出库发生在药房发药环节。// 处方保存时的库存预扣减逻辑伪代码 Transactional public void savePrescription(PrescriptionDTO dto) { // 1. 保存处方主表和明细表 prescriptionMapper.insert(dto.getMain()); dto.getDetails().forEach(detail - { // 2. 检查库存 DrugStock stock stockMapper.selectByDrugId(detail.getDrugId()); if (stock.getQuantity() detail.getQuantity()) { throw new BusinessException(药品库存不足 detail.getDrugName()); } // 3. 预扣减库存 stock.setQuantity(stock.getQuantity() - detail.getQuantity()); stockMapper.updateById(stock); }); }这段代码的关键点是Transactional注解保证处方保存和库存扣减在同一个事务里。如果库存不足抛异常整个事务回滚不会出现「处方存了但库存没扣」的脏数据。参数上要注意detail.getQuantity()的单位有些药品按盒开有些按片开单位不统一会导致库存对不上。3.3 收费结算与药房发药的联动收费环节是门诊闭环的收口。收费员看到待收费列表选择患者系统自动带出挂号费和处方费收费方式支持现金、微信、支付宝、医保个人账户。收费成功后挂号记录状态变为「已收费」处方状态变为「已收费」同时生成收费流水记录。药房发药是最后一步。药房工作人员看到「待发药」列表核对处方和药品后点击发药系统执行实际库存出库处方状态变为「已发药」。到这里一个完整的门诊流程才算走完。-- 查询某个患者完整的门诊流转记录 SELECT p.name AS 患者姓名, r.reg_no AS 挂号单号, r.status AS 挂号状态, pr.prescription_no AS 处方单号, pr.status AS 处方状态, c.charge_no AS 收费单号, c.amount AS 收费金额 FROM patient p LEFT JOIN registration r ON p.id r.patient_id LEFT JOIN prescription pr ON r.id pr.registration_id LEFT JOIN charge c ON pr.id c.prescription_id WHERE p.patient_no P20250101001 ORDER BY r.create_time DESC;这条 SQL 能帮你快速验证数据是否串起来了。如果某个环节的关联字段为空说明业务代码里漏了回写关联 ID这是排查流程断点时最常用的手段。4. 避坑与排查二次开发时最容易翻车的五个地方4.1 中文乱码从数据库到前端的三层排查现象患者姓名或药品名称显示为???或乱码。原因通常有三层数据库字符集不是 utf8mb4、JDBC 连接串没指定编码、前端页面 meta 标签没声明 UTF-8。解决顺序是先从数据库查起SHOW VARIABLES LIKE character%;确认character_set_server和character_set_database都是 utf8mb4。然后检查application.yml里的连接串最后看 HTML 的meta charsetUTF-8。三层都对了乱码基本消失。4.2 挂号后收费单不生成事务失效的典型场景现象挂号记录插入成功但charge表没有对应的待收费记录。原因多半是挂号服务方法上没加Transactional或者加了但异常被 catch 后没重新抛出导致事务没回滚也没提交。解决方法是检查服务层方法注解确保异常能传播到 Spring 的事务管理器。另外如果用的是 MyBatis-Plus 的save()方法确认没有在同一个方法里手动try-catch把异常吞掉。4.3 药品库存扣成负数并发下的超卖问题现象两个医生同时给不同患者开同一种药库存只剩 1 盒结果两个处方都保存成功库存变成 -1。原因是库存检查是「先查后改」没有加锁。解决办法有两种一是用SELECT ... FOR UPDATE在查询时就加行锁二是用乐观锁在stock表加version字段更新时带版本号条件。我一般推荐乐观锁因为 HIS 的并发量通常不会太高乐观锁足够且不会造成大量锁等待。4.4 时间字段差 8 小时时区配置的连锁反应现象挂号时间、收费时间比实际时间少 8 小时。原因是 MySQL 的serverTimezone没配或配成了 UTC而 Java 应用用的是东八区。解决方法是 JDBC 连接串加serverTimezoneAsia/Shanghai同时检查 MySQL 全局时区SET GLOBAL time_zone 8:00;。如果用了 Docker还要确认容器内的时区文件是否正确。4.5 前端页面 404静态资源路径与上下文根现象登录页能打开但 CSS、JS 加载失败页面样式全乱。原因是静态资源路径配置不对或者项目部署时改了server.servlet.context-path但前端引用路径没跟着改。解决方法是检查application.yml里的context-path配置以及前端 HTML 里引用资源用的是相对路径还是绝对路径。用相对路径更稳妥部署到子目录时不容易翻车。5. 进阶技巧用状态机重构处方流转让业务逻辑不再散落这个简化版 HIS 最值得动手改造的地方是处方状态的管理。原始代码里处方状态的变更散落在医生站、收费处、药房三个模块的 Service 里每个地方都在写prescription.setStatus(已收费)这样的硬编码。一旦要加一个新状态比如「已退药」就得翻遍所有模块。我一般会用状态机模式把它收拢。核心思路是定义一个处方状态枚举和允许的流转路径public enum PrescriptionStatus { PENDING_CHARGE(待收费), CHARGED(已收费), DISPENSED(已发药), RETURNED(已退药); private final String desc; PrescriptionStatus(String desc) { this.desc desc; } public String getDesc() { return desc; } // 定义允许的流转 public static boolean canTransfer(PrescriptionStatus from, PrescriptionStatus to) { switch (from) { case PENDING_CHARGE: return to CHARGED; case CHARGED: return to DISPENSED || to RETURNED; case DISPENSED: return to RETURNED; default: return false; } } }然后在处方服务里统一做状态变更public void changeStatus(Long prescriptionId, PrescriptionStatus target) { Prescription p prescriptionMapper.selectById(prescriptionId); if (!PrescriptionStatus.canTransfer(p.getStatus(), target)) { throw new BusinessException(非法状态流转 p.getStatus() - target); } p.setStatus(target); prescriptionMapper.updateById(p); }这样改造之后所有状态变更都走同一个入口加新状态只需要改枚举和流转规则不用动业务代码。验证方法也很直接写一个单元测试把所有合法和非法流转都跑一遍确保非法流转能抛异常。原状态目标状态是否允许触发场景待收费已收费是收费成功待收费已发药否跳过收费直接发药已收费已发药是药房发药已收费已退药是患者退药已发药已退药是发药后异常退药已发药已收费否状态回退从那以后我每次接手这类业务系统都会先找状态字段把它们收拢到状态机里再动其他逻辑。散落的状态判断是后期维护最大的黑匣子早收拢早省心。希望帮到你。本文还有配套的精品资源点击获取