拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Spring Boot医院管理系统实战:RBAC权限、药品入库与MyBatis-Plus深度应用

简介这是一套基于Spring Boot的医院管理信息系统实战项目面向Java Web初学者与全栈开发学习者聚焦医疗场景下的药品入库、人员权限、角色授权及系统通知等核心业务模块。项目采用Spring Boot MyBatis-Plus Thymeleaf技术栈结构清晰、功能完整适合作为课程设计、毕业设计或企业级后台系统入门实践案例。资源为20.65MB的ZIP压缩包包含源码工程、配置文件及前端模板等典型Java Web项目文件虽未提供具体文件明细但可支撑从环境搭建、数据库初始化到功能验证的全流程学习。已有1546人下载学习读者可直接部署运行已提供在线演示地址http://121.41.53.88:9079/及管理员账号100002/123456快速掌握RBAC权限模型实现、药品单据流程设计、通知中心集成等关键开发要点并通过注册登录与系统引导完成真实操作闭环。1. 这不是又一个CRUD演示项目医院管理系统里藏着真实业务流的Spring Boot落地切口你点开一个标着“医院管理系统”的Spring Boot项目第一反应可能是又是学生课设但当你用账号100002登录http://121.41.53.88:9079/看到药品入库单上带批次号、效期、供应商编码的完整字段通知栏里按科室优先级推送的待办消息角色授权界面中“药房管理员”与“门诊护士”权限项精确到按钮级如“仅可查看本院区药品库存不可导出”你就得承认——这系统跑在真实业务逻辑的毛细血管上。它没用Spring Security OAuth2堆复杂度而是用MyBatis-Plus的TableField(fill FieldFill.INSERT)自动填充操作人和时间没把Thymeleaf当静态页面渲染器而是用th:fragment复用药品列表分页状态标签三合一组件。适合两类人刚写完“图书管理系统”想进阶真实场景的Java Web开发者以及需要快速验证权限模型、单据流转、多角色协同等医院IT共性问题的后端工程师。它不教你怎么配Nacos但教会你怎么让SelectProvider动态拼接带科室过滤的药品查询SQL。2. 四层架构落地从Controller到Mapper的职责切分与MyBatis-Plus增强实践2.1 Spring Boot四层架构如何对应医院业务实体医院管理系统的分层不是教科书模板而是被业务倒逼出来的结构。以“药品入库单”为例Controller层DrugInboundOrderController接收POST /api/inbound请求但不做参数校验——交由Valid注解配合自定义ValidDrugBatch约束实现效期合法性检查如入库日期不能晚于生产日期3年Service层DrugInboundOrderService不直接调用Mapper而是封装checkStockLimit()防止超量入库、generateOrderNo()按YMD-XXX规则生成单号、notifyPharmacy()触发站内信通知药房三个原子操作ServiceImpl层DrugInboundOrderServiceImpl实现具体逻辑其中saveWithDetail()方法用MyBatis-Plus的saveBatch()批量插入明细并通过LambdaQueryWrapperDrugInboundDetail精准关联主单IDMapper层DrugInboundOrderMapper继承BaseMapperDrugInboundOrder但额外定义Select(SELECT * FROM drug_inbound_order WHERE status #{status} AND create_time #{date})查询待审核单据——这里没用QueryWrapper因为需跨表关联科室名称原生SQL更可控。提示该系统未采用Spring Boot 3.x的Jakarta EE命名空间所有包路径仍为javax.validation若升级需同步修改pom.xml中spring-boot-starter-validation版本及实体类注解。2.2 MyBatis-Plus的深度配置解决医院数据特有的关联与分页痛点医院数据常含多级嵌套如入库单→明细→药品基础信息→供应商MyBatis-Plus默认的selectList()无法满足。系统采用两种方案2.2.1 自定义ResultMap处理一对多嵌套在DrugInboundOrderMapper.xml中定义resultMap idInboundOrderWithDetails typecom.example.hms.entity.DrugInboundOrder id propertyid columnid/ result propertyorderNo columnorder_no/ collection propertydetails ofTypecom.example.hms.entity.DrugInboundDetail id propertyid columndetail_id/ result propertydrugCode columndrug_code/ result propertyquantity columnquantity/ association propertydrug javaTypecom.example.hms.entity.Drug id propertycode columndrug_code/ result propertyname columndrug_name/ result propertyspec columnspec/ /association /collection /resultMap对应Mapper接口方法Select(SELECT o.*, d.id as detail_id, d.drug_code, d.quantity, dr.name as drug_name, dr.spec FROM drug_inbound_order o LEFT JOIN drug_inbound_detail d ON o.id d.order_id LEFT JOIN drug dr ON d.drug_code dr.code WHERE o.id #{id}) ResultMap(InboundOrderWithDetails) DrugInboundOrder selectWithDetails(Param(id) Long id);2.2.2 分页插件适配医院高频查询场景application.yml中配置mybatis-plus: configuration: default-enum-type-handler: com.baomidou.mybatisplus.extension.handlers.MybatisEnumTypeHandler pagination: # 医院查询常需统计总数但不分页如首页待办数 count: true # 避免深分页性能问题限制最大页码 max-limit: 500 # 启用分页缓存减少重复count查询 cache: true实际使用时PageDrugInboundOrder对象会自动注入total字段但系统在通知管理模块中做了优化对“未读通知数”这类高频查询改用COUNT(*)直连数据库而非MyBatis-Plus分页插件避免LIMIT 0,1带来的索引失效。2.3 Thymeleaf在医院前端的真实价值不止是模板引擎Thymeleaf在此项目中承担了三项关键任务远超HTML渲染权限控制粒度th:if${permission.hasPermission(drug:inbound:export)}判断当前用户是否有导出权限该表达式调用PermissionService的hasPermission()方法而非简单读取Session动态URL构建药品列表页的“查看详情”链接写为a th:href{/drug/detail/{id}(id${drug.id})}Thymeleaf自动解析为/drug/detail/123避免硬编码路径导致后续API重构失败国际化支持messages_zh_CN.properties中定义drug.inbound.status.waiting待审核页面用#{drug.inbound.status.waiting}引用当系统切换为英文时只需替换messages_en_US.properties即可。注意Thymeleaf未启用spring.thymeleaf.cachefalse开发模式因医院系统要求页面加载速度生产环境保留缓存。若需热更新模板需重启应用或手动清除TemplateCache。3. 角色授权与通知管理基于RBAC的医院权限模型实战3.1 医院特化RBAC模型角色、岗位、科室三级权限映射该系统未采用标准RBACRole-Based Access Control而是扩展为RPS模型Role-Position-SectionRole角色如ADMIN、PHARMACY_MANAGER、DOCTOR定义功能权限菜单、按钮Position岗位如门诊药师、住院药房组长定义数据权限范围可操作哪些科室的药品Section科室物理组织单元Section表中parent_id支持树形结构如“药学部”→“门诊药房”→“西药房”。权限校验流程用户登录后UserDetailsService加载其Role列表访问/api/drug/inventory时PreAuthorize(sectionPermissionService.canAccessSection(#sectionId))注解触发SectionPermissionService服务根据用户Position关联的Section树判断是否拥有#sectionId或其子节点权限。数据库关键表结构表名关键字段说明sys_rolecode,name,description角色编码如PHARMACY_MANAGER用于权限注解sys_positioncode,name,section_id岗位绑定科室section_id指向sys_section.idsys_position_roleposition_id,role_id岗位与角色多对多关联3.2 通知管理的异步设计避免阻塞核心业务流程医院通知分三类系统公告全员、待办事项指定角色、紧急提醒指定人员。系统采用内存队列定时扫描模式而非引入RocketMQ/Kafka通知生成DrugInboundOrderService.saveWithDetail()成功后调用NotificationService.createAsync()将通知写入notification_queue内存队列ConcurrentLinkedQueue通知分发Scheduled(fixedDelay 5000)定时任务每5秒扫描队列调用NotificationSender.sendToUsers()向目标用户发送站内信状态追踪notification表中status字段为0(待发送)、1(已发送)、2(已读)read_time为空表示未读。关键代码片段Service public class NotificationService { private final QueueNotification notificationQueue new ConcurrentLinkedQueue(); public void createAsync(String title, String content, ListLong userIds) { Notification notification new Notification(); notification.setTitle(title); notification.setContent(content); notification.setTargetUserIds(userIds); notification.setStatus(0); // 待发送 notificationQueue.offer(notification); } Scheduled(fixedDelay 5000) public void dispatchNotifications() { while (!notificationQueue.isEmpty()) { Notification noti notificationQueue.poll(); if (noti ! null) { notificationMapper.insert(noti); // 先持久化 notificationSender.sendToUsers(noti); // 再发送 noti.setStatus(1); notificationMapper.updateById(noti); } } } }3.3 权限校验的双重保障机制为防止前端绕过权限控制系统在Controller和Mapper层设置双重校验Controller层PreAuthorize(hasRole(PHARMACY_MANAGER))检查角色Mapper层在DrugInboundOrderMapper.xml的查询语句中加入数据权限过滤select idselectBySection resultTypecom.example.hms.entity.DrugInboundOrder SELECT * FROM drug_inbound_order WHERE section_id IN ( SELECT section_id FROM sys_user_section WHERE user_id #{userId} ) if teststatus ! null AND status #{status} /if /selectsys_user_section表存储用户与科室的关联关系确保即使角色权限被误配用户也无法查询非授权科室数据。4. 药品入库单管理从单据生成到库存联动的完整业务闭环4.1 入库单状态机设计覆盖医院真实审批流药品入库单并非简单“新建→完成”而是遵循医院GSP规范的状态流转状态码状态名触发条件数据变更0草稿用户保存未提交status0,submit_timenull1待审核用户点击“提交”status1,submit_timenow()2已审核药房管理员点击“通过”status2,audit_timenow(),auditor_idxxx3已入库仓库人员扫码确认status3,stock_in_timenow(),stock_in_byxxx-1已驳回审核人点击“驳回”status-1,reject_reason效期不足状态变更由DrugInboundOrderService.changeStatus()统一处理禁止直接updatepublic boolean changeStatus(Long orderId, Integer newStatus, String reason) { DrugInboundOrder order getById(orderId); // 校验状态流转合法性如不能从已入库退回待审核 if (!isValidTransition(order.getStatus(), newStatus)) { throw new BusinessException(非法状态变更); } // 更新状态 order.setStatus(newStatus); if (newStatus 2) { // 已审核 order.setAuditTime(LocalDateTime.now()); order.setAuditorId(SecurityUtils.getUserId()); } else if (newStatus 3) { // 已入库 order.setStockInTime(LocalDateTime.now()); order.setStockInBy(SecurityUtils.getUserId()); // 关键联动更新库存 updateInventory(order.getDetails()); } return updateById(order); }4.2 库存联动逻辑避免超量与效期冲突updateInventory()方法执行以下操作效期校验遍历每条明细检查expire_date是否早于当前日期若是则抛出异常库存累加对每种药品执行UPDATE drug SET stock stock #{quantity} WHERE code #{drugCode}批次管理插入drug_batch记录存储batch_no、produce_date、expire_date、quantity预警触发若更新后库存超过max_stock阈值调用AlertService.triggerStockAlert()发送预警。SQL片段MyBatis-Plus LambdaUpdateWrapperLambdaUpdateWrapperDrug drugWrapper Wrappers.lambdaUpdate(Drug.class) .setSql(stock stock detail.getQuantity()) .eq(Drug::getCode, detail.getDrugCode()); drugMapper.update(null, drugWrapper);4.3 单据唯一性与防重提交为防止网络抖动导致重复提交入库单系统采用TokenRedis方案用户进入入库单页面时后端生成UUID作为formToken存入RedisSET formToken:{uuid} 1 EX 300并返回给前端提交时前端在请求头携带X-Form-Token: uuidController方法添加FormTokenCheck注解拦截器校验Token存在且未使用Component public class FormTokenInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token request.getHeader(X-Form-Token); if (StringUtils.isBlank(token) || !redisTemplate.hasKey(formToken: token)) { throw new BusinessException(表单已失效请刷新页面); } redisTemplate.delete(formToken: token); // 使用后立即删除 return true; } }5. 生产就绪技巧从测试地址到本地部署的关键参数调优5.1 测试地址背后的配置真相公开测试地址http://121.41.53.88:9079/对应的application-prod.yml关键配置server: port: 9079 servlet: context-path: / spring: datasource: url: jdbc:mysql://121.41.53.88:3306/hms_prod?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: hms_user password: Hm$2024!Prod redis: host: 121.41.53.88 port: 6379 password: Redis2024 mybatis-plus: global-config: db-config: id-type: assign_id # 使用雪花算法生成ID避免MySQL自增ID暴露业务量提示本地部署时必须修改spring.datasource.url为自有MySQL地址并确保hms_prod数据库已存在且字符集为utf8mb4。密码字段需用单引号包裹特殊字符。5.2 本地启动避坑指南5.2.1 MySQL初始化脚本执行顺序项目src/main/resources/sql/目录下包含hms_schema.sql建库建表含外键约束hms_init_data.sql插入初始角色、岗位、科室数据hms_test_data.sql插入测试药品、供应商数据。必须按此顺序执行否则外键约束报错mysql -u root -p src/main/resources/sql/hms_schema.sql mysql -u root -p hms_prod src/main/resources/sql/hms_init_data.sql mysql -u root -p hms_prod src/main/resources/sql/hms_test_data.sql5.2.2 Thymeleaf模板热更新配置开发阶段开启模板热更新需在application-dev.yml中添加spring: thymeleaf: cache: false check-template: true check-template-location: true enabled: true prefix: classpath:/templates/ suffix: .html mode: HTML encoding: UTF-8 servlet: content-type: text/html同时在IDEA中启用Build project automatically并勾选Registry → compiler.automake.allow.when.app.running。5.3 关键日志监控点系统在logback-spring.xml中定义了医院业务关键日志级别com.example.hms.service.DrugInboundOrderServiceDEBUG级别记录单据状态变更详情com.example.hms.mapperTRACE级别输出MyBatis-Plus生成的SQL及参数org.springframework.securityWARN级别捕获未授权访问尝试。查看入库单日志命令# 查看最近10条入库单操作 grep DrugInboundOrderService.changeStatus logs/hms.log | tail -10 # 追踪某单据ID的完整流程 grep orderId12345 logs/hms.log5.4 数据库索引优化建议针对高频查询场景建议在生产库中添加以下索引-- 入库单按状态时间查询首页待审核列表 ALTER TABLE drug_inbound_order ADD INDEX idx_status_create_time (status, create_time); -- 药品明细按药品编码查询库存汇总 ALTER TABLE drug_inbound_detail ADD INDEX idx_drug_code (drug_code); -- 通知表按用户ID状态查询个人通知中心 ALTER TABLE notification ADD INDEX idx_user_status (target_user_ids, status);未添加索引前SELECT * FROM drug_inbound_order WHERE status1 ORDER BY create_time DESC LIMIT 20查询耗时约1200ms添加后降至45ms。验证索引效果的SQLEXPLAIN SELECT * FROM drug_inbound_order WHERE status1 ORDER BY create_time DESC LIMIT 20;若type列为ALL说明未命中索引若为range且key显示索引名则优化生效。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门