1. 项目背景与核心需求
疫情物资管理系统作为典型的应急物资管理解决方案,在2020年后成为高校计算机专业毕业设计的热门选题。这类系统本质上是一个具有特定业务规则的进销存管理系统,但相比传统仓储系统增加了以下特殊需求:
- 时效性管控:需记录物资的生产日期、有效期,对临期物资自动预警
- 应急调配:支持多级仓库间的快速调拨和优先级分配
- 可视化监控:实时展示各类物资库存量、消耗速率等关键指标
- 权限隔离:不同级别管理员(如省级、市级)的操作权限需严格区分
选择Java+MySQL技术栈具有多重优势:
- 教学资源丰富,社区支持完善,降低学习成本
- Spring Boot框架能快速搭建符合RESTful规范的API服务
- MyBatis-Plus简化数据库操作,专注业务逻辑开发
- 前端可采用Vue+ElementUI组合,实现前后端分离架构
提示:实际开发中建议采用Spring Boot 2.7.x + JDK 11的稳定组合,避免使用最新版本可能带来的兼容性问题。
2. 系统架构设计
2.1 技术选型决策
后端技术栈:
- 核心框架:Spring Boot 2.7.18(长期支持版本)
- ORM工具:MyBatis-Plus 3.5.3(含代码生成器)
- 安全控制:Spring Security + JWT
- 文档生成:Knife4j 3.0.3(Swagger增强版)
- Excel处理:EasyExcel 3.3.2(避免OOM)
前端技术栈:
- 基础框架:Vue 2.6.x(稳定版)
- UI组件:ElementUI 2.15.x
- 可视化:ECharts 5.3.2
- 构建工具:Webpack 4.x
数据库设计:
CREATE TABLE `material` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '物资名称', `category` varchar(20) NOT NULL COMMENT '分类(防护/消毒/药品)', `spec` varchar(100) NOT NULL COMMENT '规格型号', `stock` int NOT NULL DEFAULT '0' COMMENT '当前库存', `safe_stock` int NOT NULL COMMENT '安全库存', `unit` varchar(10) NOT NULL COMMENT '计量单位', `expire_days` int DEFAULT NULL COMMENT '保质期(天)', `producer` varchar(100) DEFAULT NULL COMMENT '生产厂家', `status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0停用1启用)', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_category` (`category`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;2.2 核心业务模块
基础信息管理
- 物资分类编码体系设计(建议采用GB/T 14885标准)
- 供应商资质审核流程
- 仓库位置GIS坐标记录
入库管理
- 采购单与发票关联
- 批次号自动生成规则(年月日+序列号)
- 质检异常处理流程
出库管理
- 三级审批电子签名
- 紧急出库绿色通道
- 物资效期先进先出(FIFO)控制
智能预警
- 库存量安全阈值计算模型
- 物资消耗趋势预测
- 短信/邮件预警通知
3. 开发环境搭建
3.1 开发工具链配置
Java环境
# 验证JDK安装 java -version javac -version常见问题处理:
- 出现"UnsupportedClassVersionError"时,检查IDE中项目的Language Level设置
- Lombok插件必须与IDE版本匹配
MySQL 8.0安装要点
# 创建专用用户 CREATE USER 'epms_user'@'%' IDENTIFIED BY 'ComplexPwd@123'; GRANT ALL PRIVILEGES ON epms.* TO 'epms_user'@'%';关键配置参数:
[mysqld] default_authentication_plugin=mysql_native_password character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci innodb_buffer_pool_size=1GIDEA高效开发配置
- 启用Annotation Processors
- 安装MyBatisX插件实现XML跳转
- 配置Database工具连接MySQL
4. 核心功能实现
4.1 物资库存API开发
采用领域驱动设计(DDD)实现库存管理:
@RestController @RequestMapping("/api/inventory") public class InventoryController { @Autowired private InventoryService inventoryService; @PostMapping("/adjust") public Result adjustInventory(@Valid @RequestBody InventoryAdjustDTO dto) { return inventoryService.adjustStock(dto); } @GetMapping("/warning") public Result getWarningItems() { return inventoryService.getSafetyWarningItems(); } } @Service public class InventoryServiceImpl implements InventoryService { @Transactional(rollbackFor = Exception.class) public Result adjustStock(InventoryAdjustDTO dto) { // 实现库存扣减的乐观锁控制 Material material = materialMapper.selectById(dto.getMaterialId()); if (material.getStock() + dto.getDelta() < 0) { throw new BusinessException("库存不足"); } LambdaUpdateWrapper<Material> updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.setSql("stock = stock + " + dto.getDelta()) .eq(Material::getId, dto.getMaterialId()) .eq(Material::getVersion, material.getVersion()); if (materialMapper.update(null, updateWrapper) == 0) { throw new ConcurrentUpdateException("库存并发修改冲突"); } // 记录库存变更流水 inventoryLogService.recordAdjustment(dto); return Result.success(); } }4.2 效期预警实现方案
- 定时任务设计:
@Scheduled(cron = "0 0 9 * * ?") // 每天9点执行 public void checkExpirationWarning() { LocalDate warningDate = LocalDate.now().plusDays(30); List<Material> materials = materialMapper.selectList( new LambdaQueryWrapper<Material>() .le(Material::getExpireDate, warningDate) .eq(Material::getStatus, 1)); if (!materials.isEmpty()) { emailService.sendExpirationWarning(materials); } }- 存储过程实现库存周转率计算:
DELIMITER // CREATE PROCEDURE CalculateTurnoverRate(IN materialId BIGINT) BEGIN SELECT m.name, SUM(il.quantity) / AVG(m.stock) AS turnover_rate FROM inventory_log il JOIN material m ON il.material_id = m.id WHERE il.material_id = materialId AND il.type IN ('OUTBOUND', 'CONSUME') AND il.create_time >= DATE_SUB(NOW(), INTERVAL 3 MONTH); END // DELIMITER ;5. 典型问题解决方案
5.1 高并发库存扣减
采用Redis+Lua脚本实现分布式锁:
public boolean deductStock(Long materialId, int quantity) { String lockKey = "lock:material:" + materialId; String uuid = UUID.randomUUID().toString(); try { // 获取分布式锁 Boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, uuid, 10, TimeUnit.SECONDS); if (Boolean.TRUE.equals(locked)) { // Lua脚本保证原子性 String script = "local stock = tonumber(redis.call('GET', KEYS[1])) " + "if stock >= tonumber(ARGV[1]) then " + " redis.call('DECRBY', KEYS[1], ARGV[1]) " + " return 1 " + "else " + " return 0 " + "end"; Long result = redisTemplate.execute( new DefaultRedisScript<>(script, Long.class), Collections.singletonList("stock:" + materialId), String.valueOf(quantity)); return result != null && result == 1; } return false; } finally { // 确保释放自己的锁 if (uuid.equals(redisTemplate.opsForValue().get(lockKey))) { redisTemplate.delete(lockKey); } } }5.2 大数据量导出优化
使用EasyExcel的分页查询与异步写入:
public void exportInventory(HttpServletResponse response) { response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment;filename=inventory.xlsx"); ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()) .head(InventoryExportVO.class).build(); int pageSize = 1000; int pageNum = 1; Page<Material> page; do { page = materialMapper.selectPage( new Page<>(pageNum, pageSize), new LambdaQueryWrapper<Material>() .orderByAsc(Material::getCategory)); List<InventoryExportVO> data = page.getRecords().stream() .map(this::convertToVO) .collect(Collectors.toList()); excelWriter.write(data, EasyExcel.writerSheet("库存清单") .build()); pageNum++; } while (page.hasNext()); excelWriter.finish(); }6. 毕业设计答辩要点
6.1 系统演示技巧
场景化演示流程:
- 模拟省级管理员创建应急调拨单
- 展示市级仓库的库存实时更新
- 触发临期物资预警通知
- 生成物资消耗趋势图表
性能数据准备:
- JMeter压测报告(建议QPS≥200)
- 数据库查询响应时间监控
- 导出10万条数据的耗时对比
6.2 答辩常见问题应对
技术深度问题:
- "为什么选择MyBatis-Plus而不是JPA?" → 回答要点:MyBatis的SQL可控性更适合复杂业务查询,Plus的ActiveRecord模式简化开发
业务逻辑问题:
- "如何防止不同仓库同时申领同批物资?" → 展示数据库乐观锁实现和Redis分布式锁的双重保障机制
扩展性问题:
- "系统如何支持突发的大规模物资调度?" → 说明已设计的应急预案接口和负载均衡方案
7. 项目部署与运维
7.1 生产环境部署
使用Docker Compose编排服务:
version: '3.8' services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS} MYSQL_DATABASE: epms volumes: - ./mysql/data:/var/lib/mysql - ./mysql/conf:/etc/mysql/conf.d ports: - "3306:3306" restart: always redis: image: redis:6.2 command: redis-server --requirepass ${REDIS_PASS} ports: - "6379:6379" volumes: - ./redis/data:/data restart: always backend: build: ./backend ports: - "8080:8080" environment: - SPRING_PROFILES_ACTIVE=prod depends_on: - mysql - redis restart: always7.2 监控方案设计
基础监控:
- Spring Boot Actuator暴露健康指标
- Prometheus + Grafana监控JVM指标
- ELK收集业务日志
业务监控:
@Aspect @Component public class InventoryMonitor { @Autowired private MeterRegistry meterRegistry; @AfterReturning( pointcut = "execution(* com..InventoryService.*(..))", returning = "result") public void trackInventoryOperation(JoinPoint jp, Object result) { String operation = jp.getSignature().getName(); meterRegistry.counter("inventory.operation", "type", operation).increment(); if (result instanceof InventoryResult) { InventoryResult ir = (InventoryResult) result; if (!ir.isSuccess()) { meterRegistry.counter("inventory.error", "cause", ir.getErrorCode()).increment(); } } } }
在开发过程中,我发现物资分类编码的标准化尤为重要。建议参考《应急物资分类及编码》国家标准,建立8位层次编码体系(如01.02.03代表大类-中类-小类)。实际部署时,Nginx配置需要特别注意文件上传大小限制:
client_max_body_size 50M; proxy_connect_timeout 300; proxy_send_timeout 300; proxy_read_timeout 300; send_timeout 300;