1. 项目背景与核心价值
2020年以来的全球公共卫生事件让疫情信息管理成为刚需。作为Java开发者,我去年为某区疾控中心开发了一套基于SpringBoot的疫情信息管理系统,今天就把这个实战项目的完整实现方案分享给大家。这个系统最终实现了日处理10万+条疫情数据的能力,在区级防疫工作中发挥了重要作用。
这套系统最核心的价值在于:
- 实现了疫情数据的全流程数字化管理(从采集到分析)
- 采用微服务架构应对高并发数据上报场景
- 通过可视化看板辅助决策
- 完整的权限控制保障数据安全
提示:系统采用SpringBoot 2.7 + MyBatis Plus + Vue.js技术栈,下文会详细拆解每个模块的实现方案
2. 系统架构设计
2.1 技术选型决策
在技术选型时我们重点考虑了以下因素:
- 开发效率:SpringBoot的自动配置和起步依赖能快速搭建项目骨架
- 性能要求:需要支撑500+并发上报请求
- 数据安全:涉及敏感健康数据,需要完善的权限控制
- 可维护性:后续区县可能自行二次开发
最终确定的架构方案:
code复制前端:Vue.js + Element UI
网关:Spring Cloud Gateway
认证:Spring Security + JWT
持久层:MyBatis Plus + Druid
缓存:Redis集群
消息队列:RabbitMQ(用于异步处理数据上报)
2.2 微服务拆分方案
将系统拆分为四个微服务:
- auth-service:认证授权中心
- data-service:核心数据服务(含疫情数据CRUD)
- report-service:数据上报服务
- visual-service:可视化服务
每个服务独立数据库,通过FeignClient进行通信。这种设计的优势在于:
- 上报服务可以单独扩容应对高峰流量
- 数据服务可保证核心业务稳定性
- 可视化复杂查询不影响事务性能
3. 核心功能实现
3.1 疫情数据上报模块
这是系统最关键的模块,代码结构如下:
code复制src/main/java/com/epidemic/report
├── controller
│ └── ReportController.java // 暴露REST接口
├── service
│ ├── impl
│ │ └── ReportServiceImpl.java
│ └── ReportService.java
├── mapper
│ └── ReportMapper.java
└── entity
└── ReportData.java
关键实现细节:
- 采用RabbitMQ实现异步上报,防止高峰期系统过载
java复制@RabbitListener(queues = "report.queue")
public void processReport(ReportData data) {
// 数据清洗和持久化
}
- 使用Hibernate Validator进行数据校验
java复制public class ReportData {
@NotBlank(message = "身份证号不能为空")
@Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx]$")
private String idCard;
@NotNull(message = "体温必填")
@DecimalMin(value = "35.0", message = "体温过低")
@DecimalMax(value = "42.0", message = "体温过高")
private BigDecimal temperature;
}
3.2 可视化分析模块
采用ECharts实现动态数据展示,核心配置:
javascript复制option = {
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: ['周一','周二','周三','周四','周五','周六','周日']
},
yAxis: {
type: 'value'
},
series: [{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
showBackground: true,
backgroundStyle: {
color: 'rgba(180, 180, 180, 0.2)'
}
}]
};
后端使用MyBatis Plus的Wrapper构建动态查询:
java复制public List<Map<String, Object>> getAreaStat(LocalDate start, LocalDate end) {
return reportMapper.selectMaps(new QueryWrapper<ReportData>()
.select("area", "count(*) as count", "avg(temperature) as avg_temp")
.between("report_time", start, end)
.groupBy("area"));
}
4. 关键问题解决方案
4.1 高并发上报优化
我们遇到了上报高峰期的性能瓶颈,通过以下方案解决:
- Redis缓存热点数据:缓存地区编码等基础数据
java复制@Cacheable(value = "areaCache", key = "#code")
public Area getByCode(String code) {
return areaMapper.selectById(code);
}
- 数据库分库分表:按地区ID取模分片
yaml复制spring:
shardingsphere:
datasource:
names: ds0,ds1
sharding:
tables:
report_data:
actual-data-nodes: ds$->{0..1}.report_data_$->{0..15}
table-strategy:
inline:
sharding-column: area_id
algorithm-expression: report_data_$->{area_id % 16}
- 限流保护:使用Guava RateLimiter
java复制private final RateLimiter limiter = RateLimiter.create(1000); // 每秒1000个请求
@PostMapping("/report")
public Result report(@RequestBody ReportData data) {
if (!limiter.tryAcquire()) {
throw new BusinessException("系统繁忙,请稍后再试");
}
// 处理逻辑
}
4.2 数据权限控制
系统需要实现多级权限管理:
- 市级管理员:查看全市数据
- 区级管理员:仅查看本区数据
- 社区管理员:仅查看本社区数据
实现方案:
- 自定义Security表达式
java复制@PreAuthorize("@dataPermission.check(authentication, #areaCode)")
@GetMapping("/data/{areaCode}")
public Result getData(@PathVariable String areaCode) {
// 查询逻辑
}
- 数据权限拦截器
java复制@Component
public class DataPermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String currentArea = SecurityUtils.getCurrentUserArea();
String requestArea = request.getParameter("area");
if (!currentArea.equals(requestArea)) {
throw new AccessDeniedException("无权访问该区域数据");
}
return true;
}
}
5. 部署与运维实践
5.1 容器化部署方案
使用Docker Compose编排服务:
yaml复制version: '3'
services:
mysql:
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: 123456
volumes:
- ./mysql/data:/var/lib/mysql
redis:
image: redis:6
ports:
- "6379:6379"
app:
build: .
ports:
- "8080:8080"
depends_on:
- mysql
- redis
5.2 监控配置
集成Spring Boot Admin进行服务监控:
java复制@Configuration
@EnableAdminServer
public class AdminConfig {
@Bean
public MailNotifier mailNotifier() {
MailNotifier notifier = new MailNotifier(Collections.emptyList());
notifier.setIgnoreChanges(Set.of("status"));
return notifier;
}
}
关键监控指标:
- JVM内存使用情况
- 接口响应时间P99
- 消息队列积压量
- 数据库连接池使用率
6. 项目经验总结
在实际开发中,有几个特别值得分享的经验:
-
MyBatis Plus的乐观锁陷阱:
在并发更新场景下,乐观锁版本号更新可能失败。我们的解决方案是:java复制int retry = 3; while (retry-- > 0) { ReportData data = getById(id); data.setStatus(newStatus); if (updateById(data)) { break; } } -
Redis缓存穿透防护:
对于不存在的地区编码查询,采用布隆过滤器+空值缓存:java复制public Area getArea(String code) { if (!bloomFilter.mightContain(code)) { return null; } Area area = redisTemplate.opsForValue().get("area:" + code); if (area == null) { area = areaMapper.selectById(code); redisTemplate.opsForValue().set("area:" + code, area != null ? area : new Area(), 5, TimeUnit.MINUTES); } return area; } -
Excel导出性能优化:
使用EasyExcel的异步导出避免OOM:java复制public void export(HttpServletResponse response) { ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()) .head(ReportData.class).build(); reportService.exportData(data -> { WriteSheet sheet = EasyExcel.writerSheet("数据").build(); excelWriter.write(data, sheet); }); excelWriter.finish(); }
这套系统从开发到上线历时3个月,期间遇到了各种技术挑战,但最终成功支撑了区域疫情管理工作。对于想要学习SpringBoot实战开发的同学,这个项目涵盖了微服务、高并发、数据安全等典型场景,具有很好的参考价值。
