1. 项目背景与核心需求
最近在整理去年参与的一个医疗健康类小程序项目,当时为了应对特殊时期的需求,我们团队开发了一套基于SSM框架的疫苗预约系统。这个系统主要解决线下排队拥挤、信息不对称等问题,通过微信小程序让用户可以随时查看疫苗库存、选择接种时段并完成线上预约。
从技术角度看,这个项目融合了后端业务逻辑处理(SSM框架)、前端小程序交互(微信生态)和数据库设计三大模块。其中最关键的是要保证高并发场景下的系统稳定性,特别是在预约开放时段可能出现的瞬时流量高峰。
2. 技术架构设计解析
2.1 整体技术栈选型
选择SSM(Spring+SpringMVC+MyBatis)作为后端框架主要基于以下考虑:
- Spring的IoC容器管理服务层组件
- SpringMVC处理微信小程序端的RESTful请求
- MyBatis灵活操作MySQL关系型数据
- 三者组合成熟度高,社区资源丰富
微信小程序端采用原生开发模式,没有选用uniapp等跨平台方案,主要考虑到:
- 需要深度使用微信原生API(如订阅消息、地理位置)
- 性能要求较高(列表页需快速加载大量接种点数据)
- 项目周期紧张(团队更熟悉原生开发)
2.2 数据库关键设计
核心表结构包括:
sql复制CREATE TABLE `vaccine` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL COMMENT '疫苗名称',
`manufacturer` varchar(100) DEFAULT NULL,
`doses_required` tinyint(4) DEFAULT '1' COMMENT '需接种剂次',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `appointment` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` varchar(32) NOT NULL COMMENT '微信openid',
`vaccine_id` int(11) NOT NULL,
`site_id` int(11) NOT NULL COMMENT '接种点ID',
`appoint_time` datetime NOT NULL,
`status` tinyint(4) DEFAULT '0' COMMENT '0-待确认 1-已预约 2-已取消',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user` (`user_id`),
KEY `idx_time` (`appoint_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
特别注意的点:
- 使用utf8mb4字符集支持emoji等特殊字符
- 为高频查询字段建立复合索引
- 时间类字段统一使用datetime类型
3. 核心功能实现细节
3.1 预约流程的并发控制
当热门接种点放号时,采用Redis分布式锁防止超卖:
java复制public boolean makeAppointment(String userId, Long vaccineId, Long siteId, Date appointTime) {
String lockKey = "lock:site:" + siteId + ":time:" + DateFormatUtils.format(appointTime, "yyyyMMddHH");
try {
// 获取分布式锁(设置3秒超时)
boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 3, TimeUnit.SECONDS);
if (!locked) {
throw new BusinessException("当前时段预约人数过多,请稍后再试");
}
// 检查库存
Integer remaining = vaccineMapper.selectRemaining(siteId, appointTime);
if (remaining <= 0) {
throw new BusinessException("该时段已约满");
}
// 创建预约记录
Appointment appt = new Appointment();
appt.setUserId(userId);
appt.setVaccineId(vaccineId);
appt.setSiteId(siteId);
appt.setAppointTime(appointTime);
appointmentMapper.insert(appt);
// 扣减库存
vaccineMapper.decrementRemaining(siteId, appointTime);
return true;
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
}
3.2 微信小程序端关键实现
首页地图展示接种点采用腾讯地图SDK:
javascript复制// pages/index/index.js
onLoad() {
this.mapCtx = wx.createMapContext('map')
wx.getLocation({
type: 'gcj02',
success: (res) => {
this.setData({ currentLocation: res })
this.loadNearbySites(res.latitude, res.longitude)
}
})
},
loadNearbySites(lat, lng) {
wx.request({
url: 'https://yourdomain.com/api/sites/nearby',
data: { lat, lng, radius: 5000 },
success: (res) => {
this.setData({
sites: res.data,
markers: res.data.map(site => ({
id: site.id,
latitude: site.lat,
longitude: site.lng,
title: site.name,
iconPath: '/assets/marker.png'
}))
})
}
})
}
4. 性能优化实践
4.1 缓存策略设计
采用多级缓存架构:
- 热点数据(如接种点信息)使用Redis缓存
- 静态资源通过CDN分发
- 小程序端本地缓存用户历史记录
缓存更新策略对比:
| 策略类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 定时过期 | 实现简单 | 实时性差 | 变更不频繁的基础数据 |
| 主动更新 | 实时性强 | 系统复杂度高 | 核心业务数据 |
| 延迟双删 | 平衡性能与一致性 | 仍有短暂不一致窗口 | 高并发写场景 |
4.2 数据库查询优化
针对预约查询的慢SQL优化示例:
sql复制-- 优化前(全表扫描)
EXPLAIN SELECT * FROM appointment
WHERE user_id = 'oX123456' AND status = 1;
-- 优化后(使用复合索引)
ALTER TABLE appointment ADD INDEX idx_user_status (user_id, status);
-- 分页查询优化
SELECT * FROM appointment
WHERE user_id = 'oX123456'
ORDER BY create_time DESC
LIMIT 10 OFFSET 0; -- 避免使用SELECT COUNT(*)
5. 安全防护措施
5.1 接口防刷策略
- 关键接口(如提交预约)实施验证码
- 用户行为分析(同一设备/IP的异常请求)
- 接口签名验证(防止参数篡改)
示例防刷配置:
properties复制# application-security.properties
security.rate.limit.enabled=true
security.rate.limit.capacity=100
security.rate.limit.time=60
security.captcha.enabled=true
5.2 数据隐私保护
- 敏感字段(如身份证号)加密存储
- 日志脱敏处理
- 遵循最小权限原则设计数据库账户
6. 项目部署方案
6.1 服务器环境配置
推荐配置(日预约量1万+):
- 应用服务器:4核8G × 2(负载均衡)
- Redis集群:哨兵模式(1主2从)
- MySQL:主从复制(读写分离)
- 带宽:10Mbps以上
6.2 监控体系搭建
必备监控项:
- 接口响应时间(P99 < 500ms)
- 数据库连接池使用率(<80%)
- Redis内存使用率(<70%)
- 小程序页面加载耗时(首屏<1s)
7. 踩坑经验总结
-
微信登录会话管理:
初期直接使用openid作为用户标识,后来发现需要结合unionid实现多端统一。建议在设计初期就考虑多平台兼容性。 -
时间戳时区问题:
数据库服务器与应用服务器时区不一致导致预约时间显示错误。统一使用UTC时间存储,前端按需转换。 -
小程序审核注意事项:
医疗类小程序需提供资质文件,且不得出现诱导分享等违规内容。首次提交被拒3次后才通过。 -
高并发场景测试:
使用JMeter模拟2000+并发请求时,发现Nginx需要调整以下参数:nginx复制worker_connections 10240; keepalive_timeout 60; client_header_buffer_size 8k;
这个项目让我深刻体会到医疗健康类系统开发的特殊性——既要保证技术方案的可靠性,又要充分考虑用户体验和隐私保护。如果现在重新设计,我会在以下方面进行改进:
- 引入Elasticsearch实现接种点的智能推荐
- 采用微服务架构拆分预约、支付、通知等模块
- 增加预约前的健康问卷功能