1. 项目概述
这个线上医院挂号系统采用前后端分离架构,后端使用SpringBoot+MyBatis+MySQL技术栈,前端基于Vue.js框架开发。系统实现了医院挂号的核心业务流程,包括科室管理、医生排班、患者预约、订单支付等功能模块。
我在实际开发过程中发现,医疗类系统对数据一致性和事务完整性要求极高。比如当多个患者同时预约同一位医生的最后一个号源时,系统必须保证不会出现超卖情况。这需要在数据库设计和业务逻辑层做特殊处理。
2. 技术架构解析
2.1 后端技术栈选型
SpringBoot作为基础框架,主要考虑了其快速启动和自动配置特性。医疗系统通常需要快速迭代开发,SpringBoot的starter机制可以大幅减少依赖配置时间。我们特别使用了:
- spring-boot-starter-web:提供RESTful API支持
- spring-boot-starter-aop:用于日志记录和权限校验
- spring-boot-starter-data-redis:缓存科室和医生信息
MyBatis作为ORM框架,相比Hibernate更适合医疗系统这种需要精细控制SQL的场景。我们在mapper.xml中大量使用了动态SQL来处理复杂的查询条件,比如:
xml复制<select id="findAvailableDoctors" resultType="Doctor">
SELECT * FROM doctor
WHERE department_id = #{deptId}
<if test="date != null">
AND schedule_date = #{date}
</if>
ORDER BY rating DESC
</select>
2.2 前端技术方案
Vue 3.x版本提供了更好的TypeScript支持,这对医疗系统这种业务逻辑复杂的项目特别重要。核心功能实现包括:
- 使用Vue Router实现路由权限控制
- Pinia状态管理处理全局数据
- Element Plus组件库快速搭建UI
特别值得注意的是预约日历组件的实现,我们基于Day.js开发了一个高性能的排班展示组件,支持:
- 按科室筛选医生
- 显示剩余号源数量
- 实时更新时间段可用状态
3. 核心功能实现
3.1 挂号业务流程设计
完整的挂号流程包含以下关键步骤:
- 科室选择:采用树形结构展示科室层级,使用Redis缓存提高查询性能
- 医生排班查询:基于MyBatis的动态SQL构建复杂查询
- 时段选择:实现分钟级的时间段划分和并发控制
- 订单创建:使用数据库乐观锁防止超卖
- 支付集成:对接第三方支付平台的SDK
挂号业务的核心SQL如下,特别注意FOR UPDATE的使用:
sql复制BEGIN TRANSACTION;
SELECT remain_count FROM schedule WHERE id = #{scheduleId} FOR UPDATE;
UPDATE schedule SET remain_count = remain_count - 1 WHERE id = #{scheduleId} AND remain_count > 0;
COMMIT;
3.2 高并发场景处理
医院挂号系统在放号时段经常面临高并发压力,我们采用多级缓存策略:
- 本地缓存:使用Caffeine缓存静态科室数据
- 分布式缓存:Redis存储医生排班信息
- 数据库优化:MySQL配置连接池和读写分离
对于特别热门的专家号,我们实现了预约排队机制:
java复制public AppointmentResult makeAppointment(AppointmentRequest request) {
// 获取分布式锁
String lockKey = "lock:schedule:" + request.getScheduleId();
try {
boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
if (!locked) {
return AppointmentResult.builder().code(ErrorCode.TOO_MANY_REQUESTS).build();
}
// 核心业务逻辑
return appointmentService.processAppointment(request);
} finally {
redisTemplate.delete(lockKey);
}
}
4. 安全与合规设计
4.1 数据安全措施
医疗系统对数据安全有严格要求,我们实施了以下保护措施:
- 敏感数据加密:患者身份证号、手机号等采用AES加密存储
- SQL注入防护:严格使用MyBatis参数绑定,禁止${}拼接SQL
- 日志脱敏:使用自定义Converter处理日志中的敏感信息
4.2 权限控制方案
基于Spring Security实现了RBAC权限模型:
- 定义5种角色:患者、医生、科室管理员、医院管理员、系统管理员
- 接口级别权限控制:
java复制@PreAuthorize("hasRole('DOCTOR') || hasRole('DEPARTMENT_ADMIN')")
@GetMapping("/schedules")
public List<Schedule> getSchedules(...) {
// ...
}
- 数据权限过滤:在Service层自动添加部门过滤条件
5. 系统部署实践
5.1 后端部署要点
SpringBoot应用部署时特别注意:
- 使用Actuator进行健康监控
- 配置JVM参数:
bash复制java -Xms512m -Xmx1024m -XX:MaxMetaspaceSize=256m -jar hospital.jar
- Nginx反向代理配置:
nginx复制location /api/ {
proxy_pass http://localhost:8080;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
}
5.2 前端部署优化
Vue项目生产环境构建建议:
- 开启Gzip压缩:
javascript复制// vue.config.js
module.exports = {
configureWebpack: {
plugins: [
new CompressionPlugin({
algorithm: 'gzip'
})
]
}
}
- 配置合理的缓存策略:
nginx复制location / {
try_files $uri $uri/ /index.html;
expires 1y;
add_header Cache-Control "public";
}
6. 典型问题排查
6.1 数据库连接池耗尽
现象:系统运行一段时间后出现获取连接超时。
解决方案:
- 检查连接泄漏:
java复制@Bean
public DataSource dataSource() {
HikariDataSource ds = new HikariDataSource();
ds.setLeakDetectionThreshold(30000); // 30秒
return ds;
}
- 合理设置连接池参数:
properties复制spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.idle-timeout=30000
6.2 Vue路由刷新404
问题:生产环境刷新非首页路由返回404。
解决方法:配置Nginx fallback到index.html:
nginx复制location / {
try_files $uri $uri/ /index.html;
}
7. 性能优化实践
7.1 数据库优化
针对挂号系统特有的查询模式:
- 建立复合索引:
sql复制CREATE INDEX idx_doctor_schedule ON schedule (doctor_id, schedule_date, status);
- 优化大表查询:
java复制@Select("SELECT * FROM registration WHERE patient_id = #{patientId} LIMIT 1000")
@Options(fetchSize = 100)
List<Registration> findRecentRegistrations(@Param("patientId") String patientId);
7.2 前端性能提升
- 组件懒加载:
javascript复制const DoctorList = () => import('./components/DoctorList.vue')
-
API请求合并:使用GraphQL替代部分REST接口
-
虚拟滚动处理长列表:
html复制<el-table-v2
:columns="columns"
:data="doctors"
:height="600"
:width="1000"
:row-height="60"
/>
8. 扩展功能设计
8.1 智能推荐系统
基于历史数据实现医生推荐算法:
- 协同过滤算法实现相似患者推荐
- 内容基于规则推荐:
python复制# 伪代码
def recommend_doctors(patient):
if patient.age > 60:
return filter(doctors, specialty='老年病科')
elif patient.symptoms.contains('儿科'):
return filter(doctors, specialty='儿科')
8.2 消息通知集成
多渠道通知实现:
- 短信通知:对接阿里云短信服务
- 微信模板消息:通过公众号API发送
- 站内信:使用WebSocket实时推送
消息发送服务设计:
java复制public interface NotificationService {
void sendAppointmentSuccess(Appointment appointment);
void sendPaymentReminder(Appointment appointment);
void sendCancelNotification(Appointment appointment);
}
// 实现类使用@Async实现异步发送
9. 监控与运维
9.1 应用监控配置
- Prometheus监控指标暴露:
java复制@Bean
MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags("application", "hospital-system");
}
- 关键业务指标监控:
- 每分钟挂号成功数
- 支付成功率
- 接口响应时间P99
9.2 日志收集方案
ELK日志系统集成:
- Logstash配置示例:
ruby复制input {
file {
path => "/var/log/hospital/*.log"
codec => json
}
}
- 在logback-spring.xml中配置JSON格式输出
10. 项目演进方向
基于实际运营数据,后续可以重点优化:
- 预约取消率分析:通过数据分析找出高频取消时段和科室
- 医生接诊效率统计:计算各医生平均接诊时长
- 智能分诊功能:基于症状的自动科室推荐
技术债偿还计划:
- 逐步将部分服务迁移到Spring Cloud
- 实现基于Kubernetes的容器化部署
- 引入Service Mesh管理微服务通信
在开发医疗系统过程中,我深刻体会到事务完整性的重要性。一个挂号操作可能涉及5张以上的关联表更新,任何一步失败都必须完整回滚。我们最终采用了Spring的@Transactional注解配合自定义业务异常处理,确保在任何异常情况下数据都能保持一致状态。
