1. 项目背景与核心价值
医疗资源分配不均和挂号难问题长期困扰着就医群众。传统窗口挂号模式存在三大痛点:患者需现场排队耗费时间、号源信息不透明导致黄牛倒号、医院管理效率低下。这套基于SpringBoot+Vue的线上挂号系统正是为解决这些问题而设计。
技术选型上采用前后端分离架构,后端使用SpringBoot 2.7.x构建RESTful API服务,前端采用Vue 3组合式API开发管理界面,数据库选用MySQL 8.0作为持久化存储。这种技术组合在保证系统稳定性的同时,具备以下优势:
- 开发效率:SpringBoot的自动配置机制减少70%的XML配置工作量
- 性能表现:Vue的虚拟DOM技术使页面响应速度提升40%
- 数据安全:采用JWT+RBAC实现细粒度的权限控制
2. 系统架构设计
2.1 技术栈全景图
code复制[前端层]
Vue 3 + Vue Router + Pinia + Element Plus
Axios拦截器处理HTTP请求
ECharts实现数据可视化
[网关层]
Nginx反向代理
负载均衡配置
静态资源缓存
[业务层]
SpringBoot 2.7.18
MyBatis-Plus 3.5.3
Hutool工具集
Lombok
[数据层]
MySQL 8.0主从集群
Redis 7缓存热点数据
Elasticsearch全文检索
2.2 核心数据库设计
患者信息表采用垂直分表设计,将基础信息与敏感信息分离:
sql复制CREATE TABLE `patient_basic` (
`patient_id` BIGINT PRIMARY KEY COMMENT '雪花算法ID',
`patient_name` VARCHAR(50) NOT NULL COMMENT '姓名',
`gender` CHAR(1) NOT NULL COMMENT 'M/F',
`birth_date` DATE NOT NULL COMMENT '出生日期',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `patient_secure` (
`patient_id` BIGINT PRIMARY KEY,
`id_card` VARCHAR(18) NOT NULL COMMENT 'AES加密存储',
`phone` VARCHAR(20) NOT NULL COMMENT '加密存储',
`medical_history` TEXT COMMENT '就诊史'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 关键功能实现
3.1 号源动态分配算法
采用时间片轮转+弹性配额机制解决号源分配难题:
java复制public List<ScheduleVO> generateSchedules(Long doctorId, LocalDate startDate, int days) {
// 获取医生基础出诊偏好
DoctorConfig config = configService.getByDoctor(doctorId);
// 生成初始时间表
List<ScheduleVO> schedules = new ArrayList<>();
for (int i = 0; i < days; i++) {
LocalDate currentDate = startDate.plusDays(i);
// 智能避开节假日
if (holidayService.isHoliday(currentDate)) continue;
// 动态调整号源数量
int baseCount = config.getBaseAppointments();
int dynamicCount = calculateDynamicCount(doctorId, currentDate);
schedules.add(new ScheduleVO(
doctorId,
currentDate,
config.getStartTime(),
config.getEndTime(),
baseCount + dynamicCount
));
}
return schedules;
}
3.2 高并发预约处理
采用Redis+Lua脚本保证在高并发下的数据一致性:
lua复制-- KEYS[1] 排班ID
-- ARGV[1] 患者ID
local remaining = tonumber(redis.call('GET', 'schedule:'..KEYS[1]..':remaining'))
if remaining and remaining > 0 then
redis.call('DECR', 'schedule:'..KEYS[1]..':remaining')
redis.call('HSET', 'appointment:'..KEYS[1], ARGV[1], os.time())
return 1
else
return 0
end
4. 部署与运维方案
4.1 容器化部署
使用Docker Compose编排服务:
yaml复制version: '3.8'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PWD}
MYSQL_DATABASE: hospital
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/conf:/etc/mysql/conf.d
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "80:80"
4.2 监控配置
SpringBoot Actuator关键指标监控:
properties复制# application-prod.properties
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
management.metrics.export.prometheus.enabled=true
management.metrics.tags.application=${spring.application.name}
5. 典型问题解决方案
5.1 患者重复预约问题
解决方案:
- 数据库唯一索引:
sql复制ALTER TABLE appointment_order
ADD UNIQUE INDEX idx_patient_schedule (patient_id, schedule_id);
- 分布式锁控制:
java复制public boolean makeAppointment(Long patientId, Long scheduleId) {
String lockKey = "appoint:lock:" + patientId;
try {
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(locked)) {
// 业务处理
}
} finally {
redisTemplate.delete(lockKey);
}
}
5.2 医生排班冲突检测
使用时间区间算法检测排班重叠:
java复制public boolean checkScheduleConflict(DoctorSchedule newSchedule) {
return scheduleMapper.exists(new QueryWrapper<DoctorSchedule>()
.eq("doctor_id", newSchedule.getDoctorId())
.eq("schedule_date", newSchedule.getScheduleDate())
.and(wrapper -> wrapper
.between("start_time", newSchedule.getStartTime(), newSchedule.getEndTime())
.or()
.between("end_time", newSchedule.getStartTime(), newSchedule.getEndTime())
)
);
}
6. 性能优化实践
6.1 查询优化方案
- 患者历史记录分页查询优化:
java复制public Page<AppointmentVO> queryHistory(Long patientId, PageParam param) {
return appointmentMapper.selectPage(new Page<>(param.getPage(), param.getSize()),
new QueryWrapper<Appointment>()
.eq("patient_id", patientId)
.select("order_id", "schedule_id", "order_time", "order_status")
.orderByDesc("order_time")
);
}
- 添加复合索引:
sql复制ALTER TABLE appointment_order
ADD INDEX idx_patient_status_time (patient_id, order_status, order_time);
6.2 缓存策略设计
采用多级缓存架构:
- 热点数据缓存:使用Redis Hash存储医生排班信息
- 本地缓存:Caffeine缓存科室列表等不变数据
- 查询缓存:MyBatis二级缓存开启统计查询
缓存更新策略:
java复制@CacheEvict(value = "schedules", key = "#doctorId")
public void updateSchedule(DoctorSchedule schedule) {
scheduleMapper.updateById(schedule);
// 异步更新ES索引
elasticsearchTemplate.save(schedule);
}
7. 安全防护措施
7.1 敏感数据加密
采用国密SM4算法加密患者身份证号:
java复制public class IdCardEncryptor {
private static final String KEY = "${encrypt.key}";
public String encrypt(String idCard) {
return SmUtil.sm4(KEY.getBytes()).encryptHex(idCard);
}
public String decrypt(String cipherText) {
return StrUtil.utf8Str(SmUtil.sm4(KEY.getBytes()).decrypt(cipherText));
}
}
7.2 接口防刷策略
- 滑动窗口限流:
java复制@RateLimiter(value = 10, key = "#patientId")
@PostMapping("/appointment")
public R makeAppointment(@RequestBody AppointmentDTO dto) {
// 业务逻辑
}
- 验证码二次校验:
java复制public boolean verifyCaptcha(String key, String code) {
String redisCode = redisTemplate.opsForValue().get("captcha:" + key);
redisTemplate.delete("captcha:" + key);
return StringUtils.equalsIgnoreCase(redisCode, code);
}
8. 扩展开发建议
8.1 微服务化改造
建议拆分以下服务:
- 用户中心服务:处理所有用户相关业务
- 预约服务:专注挂号业务流程
- 排班服务:管理医生出诊计划
- 支付服务:处理缴费业务
服务通信方案:
java复制@FeignClient(name = "schedule-service")
public interface ScheduleServiceClient {
@GetMapping("/api/schedules/{id}")
ScheduleDTO getSchedule(@PathVariable Long id);
}
8.2 智能推荐扩展
基于就诊历史推荐科室:
python复制# 使用协同过滤算法
def recommend_departments(patient_id):
history = get_visit_history(patient_id)
similar_patients = find_similar_patients(history)
return aggregate_recommendations(similar_patients)
