SpringBoot+Vue3抗疫物资管理系统开发实战
1. 项目概述抗疫物资管理系统的技术架构与价值最近在整理疫情期间参与的几个开源项目发现这套基于SpringBootVue3MyBatis的抗疫物资管理系统特别值得分享。这个系统最初是为某公益组织开发的后来经过多次迭代形成了现在这个通用版本。核心功能包括物资入库、调拨审批、库存预警和捐赠追踪等模块特别适合中小型医疗机构和社区防疫站使用。技术栈选择上后端采用SpringBoot 2.7 MyBatis-Plus 3.5的组合前端用Vue3 Element Plus构建数据库是MySQL 8.0。这套组合拳的亮点在于SpringBoot的自动配置简化了微服务搭建MyBatis-Plus的代码生成器节省了80%的CRUD代码量Vue3的Composition API让前端状态管理更清晰Element Plus的现成组件加速了管理后台开发提示系统默认使用JWT进行认证如果对接微信小程序需要额外配置WxJava依赖2. 核心模块设计与实现2.1 后端工程结构解析项目采用经典的三层架构但有几个特别的设计点值得注意src/main/java ├── config # 自定义配置类 ├── controller # 响应前端请求 ├── entity # 数据库实体 ├── mapper # MyBatis接口 ├── service # 业务逻辑 │ ├── impl # 服务实现类 └── util # 工具包物资入库接口的典型实现流程PostMapping(/storage) public Result addStorage(RequestBody MaterialDTO dto) { // 1. 参数校验 ValidatorUtil.validate(dto); // 2. DTO转Entity Material material ConvertUtil.toEntity(dto); // 3. 调用服务层 return storageService.saveMaterial(material); }2.2 前端工程关键配置Vue3项目采用Vite构建主要依赖包括vue-router4用于路由管理pinia2状态管理库axios网络请求element-plusUI组件库路由拦截器的典型实现router.beforeEach((to, from) { const token localStorage.getItem(token) if (!token to.meta.requiresAuth) { return { path: /login } } })3. 数据库设计与优化3.1 核心表结构CREATE TABLE material ( id bigint NOT NULL AUTO_INCREMENT, name varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT 物资名称, category varchar(20) COLLATE utf8mb4_bin NOT NULL COMMENT 分类, spec varchar(100) COLLATE utf8mb4_bin DEFAULT NULL COMMENT 规格, stock int NOT NULL DEFAULT 0 COMMENT 当前库存, threshold int DEFAULT NULL COMMENT 库存阈值, PRIMARY KEY (id), KEY idx_category (category) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_bin;3.2 查询性能优化对于物资调拨记录这类高频查询表我们采用了以下优化策略为常用查询字段建立联合索引大文本字段使用垂直分表使用MyBatis二级缓存热点数据走Redis缓存典型的分页查询Mapper配置select idselectTransferPage resultMapTransferResult SELECT * FROM material_transfer where if testquery.status ! null AND status #{query.status} /if /where ORDER BY create_time DESC /select4. 典型业务场景实现4.1 物资调拨审批流采用状态机模式实现审批流程public enum TransferState { PENDING, // 待审批 APPROVED, // 已批准 REJECTED, // 已拒绝 COMPLETED // 已完成 } Service public class TransferService { Transactional public void approve(Long id) { Transfer transfer getById(id); if (transfer.getStatus() ! TransferState.PENDING) { throw new BizException(当前状态不可审批); } transfer.setStatus(TransferState.APPROVED); updateById(transfer); } }4.2 库存预警机制通过定时任务观察者模式实现Scheduled(cron 0 0 9 * * ?) // 每天9点执行 public void checkStock() { ListMaterial materials materialMapper.selectLowStock(); materials.forEach(m - { // 发送企业微信通知 wechatService.sendAlert(m); }); }5. 部署与运维实践5.1 多环境配置SpringBoot的profile配置示例# application-dev.yml spring: datasource: url: jdbc:mysql://localhost:3306/medical_dev username: devuser password: dev123 # application-prod.yml spring: datasource: url: jdbc:mysql://prod-db:3306/medical_prod username: ${DB_USER} password: ${DB_PWD}5.2 前端部署优化Vite构建配置要点// vite.config.js export default defineConfig({ build: { rollupOptions: { output: { manualChunks(id) { if (id.includes(node_modules)) { return vendor } } } } } })6. 常见问题排查指南6.1 MyBatis映射异常典型错误Invalid bound statement (not found)排查步骤检查Mapper接口与XML文件是否在同一个包确认mybatis.mapper-locations配置正确检查方法名是否严格匹配6.2 Vue3响应式失效当使用reactive包裹的对象属性丢失响应性时确保始终通过代理对象访问属性对基本类型使用ref解构时使用toRefs保持响应式const state reactive({ count: 0 }) const { count } toRefs(state) // 保持响应式7. 性能优化实战记录7.1 接口响应优化通过Arthas诊断慢查询# 监控Controller方法耗时 watch com.example.controller.* * {params,returnObj} -x 2 -n 5优化措施添加Cacheable缓存热点数据使用Async异步处理非核心逻辑启用MyBatis二级缓存7.2 前端加载优化实测有效的方案路由懒加载组件异步加载使用unplugin-vue-components按需引入Element Plus// 路由懒加载示例 const routes [ { path: /report, component: () import(/views/Report.vue) } ]8. 安全防护方案8.1 接口防刷策略采用Guava RateLimiter实现Aspect Component public class RateLimitAspect { private final RateLimiter limiter RateLimiter.create(100); // 100次/秒 Around(annotation(rateLimit)) public Object around(ProceedingJoinPoint pjp) { if (!limiter.tryAcquire()) { throw new BizException(请求过于频繁); } return pjp.proceed(); } }8.2 SQL注入防护MyBatis的安全写法!-- 错误示范 -- SELECT * FROM user WHERE name ${name} !-- 正确写法 -- SELECT * FROM user WHERE name #{name}9. 扩展开发建议9.1 微信小程序对接集成WxJava的配置示例Configuration public class WxConfig { Value(${wx.appid}) private String appid; Bean public WxMaService wxMaService() { WxMaDefaultConfigImpl config new WxMaDefaultConfigImpl(); config.setAppid(appid); return new WxMaServiceImpl().setWxMaConfig(config); } }9.2 可视化报表扩展结合ECharts实现物资流向可视化import * as echarts from echarts const initChart () { const chart echarts.init(document.getElementById(chart)) chart.setOption({ tooltip: {}, series: [{ type: pie, data: [ { value: 1048, name: 口罩 }, { value: 735, name: 防护服 } ] }] }) }10. 项目二次开发指南10.1 代码生成器使用MyBatis-Plus代码生成器配置示例FastAutoGenerator.create(dataSourceConfig) .globalConfig(builder - builder.author(dev).outputDir(src/main/java)) .packageConfig(builder - builder.parent(com.medical)) .strategyConfig(builder - builder.addInclude(material)) .execute();10.2 自定义业务扩展添加新物资类型的步骤数据库material表新增type字段修改Entity和DTO类扩展前端表单组件更新类型枚举类public enum MaterialType { MEDICAL, // 医疗用品 PROTECTIVE, // 防护装备 DAILY // 生活物资 }在项目实际部署时发现当并发调拨请求量较大时数据库会出现死锁情况。我们的解决方案是对调拨操作添加分布式锁采用乐观锁控制版本号将库存扣减与调拨记录拆分为两个事务Transactional public void transferMaterial(Long materialId, Integer amount) { // 使用SELECT...FOR UPDATE加行锁 Material material materialMapper.selectForUpdate(materialId); if (material.getStock() amount) { throw new BizException(库存不足); } material.setStock(material.getStock() - amount); materialMapper.updateById(material); // 记录调拨日志 TransferLog log new TransferLog(materialId, amount); transferLogMapper.insert(log); }