简介这是一份基于Java实现的智慧医院门诊管理系统项目资料面向Java Web课程设计、毕业设计及医疗信息化学习者可用于理解预约挂号、就诊记录、药品管理、医生排班等核心业务。压缩包共289个文件整体约48.14MB其中65个java源码与65个class编译文件构成主体144个xml承担配置映射另有sql数据库脚本、yml/properties配置、jar依赖及md文档代码结构清晰便于导入调试。资源附带设计文档、实验报告与详细说明覆盖MVC、Spring、Hibernate/MyBatis、RESTful API、安全认证等技术模块并从需求分析、系统架构、数据库设计延伸到前后端联调能支撑论文撰写和答辩梳理项目中还涉及Spring Security/Shiro权限控制、JUnit/Mockito测试、Git版本管理等内容对掌握完整Java Web开发流程很有帮助。目前已有155人学习适合需要完整项目源码与配套资料进行二次开发或毕业设计的开发者。1. 智慧医院门诊管理系统到底解决了什么问题从叫号混乱到全流程线上化门诊高峰期是什么状态挂号窗口排长队、护士扯着嗓子叫号、患者在不同窗口之间来回跑。一个基于Java实现的智慧医院门诊管理系统核心就是把这套挂号、候诊、看诊、收费、取药流程搬到线上让每个环节的状态都能被系统记录和追踪。这个项目在高校的课程设计和毕业设计里出现频率很高因为它是一个完整的业务闭环前端做交互后端管流程数据库管状态。对两类人价值最大一类是需要一个能写入文档、能演示、能答辩的完整工程的学生另一类是打算转医疗信息化方向、想快速理解门诊业务模型的后端开发者。这篇笔记按“业务链路→技术选型→数据库设计→部署运行→验证扩展”的顺序讲清楚你照着走就能把项目跑起来并讲明白。2. 门诊业务全景先看懂挂号到取药的整条链路拿到这套源码第一件事不是打开代码而是先顺着业务走一遍。门诊系统的难点不在单个功能而在业务链路长、状态变化多。如果一开始就把挂号、收费、药房全塞进一张大表后期数据一定乱成一锅粥。先建立业务全景再去看代码和表结构效率会高很多也不会被细节带偏。2.1 一次完整门诊的主链路与状态流转一次门诊就诊在系统里是这样流转的患者建档 → 挂号选科室、医生、号源→ 候诊排队 → 医生接诊写病历、开处方→ 收费 → 药房发药。每一步都对应系统里的一条记录和一个状态变化。理解这套系统的关键是抓住两个状态对象挂号单和处方。挂号单状态表示“这个号挂没挂、看了没有”处方状态表示“这张方子走到哪一步了”。这两个状态把整条链路串起来也是后面所有接口逻辑的核心。状态对象典型状态值触发动作挂号单待就诊 / 已就诊 / 已退号 / 爽约挂号成功、医生接诊、退号、过时未到处方已开立 / 已收费 / 已发药 / 已退药医生开方、收费确认、药房发药、退药建议你拿到项目后先对照设计文档里的状态图或时序图把每个状态迁移对应到界面的按钮和后端的接口上。状态机清楚了后面读代码会非常快因为controller里的每个方法基本对应一个状态迁移动作。2.2 六大核心模块与职责边界门诊系统通常按职责拆成六个模块每个模块负责一段业务模块之间通过数据状态联动而不是互相直接操作对方的表。模块主要职责核心数据患者管理建档、修改资料、查询历史就诊患者表挂号管理排班展示、号源扣减、挂号单生成、退号排班表、挂号单表排队叫号按挂号顺序生成队列、叫号、过号处理挂号单状态、队列记录医生工作站接诊、写病历、开处方、查看既往记录病历表、处方表收费结算处方收费、退费、打印发票收费记录表、处方表药房发药按已收费处方发药、库存扣减处方表、药品库存表这里特别提醒排队叫号模块不是简单地从数据库查一个列表而是“挂完号就入队医生点接诊就出队”。很多初版实现把这个逻辑写死在挂号接口里导致医生想叫下一个患者时还得靠刷新页面。规范的实现是把队列状态挂在挂号单上再加一个独立的叫号记录表去维护顺序。2.3 角色权限模型不同角色能操作的菜单和按钮完全不同。常见的有五种角色挂号员、医生、收费员、药师、管理员。权限控制的常见做法是自定义拦截器加Session角色判断按请求路径前缀做白名单控制比如/doctor/**只允许医生角色访问/cashier/**只允许收费员访问。不建议一上来就引入Spring Security加JWT对这个体量的系统来说学习成本和配置成本都偏高。自定义一个HandlerInterceptor在preHandle里从Session拿当前用户角色再比对路径前缀已经足够支撑课设和演示场景。权限模型记得在实验报告里写清楚这是答辩时容易被追问的一块。2.4 设计文档和实验报告怎么读才高效压缩包里带了设计文档和实验报告很多人拿到后不知道怎么用。我的建议是别按页数顺序读按这个顺序翻先看需求分析和用例图搞清楚系统给谁用再看E-R图搞清楚有哪些表和关系然后对着核心流程看时序图搞清楚接口调用链最后翻实验报告里的测试用例设计它可以当验收清单用。这样读的原因很简单设计文档是“系统应该长什么样”源码是“系统实际怎么实现”实验报告是“怎么证明它是对的”。三者对照着看比单独啃任何一份都有效。如果文档里的表结构和你看到的SQL脚本对不上以SQL脚本为准说明文档出版后表结构又改过这点要注意。3. 技术选型与工程骨架Spring Boot MyBatis 怎么组织最顺手这套项目的主流技术组合是Spring Boot加MyBatis搭配MySQL数据库。这个选型不是赶时髦而是门诊系统的业务特点决定的配置少、上手快、SQL可控。先把选型逻辑搞清楚再去看工程结构和配置文件项目在你手里就能改得动。3.1 选型逻辑与替代方案Spring Boot的核心价值是省掉了繁琐的XML配置内嵌Tomcat一个main方法就能启动整个Web服务。MyBatis则是半自动化ORMSQL由你写映射关系由框架处理这在门诊这种多表关联查询多的场景里非常合适。比如查“某位医生某天还剩多少号”一行SQL就能控制逻辑换成Hibernate自动生成SQL反而难把控。前后端形态上常见的有两类一类是JSP或Thymeleaf模板渲染适合单人开发另一类是前后端分离Vue或React页面加接口调用。如果是前后端分离版本要多关注接口文档和跨域配置这两处前端页面的静态资源路径、接口baseURL都是容易踩坑的地方。不建议用微服务架构。门诊系统不是高并发互联网应用单机能扛住几百并发已经足够。微服务带来的分布式事务、服务注册发现、链路追踪对这个场景全是负收益。这个系统真正的技术难点在业务状态一致性不在架构规模。3.2 标准分层目录结构拿到源码后先看包结构。一个规范的Spring Boot单体项目目录应该是清晰分层的src/main/java ├── com.hospital.outpatient │ ├── OutpatientApplication.java │ ├── controller │ │ ├── PatientController.java │ │ ├── RegistrationController.java │ │ └── PrescriptionController.java │ ├── service │ │ └── RegistrationService.java │ ├── mapper │ │ └── RegistrationMapper.java │ ├── entity │ │ ├── Patient.java │ │ └── Registration.java │ ├── common │ │ ├── Result.java │ │ └── GlobalExceptionHandler.java │ └── config │ └── AuthInterceptor.java src/main/resources ├── mapper │ └── RegistrationMapper.xml ├── static │ ├── css │ └── js └── templates └── login.html请求流向是固定的页面或接口请求 → Controller接收参数 → Service处理业务逻辑 → Mapper访问数据库。entity对应数据库表结构common放统一返回体和全局异常处理config放拦截器和数据源配置。拿到源码后第一件事就是确认目录是否分层如果发现Controller里直接写JDBC查询这种版本的重构成本会非常高。3.3 pom.xml 依赖配置如果工程是Maven项目pom.xml里通常包含这几类核心依赖。下面是一段常用的依赖清单parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.20/version /dependency /dependencies这段配置里要注意三件事。mysql驱动用runtime作用域说明它只在运行期需要编译期不参与。MyBatis starter的版本要和Spring Boot版本匹配Spring Boot 2.x对应MyBatis starter 2.xSpring Boot 3.x要换3.x版本。Druid连接池的版本选择稳定版就行不建议追最新新版本有时会改配置项名称。3.4 application.yml 数据源配置工程启动前数据源配置必须确认正确。常见配置如下server: port: 8080 servlet: context-path: /hospital spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/hospital_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource druid: initial-size: 5 min-idle: 5 max-active: 20 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: trueURL里的三个参数是重点useUnicode和characterEncoding保证中文不乱码serverTimezone指定时区缺少它MySQL 8驱动会直接报连接错误。Druid的三个数字是连接池的初始连接数、最小空闲数、最大活跃数课设场景保持5到20即可。map-underscore-to-camel-case开启后数据库里的snake_case字段能自动映射到Java的驼峰属性省掉大量手写resultMap的工作。验证启动是否成功只需要右键运行OutpatientApplication看到控制台输出Started之后访问http://localhost:8080/hospital/看能不能出登录页。这一步过了工程骨架就算立起来了。4. 数据库设计把号源库存和处方状态这两件事想清楚门诊系统80%的难度在数据库设计。表结构定下来接口逻辑基本就定了一半。这一章聚焦三块核心实体关系、挂号单与号源库存设计、处方状态设计。设计文档里的E-R图可以对照着看但以SQL脚本里的实际表结构为准。4.1 核心实体关系先看实体关系。系统中最重要的几个实体是患者、医生、排班号源、挂号单、处方、收费记录。它们之间是典型的1对多关系一个患者可以有多条挂号记录一次挂号对应一张处方一张处方含多条明细。这里最容易被忽略的是排班与号源的关系——它不是一张“排班表”加一张“号源明细表”而是把总号量和余号直接放在同一条排班记录里这样查询和扣减都是一条SQL的事。表设计的核心是控制号源库存这是门诊系统区别于普通CRUD系统的关键差异点。号源控制不好就会出现在同一时间段两个患者挂到同一个医生最后一个号的情况这在真实门诊里无法接受。4.2 患者表与医生表设计患者表是基础表设计要点是业务号与自增主键分离。患者编号patient_no给窗口发就诊卡用不用自增id因为自增id会在导出数据或做历史归档时暴露真实数据量。字段上注意两点身份证号可以空不要加唯一约束测试数据经常不填预留created_time和updated_time两个时间字段排查数据问题时非常有用。CREATE TABLE t_patient ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, patient_no VARCHAR(32) NOT NULL UNIQUE COMMENT 就诊卡号, name VARCHAR(32) NOT NULL COMMENT 患者姓名, gender TINYINT NOT NULL COMMENT 0男 1女, phone VARCHAR(20) DEFAULT NULL COMMENT 联系电话, id_card VARCHAR(20) DEFAULT NULL COMMENT 身份证号, created_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT患者表;医生表类似但要注意医生信息与登录账号分开。医生表的职称、科室等是业务属性登录账号和密码属于系统属性放一张表会互相污染。常见的做法是t_doctor存业务信息t_user存账号信息通过user_id关联这种设计在“医生职称变了但账号不动”的场景下维护起来很省心。4.3 挂号单与号源库存表设计这是整个系统最核心的部分。排班表保存某位医生某天某个时段的总号量和余号挂号单表保存一次挂号行为。两张表的事务性更新是系统正确性的底线。CREATE TABLE t_schedule ( id BIGINT PRIMARY KEY AUTO_INCREMENT, doctor_id BIGINT NOT NULL COMMENT 医生ID, dept_id BIGINT NOT NULL COMMENT 科室ID, schedule_date DATE NOT NULL COMMENT 出诊日期, period TINYINT NOT NULL COMMENT 1上午 2下午, total_count INT NOT NULL COMMENT 总号源数, remain_count INT NOT NULL COMMENT 剩余号源数, version INT NOT NULL DEFAULT 0 COMMENT 乐观锁版本号, UNIQUE KEY uk_doctor_date (doctor_id, schedule_date, period) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT医生排班(号源)表; CREATE TABLE t_registration ( id BIGINT PRIMARY KEY AUTO_INCREMENT, reg_no VARCHAR(32) NOT NULL UNIQUE COMMENT 挂号单号, patient_id BIGINT NOT NULL COMMENT 患者ID, schedule_id BIGINT NOT NULL COMMENT 排班ID, doctor_id BIGINT NOT NULL COMMENT 医生ID, reg_type TINYINT NOT NULL COMMENT 1普通号 2专家号, fee DECIMAL(10,2) NOT NULL COMMENT 挂号费, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待就诊 1已就诊 2已退号, created_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_patient (patient_id), KEY idx_schedule (schedule_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT挂号单表;号源扣减必须用条件更新加乐观锁这是防止超卖的关键。伪逻辑是先查排班拿当前version再执行下面的更新UPDATE t_schedule SET remain_count remain_count - 1, version version 1 WHERE id #{scheduleId} AND remain_count 0 AND version #{version};这条SQL的意思是只有“余号大于0且版本号没变”时才扣减返回影响行数为0就说明号已被抢完业务层直接提示“该号源已约满”。为什么不用select for update悲观锁会把排班这行锁住直到事务结束同一医生的号源在高峰期会被串行化处理接口堆积明显。乐观锁在冲突不高的场景下吞吐更好实现也简单。挂号接口还需要一个关键约定插入挂号单和扣减号源必须在同一个数据库事务里。只插入不扣减会超卖只扣减不插入会丢号。在Service方法上加上Transactional注解任何一个操作失败都会整体回滚。4.4 处方与收费记录设计处方表是第二优先级。一次就诊可能开多张处方一张处方有多行药品明细所以拆成处方头表和处方明细表。处方头记录整张单子的状态和总额明细表记录具体药品。CREATE TABLE t_prescription ( id BIGINT PRIMARY KEY AUTO_INCREMENT, rx_no VARCHAR(32) NOT NULL UNIQUE COMMENT 处方号, registration_id BIGINT NOT NULL COMMENT 关联挂号单, patient_id BIGINT NOT NULL COMMENT 患者ID, doctor_id BIGINT NOT NULL COMMENT 医生ID, total_amount DECIMAL(10,2) DEFAULT 0 COMMENT 总金额, status TINYINT NOT NULL DEFAULT 0 COMMENT 0已开立 1已收费 2已发药 3已退药, created_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_registration (registration_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT门诊处方表; CREATE TABLE t_prescription_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, rx_id BIGINT NOT NULL COMMENT 处方ID, drug_name VARCHAR(64) NOT NULL COMMENT 药品名称, spec VARCHAR(64) DEFAULT NULL COMMENT 规格, quantity INT NOT NULL COMMENT 数量, unit_price DECIMAL(10,2) NOT NULL COMMENT 单价, amount DECIMAL(10,2) NOT NULL COMMENT 金额小计, KEY idx_rx (rx_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT处方明细表;处方金额不存明细表冗余金额而是在收费时通过sum明细表的amount计算。这种设计保证总额始终等于明细之和不会出现“处方头金额和明细对不上”的脏数据。状态流转上注意退药只能发生在已收费未发药或已发药但满足退货条件的情况下这个判断逻辑建议写在Service层不要写成DB触发器方便维护和测试。索引方面挂号单表的patient_id和schedule_id、处方表的registration_id这三个字段作为高频查询条件必须建普通索引。索引不是越多越好像id_card这类几乎不按它查询的字段不需要加索引否则反而拖慢写入。5. 本地部署与运行排查五个容易翻车的坑把项目跑起来是绝大多数人拿到源码后的第一道坎。下面五个问题是我在帮别人排查时反复遇到的按“现象→原因→解决”展开每个都对应一次真实的翻车经历。5.1 启动报 ClassNotFoundException 或 NoSuchMethodError现象点击运行后控制台立刻抛ClassNotFoundException或NoSuchMethodError点名某个Spring或MyBatis的类找不到。原因基本是JDK版本和Spring Boot版本不匹配。比如用JDK 17去跑Spring Boot 2.2.x的工程类加载阶段就会挂。还有一个隐蔽原因Maven默认编译级别是1.5代码用了新语法直接编译不过。解决先统一JDK版本。Spring Boot 2.x用JDK 8最省心Spring Boot 3.x用JDK 17。然后在IDEA的Project Structure里把Project SDK和Module的language level设成一致再在pom.xml的properties里显式声明properties java.version1.8/java.version /properties最后在Maven面板执行clean和package重新拉取依赖。如果本地仓库里有损坏的jar把~/.m2/repository下对应目录删掉让Maven重新下载。5.2 连不上数据库或建表脚本执行顺序不对现象启动日志出现Communications link failure或者后面接口报Table xxx doesnt exist。原因有两类一是MySQL服务没启动或者root密码和配置不一致二是建表SQL没执行或执行顺序错了导致外键关联的表先建、被关联的表后建。解决先在MySQL命令行里执行建库语句再看SQL脚本。建库必须指定字符集CREATE DATABASE IF NOT EXISTS hospital_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;导入脚本时按照“先建表、后插入基础数据”的顺序执行。很多源码包把菜单表、角色表的insert语句放在建表脚本之后如果只导了一半登录后首页会白屏或报空指针。5.3 并发挂号时号源超卖和重复挂号现象用两个浏览器同时挂同一个医生的最后一个号两边都提示挂号成功数据库里余号变成负数。原因就是经典的“先查再改”竞态。两个请求同时读到remain_count等于1都执行减一各插入一条挂号单库存逻辑就崩了。这个问题我在验收代码时见过不止一次。解决按第4章的方案用条件更新加乐观锁。核心就这一条SQLUPDATE t_schedule SET remain_count remain_count - 1, version version 1 WHERE id #{scheduleId} AND remain_count 0 AND version #{version};影响行数为0就说明号源已满业务层把这个结果翻译成友好提示。同时把扣减库存和插入挂号单放在同一个Transactional方法里。验证方式可以用两个Tab页同时提交或者写一个简单的多线程测试断言只能有一个成功。5.4 页面样式全乱或接口404现象登录页能打开但只有纯文字CSS和JS全加载不出来或者登录成功后跳转的页面404。原因通常是两类自定义拦截器把静态资源拦截了或者配置了context-path后前端请求路径没带前缀。我见过有人排查了一下午最后发现是路径字符串少了/hospital。解决自定义拦截器要显式放行静态资源配置写法如下registry.addInterceptor(authInterceptor) .addPathPatterns(/**) .excludePathPatterns(/login, /css/**, /js/**, /images/**, /error);前端页面里不要写死绝对路径用模板引擎的处理方式link th:href{/css/main.css} relstylesheet !-- 或者JSP写法 -- link hrefc:url value/css/main.css/ relstylesheet接口请求路径同理统一拼接context-path。改配置后记得强刷浏览器缓存静态资源有时只是被本地缓存骗了。5.5 控制台和页面中文乱码现象启动日志里中文全是乱码页面上查出来的患者姓名显示成问号。原因只有一个方向——编码不一致具体会出现在三个位置IDEA控制台编码、JVM文件编码、MySQL连接和表的字符集。三处只要有一处不一致中文就会坏。解决把三处全部统一。IDEA的File Encoding设置为UTF-8Spring Boot配置里确保URL带characterEncodingutf8MySQL建库用utf8mb4。如果还乱码在代码里加一行临时输出System.out.println(中文测试)判断是代码层就乱还是数据库取出乱。JVM层面可以在启动配置里加-Dfile.encodingUTF-8一劳永逸。6. 进阶验证用端到端数据流检验全流程项目能启动、登录页能出来只是第一步。真正验证一个门诊系统是否完整要看“建档→挂号→开方→收费→发药”这条主链路能不能用测试数据完整走通。每通过一步对应的表状态就发生一次正确变化这才叫真正跑通了。6.1 构造一条端到端测试数据先在后台管理界面建一个测试患者和一条医生排班然后模拟一次完整的就诊操作# 1. 新建患者 curl -X POST http://localhost:8080/hospital/api/patient \ -H Content-Type: application/json \ -d {name:测试患者,gender:0,phone:13800000000} # 2. 查询医生某天剩余号源 curl http://localhost:8080/hospital/api/schedule?doctorId1date2025-06-10 # 3. 挂号 curl -X POST http://localhost:8080/hospital/api/registration \ -H Content-Type: application/json \ -d {patientId:1,scheduleId:1}如果项目接口路径不同以设计文档的接口清单为准替换URL即可。每次调用后去数据库检查对应表的状态患者表多一条记录排班表的remain_count减一挂号单表出现一条待就诊记录。这一步能验证接口和事务是否正常比打开页面点一遍更快。6.2 三个值得投入的扩展方向验证通过后有三个方向值得投入时间。第一个是预约放号与退号回收过号自动释放号源、退号时返还库存这是真实门诊里最常出现的运营需求。第二个是药品库存联动处方开立时锁定库存收费后扣减否则会出现“患者交了费药房却没药”的情况。第三个是门诊统计报表按科室汇总当日挂号量、按医生汇总工作量、按收费时段汇总营收几行GROUP BY的SQL就能实现演示和答辩时非常加分。不太建议在这个阶段做微服务拆分或引入消息队列。门诊系统的复杂度在业务状态的一致性把状态机做稳、把号源事务做对价值远大于换一个更新潮的架构。我自己的习惯是拿到这类项目先跑通这条主链路再开始看细节代码。一个系统能不能交付看的不是用了多新的框架而是挂号、收费、发药这条主线在任何边界情况下都不出错。把这份源码真正吃透比改个名字交出去对你更有用。希望帮到你。本文还有配套的精品资源点击获取