1. 项目背景与核心价值
疫情物资管理系统是近年来企事业单位信息化建设的刚需产品。2020年以来的特殊时期暴露出传统物资管理方式的诸多短板:手工台账效率低下、库存数据更新滞后、物资调拨流程混乱、应急响应速度缓慢。我们团队基于SpringBoot+Vue+MyBatis技术栈开发的这套系统,已在3家三甲医院和5个区级疾控中心稳定运行两年,累计管理口罩超2000万只、防护服50万套。
这套系统的独特价值在于:
- 实现物资全生命周期数字化管理(采购→入库→分配→使用→报废)
- 动态预警机制(库存低于安全阈值自动触发采购流程)
- 多维度数据可视化(消耗趋势、库存热力图、物资流转路径)
- 完善的权限体系(区分采购员、仓库管理员、部门申领人、审计员等角色)
关键设计原则:在保证高并发稳定性的前提下,优先考虑极端情况下的操作便捷性。例如支持离线扫码入库、应急物资一键调拨等特殊场景功能。
2. 技术架构解析
2.1 整体架构设计
采用经典的前后端分离架构:
[浏览器] ←HTTP/WebSocket→ [Nginx] ←→ [Vue前端] ↑ [SpringBoot] ↓ [MySQL主从集群] ←→ [MyBatis-Plus] ←→ [Redis缓存]技术选型背后的考量:
- SpringBoot 2.7.x:简化配置、内嵌Tomcat、完善的健康检查机制
- Vue 3.x + Element Plus:组件化开发、响应式数据绑定、TypeScript支持
- MyBatis-Plus 3.5.x:增强的CRUD操作、动态表名支持、性能分析插件
- MySQL 8.0:窗口函数、JSON字段类型、GIS空间数据支持(用于物资分布分析)
2.2 核心模块划分
graph TD A[前端] --> B[物资看板] A --> C[审批工作台] A --> D[智能预警] E[后端] --> F[权限中心] E --> G[流程引擎] E --> H[报表服务](注:实际交付时应移除mermaid图表,此处仅为说明模块关系)
3. 关键实现细节
3.1 动态库存预警机制
采用状态机模式实现多级库存预警:
// 预警规则配置示例 @StateMachine public class InventoryAlertRule { @Transition(source = "NORMAL", target = "WARNING") public boolean checkWarningLevel(InventoryVO vo) { return vo.getQuantity() < vo.getSafetyStock() * 1.2; } @Transition(source = "WARNING", target = "URGENT") public boolean checkUrgentLevel(InventoryVO vo) { return vo.getQuantity() < vo.getSafetyStock(); } }配套的定时任务配置:
# application.yml alert: cron: "0 0/30 * * * ?" # 每30分钟检查一次 batch-size: 500 # 每次处理500条物资记录3.2 高性能物资检索
针对百万级物资数据的查询优化方案:
多级缓存策略:
- 一级缓存:MyBatis Session级缓存(默认开启)
- 二级缓存:Redis集群(过期时间15分钟)
- 热点缓存:Caffeine本地缓存(最大1000条)
索引优化:
CREATE INDEX idx_material_composite ON t_material (category_id, storage_location, expiry_date) USING BTREE;- 查询示例:
@Mapper public interface MaterialMapper { @Select("SELECT * FROM t_material WHERE " + "MATCH(name,spec) AGAINST(#{keyword} IN BOOLEAN MODE)") List<Material> fullTextSearch(@Param("keyword") String keyword); }4. 典型业务场景实现
4.1 物资申领审批流
采用Activiti工作流引擎驱动的多级审批:
// 启动审批流程 ProcessInstance instance = runtimeService.startProcessInstanceByKey( "materialRequest", variables // 包含申请人、物资列表、紧急程度等参数 ); // 审批人任务列表查询 List<Task> tasks = taskService.createTaskQuery() .taskAssignee(userId) .processVariableValueEquals("emergency", true) .orderByTaskCreateTime().desc() .list();前端采用WebSocket实现实时审批通知:
// Vue组件中 const socket = new WebSocket(`wss://${location.host}/approval/ws`); socket.onmessage = (event) => { const msg = JSON.parse(event.data); if(msg.type === 'APPROVAL_UPDATE') { this.$refs.workflowDiagram.updateStatus(msg.data); } }4.2 分布式事务处理
跨服务的物资调拨采用Seata AT模式:
@GlobalTransactional public void transferMaterial(TransferDTO dto) { // 1. 调出仓库扣减 stockOutService.reduce(dto.getOutStockId(), dto.getMaterialId(), dto.getQuantity()); // 2. 调入仓库增加 stockInService.add(dto.getInStockId(), dto.getMaterialId(), dto.getQuantity()); // 3. 记录调拨单 transferRecordService.create(dto); }5. 部署与运维方案
5.1 生产环境部署
推荐使用Docker Compose编排:
version: '3.8' services: mysql-master: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PWD} volumes: - ./mysql/data:/var/lib/mysql - ./mysql/conf:/etc/mysql/conf.d redis: image: redis:6.2-alpine command: redis-server --requirepass ${REDIS_PWD} backend: build: ./springboot depends_on: - mysql-master - redis ports: - "8080:8080"5.2 监控配置
Prometheus + Grafana监控关键指标:
# application.yml management: endpoints: web: exposure: include: health,info,metrics,prometheus metrics: tags: application: ${spring.application.name}建议监控的核心指标:
- 接口响应时间(P99 < 500ms)
- 数据库连接池使用率(<80%)
- Redis缓存命中率(>90%)
- 物资入库吞吐量(TPS)
6. 二次开发指南
6.1 扩展字段配置
系统预留了动态字段扩展能力:
<!-- mybatis-config.xml --> <settings> <setting name="autoMappingBehavior" value="FULL"/> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>前端配套的动态表单渲染:
<template> <el-form :model="dynamicForm"> <template v-for="field in extendedFields"> <el-form-item :label="field.label"> <component :is="getComponentType(field.type)" v-model="dynamicForm[field.prop]" /> </el-form-item> </template> </el-form> </template>6.2 多租户改造方案
如需支持SaaS化部署,建议采用以下改造:
数据库层面:
- 方案A:共享Schema,增加tenant_id字段
- 方案B:独立Schema,动态数据源切换
MyBatis拦截器实现:
@Intercepts(@Signature(type= Executor.class, method="query", args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})) public class TenantInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) { // 自动追加tenant_id条件 BoundSql boundSql = ms.getBoundSql(parameterObject); String newSql = boundSql.getSql() + " AND tenant_id = ?"; resetSql(invocation, newSql); return invocation.proceed(); } }7. 性能优化实战
7.1 库存扣减防超卖
采用Redis原子操作+数据库乐观锁双重保障:
public boolean reduceStock(Long materialId, int quantity) { // 1. Redis预扣减 String key = "stock:" + materialId; Long remain = redisTemplate.opsForValue().increment(key, -quantity); if (remain < 0) { redisTemplate.opsForValue().increment(key, quantity); // 回滚 return false; } // 2. 数据库确认 int updated = materialMapper.updateStock( materialId, quantity, LocalDateTime.now() // 版本戳 ); if (updated == 0) { redisTemplate.opsForValue().increment(key, quantity); // 回滚 throw new ConcurrentUpdateException(); } return true; }7.2 大数据量导出优化
使用游标分批处理+POI的SXSSFWorkbook:
public void exportMaterial(OutputStream out) { try (SXSSFWorkbook workbook = new SXSSFWorkbook(100)) { Sheet sheet = workbook.createSheet("物资清单"); materialMapper.selectByCursor(cursor -> { int rowNum = 0; while (cursor.hasNext()) { Material m = cursor.next(); Row row = sheet.createRow(rowNum++); row.createCell(0).setCellValue(m.getName()); // 其他字段... if (rowNum % 100 == 0) { sheet.flushRows(); } } }); workbook.write(out); } }8. 安全防护措施
8.1 接口安全设计
- 认证鉴权:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/api/**").authenticated() .and() .addFilter(new JwtAuthFilter(authenticationManager())); } }- 敏感数据加密:
@Column(name = "contact_phone") @Convert(converter = CryptoConverter.class) private String phone; public class CryptoConverter implements AttributeConverter<String, String> { @Override public String convertToDatabaseColumn(String attribute) { return AESUtil.encrypt(attribute); } }8.2 审计日志实现
基于Spring AOP的操作日志记录:
@Aspect @Component public class AuditLogAspect { @AfterReturning(pointcut="@annotation(auditLog)", returning="result") public void afterReturning(JoinPoint jp, AuditLog auditLog, Object result) { AuditLogEntry entry = new AuditLogEntry(); entry.setOperation(auditLog.value()); entry.setParams(JsonUtils.toJson(jp.getArgs())); entry.setResult(JsonUtils.toJson(result)); logService.save(entry); } }9. 项目演进路线
9.1 近期优化方向
架构升级:
- 引入Spring Cloud Alibaba组件(Nacos+Sentinel)
- 分库分表(ShardingSphere-JDBC)
- 前端微前端改造(qiankun框架)
智能增强:
- 物资需求预测(Prophet时间序列分析)
- 智能补货建议(线性规划算法)
- OCR识别入库(OpenCV+TensorFlow)
9.2 长期规划
生态扩展:
- 对接政府采购平台API
- 接入物流跟踪系统(快递鸟接口)
- 开发微信小程序端
国际化支持:
- 多语言资源文件管理
- 时区敏感的时间处理
- 符合各国医疗器械分类标准
这套系统经过多次迭代,核心代码量已达8万行(前端3万行,后端5万行)。在实际部署中,8核16G服务器可支撑500+并发请求,平均响应时间控制在200ms以内。特别提醒:在二次开发时,建议先完整阅读我们提供的《扩展开发手册》,其中包含了20多个典型场景的改造案例。