1. 项目概述
疫情防控管理系统是当前社会急需的数字化解决方案,这套基于Java SpringBoot+Vue3+MyBatis的技术栈实现的前后端分离系统,为各类机构提供了高效的疫情数据管理能力。我在实际开发中发现,这种技术组合不仅能满足高并发场景下的性能需求,还能通过模块化设计快速响应政策变化。
这个系统最核心的价值在于将繁琐的疫情数据采集、统计和分析工作数字化。通过前后端分离架构,后端专注于业务逻辑和数据处理,前端则提供灵活多样的数据展示方式。MySQL数据库的稳定性和SpringBoot的快速开发特性,使得系统可以在短时间内部署上线。
2. 技术架构解析
2.1 后端技术选型
SpringBoot 2.7.x作为后端框架,提供了完善的生态支持和约定优于配置的开发体验。特别值得一提的是,我们选择了以下关键依赖:
- Spring Security:处理权限认证
- MyBatis-Plus:增强型ORM框架
- Hutool:Java工具包
- Lombok:简化实体类编写
java复制// 典型控制器示例
@RestController
@RequestMapping("/api/epidemic")
@RequiredArgsConstructor
public class EpidemicController {
private final EpidemicService epidemicService;
@GetMapping("/statistics")
public Result<StatisticsVO> getStatistics(@RequestParam String regionCode) {
return Result.success(epidemicService.getStatistics(regionCode));
}
}
2.2 前端技术栈
Vue3的组合式API相比选项式API更适合复杂业务场景。我们采用的技术方案包括:
- Vite构建工具:极快的启动和热更新
- Element Plus:UI组件库
- Axios:HTTP客户端
- Vue Router:路由管理
- Pinia:状态管理
javascript复制// 疫情数据查询示例
import { ref } from 'vue'
import { getEpidemicData } from '@/api/epidemic'
const loading = ref(false)
const tableData = ref([])
const fetchData = async (params) => {
loading.value = true
try {
const res = await getEpidemicData(params)
tableData.value = res.data
} finally {
loading.value = false
}
}
2.3 数据库设计
MySQL 8.0的关系型数据库特性非常适合疫情数据的存储和查询。核心表结构设计考虑到了数据一致性和查询效率:
sql复制CREATE TABLE `epidemic_record` (
`id` bigint NOT NULL AUTO_INCREMENT,
`region_code` varchar(20) NOT NULL COMMENT '行政区划代码',
`confirm_count` int DEFAULT '0' COMMENT '确诊人数',
`asymptomatic_count` int DEFAULT '0' COMMENT '无症状人数',
`cure_count` int DEFAULT '0' COMMENT '治愈人数',
`death_count` int DEFAULT '0' COMMENT '死亡人数',
`record_date` date NOT NULL COMMENT '记录日期',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_region_date` (`region_code`,`record_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 核心功能实现
3.1 疫情数据采集模块
数据采集需要考虑多种数据来源的兼容性。我们设计了灵活的适配器模式来处理不同格式的输入数据:
java复制public interface DataAdapter {
EpidemicData convert(String rawData);
}
@Service
public class JsonDataAdapter implements DataAdapter {
@Override
public EpidemicData convert(String rawData) {
// JSON解析实现
}
}
@Service
public class ExcelDataAdapter implements DataAdapter {
@Override
public EpidemicData convert(String rawData) {
// Excel解析实现
}
}
重要提示:数据采集环节必须加入数据校验机制,特别是对行政区划代码和日期的合法性检查,避免脏数据进入系统。
3.2 统计分析模块
统计分析采用了定时任务+缓存优化的策略。Spring的@Scheduled注解配合Redis缓存大幅提升了性能:
java复制@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
public void updateStatisticsCache() {
List<Region> regions = regionService.listAll();
regions.forEach(region -> {
StatisticsVO stats = epidemicMapper.selectStatsByRegion(region.getCode());
redisTemplate.opsForValue().set(
"stats:" + region.getCode(),
stats,
24, TimeUnit.HOURS);
});
}
3.3 权限管理系统
基于RBAC模型的权限控制,通过Spring Security实现接口级别的访问控制:
java复制@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/epidemic/export").hasAuthority('epidemic:export')
.anyRequest().authenticated();
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
4. 前后端协作实践
4.1 接口规范设计
我们采用RESTful风格设计API,并制定了严格的接口文档规范。使用Swagger UI自动生成文档:
java复制@Operation(summary = "获取疫情统计数据")
@GetMapping("/statistics")
public Result<StatisticsVO> getStatistics(
@Parameter(description = "行政区划代码") @RequestParam String regionCode,
@Parameter(description = "开始日期") @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date start,
@Parameter(description = "结束日期") @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") Date end) {
// 业务逻辑
}
前端调用时需要注意的错误处理策略:
javascript复制const handleError = (error) => {
if (error.response) {
switch (error.response.status) {
case 401:
router.push('/login')
break
case 403:
ElMessage.error('无权限访问')
break
default:
ElMessage.error(error.response.data.message || '请求失败')
}
} else {
ElMessage.error('网络错误')
}
}
4.2 文件导出功能
疫情数据导出是高频需求,我们实现了Excel和PDF两种格式的导出:
java复制@GetMapping("/export/excel")
public void exportExcel(HttpServletResponse response,
@RequestParam String regionCode) throws IOException {
String filename = URLEncoder.encode("疫情数据.xlsx", "UTF-8");
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment;filename=" + filename);
List<EpidemicData> dataList = epidemicService.listByRegion(regionCode);
EasyExcel.write(response.getOutputStream(), EpidemicData.class)
.sheet("疫情数据")
.doWrite(dataList);
}
前端调用时需要特别注意处理blob类型响应:
javascript复制const exportExcel = async () => {
try {
const res = await axios.get('/api/epidemic/export/excel', {
params: { regionCode },
responseType: 'blob'
})
const url = URL.createObjectURL(new Blob([res.data]))
const link = document.createElement('a')
link.href = url
link.download = '疫情数据.xlsx'
document.body.appendChild(link)
link.click()
URL.revokeObjectURL(url)
link.remove()
} catch (error) {
handleError(error)
}
}
5. 部署与性能优化
5.1 服务器部署方案
我们推荐使用Docker Compose进行容器化部署,docker-compose.yml配置示例如下:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
volumes:
- mysql_data:/var/lib/mysql
ports:
- "3306:3306"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/epidemic?useSSL=false
frontend:
build: ./frontend
ports:
- "80:80"
depends_on:
- backend
volumes:
mysql_data:
5.2 性能调优经验
数据库查询优化是性能关键点,我们总结了几条重要经验:
- 为高频查询添加合适的索引:
sql复制ALTER TABLE epidemic_record ADD INDEX idx_region_date (region_code, record_date);
- 使用MyBatis二级缓存配合Redis:
java复制@CacheNamespace(implementation = RedisCache.class, eviction = RedisCache.class)
public interface EpidemicMapper {
@Select("SELECT * FROM epidemic_record WHERE region_code = #{code}")
@Cache(eviction = @CacheEvict(key = "'region:'+#code"))
List<EpidemicRecord> selectByRegion(String code);
}
- 前端采用虚拟滚动优化大数据量展示:
vue复制<template>
<el-table-v2
:columns="columns"
:data="tableData"
:width="800"
:height="400"
:row-height="50"
fixed
/>
</template>
6. 常见问题与解决方案
6.1 跨域问题处理
前后端分离项目必须妥善处理跨域问题。我们的解决方案:
后端配置:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*")
.maxAge(3600);
}
}
前端开发环境代理配置(vite.config.js):
javascript复制server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
6.2 数据一致性问题
疫情数据要求高度准确性,我们采用以下策略保证数据一致:
- 数据库事务管理:
java复制@Transactional
public void batchUpdate(List<EpidemicData> dataList) {
dataList.forEach(data -> {
epidemicMapper.updateByRegionAndDate(data);
});
}
- 分布式锁防止重复提交:
java复制public void submitData(EpidemicData data) {
String lockKey = "submit:" + data.getRegionCode() + ":" + data.getRecordDate();
try {
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 5, TimeUnit.MINUTES);
if (!locked) {
throw new BusinessException("请勿重复提交");
}
// 业务处理
} finally {
redisTemplate.delete(lockKey);
}
}
6.3 高并发场景优化
针对疫情数据上报高峰期的优化方案:
- 采用消息队列削峰:
java复制@RabbitListener(queues = "epidemic.queue")
public void handleMessage(EpidemicData data) {
epidemicService.processData(data);
}
public void submitData(EpidemicData data) {
rabbitTemplate.convertAndSend("epidemic.exchange", "key", data);
}
- 前端防抖处理频繁提交:
javascript复制import { debounce } from 'lodash-es'
const submitForm = debounce(async () => {
try {
await submitApi(formData.value)
ElMessage.success('提交成功')
} catch (error) {
handleError(error)
}
}, 1000)
7. 项目扩展方向
在实际开发中,我们发现系统可以进一步扩展以下功能:
- 移动端适配:基于Vue3的响应式特性,可以轻松扩展移动端界面
- 大数据分析:集成Spark或Flink进行疫情趋势预测
- 可视化大屏:使用ECharts实现更丰富的数据展示
- 微信小程序:通过同一套API支持多端访问
技术选型上,如果项目规模继续扩大,可以考虑:
- 后端微服务化:Spring Cloud Alibaba
- 前端微前端:qiankun框架
- 数据库分库分表:ShardingSphere
java复制// 微服务架构示例
@FeignClient(name = "epidemic-service")
public interface EpidemicFeignClient {
@GetMapping("/internal/stats")
StatisticsVO getStatsInternal(@RequestParam String regionCode);
}
在权限管理方面,可以进一步细化到数据权限级别:
java复制@DataPermission({
@DataColumn(key = "regionCode", operator = Operator.IN, value = "#user.regions")
})
@Select("SELECT * FROM epidemic_record")
List<EpidemicRecord> selectAll();