1. 疫苗预约系统架构设计解析
这个基于SpringBoot+Vue的疫苗预约管理系统,本质上是一个典型的三层架构Web应用。我在实际医疗信息化项目中多次采用类似架构,其核心价值在于将传统线下预约流程数字化,解决以下几个痛点:
- 资源分配不均:通过系统实时展示各接种点库存和预约量,避免某些接种点过度拥挤而其他接种点资源闲置
- 信息透明度低:公众可随时查询疫苗类型、批次和有效期,建立接种信心
- 管理效率低下:自动生成统计报表,替代人工Excel记录方式
1.1 技术选型考量
后端选择SpringBoot的三大理由:
- 快速启动:内嵌Tomcat和自动配置让项目能在5分钟内跑起来
- 企业级支持:Spring Security和Actuator满足医疗系统对安全和监控的高要求
- 生态完整:MyBatis-Plus极大简化了疫苗库存这类CRUD密集型业务的开发
前端选用Vue.js的关键因素:
- 响应式数据绑定:预约状态变更时能实时更新UI,无需手动DOM操作
- 组件化开发:将预约日历、疫苗卡片等复用UI抽离为独立组件
- 渐进式框架:初期简单页面可快速实现,后期可平滑升级为SPA
提示:医疗系统特别要注意选择长期维护的技术栈。SpringBoot和Vue的LTS版本通常有3-5年维护期,适合这类需要稳定运行的系统。
1.2 数据库设计精要
疫苗系统的数据库设计体现了几个医疗信息化特点:
-
强审计要求:
- 所有表都有create_time字段
- 疫苗批次记录包含完整厂商信息
- 预约记录不可物理删除(用status标记状态)
-
数据一致性保障:
sql复制-- 预约时检查库存的典型事务处理
START TRANSACTION;
SELECT vax_stock FROM vaccine WHERE vax_id = 1001 FOR UPDATE;
UPDATE vaccine SET vax_stock = vax_stock - 1 WHERE vax_id = 1001;
INSERT INTO reservation(user_id, vax_id, site_id) VALUES(123, 1001, 5);
COMMIT;
- 关键业务字段:
- 疫苗有效期(expiry_date):系统应自动拦截过期疫苗的预约
- 接种点容量(daily_capacity):控制每日预约上限
- 地理位置编码(geo_code):用于就近推荐接种点
2. 核心功能实现细节
2.1 预约业务流程实现
完整的预约流程涉及前后端多个模块协同:
- 疫苗列表获取:
java复制// 后端Controller示例
@GetMapping("/vaccines")
public Result<List<Vaccine>> getAvailableVaccines(
@RequestParam(required = false) String type,
@RequestParam(required = false) String location) {
QueryWrapper<Vaccine> query = new QueryWrapper<>();
query.gt("vax_stock", 0)
.gt("expiry_date", LocalDate.now());
if (StringUtils.isNotBlank(type)) {
query.eq("vax_type", type);
}
return Result.success(vaccineService.list(query));
}
- 前端预约表单处理:
javascript复制// Vue组件方法示例
submitReservation() {
this.$axios.post('/api/reservations', {
vaxId: this.selectedVaccine.id,
siteId: this.selectedSite.id,
date: this.selectedDate
}).then(response => {
this.$message.success('预约成功!');
this.qrCode = response.data.qrCode;
}).catch(error => {
this.$handleError(error);
});
}
2.2 库存预警机制
医疗系统的库存管理需要实时监控:
java复制// 定时任务检查库存和有效期
@Scheduled(cron = "0 0 9 * * ?") // 每天9点执行
public void checkVaccineInventory() {
List<Vaccine> lowStock = vaccineService.list(
new QueryWrapper<Vaccine>()
.lt("vax_stock", 10)
.gt("expiry_date", LocalDate.now())
);
List<Vaccine> expiringSoon = vaccineService.list(
new QueryWrapper<Vaccine>()
.between("expiry_date",
LocalDate.now(),
LocalDate.now().plusWeeks(2))
);
// 发送预警邮件/消息
alertService.sendLowStockAlert(lowStock);
alertService.sendExpiryAlert(expiringSoon);
}
2.3 多角色权限控制
系统采用RBAC模型实现权限分离:
| 角色 | 权限范围 | 前端路由守卫配置 |
|---|---|---|
| 公众用户 | 查询/预约疫苗 | meta: { requiresAuth: true } |
| 医护人员 | 确认接种/修改接种状态 | meta: { role: 'staff' } |
| 系统管理员 | 疫苗入库/接种点管理/用户管理 | meta: { role: 'admin' } |
JWT token中会包含角色信息:
java复制// 登录时生成带角色的token
public String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getUsername())
.claim("roles", user.getRoles())
.setExpiration(new Date(System.currentTimeMillis() + 3600_000))
.signWith(SignatureAlgorithm.HS512, secretKey)
.compact();
}
3. 典型问题排查实录
3.1 高并发预约冲突
现象:秒杀式预约时出现超卖
解决方案:
- 数据库层面加悲观锁:
java复制@Transactional
public boolean makeReservation(Long vaccineId) {
Vaccine v = vaccineMapper.selectByIdForUpdate(vaccineId);
if (v.getStock() <= 0) {
return false;
}
vaccineMapper.updateStock(vaccineId, v.getStock() - 1);
// 创建预约记录...
}
- 引入Redis分布式锁:
java复制public boolean reserveWithLock(Long vaccineId) {
String lockKey = "lock:vaccine:" + vaccineId;
try {
// 尝试获取锁,有效期10秒
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(locked)) {
// 执行库存扣减
return doReserve(vaccineId);
}
return false;
} finally {
redisTemplate.delete(lockKey);
}
}
3.2 二维码核销延迟
现象:接种点扫描二维码后状态更新不及时
优化方案:
- 前端采用WebSocket实时通信:
javascript复制// 建立WebSocket连接
const socket = new WebSocket(`wss://${location.host}/api/ws`);
socket.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'RESERVATION_UPDATE') {
this.reservationStatus = msg.status;
}
};
- 后端使用Spring STOMP支持:
java复制@Controller
public class NotificationController {
@MessageMapping("/reservation/update")
@SendToUser("/queue/updates")
public UpdateMessage handleStatusUpdate(UpdateRequest request) {
reservationService.updateStatus(request);
return new UpdateMessage(request.getReservationId(), "UPDATED");
}
}
4. 性能优化实践
4.1 数据库查询优化
疫苗列表页的典型优化手段:
- 添加复合索引:
sql复制CREATE INDEX idx_vaccine_search ON vaccine(vax_type, expiry_date, vax_stock);
- 使用MyBatis-Plus的QueryWrapper避免N+1查询:
java复制public Page<VaccineVO> getVaccinePage(PageParam param) {
return vaccineMapper.selectPage(new Page<>(param.getPage(), param.getSize()),
new QueryWrapper<Vaccine>()
.select("vax_id", "vax_type", "producer", "expiry_date")
.gt("expiry_date", LocalDate.now())
.orderByAsc("expiry_date")
).convert(this::toVO);
}
4.2 前端渲染优化
对于接种点地图展示:
- 虚拟滚动处理大量标记点:
vue复制<template>
<div class="map-container" @scroll="handleScroll">
<div class="marker-container" :style="containerStyle">
<div
v-for="site in visibleSites"
:key="site.id"
class="marker"
:style="getMarkerStyle(site)"
></div>
</div>
</div>
</template>
<script>
export default {
computed: {
visibleSites() {
return this.sites.slice(this.startIndex, this.endIndex);
}
}
}
</script>
- 使用Intersection Observer懒加载图片:
javascript复制const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
});
document.querySelectorAll('.lazy-img').forEach(img => {
observer.observe(img);
});
5. 安全防护措施
5.1 防脚本攻击
- 输入过滤:
java复制@PostMapping("/vaccines")
public Result addVaccine(@Valid @RequestBody VaccineDTO dto) {
// 自动校验@NotBlank等注解
return Result.success(vaccineService.save(dto));
}
- XSS防护:
javascript复制// 前端显示时自动转义
function safeHtml(str) {
return str.replace(/</g, '<').replace(/>/g, '>');
}
5.2 数据脱敏处理
用户敏感信息处理:
java复制public UserVO toVO(User user) {
UserVO vo = new UserVO();
BeanUtils.copyProperties(user, vo);
// 身份证号脱敏
vo.setIdCard(DesensitizedUtil.idCardNum(user.getIdCard()));
// 手机号脱敏
vo.setPhone(DesensitizedUtil.mobilePhone(user.getPhone()));
return vo;
}
6. 部署实践建议
6.1 容器化部署
推荐Docker Compose方案:
yaml复制version: '3'
services:
app:
image: openjdk:11-jre
ports:
- "8080:8080"
volumes:
- ./app.jar:/app.jar
command: ["java", "-jar", "/app.jar"]
depends_on:
- mysql
- redis
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6
ports:
- "6379:6379"
volumes:
mysql_data:
6.2 监控配置
SpringBoot Actuator关键配置:
properties复制# application-prod.properties
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=always
management.metrics.export.prometheus.enabled=true
配合Grafana监控看板:
- JVM内存使用
- 接口响应时间P99
- 数据库连接池使用率
- 疫苗预约成功率
7. 扩展方向建议
在实际项目中,可以考虑以下增强功能:
-
智能推荐引擎:
- 基于用户位置推荐最近接种点
- 根据年龄自动筛选适用疫苗类型
- 接种时间预测(基于历史等待时间)
-
接种证明区块链存证:
java复制public class BlockchainService {
public String createVaccineCert(VaccineCert cert) {
String txHash = blockchainClient.sendTransaction(
"0xVaccineContract",
"createCert",
cert.getUserHash(),
cert.getVaccineHash(),
cert.getTimestamp()
);
return txHash;
}
}
- 多语言支持:
javascript复制// 前端i18n配置
const messages = {
en: {
vaccine: {
type: "Vaccine Type",
inStock: "{count} in stock"
}
},
zh: {
vaccine: {
type: "疫苗类型",
inStock: "剩余{count}剂"
}
}
};
这个系统我在多个医疗机构落地时发现,最大的挑战不是技术实现,而是如何平衡不同接种点的资源分配。建议在实际部署时,根据区域人口密度动态调整接种点的每日配额,并保留10%的应急预约容量用于特殊需求