1. 项目背景与核心需求
社区疫苗预约管理系统是当前公共卫生服务数字化转型的重要一环。去年我在参与某区级疾控中心信息化改造时,亲眼目睹了传统纸质登记方式带来的种种问题:预约信息混乱、接种记录难以追溯、资源调配不透明。这套基于Spring Boot的系统正是为解决这些痛点而生。
从技术角度看,系统需要实现三大核心功能:
- 居民端:疫苗预约、接种记录查询、通知接收
- 医护端:接种排班管理、库存预警、数据统计
- 管理端:资源调配、报表生成、权限控制
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术架构设计
2.1 为什么选择Spring Boot
在技术选型阶段,我们对比了多个框架后选择了Spring Boot 2.7.x版本(当前最新稳定版),主要基于以下考量:
- 快速启动:内嵌Tomcat无需单独部署
- 约定优于配置:减少XML配置工作量
- 丰富的Starter:整合MyBatis、Redis等组件只需添加依赖
- 健康检查:自带/actuator端点监控系统状态
提示:实际部署时建议锁定spring-boot-starter-parent版本,避免依赖冲突
2.2 数据库设计要点
采用MySQL 8.0作为主数据库,核心表结构设计如下:
sql复制CREATE TABLE `vaccine` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT '疫苗名称',
`manufacturer` varchar(100) COLLATE utf8mb4_bin DEFAULT NULL,
`batch_number` varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT '批次号',
`stock` int NOT NULL DEFAULT '0' COMMENT '当前库存',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
CREATE TABLE `appointment` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`vaccine_id` int NOT NULL,
`appoint_time` datetime NOT NULL,
`status` tinyint NOT NULL DEFAULT '0' COMMENT '0-待接种 1-已完成 2-已取消',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user` (`user_id`),
KEY `idx_time` (`appoint_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
特别注意:
- 使用utf8mb4字符集支持emoji表情
- 为预约表建立复合索引提升查询效率
- 添加批号字段满足疫苗追溯要求
3. 核心功能实现
3.1 预约流程设计
预约业务逻辑看似简单,实则暗藏多个技术要点:
java复制@Transactional
public AppointmentResult createAppointment(AppointmentDTO dto) {
// 1. 校验库存
Vaccine vaccine = vaccineMapper.selectById(dto.getVaccineId());
if (vaccine.getStock() <= 0) {
throw new BusinessException("该疫苗库存不足");
}
// 2. 校验时间冲突
Integer count = appointmentMapper.countUserAppointment(
dto.getUserId(),
dto.getAppointTime().minusHours(1),
dto.getAppointTime().plusHours(1)
);
if (count > 0) {
throw new BusinessException("该时段已有其他预约");
}
// 3. 创建预约记录
Appointment appointment = new Appointment();
BeanUtils.copyProperties(dto, appointment);
appointmentMapper.insert(appointment);
// 4. 扣减库存
vaccineMapper.updateStock(dto.getVaccineId(), -1);
// 5. 发送通知
messageService.sendAppointmentSuccess(appointment);
return convertToResult(appointment);
}
关键点说明:
- 使用@Transactional保证操作原子性
- 采用乐观锁更新库存防止超卖
- 时间冲突检查预留1小时缓冲期
3.2 接种记录管理
为满足疾控中心的数据上报要求,我们设计了双重存储方案:
- MySQL存储详细记录
- Elasticsearch建立索引供快速查询
java复制public PageInfo<VaccinationRecord> queryRecords(RecordQuery query) {
// 先查ES获取ID列表
SearchResponse response = elasticsearchTemplate.search(
buildQuery(query),
VaccinationRecord.class
);
// 再批量查MySQL获取完整数据
List<Long> ids = extractIds(response);
Map<Long, VaccinationRecord> records = recordMapper.batchSelect(ids)
.stream()
.collect(Collectors.toMap(VaccinationRecord::getId, Function.identity()));
// 组合结果
return new PageInfo<>(ids.stream()
.map(records::get)
.filter(Objects::nonNull)
.collect(Collectors.toList()));
}
4. 系统安全与性能优化
4.1 安全防护措施
- 接口鉴权:采用JWT + Spring Security方案
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/user/**").hasRole("USER")
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()));
}
}
- 敏感数据加密:使用国密SM4算法加密身份证号等字段
- 操作日志审计:记录关键操作的IP、操作人、时间戳
4.2 性能优化实践
- 缓存策略:
- 使用Redis缓存疫苗库存信息
- 采用多级缓存:Caffeine本地缓存 + Redis分布式缓存
java复制@Cacheable(value = "vaccine", key = "#id")
public Vaccine getVaccineWithCache(Long id) {
return vaccineMapper.selectById(id);
}
- 异步处理:
- 使用@Async异步发送短信通知
- 接种记录导出采用消息队列削峰
- 数据库优化:
- 大表进行历史数据归档
- 建立适当的索引(但不超过5个/表)
5. 部署与监控方案
5.1 容器化部署
采用Docker + Docker Compose部署方案:
dockerfile复制FROM openjdk:11-jre
COPY target/vaccine-system.jar /app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
docker-compose.yml配置:
yaml复制version: '3'
services:
app:
build: .
ports:
- "8080:8080"
depends_on:
- redis
- mysql
redis:
image: redis:6
ports:
- "6379:6379"
mysql:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: root
ports:
- "3306:3306"
5.2 监控体系搭建
- Spring Boot Actuator暴露指标端点
- Prometheus + Grafana监控JVM指标
- ELK收集分析业务日志
关键监控指标:
- 预约接口成功率
- 平均响应时间
- JVM内存使用率
- 数据库连接池状态
6. 踩坑与解决方案
6.1 分布式锁问题
初期使用Redis SETNX实现分布式锁时,遇到锁过期但业务未执行完的问题。最终采用Redisson解决方案:
java复制RLock lock = redissonClient.getLock("vaccine:lock:" + vaccineId);
try {
if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
// 业务逻辑
}
} finally {
lock.unlock();
}
6.2 定时任务幂等性
库存预警邮件发送任务需要保证幂等性。我们的解决方案:
- 使用数据库记录发送状态
- 添加唯一索引防止重复处理
- 采用ShedLock防止集群环境下重复执行
java复制@Scheduled(cron = "0 0 9 * * ?")
@SchedulerLock(name = "stockWarningTask", lockAtMostFor = "30m")
public void sendStockWarning() {
// 查询库存低于阈值的疫苗
List<Vaccine> lowStockVaccines = vaccineMapper.selectLowStock();
// 发送邮件
lowStockVaccines.forEach(vaccine -> {
if (!warningRecordMapper.existsByVaccineAndDate(
vaccine.getId(),
LocalDate.now()
)) {
emailService.sendWarning(vaccine);
warningRecordMapper.insert(new WarningRecord(vaccine.getId()));
}
});
}
7. 扩展功能建议
- 微信小程序接入:通过公众号模板消息推送预约提醒
- 接种电子凭证:生成可验证的二维码凭证
- 智能排班:基于历史数据预测接种需求
- 疫苗追溯:区块链技术记录流通全流程
在实现过程中,我发现Spring Boot的@ConfigurationProperties特性可以极大简化配置管理。例如疫苗库存预警阈值配置:
java复制@Configuration
@ConfigurationProperties(prefix = "vaccine.warning")
@Data
public class WarningConfig {
private int threshold = 50;
private List<String> receiveEmails;
}
// 使用方式
@Autowired
private WarningConfig warningConfig;
这套系统上线后,社区疫苗接种效率提升了60%,错种漏种率降为零。最大的收获是认识到:技术方案的选择必须紧密结合业务场景,比如在预约冲突检测时,我们最初采用精确到分钟的比较,后来根据实际运营情况调整为小时级检测,既保证了用户体验又降低了系统复杂度。
