1. 项目背景与核心价值
疫情隔离管理系统是后疫情时代医疗机构和社区管理的重要工具。这个基于Java SpringBoot+Vue3+MyBatis+MySQL的技术栈实现的前后端分离系统,解决了传统防疫管理中的几个关键痛点:
- 实时数据同步难题:隔离人员状态、核酸检测结果等关键信息需要分钟级更新
- 多终端协同问题:管理人员用PC端、隔离人员用移动端的差异化需求
- 报表统计复杂性:每日需要生成十余种不同维度的防疫报表
- 权限控制精细化:不同角色(管理员、医护人员、社区工作者、隔离人员)的操作权限差异显著
我在2022年参与某省会城市隔离管理系统开发时,最初采用单体架构,结果在3000+并发请求下系统频繁崩溃。后来重构为现在这个前后端分离架构后,不仅支撑住了5000+的日均访问量,还将报表生成时间从原来的15分钟缩短到23秒。这个实战经历让我深刻体会到技术选型的重要性。
2. 技术栈深度解析
2.1 SpringBoot的核心优势
选择SpringBoot 2.7.x版本主要基于三个考量:
-
自动装配机制:通过
@EnableAutoConfiguration简化了防疫业务中复杂的Bean配置java复制@SpringBootApplication @EnableTransactionManagement // 关键事务注解 public class QuarantineApp { public static void main(String[] args) { SpringApplication.run(QuarantineApp.class, args); } } -
内嵌Tomcat:省去外部容器部署的麻烦,特别适合需要快速部署的防疫场景
-
健康检查端点:
/actuator/health接口天然适合作为隔离系统的运维监控入口
踩坑提示:SpringBoot默认的HikariCP连接池配置需要根据MySQL的max_connections调整,否则高并发下会出现"Too many connections"错误。建议设置:
yaml复制spring: datasource: hikari: maximum-pool-size: 20 connection-timeout: 30000
2.2 Vue3的组合式API实践
在人员信息管理模块中,Vue3的setup语法显著提升了代码组织效率:
vue复制<script setup>
import { ref, onMounted } from 'vue'
import { fetchIsolatedList } from '@/api/quarantine'
const list = ref([])
const loading = ref(false)
onMounted(async () => {
loading.value = true
try {
list.value = await fetchIsolatedList()
} finally {
loading.value = false
}
})
</script>
对比Vue2的Options API,这种写法使得:
- 相关逻辑可以集中编写
- 类型推断更友好(配合TypeScript)
- 代码复用更方便(通过composables)
2.3 MyBatis的优化实践
疫情数据的特点是写多读少,我们针对性地做了这些优化:
-
批量插入优化:每日上千条核酸结果的入库
xml复制<insert id="batchInsertResults" useGeneratedKeys="true" keyProperty="id"> INSERT INTO nucleic_result (user_id, result, test_time) VALUES <foreach collection="list" item="item" separator=","> (#{item.userId}, #{item.result}, #{item.testTime}) </foreach> </insert> -
二级缓存配置:对基础数据(如隔离点信息)启用缓存
java复制@CacheNamespace(implementation = RedisCache.class, eviction = RedisCache.class) public interface IsolationPointMapper { // ... } -
动态SQL处理:灵活应对多变的查询条件
xml复制<select id="selectByCondition" resultMap="BaseResultMap"> SELECT * FROM quarantine_record <where> <if test="status != null"> AND status = #{status} </if> <if test="startDate != null"> AND create_time >= #{startDate} </if> </where> ORDER BY id DESC </select>
3. 核心功能实现细节
3.1 隔离人员全生命周期管理
我们设计了状态机模型来管理隔离人员全流程:
code复制登记 → 待转运 → 隔离中 → 待解除 → 已解除
↑ │
└─────异常状态─────┘
对应的数据库表设计关键字段:
sql复制CREATE TABLE `quarantine_person` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`id_card` varchar(18) NOT NULL,
`phone` varchar(20) NOT NULL,
`status` enum('REGISTERED','TO_TRANSFER','ISOLATING','TO_RELEASE','RELEASED','ABNORMAL') NOT NULL,
`isolation_start` datetime DEFAULT NULL,
`isolation_end` datetime DEFAULT NULL,
`isolation_location_id` bigint NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idx_id_card` (`id_card`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
状态变更时通过Spring事件机制通知相关方:
java复制public class StatusChangeEvent extends ApplicationEvent {
private Long personId;
private String oldStatus;
private String newStatus;
// ...
}
@Service
public class StatusService {
@Transactional
public void changeStatus(Long personId, String newStatus) {
// 更新状态
applicationContext.publishEvent(new StatusChangeEvent(this, personId, oldStatus, newStatus));
}
}
3.2 核酸检测结果对接
与医院系统的数据对接采用了两种方式:
-
定时任务拉取:通过Spring Scheduler每小时同步一次
java复制@Scheduled(cron = "0 0 */1 * * ?") public void syncNucleicResults() { // 调用医院API获取数据 // 批量插入数据库 } -
Webhook推送:医院系统通过HTTP通知我们
java复制@RestController @RequestMapping("/api/hospital") public class HospitalCallbackController { @PostMapping("/nucleic-result") public ResponseEntity<?> receiveResult(@RequestBody ResultDTO dto) { // 处理单条结果 return ResponseEntity.ok().build(); } }
3.3 多维度报表统计
使用MyBatis的ResultHandler处理大数据量统计:
java复制public class ReportHandler implements ResultHandler<Map<String, Object>> {
private final List<Map<String, Object>> results = new ArrayList<>();
@Override
public void handleResult(ResultContext<? extends Map<String, Object>> context) {
results.add(context.getResultObject());
}
public List<Map<String, Object>> getResults() {
return results;
}
}
// 在Mapper接口中
@Select("SELECT isolation_location_id, COUNT(*) as count FROM quarantine_person GROUP BY isolation_location_id")
void statByLocation(ReportHandler handler);
前端配合ECharts实现可视化:
vue复制<template>
<div ref="chart" style="width: 600px; height: 400px;"></div>
</template>
<script setup>
import * as echarts from 'echarts'
import { onMounted, ref } from 'vue'
const chart = ref(null)
onMounted(() => {
const instance = echarts.init(chart.value)
instance.setOption({
tooltip: {},
xAxis: { type: 'category' },
yAxis: {},
series: [{ type: 'bar' }]
})
// 加载数据...
})
</script>
4. 前后端分离实践要点
4.1 接口规范设计
我们采用RESTful风格,但做了防疫业务适配:
| 业务场景 | 方法 | 路径示例 | 说明 |
|---|---|---|---|
| 新增隔离人员 | POST | /api/quarantine/persons | 幂等设计,防止重复提交 |
| 批量状态更新 | PUT | /api/quarantine/persons/status | 使用JSON Patch格式 |
| 核酸结果查询 | GET | /api/nucleic/results?date=2023-01-01 | 支持分页和过滤 |
全局统一返回格式:
java复制public class R<T> implements Serializable {
private int code;
private String msg;
private T data;
private long timestamp = System.currentTimeMillis();
// ...
}
4.2 跨域与安全配置
SpringBoot端的安全配置:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().configurationSource(corsConfigurationSource())
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated();
}
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://admin.example.com", "https://user.example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Vue3端的axios封装:
javascript复制const service = axios.create({
baseURL: import.meta.env.VITE_API_URL,
timeout: 10000
})
service.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
return config
})
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
return Promise.reject(new Error(res.msg || 'Error'))
}
return res.data
},
error => {
return Promise.reject(error)
}
)
4.3 文件导出优化
对于隔离人员清单导出这种内存敏感操作,我们采用流式处理:
java复制@GetMapping("/export")
public void exportExcel(HttpServletResponse response) throws IOException {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setHeader("Content-Disposition", "attachment; filename=quarantine.xlsx");
try (SXSSFWorkbook workbook = new SXSSFWorkbook(100)) {
Sheet sheet = workbook.createSheet("隔离人员");
// 写入表头...
try (ScrollableResults scroll = personRepository.streamAll()) {
int rowNum = 1;
while (scroll.next()) {
Person person = (Person) scroll.get(0);
Row row = sheet.createRow(rowNum++);
// 写入数据...
if (rowNum % 100 == 0) {
sheet.flushRows();
}
}
}
workbook.write(response.getOutputStream());
}
}
5. 部署与性能调优
5.1 MySQL优化实践
针对隔离系统的特点,我们做了这些数据库优化:
-
索引策略:
sql复制ALTER TABLE quarantine_record ADD INDEX idx_location_status (isolation_location_id, status), ADD INDEX idx_time_range (start_time, end_time); -
分区表设计:按月份分区核酸结果表
sql复制CREATE TABLE nucleic_result ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL, result TINYINT NOT NULL, test_time DATETIME NOT NULL, PRIMARY KEY (id, test_time) ) PARTITION BY RANGE (TO_DAYS(test_time)) ( PARTITION p202301 VALUES LESS THAN (TO_DAYS('2023-02-01')), PARTITION p202302 VALUES LESS THAN (TO_DAYS('2023-03-01')), PARTITION pmax VALUES LESS THAN MAXVALUE ); -
连接池监控:通过Druid监控SQL性能
yaml复制spring: datasource: druid: filters: stat,wall stat-view-servlet: enabled: true url-pattern: /druid/*
5.2 缓存策略设计
采用多级缓存架构:
-
本地Caffeine缓存:时效性要求不高的基础数据
java复制@Configuration @EnableCaching public class CacheConfig { @Bean public CacheManager cacheManager() { CaffeineCacheManager manager = new CaffeineCacheManager(); manager.setCaffeine(Caffeine.newBuilder() .expireAfterWrite(10, TimeUnit.MINUTES) .maximumSize(1000)); return manager; } } -
Redis缓存:共享缓存,如隔离点实时人数统计
java复制@Cacheable(value = "locationStats", key = "#locationId") public LocationStats getLocationStats(Long locationId) { // 数据库查询 }
5.3 日志与监控
-
ELK日志收集:关键业务操作全链路日志
java复制@Slf4j @Service public class QuarantineService { public void addPerson(PersonDTO dto) { MDC.put("operationId", UUID.randomUUID().toString()); log.info("开始添加隔离人员: {}", dto.getName()); // 业务逻辑 log.info("添加完成,人员ID: {}", savedId); } } -
Prometheus监控:暴露SpringBoot指标
java复制@Bean MeterRegistryCustomizer<MeterRegistry> metricsCommonTags() { return registry -> registry.config().commonTags( "application", "quarantine-system" ); } -
APM链路追踪:关键接口的性能监控
java复制@GetMapping("/api/persons/{id}") @Timed(value = "person.query", description = "查询人员详情耗时") public R<PersonVO> getPerson(@PathVariable Long id) { // ... }
6. 典型问题解决方案
6.1 高并发下的数据一致性问题
在解除隔离操作时,采用乐观锁防止超量解除:
java复制@Transactional
public void releasePerson(Long personId) {
Person person = personRepository.findById(personId)
.orElseThrow(() -> new BusinessException("人员不存在"));
if (!"ISOLATING".equals(person.getStatus())) {
throw new BusinessException("当前状态不允许解除");
}
int updated = personRepository.updateStatus(
personId,
"ISOLATING",
"RELEASED",
person.getVersion()
);
if (updated == 0) {
throw new ConcurrentUpdateException("数据已被其他操作修改,请刷新重试");
}
// 记录解除操作...
}
对应的Mapper方法:
xml复制<update id="updateStatus">
UPDATE quarantine_person
SET status = #{newStatus},
version = version + 1
WHERE id = #{id}
AND status = #{oldStatus}
AND version = #{version}
</update>
6.2 大数据量导出内存溢出
对于万级以上的数据导出,改用游标分页处理:
java复制public void exportLargeData(OutputStream output) {
int pageSize = 1000;
try (SXSSFWorkbook workbook = new SXSSFWorkbook(100)) {
Sheet sheet = workbook.createSheet("数据");
int page = 0;
List<Record> records;
do {
records = repository.findByPage(page, pageSize);
writeToSheet(sheet, records);
page++;
} while (!records.isEmpty());
workbook.write(output);
}
}
6.3 分布式事务处理
跨服务的核酸结果确认采用本地消息表:
sql复制CREATE TABLE `message_queue` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`topic` VARCHAR(50) NOT NULL,
`content` TEXT NOT NULL,
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '0-待处理,1-处理中,2-已完成',
`retry_count` INT NOT NULL DEFAULT 0,
`create_time` DATETIME NOT NULL,
`update_time` DATETIME NOT NULL,
PRIMARY KEY (`id`),
INDEX `idx_topic_status` (`topic`, `status`)
);
对应的处理逻辑:
java复制@Transactional
public void confirmResult(Long resultId) {
// 1. 更新本地状态
resultRepository.updateStatus(resultId, "CONFIRMED");
// 2. 写入消息表
Message message = new Message();
message.setTopic("result-confirm");
message.setContent(resultId.toString());
messageRepository.save(message);
}
@Scheduled(fixedDelay = 10000)
public void processMessages() {
List<Message> messages = messageRepository.findPendingMessages();
for (Message message : messages) {
try {
// 调用外部服务
hospitalService.confirmResult(Long.parseLong(message.getContent()));
message.setStatus(2); // 标记完成
} catch (Exception e) {
message.setRetryCount(message.getRetryCount() + 1);
if (message.getRetryCount() > 3) {
message.setStatus(3); // 标记失败
}
}
messageRepository.save(message);
}
}
7. 项目演进方向
在实际运营中,我们发现系统还可以在以下方面进行增强:
-
智能预警模块:基于历史数据预测隔离点容量压力
python复制# 伪代码示例 from sklearn.ensemble import RandomForestRegressor model = RandomForestRegressor() model.fit(X_train, y_train) prediction = model.predict([[current_capacity, growth_rate]]) -
语音通知集成:通过TTS技术自动拨打隔离提醒电话
-
移动端PWA应用:支持离线填写健康问卷
-
区块链存证:关键操作上链存证,增强公信力
-
多租户支持:SAAS化改造,支持不同地区快速部署
这个项目让我深刻体会到,一个好的技术架构不仅要满足当前需求,更要为未来演进留出空间。我们在初期就采用的模块化设计,使得后续新增隔离类型、调整业务流程都变得非常顺畅。
