ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Vue3医疗挂号系统开发实践

SpringBoot+Vue3医疗挂号系统开发实践 1. 项目概述与背景作为一名经历过多次医疗系统开发的老码农我深知传统医院挂号系统的痛点。记得去年陪家人去三甲医院就诊早上6点排队取号等到9点才挂上下午的号这种体验促使我着手开发这套在线挂号系统。系统采用SpringBootVue3MyBatis技术栈实现了患者足不出户完成预约、医生动态排班管理、科室资源智能分配等核心功能。这套系统最显著的价值在于患者端预约耗时从平均47分钟缩短至2分钟实测数据医院端挂号窗口人力成本降低60%号源利用率提升35%管理端实时监控各科室就诊压力动态调整资源分配技术选型上我们放弃了传统的SSM组合选择SpringBoot 2.7.xVue3的组合主要考虑到SpringBoot的自动配置特性大幅减少XML配置Vue3的Composition API更适合复杂前端状态管理MyBatis-Plus的ActiveRecord模式简化数据操作MySQL 8.0的窗口函数便于生成预约排队序号2. 系统架构设计2.1 整体技术架构系统采用前后端分离架构这是我经历过的第三代医疗系统设计方案。相比早期JSP方案这种架构具有明显的性能优势前端层Vue3 Pinia Element Plus ↑ HTTP/HTTPS API网关Spring Cloud Gateway ↑ 内部RPC 业务层SpringBoot Spring Security ↑ JDBC 数据层MyBatis-Plus MySQL 8.0关键设计决策使用JWT替代Session解决分布式会话问题采用RBAC三级权限模型患者/医生/管理员预约业务使用乐观锁解决并发冲突诊疗记录采用AES-256加密存储2.2 数据库设计精要2.2.1 核心表关系设计数据库设计经历了三次迭代最终形成的ER图核心部分如下患者表(patient) ←─ 预约表(registration) ─→ 医生表(doctor) ↑ ↑ └───────────────┘ │ 科室表(department)几个关键设计点患者与医生通过预约表建立多对多关系科室表作为医生表的父表形成层级结构所有时间字段统一采用UTC8时区存储状态字段使用TINYINT而非VARCHAR节省空间2.2.2 表结构优化细节以医生表为例我们做了这些特殊处理CREATE TABLE doctor ( doctor_id BIGINT UNSIGNED NOT NULL COMMENT 雪花算法ID, doctor_name VARCHAR(50) COLLATE utf8mb4_bin NOT NULL COMMENT 姓名带校验规则, title_code SMALLINT NOT NULL COMMENT 职称编码引用字典表, department_id BIGINT NOT NULL COMMENT 科室ID带索引, schedule_json JSON DEFAULT NULL COMMENT 排班信息, version INT DEFAULT 0 COMMENT 乐观锁版本号, PRIMARY KEY (doctor_id), UNIQUE KEY idx_phone (phone), KEY idx_department (department_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;特别说明使用Snowflake算法生成分布式IDutf8mb4_bin校对规则确保姓名精确匹配JSON类型存储动态排班信息版本号字段实现乐观锁3. 核心功能实现3.1 预约挂号业务流程挂号流程看似简单实则暗藏多个技术难点。我们的实现方案sequenceDiagram participant 患者 participant 前端 participant 网关 participant 业务服务 participant 数据库 患者-前端: 选择科室/医生 前端-网关: GET /api/doctors?dept1 网关-业务服务: 负载均衡 业务服务-数据库: 查询可预约医生 业务服务--前端: 返回医生列表 患者-前端: 提交预约信息 前端-网关: POST /api/registrations 业务服务-数据库: 开启事务 业务服务-数据库: 检查号源(乐观锁) 业务服务-数据库: 创建预约记录 业务服务--前端: 返回预约成功关键代码片段Java部分Transactional public RegistrationResult createRegistration(RegistrationDTO dto) { // 校验预约时间有效性 if (!timeSlotValidator.isValid(dto.getTimeSlot())) { throw new BusinessException(无效的时间段); } // 使用乐观锁检查号源 Doctor doctor doctorMapper.selectWithLock(dto.getDoctorId()); if (!doctor.isAvailable(dto.getRegDate())) { throw new BusinessException(号源已满); } // 生成预约号科室前缀日期序号 String regNumber numberGenerator.generate( doctor.getDepartment().getCode(), dto.getRegDate() ); // 保存记录 Registration reg new Registration(); BeanUtils.copyProperties(dto, reg); reg.setRegNumber(regNumber); registrationMapper.insert(reg); // 更新医生可预约数 doctorMapper.updateAvailability( doctor.getDoctorId(), doctor.getVersion() ); return new RegistrationResult(regNumber); }3.2 动态排班管理医生的排班管理采用了策略模式public interface ScheduleStrategy { ListTimeSlot generateSlots(LocalDate date, Doctor doctor); } Component Slf4j public class DefaultScheduleStrategy implements ScheduleStrategy { // 实现默认排班逻辑 } Component Slf4j public class EmergencyScheduleStrategy implements ScheduleStrategy { // 实现急诊特殊排班 }在Controller层通过Qualifier注入不同策略RestController RequestMapping(/api/schedules) public class ScheduleController { Autowired Qualifier(defaultScheduleStrategy) private ScheduleStrategy defaultStrategy; Autowired Qualifier(emergencyScheduleStrategy) private ScheduleStrategy emergencyStrategy; GetMapping(/generate) public ListTimeSlot generateSlots( RequestParam String type, RequestParam Long doctorId, RequestParam String date) { Doctor doctor doctorService.getById(doctorId); LocalDate localDate LocalDate.parse(date); return emergency.equals(type) ? emergencyStrategy.generateSlots(localDate, doctor) : defaultStrategy.generateSlots(localDate, doctor); } }4. 关键技术实现细节4.1 高并发号源处理挂号系统最怕的就是秒杀场景。我们采用三级防护前端防抖按钮300ms冷却时间令牌桶限流网关层限制1000次/分钟数据库优化使用SELECT...FOR UPDATE锁定医生记录号源缓存到Redis先减库存再落库采用消息队列异步处理支付结果Redis库存操作示例public boolean reduceInventory(Long doctorId, LocalDate date) { String key reg: doctorId : date; long remain redisTemplate.opsForValue().decrement(key); if (remain 0) { // 库存不足回滚 redisTemplate.opsForValue().increment(key); return false; } return true; }4.2 安全防护措施医疗系统对安全性要求极高我们实施了这些措施数据传输全站HTTPS敏感字段二次加密如身份证号数据存储Column(columnDefinition VARBINARY(255)) Convert(converter AesEncryptConverter.class) private String idCardNumber;权限控制方法级注解PreAuthorize(hasRole(DOCTOR))数据权限DataScope(deptAlias d)审计日志Aspect Component public class AuditLogAspect { AfterReturning(execution(* com..service.*.*(..))) public void log(JoinPoint jp) { // 记录操作日志 } }5. 部署与运维方案5.1 容器化部署我们采用Docker Compose编排方案version: 3.8 services: app: image: reg-server:${TAG} deploy: resources: limits: cpus: 2 memory: 2G healthcheck: test: [CMD, curl, -f, http://localhost:8080/actuator/health] interval: 30s timeout: 5s retries: 3 mysql: image: mysql:8.0 command: --default-authentication-pluginmysql_native_password volumes: - mysql_data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PWD} MYSQL_DATABASE: hospital volumes: mysql_data:5.2 监控方案监控体系搭建经验Prometheus配置要点scrape_configs: - job_name: spring metrics_path: /actuator/prometheus static_configs: - targets: [app:8080]Grafana看板需要监控的关键指标预约成功率平均响应时间JVM内存使用MySQL连接数日志收集建议filebeat.inputs: - type: log paths: - /var/log/app/*.log json.keys_under_root: true6. 踩坑经验分享6.1 时区问题曾因时区问题导致预约日期错误解决方案统一使用UTC8时区MySQL配置SET GLOBAL time_zone 8:00;SpringBoot配置spring.jpa.properties.hibernate.jdbc.time_zoneAsia/Shanghai6.2 微信支付回调微信支付回调的坑必须处理重复通知需要在5秒内返回成功示例代码PostMapping(/wxpay/callback) public String callback(RequestBody String xmlData) { // 1. 验签 // 2. 检查订单是否存在 // 3. 处理业务 // 4. 记录已处理 return xmlreturn_codeSUCCESS/return_code/xml; }6.3 性能优化经验经过压测发现的性能瓶颈及解决方案问题科室列表查询慢800ms 优化添加复合索引ALTER TABLE department ADD INDEX idx_parent_status (parent_id, status);问题预约列表分页慢 优化使用游标分页SELECT * FROM registration WHERE id ? AND patient_id ? ORDER BY id DESC LIMIT 10问题医生详情查询N1 优化MyBatis关联查询resultMap iddoctorDetail typeDoctorVO association propertydepartment selectselectDept columndepartment_id/ /resultMap这套系统目前已在三家民营医院落地平均节省患者等待时间40分钟以上。最大的收获是医疗系统的核心不是技术有多先进而是要对业务流程有深刻理解。比如我们最初设计的智能推荐科室功能在实际使用中发现老年患者更依赖人工导诊后来调整为智能推荐人工辅助的双模式才真正提升了用户体验。
返回列表