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