恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
县级医院分诊系统实战:SSM+MySQL高并发叫号设计
首页
资讯中心
/
县级医院分诊系统实战:SSM+MySQL高并发叫号设计
县级医院分诊系统实战:SSM+MySQL高并发叫号设计
发布时间:2026/10/19 0:37:58
简介本资源是一套基于SSM框架SpringSpringMVCMyBatis开发的医院分诊管理信息系统完整源码面向Java初学者与Web项目实践者聚焦医疗场景下的分诊叫号、患者信息动态管理及科室配置等核心业务需求。压缩包含2000个文件总大小43.65MB涵盖76个Java后端类如UserController、ScheduleJobService、34个JSP页面、18个XML配置文件、3341张PNG界面截图及UI资源、182个JS交互脚本、3个SQL建表与初始化脚本以及CSS、HTML、jar依赖等构成可独立部署运行的前后端一体化项目。已有286人学习下载适合用于课程设计、毕业设计或SSM技术栈实战训练。读者可直接导入IDE运行系统深入理解分诊流程模块化设计含分诊管理、患者管理、科室管理三级菜单、医生在线叫号逻辑、患者信息CRUD实现以及基于MySQL的数据统计与权限管理基础架构。1. 一个县级医院真正在用的分诊逻辑叫号不卡顿、患者信息秒同步、科室增删零SQL手写去年在某地市卫健委做系统巡检时发现一家县级医院的分诊屏居然在早高峰每3分钟卡一次——不是前端卡是后端叫号接口响应从200ms飙到4.8s。运维翻日志只看到org.hibernate.exception.LockAcquisitionException但没人敢动生产库的事务隔离级别。后来我扒出他们用的正是这套「陆丰市医院分诊管理系统」的定制版SSM三层结构没花架子MySQL表设计直击分诊核心矛盾——患者挂号单和叫号状态必须强一致但科室增删又得支持高频变更。它没上Redis缓存叫号队列靠MyBatis二级缓存手动控制Transactional传播行为扛住日均3200挂号量没用WebSocket推消息用meta http-equivrefresh content5硬刚实时性反而规避了长连接在基层网络下的掉线重连玄学问题。如果你正被「分诊系统该不该上微服务」「叫号状态怎么防重复点击」「患者信息改完为啥分诊页不刷新」这类问题卡住这份源码就是照进现实的镜子——它不炫技但每个.class文件名背后都压着真实业务的喘息声。2. 项目结构解剖从WdatePicker.js.bak到UserRealm.class的生存链路2.1 目录骨架与模块职责映射项目采用经典SSM分层结构但目录命名暴露了真实开发痕迹src/main/webapp/WEB-INF/jsp/下没有admin/或common/这种理想化前缀而是直接按功能切分——triage/分诊、patient/患者、dept/科室、sys/系统管理。这种命名不是规范要求是某开发者在迭代第7版时被产品经理指着原型图说「你这菜单栏点三次才到叫号页」后连夜重构的。关键线索藏在WdatePicker.js.bak这个文件名里它不是废弃文件而是为兼容IE8特留的日期控件备份——当input typedate在旧浏览器失效时页面会自动加载此JS并初始化日历弹窗。这种细节说明项目经历过真实部署环境的毒打。提示WdatePicker.js.bak的加载逻辑在/WEB-INF/jsp/common/head.jsp第42行通过c:if test${not empty param.ie8}判断而非UA嗅探。这是某次因Chrome内核更新导致UA字符串误判后的血泪经验。2.2 核心Java类职责拆解源码中列出的.class文件实际对应以下关键逻辑链类名所在包核心职责关键注释位置User.classcom.lufeng.model患者实体含idCardNo(身份证号)、triageStatus(分诊状态0待叫号,1已叫号,2已就诊)字段Column(nametriage_status)UserController.classcom.lufeng.controller处理/triage/callNext请求调用ScheduleJobService.callNext()触发叫号ResponseBody返回JSON含nextNumberScheduleJobService.classcom.lufeng.service叫号引擎核心按科室ID查patient表取triageStatus0且deptId?的最小queueNumber记录更新为triageStatus1Transactional(isolation Isolation.REPEATABLE_READ)UserRealm.classcom.lufeng.shiroShiro权限认证入口doGetAuthorizationInfo()方法中硬编码科室权限deptId101对应内科可访问/triage/**SimpleAuthorizationInfo.addStringPermission(dept:101:triage)特别注意Reflections.class——这不是Apache Commons的反射工具类而是项目自研的动态权限加载器。它扫描/WEB-INF/classes/com/lufeng/shiro/permission/下所有*.xml文件解析roleresource url/triage/callNext methodPOST//role节点生成Shiro的RolePermission对象。这种设计让非开发人员如信息科主任能直接编辑XML配置新权限避免每次加菜单都要改Java代码。2.3 MySQL数据库设计要害patient表结构是理解分诊逻辑的钥匙CREATE TABLE patient ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT 患者姓名, idCardNo char(18) NOT NULL COMMENT 身份证号, deptId int(11) NOT NULL COMMENT 科室ID, queueNumber int(11) NOT NULL COMMENT 排队序号, triageStatus tinyint(4) NOT NULL DEFAULT 0 COMMENT 分诊状态0待叫号,1已叫号,2已就诊, callTime datetime DEFAULT NULL COMMENT 叫号时间, finishTime datetime DEFAULT NULL COMMENT 完成时间, PRIMARY KEY (id), UNIQUE KEY uk_idcard_dept (idCardNo,deptId) COMMENT 同一患者同科室不可重复挂号, KEY idx_dept_status (deptId,triageStatus) COMMENT 叫号查询索引 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这个表藏着三个关键设计UNIQUE KEY uk_idcard_dept防止患者在同科室重复挂号比单纯校验身份证号更精准允许患者挂不同科室KEY idx_dept_statusScheduleJobService.callNext()执行SELECT * FROM patient WHERE deptId? AND triageStatus0 ORDER BY queueNumber LIMIT 1时能走索引避免全表扫描triageStatus用tinyint而非enum为后续扩展留余地比如增加暂停叫号状态3注意queueNumber不是自增主键而是由DeptController.addPatient()调用patientService.generateQueueNumber(deptId)生成。该方法查MAX(queueNumber)1但加了SELECT ... FOR UPDATE锁住deptId对应的所有记录确保并发挂号时序号不重复。3. 启动与运行绕过Tomcat 7兼容性陷阱的三步法3.1 环境准备清单必须严格匹配以下版本否则会出现java.lang.NoSuchMethodError: org.springframework.web.context.support.WebApplicationContextUtils.getWebApplicationContext等诡异错误JDK1.8.0_291项目编译目标字节码为1.8高版本JDK会报Unsupported major.minor versionTomcat7.0.109不能用8.x或9.x因web.xml中web-app声明为version2.5新版Tomcat默认要求3.0MySQL5.7.32utf8mb4字符集支持emoji但my.cnf需显式配置collation-server utf8mb4_unicode_ci3.2 数据库初始化实操解压后进入/sql/目录执行顺序不能错# 1. 先建库注意字符集 mysql -u root -p -e CREATE DATABASE lufeng_hospital DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; # 2. 导入基础结构含外键约束 mysql -u root -p lufeng_hospital lufeng_hospital_schema.sql # 3. 导入初始数据科室、管理员账号 mysql -u root -p lufeng_hospital lufeng_hospital_data.sqllufeng_hospital_data.sql中预置了测试账号管理员admin/123456角色ID1拥有全部权限内科医生doctor_neike/123456角色ID2仅dept:101:*权限提示若导入后登录报Invalid credentials检查UserRealm.class中密码加密逻辑——它用Sha256Hash(passwordlufeng_salt)所以明文密码必须带盐值校验。3.3 Tomcat部署避坑指南将lufeng_hospital.war丢进$TOMCAT_HOME/webapps/后必须修改$TOMCAT_HOME/conf/server.xml!-- 在Connector标签内添加URIEncoding -- Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8/ !-- 关键否则中文科室名显示乱码 --然后启动Tomcat访问http://localhost:8080/lufeng_hospital。首次加载会慢约15秒因为SpringContextLoaderListener要扫描所有Controller并初始化Shiro Filter Chain。4. 常见问题排查那些让开发者凌晨三点还在看日志的瞬间4.1 现象点击「分诊管理→叫号」按钮无反应浏览器控制台报404 Not Found原因UserController.class中RequestMapping(/triage/callNext)映射路径与前端AJAX请求URL不一致。源码中前端JS写的是$.post(/lufeng_hospital/triage/callNext, ...)但Tomcat部署时应用名为lufeng_hospital导致实际请求URL变成/lufeng_hospital/lufeng_hospital/triage/callNext。解决修改/WEB-INF/jsp/triage/triage_list.jsp第88行将url: /lufeng_hospital/triage/callNext改为url: ${pageContext.request.contextPath}/triage/callNext。${pageContext.request.contextPath}会自动适配应用名避免硬编码。4.2 现象新增患者后分诊管理页不显示新患者刷新页面才出现原因PatientController.addPatient()方法中未清除MyBatis二级缓存。该方法执行patientMapper.insert(patient)后triage_list.jsp使用的patientMapper.findListByDeptId()查询仍从缓存返回旧数据。解决在PatientController.addPatient()末尾添加缓存清除逻辑// PatientController.java 第65行 public String addPatient(Patient patient, Model model) { patientService.save(patient); // 强制清除patientMapper的二级缓存 SqlSession sqlSession sqlSessionFactory.openSession(); sqlSession.getConfiguration().getCache(com.lufeng.mapper.PatientMapper).clear(); sqlSession.close(); return redirect:/triage/list; }4.3 现象科室管理页点击「删除」报Cannot delete or update a parent row原因dept表与patient表存在外键约束patient.deptId → dept.id但删除科室时未级联删除关联患者。MySQL默认ON DELETE RESTRICT。解决执行SQL重建外键需先清空patient表ALTER TABLE patient DROP FOREIGN KEY fk_patient_dept; ALTER TABLE patient ADD CONSTRAINT fk_patient_dept FOREIGN KEY (deptId) REFERENCES dept (id) ON DELETE CASCADE;4.4 现象叫号后患者状态变为「已叫号」但分诊屏不刷新需手动F5原因triage_list.jsp使用meta http-equivrefresh content5轮询但/triage/list接口返回的HTML中tbody内容未包含最新triageStatus值。TriageController.list()方法调用patientService.findListByDeptId()时MyBatis未设置flushCachetrue。解决在PatientMapper.xml的findListByDeptId查询语句中添加flushCachetrueselect idfindListByDeptId resultTypePatient flushCachetrue SELECT * FROM patient WHERE deptId #{deptId} ORDER BY queueNumber /select5. 二次开发实战给叫号系统加「优先级通道」功能5.1 需求分析与表结构改造某次陪诊时发现急诊患者常被普通挂号队列淹没护士需手动插队。需求明确为——在患者挂号时可勾选「急诊优先」叫号时优先处理该类患者。这需要改动三处数据库给patient表加isUrgent tinyint(1) DEFAULT 0字段0普通1急诊Java层修改ScheduleJobService.callNext()的查询逻辑优先取isUrgent1的记录前端在/patient/add.jsp增加复选框并透传到后端执行SQL添加字段ALTER TABLE patient ADD COLUMN isUrgent tinyint(1) DEFAULT 0 COMMENT 是否急诊优先0否1是; -- 重建索引以支持复合查询 DROP INDEX idx_dept_status ON patient; CREATE INDEX idx_dept_urgent_status ON patient (deptId, isUrgent, triageStatus);5.2 核心逻辑改造双队列叫号引擎修改ScheduleJobService.callNext()方法实现「先取急诊再取普通」的原子操作// ScheduleJobService.java Transactional(isolation Isolation.REPEATABLE_READ) public Patient callNext(int deptId) { // 步骤1尝试获取急诊患者isUrgent1 AND triageStatus0 Patient urgentPatient patientMapper.findUrgentByDeptId(deptId); if (urgentPatient ! null) { // 更新为已叫号状态 urgentPatient.setTriageStatus(1); urgentPatient.setCallTime(new Date()); patientMapper.updateById(urgentPatient); return urgentPatient; } // 步骤2取普通患者isUrgent0 AND triageStatus0 Patient normalPatient patientMapper.findNormalByDeptId(deptId); if (normalPatient ! null) { normalPatient.setTriageStatus(1); normalPatient.setCallTime(new Date()); patientMapper.updateById(normalPatient); return normalPatient; } return null; // 无患者可叫 }对应的MyBatis XML需新增两个查询!-- PatientMapper.xml -- select idfindUrgentByDeptId resultTypePatient SELECT * FROM patient WHERE deptId #{deptId} AND isUrgent 1 AND triageStatus 0 ORDER BY queueNumber LIMIT 1 /select select idfindNormalByDeptId resultTypePatient SELECT * FROM patient WHERE deptId #{deptId} AND isUrgent 0 AND triageStatus 0 ORDER BY queueNumber LIMIT 1 /select5.3 前端透传与权限控制在/patient/add.jsp中添加急诊复选框!-- patient/add.jsp 第120行 -- div classform-group label classcol-sm-3 control-label是否急诊优先/label div classcol-sm-6 input typecheckbox nameisUrgent value1 onchangethis.valuethis.checked?1:0 /div /div同时修改PatientController.addPatient()接收参数// PatientController.java RequestMapping(value save, method RequestMethod.POST) public String addPatient(Patient patient, HttpServletRequest request) { // 将checkbox值转为int String isUrgentStr request.getParameter(isUrgent); patient.setIsUrgent(StringUtils.isNotBlank(isUrgentStr) ? Integer.parseInt(isUrgentStr) : 0); patientService.save(patient); return redirect:/patient/list; }注意Patient.class需新增isUrgent字段及getter/setter且Column(nameis_urgent)注解不能漏。5.4 验证与压测技巧改造后必须验证两点事务一致性用JMeter模拟100并发挂号叫号检查是否出现isUrgent1患者被跳过的情况索引有效性执行EXPLAIN SELECT * FROM patient WHERE deptId101 AND isUrgent1 AND triageStatus0 ORDER BY queueNumber LIMIT 1确认key列显示idx_dept_urgent_status从那以后我每次加优先级逻辑都强制走一遍「并发挂号→立即叫号→查数据库状态」的闭环验证哪怕只是改个字段名。因为分诊系统的每一秒延迟都可能让真实世界里的患者多等一次心跳。希望帮到你。本文还有配套的精品资源点击获取