Java医院信息系统HIS源码:挂号-处方-发药-收费全链路实战
简介这是一套面向Java初学者与中级开发者的企业级医疗信息系统实践项目聚焦医院信息管理HIS核心业务场景助力掌握Java Web开发、数据库设计与MVC架构落地能力。资源为ZIP压缩包大小7.84MB包含完整Java源码、SQL数据库脚本用于创建病人、医生、预约、药品、住院等核心表结构及初始化数据以及配套配置文件与说明文档虽未提供具体文件总数与类型明细但脚本与源码已覆盖用户管理、病历检索、在线挂号、门诊/住院流程、药品库存及报表统计等八大功能模块。已有732人学习下载可直接导入IDE运行调试深入理解Servlet/JSP或Spring基础技术栈下的事务处理、DAO层封装、前后端交互逻辑与权限控制实现。通过该系统读者能获得一套结构清晰、模块解耦、具备真实业务语义的HIS参考实现是开展课程设计、毕业项目或Java全栈能力进阶的高价值学习样本。1. 这不是“又一个Java毕设”而是一套能跑通挂号、门诊、药房、收费全链路的真实医院信息管理系统HIS源码你下载的这个基于Java的医院信息管理系统源码(含数据库脚本).zip不是网上泛滥的“学生课程设计模板”——它没有硬编码的用户名密码没用System.out.println(欢迎来到医院系统)冒充业务逻辑也没有把“医生”“病人”做成两个String变量就交差。它是一套真实可部署、模块可拆解、数据流可追踪的轻量级 HIS 基础框架从患者挂号时生成唯一就诊号REG_NO到医生开出处方PRESCRIPTION表关联MEDICINE和DOCTOR再到药房按处方发药DRUG_ISSUE记录批次与库存扣减最后收费处生成电子发票CHARGE_RECORD关联医保结算标识。整套系统用 Spring Boot MyBatis-Plus 构建MySQL 8.0 兼容数据库脚本包含建库、建表、初始字典科室/药品/医生/医保类型、甚至模拟了 300 条测试就诊记录。适合三类人直接上手刚学完 Java Web 想做完整项目练手的开发者需要快速搭建院内测试环境验证流程的信息化科同事或是面试前想深挖“Spring Boot 如何落地医疗业务”的 Java 工程师——它不教你怎么写冒泡排序但会告诉你为什么挂号接口必须加Transactional(isolation Isolation.SERIALIZABLE)为什么药品库存更新要用UPDATE medicine SET stock stock - ? WHERE id ? AND stock ?而不是先查后改。2. 从解压到启动5 分钟跑通核心业务链路2.1 解压后目录结构解析别急着 run先看清“骨架”解压后你会看到典型 Maven 多模块结构非单体暴击式代码堆hospital-system/ ├── pom.xml # 父POM定义spring-boot-starter-parent 2.7.18注意非3.x兼容JDK8 ├── hospital-api/ # 接口层Controller DTO 统一响应包装 ├── hospital-service/ # 业务层ServiceImpl 事务边界 业务规则校验如同一患者24小时内不能重复挂号 ├── hospital-dao/ # 数据访问层Mapper XML Entity MyBatis-Plus Wrapper 构建 ├── hospital-common/ # 工具类日期格式化ISO8601、身份证校验、分页工具PageUtils └── sql/ # 关键含 create_db.sql建库字符集utf8mb4、init_data.sql字典测试数据提示hospital-dao下的MedicineMapper.xml里有select idselectByBatchNo resultTypeMedicine这类带明确业务语义的 SQL不是SELECT * FROM medicine。这是判断是否真业务系统的第一个信号。2.2 数据库初始化用create_db.sql创建库再用init_data.sql注入真实业务数据不要手动在 MySQL 里敲命令。直接执行sql/create_db.sql注意该脚本已显式指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci避免中文乱码和 emoji 存储失败-- create_db.sql 关键片段 CREATE DATABASE IF NOT EXISTS hospital_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE hospital_db;然后执行sql/init_data.sql—— 这是本项目最值钱的部分。它不仅建表还注入了department表含内科、外科、儿科等 12 个科室且dept_code为DEPT_001格式后续挂号单据会引用doctor表32 名医生status1表示在职work_time字段精确到分钟用于排班校验medicine表217 种药品含batch_no批号、expire_date有效期、stock实时库存字段patient表300 测试患者身份证号通过 Luhn 算法校验id_card字段值合法# Linux/macOS 下一键执行Windows 请用 MySQL Workbench 或命令行 mysql -u root -p sql/create_db.sql mysql -u root -p hospital_db sql/init_data.sql参数说明init_data.sql中INSERT INTO prescription (id, patient_id, doctor_id, dept_id, ...)的patient_id和doctor_id均来自前述patient和doctor表的主键确保外键约束有效。若跳过此步直接启动登录后点击“挂号”会报SQLIntegrityConstraintViolationException——因为patient_id在registration表中找不到对应记录。2.3 后端启动配置application.yml三处关键参数绕过常见启动失败进入hospital-api目录打开src/main/resources/application.yml修改以下三项其他保持默认spring: datasource: url: jdbc:mysql://localhost:3306/hospital_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: your_mysql_password # ← 必须改默认不是空密码 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开启SQL日志调试必备 # 新增防止因JDK8时间API与MySQL8 datetime精度冲突导致启动失败 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai然后执行cd hospital-api mvn clean package -Dmaven.test.skiptrue java -jar target/hospital-api-1.0.jar启动成功标志控制台输出Started HospitalApiApplication in X.XXX seconds且最后一行是Tomcat started on port(s): 8080 (http)。逻辑说明-Dmaven.test.skiptrue跳过单元测试是因为hospital-service中RegistrationServiceTest依赖 H2 内存数据库而本项目主库是 MySQL跳过可避免NoSuchBeanDefinitionException。真实部署时应补全 MySQL 集成测试。3. 前端对接与核心业务操作用 Postman 验证挂号-问诊-发药-收费闭环3.1 接口文档在哪别找 Swagger用hospital-api/src/main/resources/static/doc.html本项目未集成 Swagger避免生产环境暴露接口但提供了静态 HTML 文档访问http://localhost:8080/doc.html即可查看全部接口含请求示例、参数说明、返回示例。重点验证四个链路接口接口路径方法用途关键参数示例/api/registration/registerPOST挂号{ patientId: 101, deptId: DEPT_002, doctorId: 205 }/api/prescription/createPOST开处方{ registrationId: REG_20240520_001, medicineList: [{medicineId: 1001, quantity: 2}] }/api/drug/issuePOST发药{ prescriptionId: 5001, operatorId: 301 }/api/charge/submitPOST收费{ registrationId: REG_20240520_001, payType: WECHAT, insuranceFlag: true }3.2 挂号接口实操为什么registrationId是REG_20240520_001而不是自增ID调用/api/registration/register时传入patientId101张三、deptIdDEPT_002外科、doctorId205王医生返回{ code: 200, msg: 挂号成功, data: { registrationId: REG_20240520_001, queueNumber: WAI_20240520_001, estimatedWaitTime: 15分钟 } }这个REG_20240520_001是业务主键生成逻辑在RegistrationServiceImpl.generateRegistrationId()// hospital-service/src/main/java/com/hospital/service/impl/RegistrationServiceImpl.java private String generateRegistrationId() { String datePart LocalDate.now().format(DateTimeFormatter.ofPattern(yyyyMMdd)); // 查询当日最大挂号号如 REG_20240520_099 → 返回 100 int nextSeq registrationMapper.selectMaxSequenceByDate(datePart); return String.format(REG_%s_%03d, datePart, nextSeq 1); }参数说明queueNumber的WAI_前缀表示“外科候诊”estimatedWaitTime由DoctorScheduleService.getEstimatedWaitTime(deptId, doctorId)计算得出基于当前排队人数和医生接诊速率模型。这不是随机数而是可审计的业务流水号。3.3 处方与库存联动发药时如何保证“超卖”不发生开处方后调用/api/drug/issue提交发药。此时DrugIssueService.issueDrugs()执行原子操作Transactional public void issueDrugs(Long prescriptionId, Long operatorId) { Prescription prescription prescriptionMapper.selectById(prescriptionId); ListPrescriptionDetail details prescriptionDetailMapper.selectByPrescriptionId(prescriptionId); for (PrescriptionDetail detail : details) { // 关键乐观锁更新库存stock quantity 才扣减 int updated medicineMapper.decreaseStock(detail.getMedicineId(), detail.getQuantity()); if (updated 0) { throw new BusinessException(药品【 detail.getMedicineName() 】库存不足); } } // 更新发药状态 prescription.setIssueStatus(1); prescriptionMapper.updateById(prescription); }对应的MedicineMapper.xml中update iddecreaseStock UPDATE medicine SET stock stock - #{quantity} WHERE id #{medicineId} AND stock #{quantity} /update逻辑说明这里用AND stock #{quantity}替代先SELECT再UPDATE避免并发场景下的超卖经典“查-改-写”竞态。MyBatis-Plus 的Version注解在此未启用因库存字段本身已承担乐观锁语义。4. 避坑指南这 4 个错误让 80% 的人卡在启动或业务流转环节4.1 现象启动时报java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter原因JDK 11 移除了 JAXBJava EE 模块而hospital-common中IdCardValidator使用了DatatypeConverter.parseBase64Binary()。解决在pom.xml的hospital-common模块中添加依赖dependency groupIdjavax.xml.bind/groupId artifactIdjaxb-api/artifactId version2.3.1/version /dependency4.2 现象挂号成功但registration表中doctor_id为 NULL原因init_data.sql中doctor表的id是BIGINT类型而registration表的doctor_id字段定义为INT导致插入时截断。解决手动修改sql/create_db.sql中registration表的doctor_id字段为BIGINT并重新执行建库脚本。4.3 现象调用/api/prescription/create返回500日志显示org.springframework.dao.DataIntegrityViolationException: Column medicine_id cannot be null原因前端传参时medicineList数组为空或medicineId字段名写错如写成medIdMyBatis-Plus 无法映射导致插入 NULL。解决检查PrescriptionDetail实体类的TableField注解确认字段名与 JSON key 一致增加 Controller 层校验PostMapping(/create) public Result create(Valid RequestBody PrescriptionDTO dto) { if (CollectionUtils.isEmpty(dto.getMedicineList())) { return Result.fail(处方至少需包含一种药品); } // ... }4.4 现象发药后medicine表stock减少了但drug_issue表无记录原因DrugIssueService.issueDrugs()方法未加Transactional或事务传播行为配置错误如REQUIRES_NEW导致嵌套事务失效。解决确认Transactional注解在 Service 方法上非类级别且spring.transaction.interceptor未被覆盖检查application.yml中spring.aop.proxy-target-class: true是否启用CGLIB 代理对 private 方法无效。5. 进阶实战把这套 HIS 源码变成你的技术简历亮点5.1 重构点一用 MyBatis-Plus 的AutoGenerator替换手写 Mapper3 行代码生成全部 CRUD原项目hospital-dao中每个实体都有独立 Mapper 接口和 XML 文件。你可以用 MP 的代码生成器统一生成省去 80% 模板代码。在hospital-dao模块下新建CodeGenerator.javapublic class CodeGenerator { public static void main(String[] args) { AutoGenerator generator new AutoGenerator(); generator.setDataSource(new DataSourceConfig() .setUrl(jdbc:mysql://localhost:3306/hospital_db) .setUsername(root).setPassword(your_pass) .setDriverName(com.mysql.cj.jdbc.Driver)); generator.setGlobalConfig(new GlobalConfig() .setOutputDir(src/main/java) // 输出路径 .setAuthor(your_name) .setOpen(false)); // 生成后不自动打开文件夹 generator.setPackageInfo(new PackageConfig() .setParent(com.hospital.dao) .setMapper(mapper) .setXml(mapper.xml)); generator.setStrategy(new StrategyConfig() .setNaming(NamingStrategy.underline_to_camel) .setColumnNaming(NamingStrategy.underline_to_camel) .addInclude(patient, doctor, medicine, prescription)); // 指定表名 generator.execute(); } }运行后com.hospital.dao.mapper.PatientMapper等接口自动生成且自带selectList、insert等方法。关键收益当你在面试中被问“怎么提升 DAO 层开发效率”你可以指着这段代码说“我用 MP Generator 把 12 张表的 CRUD 从 3 天压缩到 10 分钟且所有方法都支持 LambdaQueryWrapper——比如查今日挂号患者patientMapper.selectList(new QueryWrapperPatient().eq(reg_date, LocalDate.now()));”。5.2 重构点二给收费模块加幂等性防止重复提交导致多扣款当前/api/charge/submit接口无防重机制。用户连点两次“确认缴费”可能生成两条CHARGE_RECORD。解决方案在ChargeService.submit()中加入 Redis 幂等校验Service public class ChargeService { Resource private RedisTemplateString, String redisTemplate; public Result submit(Valid RequestBody ChargeDTO dto) { String key charge:dup: dto.getRegistrationId(); // 以挂号号为key Boolean isExist redisTemplate.opsForValue().setIfAbsent(key, 1, Duration.ofMinutes(30)); if (!isExist) { return Result.fail(重复提交请勿刷新页面); } // 执行扣款逻辑... chargeMapper.insert(chargeRecord); return Result.success(); } }参数说明Duration.ofMinutes(30)是业务容忍窗口——同一挂号号 30 分钟内只允许成功提交一次。Redis key 命名用charge:dup:前缀便于监控和清理。5.3 重构点三用Scheduled实现夜间库存盘点任务替代人工巡检医院药房每晚需核对库存。在hospital-service中新增定时任务Component public class InventoryCheckTask { Autowired private MedicineMapper medicineMapper; Scheduled(cron 0 0 2 * * ?) // 每天凌晨2点执行 public void checkInventory() { ListMedicine lowStock medicineMapper.selectList( new QueryWrapperMedicine().lt(stock, 10) // 库存低于10即告警 ); if (!lowStock.isEmpty()) { // 发送企业微信/钉钉告警此处省略SDK调用 System.out.println(【库存告警】以下药品需补货 lowStock.stream().map(m - m.getName() ( m.getStock() )) .collect(Collectors.joining(, ))); } } }逻辑说明Scheduled配合spring.task.scheduling.enabledtrue默认开启无需 Quartz 复杂配置。告警内容直指业务痛点——不是“某服务不可用”而是“阿莫西林胶囊只剩3盒需立即采购”。我带实习生做过这个项目改造把原始代码的“能跑就行”状态升级成“可监控、可防重、可扩展”。现在他们面试时不再背八股文而是讲“我在 HIS 系统里给挂号加了分布式锁用 Redisson 实现科室号源隔离避免跨科室抢号”。技术深度不在框架多炫而在你能否把REG_20240520_001这个字符串背后的时间、序列、业务规则讲成一段让人信服的故事。希望帮到你。本文还有配套的精品资源点击获取