1. 项目背景与核心需求分析
2020年以来,校园疫情防控成为教育机构面临的重要挑战。传统的人工登记、纸质报表方式存在效率低下、数据滞后、难以追溯等问题。我去年指导某高校信息化部门时,发现他们使用Excel表格统计师生健康信息,每天需要3名工作人员花费4小时整理数据,且无法实时掌握校内人员流动情况。
这个基于SSM+Vue的监控平台正是为解决以下核心痛点而生:
- 实时健康监测:通过师生每日健康打卡,自动生成可视化报表
- 出入校管理:结合门禁系统实现扫码登记与异常预警
- 接触追踪:当出现疑似病例时,快速生成密接人员图谱
- 数据决策支持:为校领导提供各区域风险等级热力图
关键设计指标:系统需支持5000人同时在线打卡,响应时间<2秒,历史数据查询延迟<5秒。这是根据普通高校师生规模(约1-2万人)和典型并发量(早8-9点签到高峰)测算得出的基准要求。
2. 技术选型与架构设计
2.1 为什么选择SSM+Vue组合
在技术验证阶段,我们对比了三种主流方案:
| 技术栈 | 开发效率 | 性能表现 | 学习成本 | 社区支持 |
|---|---|---|---|---|
| Spring Boot+Vue | 高 | 优 | 中 | 极好 |
| SSM+Vue | 中 | 良 | 低 | 好 |
| PHP+Laravel | 高 | 中 | 低 | 一般 |
最终选择SSM(Spring+SpringMVC+MyBatis)的原因:
- 教学适配性:国内高校Java课程普遍以SSM为教学重点,学生基础较好
- 轻量级优势:相比Spring Boot更便于理解底层机制,适合毕业设计展示技术深度
- 扩展空间:保留向Spring Boot演进的可能性,如后期可改用MyBatis-Plus
Vue.js的选用则基于:
- 渐进式框架:可按需引入功能,从简单表格展示到复杂疫情热力图都能胜任
- Element UI:提供现成的表单、图表组件,加速开发进程
- 前后端分离:明确职责边界,后端同学专注业务逻辑,前端专注交互体验
2.2 系统架构详解
code复制[浏览器] ←HTTP→ [Nginx] ←RESTful→ [SpringMVC] ←→ [MyBatis] ←JDBC→ [MySQL]
↑
[Vue.js]
↓
[Element UI]
关键设计要点:
-
分层解耦:
- 表现层:Vue 2.x + Element UI(考虑学校机房电脑配置,未用Vue3)
- 控制层:SpringMVC配置全局异常处理器
@ControllerAdvice - 服务层:Spring声明式事务管理
@Transactional - 持久层:MyBatis动态SQL生成复杂查询条件
-
安全防护:
- 接口防刷:Guava RateLimiter限制60次/分钟
- 数据脱敏:自定义Jackson序列化器处理身份证号(显示为110**********1234)
- XSS过滤:重写HttpServletRequestWrapper实现参数清洗
-
性能优化:
- 热点数据缓存:Redis存储最近7天健康打卡记录
- 批量插入:MyBatis的
<foreach>实现每日晨检数据批量入库 - 连接池:HikariCP配置最大50连接(实测Tomcat默认配置在300并发时会出现等待)
3. 核心功能实现细节
3.1 健康打卡模块
前端关键代码(Vue):
javascript复制// 表单验证规则
rules: {
temperature: [
{ required: true, message: '请输入体温', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value < 35 || value > 42) {
callback(new Error('请输入35-42之间的有效值'));
} else {
callback();
}
},
trigger: 'change'
}
]
}
后端处理逻辑(SpringMVC):
java复制@PostMapping("/report")
@ResponseBody
public Result submitHealthReport(@Valid HealthReport report,
BindingResult result) {
if (result.hasErrors()) {
return Result.error(result.getFieldError().getDefaultMessage());
}
// 校验当日是否已提交
if (reportService.existsTodayReport(report.getUserId())) {
return Result.error("今日已提交,请勿重复操作");
}
// 自动标记异常状态(体温≥37.3℃)
if (report.getTemperature() >= 37.3) {
report.setStatus(HealthStatus.ABNORMAL);
warningService.triggerAlert(report);
}
reportService.saveReport(report);
return Result.ok();
}
3.2 出入校管理实现
结合QRCode.js生成动态二维码:
javascript复制// 生成包含时间戳的加密令牌
generateQrcode() {
const token = CryptoJS.AES.encrypt(
`${this.userId}_${Date.now()}`,
'campus-secret-key'
).toString();
this.qrcode = new QRCode(this.$refs.qrcode, {
text: [token](https://taotoken.net?utm_source=general),
width: 120,
height: 120
});
}
门禁端校验逻辑:
java复制public boolean validateQrcode(String encryptedToken) {
try {
String decrypted = AESUtil.decrypt(encryptedToken, "campus-secret-key");
String[] parts = decrypted.split("_");
// 令牌有效期10分钟
long timestamp = Long.parseLong(parts[1]);
return System.currentTimeMillis() - timestamp < 600000;
} catch (Exception e) {
return false;
}
}
3.3 疫情可视化大屏
使用ECharts实现的三层嵌套环形图:
javascript复制// 风险等级分布图
initRiskChart() {
const chart = echarts.init(this.$refs.riskChart);
chart.setOption({
tooltip: { trigger: 'item' },
series: [{
type: 'pie',
radius: ['40%', '70%'],
data: [
{ value: 1543, name: '低风险' },
{ value: 267, name: '中风险' },
{ value: 12, name: '高风险' }
],
label: { formatter: '{b}: {c}人 ({d}%)' }
}]
});
}
4. 开发中的典型问题与解决方案
4.1 MyBatis一对多查询性能优化
初始方案直接使用<collection>嵌套查询:
xml复制<resultMap id="userWithReports" type="User">
<collection property="reports" select="selectReportsByUserId" column="id"/>
</resultMap>
导致产生N+1查询问题(5000用户=>5001次SQL)。优化后改用联合查询+内存分组:
java复制public List<User> findUsersWithReports() {
List<User> users = userMapper.selectAll();
List<HealthReport> reports = reportMapper.selectRecentWeekReports();
Map<Long, List<HealthReport>> reportMap = reports.stream()
.collect(Collectors.groupingBy(HealthReport::getUserId));
users.forEach(user ->
user.setReports(reportMap.getOrDefault(user.getId(), emptyList())));
return users;
}
4.2 Vue组件通信陷阱
在实现"院系-班级"二级联动时,错误使用$parent访问父组件:
javascript复制// 错误做法
this.$parent.selectedDepartment = newVal;
改为采用Vuex状态管理:
javascript复制// store/modules/filter.js
const actions = {
updateDepartment({ commit }, payload) {
commit('SET_DEPARTMENT', payload);
commit('RESET_CLASS'); // 自动重置班级选择
}
}
// 组件内调用
this.$store.dispatch('filter/updateDepartment', newVal);
4.3 时间处理时区问题
发现凌晨时段打卡记录显示日期错误,原因是:
- 前端
new Date()使用浏览器时区 - MySQL默认使用系统时区
统一采用UTC时间处理:
java复制// 后端接口
@JsonFormat(pattern="yyyy-MM-dd", timezone="GMT+8")
private Date reportDate;
// 前端moment.js转换
moment.utc(serverTime).local().format('YYYY-MM-DD HH:mm')
5. 部署与上线注意事项
5.1 多环境配置方案
使用Maven Profile实现环境隔离:
xml复制<profiles>
<profile>
<id>dev</id>
<properties>
<jdbc.url>jdbc:mysql://localhost:3306/campus_dev</jdbc.url>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>prod</id>
<properties>
<jdbc.url>jdbc:mysql://prod-db:3306/campus_prod</jdbc.url>
</properties>
</profile>
</profiles>
5.2 Nginx关键配置
解决Vue路由刷新404问题:
nginx复制location / {
try_files $uri $uri/ /index.html;
}
# 接口反向代理
location /api {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
}
5.3 压力测试结果
使用JMeter模拟300并发时的性能数据:
| 接口 | 平均响应时间 | 错误率 | TPS |
|---|---|---|---|
| /api/report/submit | 1.2s | 0% | 285 |
| /api/stat/daily | 3.5s | 2% | 45 |
| /api/contact/trace | 4.8s | 5% | 32 |
优化措施:
- 为
/api/contact/trace添加@Cacheable缓存 - 对统计查询接口实施预聚合策略(每日凌晨计算昨日统计)
- 调整Tomcat线程池配置:
properties复制server.tomcat.max-threads=200 server.tomcat.accept-count=50
6. 项目扩展方向建议
- 移动端适配:通过Uniapp打包成微信小程序,实现扫码签到功能
- 物联网集成:对接红外测温设备,自动上传体温数据
- 智能预警:基于历史数据训练简单ML模型,预测潜在风险区域
- 区块链存证:使用Hyperledger Fabric存储关键操作日志,确保数据不可篡改
我在实际部署中发现,学校网络环境往往存在跨校区访问延迟问题。建议在方案设计阶段就考虑多机房部署,可采用Nginx+Keepalived实现高可用架构。另外,健康数据属于敏感信息,务必在服务器配置SSL证书,强制HTTPS访问,并在数据库层面实施字段级加密。
