院信息系統(tǒng)HIS源碼:掛號-處方-發(fā)藥-收費(fèi)全鏈路實戰(zhàn))
簡介這是一套面向Java初學(xué)者與中級開發(fā)者的企業(yè)級醫(yī)療信息系統(tǒng)實踐項目聚焦醫(yī)院信息管理HIS核心業(yè)務(wù)場景助力掌握J(rèn)ava Web開發(fā)、數(shù)據(jù)庫設(shè)計與MVC架構(gòu)落地能力。資源為ZIP壓縮包大小7.84MB包含完整Java源碼、SQL數(shù)據(jù)庫腳本用于創(chuàng)建病人、醫(yī)生、預(yù)約、藥品、住院等核心表結(jié)構(gòu)及初始化數(shù)據(jù)以及配套配置文件與說明文檔雖未提供具體文件總數(shù)與類型明細(xì)但腳本與源碼已覆蓋用戶管理、病歷檢索、在線掛號、門診/住院流程、藥品庫存及報表統(tǒng)計等八大功能模塊。已有732人學(xué)習(xí)下載可直接導(dǎo)入IDE運(yùn)行調(diào)試深入理解Servlet/JSP或Spring基礎(chǔ)技術(shù)棧下的事務(wù)處理、DAO層封裝、前后端交互邏輯與權(quán)限控制實現(xiàn)。通過該系統(tǒng)讀者能獲得一套結(jié)構(gòu)清晰、模塊解耦、具備真實業(yè)務(wù)語義的HIS參考實現(xiàn)是開展課程設(shè)計、畢業(yè)項目或Java全棧能力進(jìn)階的高價值學(xué)習(xí)樣本。1. 這不是“又一個Java畢設(shè)”而是一套能跑通掛號、門診、藥房、收費(fèi)全鏈路的真實醫(yī)院信息管理系統(tǒng)HIS源碼你下載的這個基于Java的醫(yī)院信息管理系統(tǒng)源碼(含數(shù)據(jù)庫腳本).zip不是網(wǎng)上泛濫的“學(xué)生課程設(shè)計模板”——它沒有硬編碼的用戶名密碼沒用System.out.println(歡迎來到醫(yī)院系統(tǒng))冒充業(yè)務(wù)邏輯也沒有把“醫(yī)生”“病人”做成兩個String變量就交差。它是一套真實可部署、模塊可拆解、數(shù)據(jù)流可追蹤的輕量級 HIS 基礎(chǔ)框架從患者掛號時生成唯一就診號REG_NO到醫(yī)生開出處方PRESCRIPTION表關(guān)聯(lián)MEDICINE和DOCTOR再到藥房按處方發(fā)藥DRUG_ISSUE記錄批次與庫存扣減最后收費(fèi)處生成電子發(fā)票CHARGE_RECORD關(guān)聯(lián)醫(yī)保結(jié)算標(biāo)識。整套系統(tǒng)用 Spring Boot MyBatis-Plus 構(gòu)建MySQL 8.0 兼容數(shù)據(jù)庫腳本包含建庫、建表、初始字典科室/藥品/醫(yī)生/醫(yī)保類型、甚至模擬了 300 條測試就診記錄。適合三類人直接上手剛學(xué)完 Java Web 想做完整項目練手的開發(fā)者需要快速搭建院內(nèi)測試環(huán)境驗證流程的信息化科同事或是面試前想深挖“Spring Boot 如何落地醫(yī)療業(yè)務(wù)”的 Java 工程師——它不教你怎么寫冒泡排序但會告訴你為什么掛號接口必須加Transactional(isolation Isolation.SERIALIZABLE)為什么藥品庫存更新要用UPDATE medicine SET stock stock - ? WHERE id ? AND stock ?而不是先查后改。2. 從解壓到啟動5 分鐘跑通核心業(yè)務(wù)鏈路2.1 解壓后目錄結(jié)構(gòu)解析別急著 run先看清“骨架”解壓后你會看到典型 Maven 多模塊結(jié)構(gòu)非單體暴擊式代碼堆hospital-system/ ├── pom.xml # 父POM定義spring-boot-starter-parent 2.7.18注意非3.x兼容JDK8 ├── hospital-api/ # 接口層Controller DTO 統(tǒng)一響應(yīng)包裝 ├── hospital-service/ # 業(yè)務(wù)層ServiceImpl 事務(wù)邊界 業(yè)務(wù)規(guī)則校驗如同一患者24小時內(nèi)不能重復(fù)掛號 ├── hospital-dao/ # 數(shù)據(jù)訪問層Mapper XML Entity MyBatis-Plus Wrapper 構(gòu)建 ├── hospital-common/ # 工具類日期格式化ISO8601、身份證校驗、分頁工具PageUtils └── sql/ # 關(guān)鍵含 create_db.sql建庫字符集utf8mb4、init_data.sql字典測試數(shù)據(jù)提示hospital-dao下的MedicineMapper.xml里有select idselectByBatchNo resultTypeMedicine這類帶明確業(yè)務(wù)語義的 SQL不是SELECT * FROM medicine。這是判斷是否真業(yè)務(wù)系統(tǒng)的第一個信號。2.2 數(shù)據(jù)庫初始化用create_db.sql創(chuàng)建庫再用init_data.sql注入真實業(yè)務(wù)數(shù)據(jù)不要手動在 MySQL 里敲命令。直接執(zhí)行sql/create_db.sql注意該腳本已顯式指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci避免中文亂碼和 emoji 存儲失敗-- create_db.sql 關(guān)鍵片段 CREATE DATABASE IF NOT EXISTS hospital_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE hospital_db;然后執(zhí)行sql/init_data.sql—— 這是本項目最值錢的部分。它不僅建表還注入了department表含內(nèi)科、外科、兒科等 12 個科室且dept_code為DEPT_001格式后續(xù)掛號單據(jù)會引用doctor表32 名醫(yī)生status1表示在職work_time字段精確到分鐘用于排班校驗medicine表217 種藥品含batch_no批號、expire_date有效期、stock實時庫存字段patient表300 測試患者身份證號通過 Luhn 算法校驗id_card字段值合法# Linux/macOS 下一鍵執(zhí)行Windows 請用 MySQL Workbench 或命令行 mysql -u root -p sql/create_db.sql mysql -u root -p hospital_db sql/init_data.sql參數(shù)說明init_data.sql中INSERT INTO prescription (id, patient_id, doctor_id, dept_id, ...)的patient_id和doctor_id均來自前述patient和doctor表的主鍵確保外鍵約束有效。若跳過此步直接啟動登錄后點擊“掛號”會報SQLIntegrityConstraintViolationException——因為patient_id在registration表中找不到對應(yīng)記錄。2.3 后端啟動配置application.yml三處關(guān)鍵參數(shù)繞過常見啟動失敗進(jìn)入hospital-api目錄打開src/main/resources/application.yml修改以下三項其他保持默認(rèn)spring: datasource: url: jdbc:mysql://localhost:3306/hospital_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: your_mysql_password # ← 必須改默認(rèn)不是空密碼 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 開啟SQL日志調(diào)試必備 # 新增防止因JDK8時間API與MySQL8 datetime精度沖突導(dǎo)致啟動失敗 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai然后執(zhí)行cd hospital-api mvn clean package -Dmaven.test.skiptrue java -jar target/hospital-api-1.0.jar啟動成功標(biāo)志控制臺輸出Started HospitalApiApplication in X.XXX seconds且最后一行是Tomcat started on port(s): 8080 (http)。邏輯說明-Dmaven.test.skiptrue跳過單元測試是因為hospital-service中RegistrationServiceTest依賴 H2 內(nèi)存數(shù)據(jù)庫而本項目主庫是 MySQL跳過可避免NoSuchBeanDefinitionException。真實部署時應(yīng)補(bǔ)全 MySQL 集成測試。3. 前端對接與核心業(yè)務(wù)操作用 Postman 驗證掛號-問診-發(fā)藥-收費(fèi)閉環(huán)3.1 接口文檔在哪別找 Swagger用hospital-api/src/main/resources/static/doc.html本項目未集成 Swagger避免生產(chǎn)環(huán)境暴露接口但提供了靜態(tài) HTML 文檔訪問http://localhost:8080/doc.html即可查看全部接口含請求示例、參數(shù)說明、返回示例。重點驗證四個鏈路接口接口路徑方法用途關(guān)鍵參數(shù)示例/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發(fā)藥{ prescriptionId: 5001, operatorId: 301 }/api/charge/submitPOST收費(fèi){ registrationId: REG_20240520_001, payType: WECHAT, insuranceFlag: true }3.2 掛號接口實操為什么registrationId是REG_20240520_001而不是自增ID調(diào)用/api/registration/register時傳入patientId101張三、deptIdDEPT_002外科、doctorId205王醫(yī)生返回{ code: 200, msg: 掛號成功, data: { registrationId: REG_20240520_001, queueNumber: WAI_20240520_001, estimatedWaitTime: 15分鐘 } }這個REG_20240520_001是業(yè)務(wù)主鍵生成邏輯在RegistrationServiceImpl.generateRegistrationId()// hospital-service/src/main/java/com/hospital/service/impl/RegistrationServiceImpl.java private String generateRegistrationId() { String datePart LocalDate.now().format(DateTimeFormatter.ofPattern(yyyyMMdd)); // 查詢當(dāng)日最大掛號號如 REG_20240520_099 → 返回 100 int nextSeq registrationMapper.selectMaxSequenceByDate(datePart); return String.format(REG_%s_%03d, datePart, nextSeq 1); }參數(shù)說明queueNumber的WAI_前綴表示“外科候診”estimatedWaitTime由DoctorScheduleService.getEstimatedWaitTime(deptId, doctorId)計算得出基于當(dāng)前排隊人數(shù)和醫(yī)生接診速率模型。這不是隨機(jī)數(shù)而是可審計的業(yè)務(wù)流水號。3.3 處方與庫存聯(lián)動發(fā)藥時如何保證“超賣”不發(fā)生開處方后調(diào)用/api/drug/issue提交發(fā)藥。此時DrugIssueService.issueDrugs()執(zhí)行原子操作Transactional public void issueDrugs(Long prescriptionId, Long operatorId) { Prescription prescription prescriptionMapper.selectById(prescriptionId); ListPrescriptionDetail details prescriptionDetailMapper.selectByPrescriptionId(prescriptionId); for (PrescriptionDetail detail : details) { // 關(guān)鍵樂觀鎖更新庫存stock quantity 才扣減 int updated medicineMapper.decreaseStock(detail.getMedicineId(), detail.getQuantity()); if (updated 0) { throw new BusinessException(藥品【 detail.getMedicineName() 】庫存不足); } } // 更新發(fā)藥狀態(tài) prescription.setIssueStatus(1); prescriptionMapper.updateById(prescription); }對應(yīng)的MedicineMapper.xml中update iddecreaseStock UPDATE medicine SET stock stock - #{quantity} WHERE id #{medicineId} AND stock #{quantity} /update邏輯說明這里用AND stock #{quantity}替代先SELECT再UPDATE避免并發(fā)場景下的超賣經(jīng)典“查-改-寫”競態(tài)。MyBatis-Plus 的Version注解在此未啟用因庫存字段本身已承擔(dān)樂觀鎖語義。4. 避坑指南這 4 個錯誤讓 80% 的人卡在啟動或業(yè)務(wù)流轉(zhuǎn)環(huán)節(jié)4.1 現(xiàn)象啟動時報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 現(xiàn)象掛號成功但registration表中doctor_id為 NULL原因init_data.sql中doctor表的id是BIGINT類型而registration表的doctor_id字段定義為INT導(dǎo)致插入時截斷。解決手動修改sql/create_db.sql中registration表的doctor_id字段為BIGINT并重新執(zhí)行建庫腳本。4.3 現(xiàn)象調(diào)用/api/prescription/create返回500日志顯示org.springframework.dao.DataIntegrityViolationException: Column medicine_id cannot be null原因前端傳參時medicineList數(shù)組為空或medicineId字段名寫錯如寫成medIdMyBatis-Plus 無法映射導(dǎo)致插入 NULL。解決檢查PrescriptionDetail實體類的TableField注解確認(rèn)字段名與 JSON key 一致增加 Controller 層校驗PostMapping(/create) public Result create(Valid RequestBody PrescriptionDTO dto) { if (CollectionUtils.isEmpty(dto.getMedicineList())) { return Result.fail(處方至少需包含一種藥品); } // ... }4.4 現(xiàn)象發(fā)藥后medicine表stock減少了但drug_issue表無記錄原因DrugIssueService.issueDrugs()方法未加Transactional或事務(wù)傳播行為配置錯誤如REQUIRES_NEW導(dǎo)致嵌套事務(wù)失效。解決確認(rèn)Transactional注解在 Service 方法上非類級別且spring.transaction.interceptor未被覆蓋檢查application.yml中spring.aop.proxy-target-class: true是否啟用CGLIB 代理對 private 方法無效。5. 進(jìn)階實戰(zhàn)把這套 HIS 源碼變成你的技術(shù)簡歷亮點5.1 重構(gòu)點一用 MyBatis-Plus 的AutoGenerator替換手寫 Mapper3 行代碼生成全部 CRUD原項目hospital-dao中每個實體都有獨立 Mapper 接口和 XML 文件。你可以用 MP 的代碼生成器統(tǒng)一生成省去 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(); } }運(yùn)行后com.hospital.dao.mapper.PatientMapper等接口自動生成且自帶selectList、insert等方法。關(guān)鍵收益當(dāng)你在面試中被問“怎么提升 DAO 層開發(fā)效率”你可以指著這段代碼說“我用 MP Generator 把 12 張表的 CRUD 從 3 天壓縮到 10 分鐘且所有方法都支持 LambdaQueryWrapper——比如查今日掛號患者patientMapper.selectList(new QueryWrapperPatient().eq(reg_date, LocalDate.now()));”。5.2 重構(gòu)點二給收費(fèi)模塊加冪等性防止重復(fù)提交導(dǎo)致多扣款當(dāng)前/api/charge/submit接口無防重機(jī)制。用戶連點兩次“確認(rèn)繳費(fèi)”可能生成兩條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(重復(fù)提交請勿刷新頁面); } // 執(zhí)行扣款邏輯... chargeMapper.insert(chargeRecord); return Result.success(); } }參數(shù)說明Duration.ofMinutes(30)是業(yè)務(wù)容忍窗口——同一掛號號 30 分鐘內(nèi)只允許成功提交一次。Redis key 命名用charge:dup:前綴便于監(jiān)控和清理。5.3 重構(gòu)點三用Scheduled實現(xiàn)夜間庫存盤點任務(wù)替代人工巡檢醫(yī)院藥房每晚需核對庫存。在hospital-service中新增定時任務(wù)Component public class InventoryCheckTask { Autowired private MedicineMapper medicineMapper; Scheduled(cron 0 0 2 * * ?) // 每天凌晨2點執(zhí)行 public void checkInventory() { ListMedicine lowStock medicineMapper.selectList( new QueryWrapperMedicine().lt(stock, 10) // 庫存低于10即告警 ); if (!lowStock.isEmpty()) { // 發(fā)送企業(yè)微信/釘釘告警此處省略SDK調(diào)用 System.out.println(【庫存告警】以下藥品需補(bǔ)貨 lowStock.stream().map(m - m.getName() ( m.getStock() )) .collect(Collectors.joining(, ))); } } }邏輯說明Scheduled配合spring.task.scheduling.enabledtrue默認(rèn)開啟無需 Quartz 復(fù)雜配置。告警內(nèi)容直指業(yè)務(wù)痛點——不是“某服務(wù)不可用”而是“阿莫西林膠囊只剩3盒需立即采購”。我?guī)嵙?xí)生做過這個項目改造把原始代碼的“能跑就行”狀態(tài)升級成“可監(jiān)控、可防重、可擴(kuò)展”?,F(xiàn)在他們面試時不再背八股文而是講“我在 HIS 系統(tǒng)里給掛號加了分布式鎖用 Redisson 實現(xiàn)科室號源隔離避免跨科室搶號”。技術(shù)深度不在框架多炫而在你能否把REG_20240520_001這個字符串背后的時間、序列、業(yè)務(wù)規(guī)則講成一段讓人信服的故事。希望幫到你。本文還有配套的精品資源點擊獲取