SpringBoot+Vue宠物医院项目实战:从零到部署,手把手教你搞定RBAC权限与多角色流程

发布时间:2026/5/29 4:10:29

SpringBoot+Vue宠物医院项目实战:从零到部署,手把手教你搞定RBAC权限与多角色流程 SpringBootVue宠物医院项目实战从零到部署的工程化思维拆解宠物医疗行业近年来呈现爆发式增长根据《2023年中国宠物医疗行业白皮书》显示全国宠物医院数量已突破2万家数字化管理系统需求激增。本文将带您从工程实践角度完整构建一个具备RBAC权限控制、多角色流程的宠物医院管理系统。不同于简单的功能堆砌我们将重点剖析三个关键问题如何设计可扩展的权限架构如何优雅处理预约就诊的并发冲突前后端分离项目中如何实现高效协作1. 项目架构设计与技术选型在开始编码之前合理的架构设计能避免后期大量重构。我们采用经典的三层架构但针对宠物医院场景做了特殊优化前端Vue 3 TypeScript Element Plus后端Spring Boot 2.7 MyBatis-Plus Sa-Token数据库MySQL 8.0注意使用utf8mb4字符集存储emoji宠物名// 示例MyBatis-Plus配置类关键代码 Configuration MapperScan(com.pethospital.mapper) public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }数据库设计要点权限相关五张核心表用户表、角色表、权限表、用户角色关联表、角色权限关联表业务表设计特别注意预约表增加version字段实现乐观锁药品表保留价格变更历史诊断记录表采用JSON存储动态字段提示在宠物种类管理表中建议增加icon字段存储FontAwesome图标类名便于前端直观展示2. RBAC权限系统的深度实现RBAC基于角色的访问控制是本系统的安全核心。我们采用用户-角色-权限三级模型但做了以下增强设计2.1 动态菜单权限控制前端通过接口/api/menu/current获取当前用户菜单后端实现逻辑GetMapping(/current) public ResultListMenuVO getCurrentMenu() { // 获取当前用户ID Long userId StpUtil.getLoginIdAsLong(); // 查询用户所有角色 ListRole roles roleService.listByUserId(userId); // 查询这些角色的菜单权限注意去重和排序 return menuService.buildTree(roles); }权限注解的实际应用RestController RequestMapping(/doctor) SaCheckLogin SaCheckRole(DOCTOR) public class DoctorController { PostMapping(/diagnosis) SaCheckPermission(diagnosis:create) public Result createDiagnosis(Valid RequestBody DiagnosisDTO dto) { // 业务逻辑 } }2.2 数据权限的特殊处理宠物医院场景需要特别注意数据隔离医生只能查看自己负责的预约记录用户只能操作自己的宠物信息管理员可以查看全部数据实现方案!-- MyBatis动态SQL示例 -- select idselectAppointmentPage resultTypeAppointmentVO SELECT * FROM t_appointment where if testquery.doctorId ! null AND doctor_id #{query.doctorId} /if if testquery.customId ! null AND custom_id #{query.customId} /if !-- 数据权限过滤 -- if test!query.isAdmin AND (doctor_id #{query.currentUserId} OR custom_id #{query.currentUserId}) /if /where /select3. 核心业务模块的并发控制宠物医院的预约和订单系统面临典型的并发问题我们采用多级解决方案3.1 预约冲突的解决策略方案实现方式适用场景优缺点数据库唯一索引(doctor_id, appointment_time)建立唯一约束简单预约实现简单但体验生硬乐观锁版本号控制更新高并发场景需要前端配合重试机制分布式锁Redis Redisson集群环境性能开销较大乐观锁实现示例Transactional public Result bookAppointment(AppointmentDTO dto) { // 查询医生该时间段是否已有预约 Appointment exist appointmentMapper.selectOne(new QueryWrapperAppointment() .eq(doctor_id, dto.getDoctorId()) .eq(appointment_time, dto.getAppointmentTime()) .last(FOR UPDATE)); // 悲观锁 if (exist ! null) { return Result.error(该时间段已被预约); } // 创建新预约 Appointment entity new Appointment(); BeanUtils.copyProperties(dto, entity); entity.setVersion(1); appointmentMapper.insert(entity); // 发送微信通知 wechatService.sendTemplateMsg(dto.getDoctorId(), 新预约提醒); return Result.success(); }3.2 订单支付的防重复处理支付流程需要特别注意的异常情况用户重复点击支付按钮网络延迟导致多次回调第三方支付平台重试机制解决方案代码public Result payOrder(Long orderId) { // 使用Redis分布式锁防止重复支付 String lockKey order:pay: orderId; boolean locked redisTemplate.opsForValue().setIfAbsent(lockKey, 1, 30, TimeUnit.SECONDS); if (!locked) { return Result.error(支付处理中请勿重复操作); } try { Order order orderService.getById(orderId); if (order.getStatus() ! OrderStatus.UN_PAY) { return Result.error(订单状态异常); } // 调用支付接口 PaymentResult result paymentService.createPayment(order); // 更新订单状态 order.setStatus(OrderStatus.PAYING); order.setPayNo(result.getPayNo()); orderService.updateById(order); return Result.success(result); } finally { redisTemplate.delete(lockKey); } }4. 前后端协作的工程实践现代Web开发中前后端分离架构需要良好的协作规范。我们采用以下实践4.1 API设计规范请求/响应示例// 请求 POST /api/appointment { doctorId: 123, petId: 456, appointmentTime: 2023-08-20 14:30:00, symptoms: 呕吐,食欲不振 } // 成功响应 { code: 200, data: { id: 789, appointmentNo: APT20230820001 }, message: success } // 错误响应 { code: 400, data: null, message: 该时间段已被预约 }状态码约定200 成功400 参数错误401 未授权403 无权限500 服务器错误4.2 前端工程化技巧API集中管理// api/appointment.ts import request from /utils/request export const bookAppointment (data: AppointmentDTO) { return request({ url: /api/appointment, method: post, data }) }权限指令封装// directives/permission.js export default { mounted(el, binding) { const { value } binding const hasPermission checkPermission(value) if (!hasPermission) { el.parentNode?.removeChild(el) } } }优雅的错误处理// utils/request.js service.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || Error) // 特殊状态码处理 if (res.code 401) { router.push(/login) } return Promise.reject(new Error(res.message || Error)) } return res } )5. 部署与性能优化系统上线前的最后冲刺阶段这些优化能让你的项目更专业5.1 缓存策略设计多级缓存方案前端静态资源缓存Webpack打包hash命名接口响应缓存Spring Cache热点数据缓存Redis// 药品信息缓存示例 Cacheable(value drug, key #id) public Drug getById(Long id) { return drugMapper.selectById(id); } CacheEvict(value drug, key #drug.id) public void updateDrug(Drug drug) { drugMapper.updateById(drug); }5.2 监控与日志必备监控指标接口响应时间P99错误率JVM内存使用数据库连接池状态日志收集方案# Logback配置示例 appender nameELK classnet.logstash.logback.appender.LogstashTcpSocketAppender destinationlogstash:5044/destination encoder classnet.logstash.logback.encoder.LoggingEventCompositeJsonEncoder providers timestamp/ version/ logLevel/ loggerName/ threadName/ message/ stackTrace/ mdc/ /providers /encoder /appender5.3 容器化部署Docker Compose示例version: 3 services: backend: build: ./backend ports: - 8080:8080 environment: - SPRING_PROFILES_ACTIVEprod depends_on: - redis - mysql frontend: build: ./frontend ports: - 80:80 mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: pet1234 volumes: - mysql_data:/var/lib/mysql redis: image: redis:6 ports: - 6379:6379 volumes: mysql_data:在项目开发过程中我们发现医生排班界面需要特别优化加载性能。通过将周排班数据从原来的15次API调用合并为1次并使用虚拟滚动技术渲染长列表页面响应时间从3.2秒降低到400毫秒左右。这种针对具体业务场景的优化往往比通用性能建议更有效。

相关新闻