1. 项目背景与核心需求
校园疫情防控系统在2020年后成为高校信息化建设的刚需。我去年为某985高校开发的这套系统,核心目标是解决三个痛点:一是人工填报效率低下且易出错;二是各部门数据孤岛现象严重;三是应急响应缺乏可视化决策支持。
传统Excel+微信群的模式存在明显缺陷:辅导员需要手动汇总上千份健康打卡表,校医院无法实时掌握发热学生分布,后勤部门难以及时调配隔离宿舍资源。我们的系统实现了:
- 学生端:每日健康打卡(定位+体温上传)
- 教师端:异常情况预警(红黄码自动识别)
- 管理端:疫情数据驾驶舱(热力图+物资调度)
关键设计指标:系统需支持5000人同时在线填报,响应时间<1秒,数据持久化可靠性99.99%
2. 技术选型与架构设计
2.1 前后端分离架构优势
选择SpringBoot+Vue的组合主要基于:
- 开发效率:SpringBoot的starter机制快速集成MyBatis+Redis
- 性能考量:Vue的虚拟DOM优化高频表单操作
- 运维成本:Jar包部署比传统War包更适应校园服务器环境
mermaid复制graph TD
A[Vue前端] -->|Axios| B[Nginx]
B -->|API网关| C[SpringBoot]
C --> D[MySQL主从]
C --> E[Redis缓存]
D --> F[MyBatis二级缓存]
2.2 数据库关键设计
MySQL表结构设计特别注意了疫情数据的时空特性:
sql复制CREATE TABLE `health_report` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`student_id` VARCHAR(20) NOT NULL COMMENT '学号',
`temperature` DECIMAL(3,1) NOT NULL COMMENT '体温',
`location` POINT NOT NULL COMMENT 'GPS坐标',
`health_code` ENUM('green','yellow','red') NOT NULL,
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
SPATIAL INDEX(`location`),
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
踩坑提醒:POINT类型需要MySQL 5.7+版本,早期版本可用两个DECIMAL字段存储经纬度
3. 核心功能实现细节
3.1 健康打卡的并发控制
采用Redis分布式锁防止重复提交:
java复制public boolean submitHealthReport(HealthReport report) {
String lockKey = "lock:health:" + report.getStudentId();
try {
// 设置10秒过期防止死锁
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(locked)) {
return healthMapper.insert(report) > 0;
}
throw new RuntimeException("操作过于频繁");
} finally {
redisTemplate.delete(lockKey);
}
}
3.2 疫情热力图生成
前端使用高德地图API+ECharts实现:
javascript复制// Vue组件中处理地理数据
async loadHeatmapData() {
const res = await this.$http.get('/api/health/locations');
this.heatmap.setData({
data: res.data.map(item => ({
lng: item.location.longitude,
lat: item.location.latitude,
count: item.temperature > 37.3 ? 10 : 1
}))
});
}
4. 性能优化实战
4.1 MyBatis二级缓存配置
在application.yml中启用缓存:
yaml复制mybatis:
configuration:
cache-enabled: true
local-cache-scope: statement
配合Redis实现分布式缓存:
java复制@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
return RedisCacheManager.builder(factory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.disableCachingNullValues())
.build();
}
}
4.2 前端长列表优化
使用vue-virtual-scroller处理千人级列表:
vue复制<template>
<RecycleScroller
class="scroller"
:items="students"
:item-size="56"
key-field="id"
v-slot="{ item }">
<div class="student-item">
{{ item.name }} - 体温: {{ item.temperature }}
</div>
</RecycleScroller>
</template>
5. 部署与监控方案
5.1 宝塔面板部署要点
- 安装JDK17+MySQL8.0+Nginx
- 配置Nginx反向代理:
nginx复制location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
}
- 使用PM2管理前端进程
5.2 健康检查接口设计
SpringBoot Actuator配置:
java复制@Endpoint(id = "pandemic")
@Component
public class PandemicHealthIndicator {
@ReadOperation
public Map<String, Object> health() {
return Map.of(
"status", "UP",
"reportCount", healthMapper.selectCountToday(),
"abnormalCount", healthMapper.selectAbnormalCount()
);
}
}
6. 安全防护措施
6.1 敏感数据脱敏
MyBatis拦截器实现:
java复制@Intercepts(@Signature(type= ResultSetHandler.class, method="handleResultSets", args={Statement.class}))
public class DataMaskInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
List<Object> results = (List<Object>) invocation.proceed();
results.forEach(obj -> {
if(obj instanceof Student) {
Student s = (Student) obj;
s.setIdCard(s.getIdCard().replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1****$2"));
}
});
return results;
}
}
6.2 接口防刷策略
Guava RateLimiter实现:
java复制@Aspect
@Component
public class RateLimitAspect {
private final Map<String, RateLimiter> limiters = new ConcurrentHashMap<>();
@Around("@annotation(rateLimit)")
public Object limit(ProceedingJoinPoint pjp, RateLimit rateLimit) throws Throwable {
String key = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest().getRemoteAddr();
RateLimiter limiter = limiters.computeIfAbsent(
key, k -> RateLimiter.create(rateLimit.value()));
if (!limiter.tryAcquire()) {
throw new RuntimeException("操作过于频繁");
}
return pjp.proceed();
}
}
7. 项目演进方向
- 物联网集成:对接智能测温门禁设备
- 预测分析:基于历史数据的传播风险建模
- 移动端适配:Uniapp打包多端应用
- 区块链存证:关键防疫数据上链
这套系统已在3所高校稳定运行12个月,日均处理健康打卡2.3万次。核心经验是:疫情防控系统必须平衡便捷性与严谨性,在简化填报流程的同时确保数据真实性。我们通过地理围栏校验+活体检测等技术手段,将虚假填报率控制在0.3%以下。
