简介:这是一份基于SSM框架与MySQL数据库的医院分诊管理系统完整源码,适用于JavaWeb课程设计、毕业设计或医院信息化入门学习。系统覆盖医生分诊叫号、患者信息在线编辑、科室管理及统计功能,分诊管理下细分为分诊、患者、科室三个子菜单,并附带系统管理与监控模块。资源包内共2000个文件,主要包括Java源码、Class字节码、JSP页面、JS脚本、CSS样式、SQL数据库脚本及大量页面切图,压缩包约43.65MB,目录结构完整。已有286人学习下载。通过学习这份源码,读者可快速掌握SSM整合、分诊流程设计、患者信息管理及基本监控功能的实现思路,是值得参考的实战案例。
1. 分诊排队这件每天都发生的小事,被这套 SSM 源码完整落成了后台流程
医院候诊区最磨人的不是等待本身,而是你不知道前面还有几个人、自己排到了哪一步。这套医院分诊管理系统源码,把“患者建档 → 分诊 → 排队 → 叫号 → 完成就诊 → 科室统计”整条链路搬进了浏览器。后端走的是 SSM(Spring + Spring MVC + MyBatis)配 MySQL,权限交给 Shiro,前端还保留着 My97 日期控件这类老牌组件。医生登录后能直接查看候诊患者、选择科室、在线叫号,也能维护患者档案和院内科室,系统管理里再管账号权限,系统监控里跑统计任务。我拆完这套源码最大的感受是:功能不算重,但模块边界很干净,特别适合做课程设计、毕业设计,也适合想读懂传统 Java Web 项目分层思路的开发者。
2. 技术栈摸底:SSM 主体之下,还藏着一批 Shiro 和定时任务的老熟人
2.1 从残留 class 反推项目年龄:HibernateDao 与 UserRealm 为什么同时存在
解压这份源码,第一眼看到的编译产物很有意思:WdatePicker.js.bak、HibernateDao.class、SimpleHibernateDao.class、ScheduleJobService.class、Reflections.class、User.class、UserController.class、RoleController.class、UserRealm.class、JsonMapper.class。这一串名字放在一起,几乎是在报暗号:WdatePicker是 My97 日期选择器的备份文件,说明前端日期组件用的是它;HibernateDao和SimpleHibernateDao是 Spring + Hibernate 时代的通用 DAO 基类,思路是一套代码覆盖所有实体的增删改查;UserRealm则清楚指向 Apache Shiro 安全框架;ScheduleJobService背后大概率挂着 Spring Task 或 Quartz 定时调度;JsonMapper负责把 Java 对象序列化成 JSON 响应给前端。
看到这里你可能会疑惑:标题明明写着 ssm+mysql,SSM 里的 M 是 MyBatis,怎么又冒出来 HibernateDao?我当时也愣了一下,翻了几分钟才确认:Controller 和 Service 主流程已经倒向 MyBatis Mapper,残留的 HibernateDao 只是早期骨架留下的通用工具类,实际查询不再走 Hibernate。这种半新半旧的代码风格在老项目里非常典型,拿到手不用慌,主线还是 Spring 管理 Bean、Spring MVC 接收路由、MyBatis 读写 MySQL。真正要注意的是UserRealm,登录校验由 Shiro 接管,Controller 里那些/login、/logout请求要先过 Shiro 过滤链,这也为后面排查 404 埋下了伏笔。
2.2 分层目录与一次请求的完整走向
从工程结构看,这套系统是标准的 Java Web 三段式:controller层收请求、service层管业务、dao(mapper)层记数据,基层再铺一层entity实体和util工具。我把主要包结构按常见布局还原一下:
src/main/java ├── com/xxx/controller │ ├── UserController.java │ ├── RoleController.java │ └── PatientController.java ├── com/xxx/service │ ├── TriageService.java │ └── ScheduleJobService.java ├── com/xxx/dao │ ├── PatientMapper.java │ └── TriageRecordMapper.java ├── com/xxx/entity │ ├── User.java │ ├── Role.java │ ├── Patient.java │ └── Department.java ├── com/xxx/shiro │ └── UserRealm.java └── com/xxx/util ├── JsonMapper.java └── Reflections.java请求进来后的走向大致是:浏览器先命中 Shiro 过滤器,未登录的被踢到登录页;登录后才进入 Spring MVC 的 DispatcherServlet,根据 URL 映射到对应 Controller。Controller 负责最简单的参数校验和结果封装,真正的判断逻辑下沉到 Service。Service 层调 Mapper 接口,Mapper 再通过 XML 里的 SQL 语句与 MySQL 交互,最后把结果一层层返回,由JsonMapper包装成 JSON 格式吐给前端页面。异步操作如叫号、新增患者,前端拿到 JSON 后局部刷新 DOM,整条链路非常清晰。
这套结构的优点是边界明确:改动一个模块不影响另外几个。比如想调整叫号排序规则,只需要动 TriageService 和对应 Mapper XML;想加权限点,只改 Shiro 配置和 RoleController。缺点也和大多数传统项目一样,业务逻辑容易在 Service 里越堆越厚,二次开发时注意别把规则全写进 Controller。
2.3 数据模型梳理:患者、科室、分诊记录怎么串成闭环
要支撑“新增患者 → 分诊管理可见 → 医生叫号 → 完成就诊”这条闭环,系统背后至少要有五张能互相咬合的表。以下是按这套业务场景合理推断出的最小数据模型,实际源码中字段名可能会稍有偏差,但信息维度八九不离十:
| 表名 | 关键字段 | 在系统里承担的角色 |
|---|---|---|
| sys_user | id, username, password, salt, dept_id | 医生账号,绑定所属科室 |
| sys_role / user_role | id, role_name, user_id, role_id | 控制分诊菜单和系统管理菜单的权限 |
| patient | id, patient_name, card_no, phone, gender | 患者基础档案,新增后立刻进入候诊池 |
| department | id, dept_name, dept_code, location | 医院内部科室,分诊归属地 |
| triage_record | id, patient_id, dept_id, triage_no, status, create_time | 分诊主表,记录排队与叫号状态 |
patient与triage_record通过patient_id关联,triage_record与department通过dept_id关联。状态字段status我一般会设计成三挡:waiting(等待中)、doing(就诊中)、done(已完成)。分诊管理页面呈现的“是否已完成就诊”,本质上就是读取这个字段。triage_no是当天排队的流水号,create_time则用来决定叫号顺序。我拆源码时习惯先把这几张表的关系画出来,再去读 Mapper XML,读起来会轻松一个数量级。
3. 核心模块拆解:一个分诊记录从录入到叫号完成的完整链路
3.1 分诊管理:点击“叫号”按钮,后台到底执行了什么
分诊管理是整个系统的主战场。点击左侧的“分诊管理”,下属菜单展开三个入口:分诊管理、患者管理、科室管理。真正点开“分诊管理”,界面上列出当前存下的候诊病患,能明确看到每位患者选择纠正后的科室、候诊状态和是否已完成就诊。操作台最醒目的动作就是“叫号”。
叫号这件事拆到技术层面,分两步:第一,从当前科室的等待队列里取出下一位患者;第二,把这条分诊记录的status从waiting改成doing。取出下一位的查询,核心逻辑近似下面这条 SQL:
-- 选出当前科室下一位“等待中”的患者 SELECT t.id, p.patient_name, p.gender, t.triage_no FROM triage_record t JOIN patient p ON t.patient_id = p.id WHERE t.dept_id = #{deptId} AND t.status = 'waiting' ORDER BY t.create_time ASC LIMIT 1;这里的#{deptId}是当前医生所属科室的主键,status = 'waiting'保证了只有等待中的患者才会被叫到,ORDER BY create_time让排队更早的人先被处理,LIMIT 1锁死只取一个叫号对象。拿到这条记录后,再执行一条UPDATE triage_record SET status = 'doing' WHERE id = #{triageId},把状态置为就诊中。完成后由 JsonMapper 封装成 JSON 返回,前端页面立即把按钮和状态刷新出来。
注意这里有个隐藏细节:查用户和改状态是两个动作,中间没有事务包裹,并发高的时候可能两个医生同时叫到同一个人。小门诊一般不会出问题,但如果做二次开发,建议把这两步放进同一个事务方法,或者用SELECT ... FOR UPDATE加行锁,才够稳。
3.2 患者管理与科室管理:新增一条档案,凭什么立刻出现在分诊列表里
患者管理模块本质是一张典型的增删改查表。管理员点“添加”,弹出表单,填入患者姓名、性别、电话、出生日期等基本信息,保存后这条数据写进 patient 表。关键地方在于,患者管理新增的数据没有单独再插一条分诊记录,而是复用分诊管理里的查询视图,分诊列表通过 patient_id 关联去查,所以新增完的患者刷新分诊管理页面就能看到。添加患者的保存逻辑,常见写法是这样:
public Boolean savePatient(Patient patient) { // 生成就诊卡号,避免手工输入导致重复 patient.setCardNo("PA" + System.currentTimeMillis()); // 默认进入分诊队列,状态由分诊记录表维护 return patientMapper.insert(patient) > 0; }这段代码里System.currentTimeMillis()生成卡号是为了快速演示,真实场景一般会做成日期加自增序号的格式,比如20240612-001。insert返回影响行数,大于 0 说明落库成功。科室管理的逻辑更简单,就是维护 department 表的增删改查,新增科室后,分诊管理表单里“科室选择”的下拉选项随即多出一项。这里踩过坑的话会知道,下拉数据如果做了缓存,新增科室后要清缓存或重新加载,否则页面永远看不到新科室。
3.3 系统管理、系统监控与权限控制:Shiro 的 UserRealm 在幕后做了什么
系统管理和系统监控是这类后台的标配。系统管理管的是账号和角色:UserController负责用户列表、新增用户、修改密码,RoleController负责角色与菜单权限的绑定。系统监控里则是ScheduleJobService的舞台,它能定时跑统计任务,比如每天凌晨汇总各科室的就诊人次,或者清理过期分诊记录。
这些能力背后,UserRealm承担了最底层的登录认证。UserRealm是 Shiro 框架里的核心类,重写doGetAuthenticationInfo方法后,用户提交的用户名密码会先经过这里校验。标准实现长这样:
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) { // 取出前端传入的用户名 String username = (String) token.getPrincipal(); // 根据用户名查数据库 User user = userMapper.findByUsername(username); if (user == null) { return null; // 查不到用户,登录失败 } // 返回账号密码给 Shiro 做比对 return new SimpleAuthenticationInfo(user, user.getPassword(), getName()); }密码比对逻辑由 Shiro 内部完成,比对通过后用户信息会放进 Session,后续请求走 Shiro 过滤器时自动放行。系统管理里能配置的角色,最终会影响左侧菜单项的渲染,普通医生看不到系统管理入口,看到的就是分诊、患者、科室三个子菜单,这句描述和源码里的 Shiro 过滤规则是对得上的。
4. 从解压到跑通:数据库初始化、配置文件和 Tomcat 启动全流程
4.1 环境准备:JDK、MySQL、Tomcat 怎么选才不翻车
这套源码属于典型的老派 Java Web 工程,环境版本不用追新,反而用稳定版本最省事。我一般推荐 JDK 1.8、MySQL 5.7(MySQL 8.0 也能跑,但连接驱动记得换成com.mysql.cj.jdbc.Driver)、Tomcat 8.5,开发工具用 IntelliJ IDEA 或 Eclipse 都行,Maven 3.6 上下即可。
版本选择上有一个血泪经验:如果本机装的是 MySQL 8.x,却继续用源码里旧的com.mysql.jdbc.Driver,启动时大概率报Loading class 'com.mysql.jdbc.Driver'不兼容的警告,甚至直接连不上库。解决办法是改用 8.x 对应的驱动包,并把连接串改成jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai。如果手上只有旧源码没有新驱动,最省事的路径就是装 MySQL 5.7,少跟驱动版本较劲。
4.2 数据库初始化与配置文件修改
拿到压缩包后,第一步不是导入 IDE,而是先把数据库准备好。源码里一般会带一份xxx.sql或init.sql,用 Navicat 或命令行执行:
mysql -u root -p < hospital_triage.sql导入完成后检查一下表名和数据库名,记住这个库名,后面配置里要一一对应。接着找到jdbc.properties这类配置文件,把连接参数改成你自己的环境:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/hospital_triage?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=123456这里hospital_triage是数据库名,必须和 SQL 导入时创建的库名一致;characterEncoding=utf8一定不能丢,丢了后面保存中文患者姓名就是一堆问号。如果前端表单提交后中文乱码,除了这里,还要去 Tomcat 的server.xml给 Connector 加上URIEncoding="UTF-8",这属于后端工程师的日常玄学,但每一条都有明确来源。
4.3 打包部署与登录验证
配置改完后,直接在项目根目录执行 Maven 打包:
mvn clean package -DskipTests打包成功后,把target目录下的 war 包复制到 Tomcat 的webapps目录下,再启动 Tomcat,常见命令如下:
cp target/hospital-triage.war $CATALINA_HOME/webapps/ $CATALINA_HOME/bin/startup.sh启动完后看日志,出现Server startup in [xxx] milliseconds字样基本就成了。打开浏览器访问http://localhost:8080/hospital-triage/login。先去系统管理里确认管理员账号存在,然后用账号密码登录。完整的验证路径我建议这样走一遍:登录后先进入患者管理,点添加,随便录入一个测试患者;再切到分诊管理,确认这位患者出现在候诊列表里;然后点击叫号,观察状态从“等待”变成“就诊中”;最后去科室管理新增一个科室,回到分诊管理页面,看科室下拉框是否多出新选项。这条路径走通,说明数据库、前端、后端和权限过滤全部正常,项目就真正跑起来并能用了。
5. 避坑记录:登录 404、中文乱码、叫号不刷新,这几关怎么过
5.1 登录请求明明存在,却一直返回 404
现象:Tomcat 启动正常,数据库也能连上,但输入账号密码后跳转的登录接口直接报 404,后台日志里看不到 Controller 的执行记录。
原因:这套系统登录路径要先过 Shiro 过滤链。如果shiroFilter配置里拦截规则写的是/login,而 Controller 里映射到/sys/login,路径对不上,请求根本进不到 Controller,自然 404。另一种常见情况是项目部署的访问路径带了 war 包名,比如/hospital-triage/login,前端却请求/login。
解决:先看浏览器 Network 面板,确认实际请求地址;再对比 Shiro 配置里的filterChainDefinitionMap与 Controller 的@RequestMapping,把两者统一。老项目中我会顺手核对shiro.ini里authc.loginUrl,实在不行就把 Shiro 这个过滤器的日志级别调到 DEBUG,一条条看请求被哪层拦截。
5.2 患者姓名存进数据库变成一排问号
现象:页面表单填写“张三”,刷新后列表里显示“???”,数据库里存的就是问号。
原因:通常是三层有一层编码没对上。第一层是数据库连接串少了characterEncoding=utf8;第二层是 Tomcat 的 Connector 没加URIEncoding="UTF-8",导致 GET 请求参数解析成默认的 ISO-8859-1;第三层是页面本身没有声明 UTF-8 编码。
解决:按顺序检查jdbc.url、server.xml的 Connector、以及 JSP 或 HTML 头部的<meta charset="UTF-8">。改完这三处,重启 Tomcat,把旧数据清掉重新插入一次,正常就不会再出现问号。这个坑属于“一次性解决永久受益”的典型,但新手往往会漏掉 Tomcat 那一层。
5.3 点击叫号,提示成功但列表状态不刷新
现象:按钮点击后接口返回success,后端日志显示UPDATE执行成功,但页面上的患者状态仍然停在“等待中”。
原因:不是后端逻辑错,而是前端拿状态做了本地缓存。老项目喜欢用$.ajax局部刷新,但分诊列表如果存在浏览器内存或页面变量里,接口返回后没有重新拉取列表,或者浏览器对 GET 请求做了缓存,就会出现界面和数据不一致。还有一个隐蔽原因:状态更新用的是doing,而前端判断显示的是1,枚举值没对上。
解决:先打开浏览器控制台,手动重新请求一下分诊列表接口,看返回数据是否已经变成“就诊中”。如果数据是新的,问题在前端没有重新加载列表;如果数据还是旧的,去查 UPDATE 的条件是否写错了 deptId 或 triageId。我一般会在叫号成功的回调里强制刷新一次列表,并在请求 URL 后拼一个时间戳参数,彻底避开缓存。
5.4 定时任务在开发环境报一堆异常,启动日志刷屏
现象:项目一启动,控制台就开始刷定时任务相关的异常,看起来像某个 Job 调度失败,但主流程业务不受影响。
原因:ScheduleJobService被 Spring 容器注册成定时任务,开发环境下没有对应的业务数据,或者依赖的统计表还没初始化完整,到了执行点就抛异常。这类问题不影响登录和使用,但会干扰排查其他错误时的日志阅读。
解决:在 Spring 配置文件里把定时任务开关先关掉,比如把@EnableScheduling注释掉,或把调度器的auto-startup配为false。等正式部署到生产环境再打开。以后看到定时任务刷屏,先确认执行条件是否满足,再去怀疑代码逻辑,别让日志噪音带着你找错方向。
6. 改造思路:给分诊排队加上“急诊优先叫号”的优先级字段
做分诊类项目,被问到最多的改造需求就是“急诊能不能插队”。如果直接在叫号按钮里加判断,代码会越来越乱;我一般会在分诊记录表上增加一个优先级字段,一次性解决“谁先被叫”的问题。
第一步,给triage_record表加一列:priority_level TINYINT DEFAULT 0,约定 0 表示普通,1 表示急诊。然后修改 3.1 节那条叫号查询的排序条件,把优先级放在最前面:
SELECT t.id, p.patient_name, p.gender, t.triage_no FROM triage_record t JOIN patient p ON t.patient_id = p.id WHERE t.dept_id = #{deptId} AND t.status = 'waiting' ORDER BY t.priority_level DESC, t.create_time ASC LIMIT 1;这样调整之后,急诊患者无论排队先后都会排到普通患者前面。要实现“先到先得与急诊插队平衡”,优先级字段可以设计成多档,比如 0 普通、1 急诊、2 危重,排序时直接用priority_level DESC, create_time ASC,既保证危重优先,又不会让同一优先级内先来的吃亏。
第二步,在患者管理或分诊管理页面上加一个“是否急诊”的选择项,录入患者时置为 1。前端的下拉框数据来源于科室表和分诊字段,新增字段后记得同步页面表单的提交参数,否则后端接不到priorityLevel,插队逻辑永远不生效。最后验证时,先录入两个普通患者,再录入一个急诊患者,连续点两次叫号,观察第二次被叫到的人是不是急诊患者。如果第二次仍然叫了普通患者,检查priority_level是否真的写进数据库,以及查询条件里有没有被WHERE语句过滤掉。
这套改造只动了排序维度和一个字段,影响面很小,但把排队规则从“先进先出”扩展成了“权重排序”,后面再想加老年人优先、VIP 优先,都只是加字段和调整排序权重的事。从一个字段到一套规则,是我比较推荐的演进路径——先让数据层支持新规则,再逐步把入口补充完整。改完这个需求我心里一直留着一个习惯:涉及排队、排序类需求,永远先写 SQL 看清楚结果,再去动 Java 代码和前端按钮,而不是一上来就乱改逻辑层。希望帮到你。
本文还有配套的精品资源,点击获取