ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SpringBoot+Vue新冠物资管理系统开发实践

SpringBoot+Vue新冠物资管理系统开发实践 1. 项目概述与背景新冠物资管理系统是基于SpringBootVue技术栈开发的一套完整的物资管理解决方案。这个系统最初的设计目的是为了解决疫情期间医疗物资分配不均、库存管理混乱等问题。我在2020年参与开发了第一版系统后来经过多次迭代优化形成了现在这个适合作为Java Web毕业设计的成熟版本。系统采用前后端分离架构后端使用SpringBoot 2.7.18版本构建RESTful API前端使用Vue 3.x配合Element UI组件库实现用户界面。数据库采用MySQL 8.0并提供了完整的SQL脚本包含表结构设计和基础数据初始化。项目还附带了详细的接口文档使用Swagger UI自动生成方便前后端开发人员协作。提示这个项目特别适合作为计算机相关专业的毕业设计选题因为它涵盖了企业级应用开发的完整流程和技术要点同时具有实际应用价值。2. 系统架构设计2.1 技术选型分析后端技术栈SpringBoot 2.7.18简化Spring应用的初始搭建和开发过程MyBatis-Plus 3.5.3.1增强型ORM框架减少SQL编写Spring Security负责认证和授权Redis缓存热点数据和会话管理SwaggerAPI文档自动生成前端技术栈Vue 3.x主流前端框架Element PlusUI组件库AxiosHTTP客户端Vue Router前端路由管理Vuex/Pinia状态管理数据库MySQL 8.0关系型数据库Redis 7.x缓存数据库2.2 系统模块划分系统主要分为以下几个核心模块用户管理模块角色权限控制RBAC模型用户CRUD操作登录认证JWT物资管理模块物资分类管理物资基本信息管理物资图片上传库存管理模块入库记录管理出库记录管理库存预警分配管理模块物资申请审批流程分配记录统计报表模块物资流动统计库存分析可视化图表3. 数据库设计与实现3.1 核心表结构系统数据库包含约15张表以下是几个关键表的设计用户相关表CREATE TABLE sys_user ( id bigint NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 密码, real_name varchar(50) DEFAULT NULL COMMENT 真实姓名, phone varchar(20) DEFAULT NULL COMMENT 手机号, status tinyint DEFAULT 1 COMMENT 状态 0:禁用 1:正常, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY idx_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;物资信息表CREATE TABLE material ( id bigint NOT NULL AUTO_INCREMENT, category_id bigint NOT NULL COMMENT 分类ID, name varchar(100) NOT NULL COMMENT 物资名称, spec varchar(200) DEFAULT NULL COMMENT 规格, unit varchar(20) DEFAULT NULL COMMENT 单位, min_stock int DEFAULT 0 COMMENT 最低库存, max_stock int DEFAULT 0 COMMENT 最高库存, description varchar(500) DEFAULT NULL COMMENT 描述, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;库存记录表CREATE TABLE inventory ( id bigint NOT NULL AUTO_INCREMENT, material_id bigint NOT NULL COMMENT 物资ID, warehouse_id bigint NOT NULL COMMENT 仓库ID, quantity int NOT NULL DEFAULT 0 COMMENT 当前数量, lock_quantity int DEFAULT 0 COMMENT 锁定数量, version int DEFAULT 0 COMMENT 乐观锁版本号, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_material_warehouse (material_id,warehouse_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;3.2 数据库优化实践索引设计为所有外键字段添加普通索引为高频查询条件创建组合索引使用覆盖索引减少回表事务控制Transactional(rollbackFor Exception.class) public void allocateMaterial(AllocateDTO allocateDTO) { // 检查库存 Inventory inventory inventoryMapper.selectForUpdate(allocateDTO.getMaterialId()); if(inventory.getQuantity() allocateDTO.getQuantity()) { throw new BusinessException(库存不足); } // 扣减库存 inventoryMapper.deductQuantity(allocateDTO.getMaterialId(), allocateDTO.getQuantity()); // 记录分配 allocationMapper.insert(allocateDTO); }SQL优化避免SELECT *只查询需要的字段使用JOIN替代子查询大数据量分页使用延迟关联4. 后端核心实现4.1 SpringBoot应用配置多环境配置# application-dev.yml spring: datasource: url: jdbc:mysql://localhost:3306/covid_material?useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379MyBatis-Plus配置Configuration MapperScan(com.covid.mapper) public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); // 分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } }4.2 权限控制实现Spring Security配置Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(/api/auth/**).permitAll() .antMatchers(/swagger-ui/**).permitAll() .antMatchers(/v3/api-docs/**).permitAll() .anyRequest().authenticated(); http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } }JWT工具类public class JwtUtil { private static final String SECRET_KEY your-secret-key; private static final long EXPIRATION_TIME 86400000; // 24小时 public static String generateToken(UserDetails userDetails) { MapString, Object claims new HashMap(); return Jwts.builder() .setClaims(claims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRATION_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } public static Boolean validateToken(String token, UserDetails userDetails) { final String username extractUsername(token); return (username.equals(userDetails.getUsername()) !isTokenExpired(token)); } }4.3 业务逻辑实现物资入库服务Service public class MaterialServiceImpl implements MaterialService { Autowired private MaterialMapper materialMapper; Autowired private InventoryMapper inventoryMapper; Transactional Override public void stockIn(StockInDTO stockInDTO) { // 1. 检查物资是否存在 Material material materialMapper.selectById(stockInDTO.getMaterialId()); if(material null) { throw new BusinessException(物资不存在); } // 2. 更新库存 Inventory inventory inventoryMapper.selectByMaterialAndWarehouse( stockInDTO.getMaterialId(), stockInDTO.getWarehouseId()); if(inventory null) { inventory new Inventory(); inventory.setMaterialId(stockInDTO.getMaterialId()); inventory.setWarehouseId(stockInDTO.getWarehouseId()); inventory.setQuantity(stockInDTO.getQuantity()); inventoryMapper.insert(inventory); } else { inventoryMapper.addQuantity(stockInDTO.getMaterialId(), stockInDTO.getWarehouseId(), stockInDTO.getQuantity()); } // 3. 记录入库日志 StockInLog log new StockInLog(); BeanUtils.copyProperties(stockInDTO, log); log.setOperator(getCurrentUserId()); stockInLogMapper.insert(log); } }库存预警定时任务Slf4j Component public class InventoryWarningTask { Autowired private MaterialMapper materialMapper; Autowired private MailService mailService; Scheduled(cron 0 0 9 * * ?) // 每天上午9点执行 public void checkInventory() { ListMaterialWarningVO warningList materialMapper.selectWarningMaterials(); if(!warningList.isEmpty()) { mailService.sendWarningMail(warningList); log.info(发送库存预警邮件共{}条预警, warningList.size()); } } }5. 前端核心实现5.1 Vue项目结构src/ ├── api/ # 接口请求 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── router/ # 路由配置 ├── store/ # 状态管理 ├── utils/ # 工具函数 ├── views/ # 页面组件 │ ├── auth/ # 认证相关 │ ├── material/ # 物资管理 │ ├── inventory/ # 库存管理 │ ├── allocation/ # 分配管理 │ └── report/ # 报表统计 ├── App.vue # 根组件 └── main.js # 入口文件5.2 典型页面实现物资列表页template div classmaterial-container el-card div slotheader classclearfix span物资列表/span el-button typeprimary clickhandleAdd stylefloat: right;新增物资/el-button /div el-table :datatableData v-loadingloading el-table-column propname label物资名称 width180/el-table-column el-table-column propcategoryName label分类 width120/el-table-column el-table-column propspec label规格/el-table-column el-table-column propunit label单位 width80/el-table-column el-table-column proptotalQuantity label总库存 width100/el-table-column el-table-column label操作 width180 template #defaultscope el-button sizemini clickhandleEdit(scope.row)编辑/el-button el-button sizemini typedanger clickhandleDelete(scope.row)删除/el-button /template /el-table-column /el-table el-pagination size-changehandleSizeChange current-changehandleCurrentChange :current-pagepagination.current :page-sizes[10, 20, 50, 100] :page-sizepagination.size layouttotal, sizes, prev, pager, next, jumper :totalpagination.total /el-pagination /el-card /div /template script import { getMaterialList, deleteMaterial } from /api/material export default { data() { return { loading: false, tableData: [], pagination: { current: 1, size: 10, total: 0 }, queryParams: {} } }, created() { this.fetchData() }, methods: { fetchData() { this.loading true getMaterialList({ ...this.queryParams, page: this.pagination.current, size: this.pagination.size }).then(res { this.tableData res.data.records this.pagination.total res.data.total }).finally(() { this.loading false }) }, handleAdd() { this.$router.push(/material/add) }, handleEdit(row) { this.$router.push(/material/edit/${row.id}) }, handleDelete(row) { this.$confirm(确认删除该物资?, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, type: warning }).then(() { deleteMaterial(row.id).then(() { this.$message.success(删除成功) this.fetchData() }) }) }, handleSizeChange(val) { this.pagination.size val this.fetchData() }, handleCurrentChange(val) { this.pagination.current val this.fetchData() } } } /script库存统计图表template div classchart-container el-card div slotheader span库存统计/span el-date-picker v-modeldateRange typedaterange range-separator至 start-placeholder开始日期 end-placeholder结束日期 changehandleDateChange stylefloat: right; width: 350px; /el-date-picker /div el-row :gutter20 el-col :span12 div refpieChart styleheight: 400px;/div /el-col el-col :span12 div reflineChart styleheight: 400px;/div /el-col /el-row /el-card /div /template script import * as echarts from echarts import { getInventoryStats } from /api/report export default { data() { return { dateRange: [], pieChart: null, lineChart: null } }, mounted() { this.initCharts() this.loadData() }, methods: { initCharts() { this.pieChart echarts.init(this.$refs.pieChart) this.lineChart echarts.init(this.$refs.lineChart) window.addEventListener(resize, () { this.pieChart.resize() this.lineChart.resize() }) }, loadData() { const params { startDate: this.dateRange[0] ? this.dateRange[0].toISOString() : null, endDate: this.dateRange[1] ? this.dateRange[1].toISOString() : null } getInventoryStats(params).then(res { this.renderPieChart(res.data.categoryStats) this.renderLineChart(res.data.trendStats) }) }, renderPieChart(data) { const option { title: { text: 物资分类占比, left: center }, tooltip: { trigger: item, formatter: {a} br/{b}: {c} ({d}%) }, legend: { orient: vertical, left: left }, series: [ { name: 库存数量, type: pie, radius: 50%, data: data, emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: rgba(0, 0, 0, 0.5) } } } ] } this.pieChart.setOption(option) }, renderLineChart(data) { const option { title: { text: 库存变化趋势, left: center }, tooltip: { trigger: axis }, legend: { data: [入库量, 出库量, 净变化], bottom: 10 }, xAxis: { type: category, data: data.dates }, yAxis: { type: value }, series: [ { name: 入库量, type: line, data: data.inData }, { name: 出库量, type: line, data: data.outData }, { name: 净变化, type: line, data: data.netData } ] } this.lineChart.setOption(option) }, handleDateChange() { this.loadData() } } } /script6. 项目部署与运行6.1 开发环境搭建后端环境# 克隆项目 git clone https://github.com/your-repo/covid-material.git # 导入IDE(推荐IntelliJ IDEA) File - Open - 选择pom.xml文件 # 配置数据库 创建MySQL数据库covid_material 执行项目中的SQL脚本初始化数据库 # 修改配置文件 src/main/resources/application-dev.yml 配置数据库连接和Redis连接 # 启动应用 运行CovidMaterialApplication.java前端环境# 安装依赖 npm install # 开发模式运行 npm run dev # 生产环境构建 npm run build6.2 生产环境部署后端部署# 打包 mvn clean package -DskipTests # 运行 java -jar target/covid-material-0.0.1-SNAPSHOT.jar \ --spring.profiles.activeprod \ --server.port8080前端部署# 构建生产环境代码 npm run build # 部署到Nginx 将dist目录下的文件复制到Nginx的html目录 配置Nginx反向代理API请求6.3 Docker容器化部署后端DockerfileFROM openjdk:11-jre-slim VOLUME /tmp ARG JAR_FILEtarget/*.jar COPY ${JAR_FILE} app.jar ENTRYPOINT [java,-Djava.security.egdfile:/dev/./urandom,-jar,/app.jar]前端DockerfileFROM nginx:alpine COPY dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80 CMD [nginx, -g, daemon off;]docker-compose.ymlversion: 3 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: covid_material volumes: - mysql_data:/var/lib/mysql ports: - 3306:3306 redis: image: redis:7 ports: - 6379:6379 backend: build: ./backend depends_on: - mysql - redis ports: - 8080:8080 environment: SPRING_PROFILES_ACTIVE: prod SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/covid_material SPRING_REDIS_HOST: redis frontend: build: ./frontend ports: - 80:80 volumes: mysql_data:7. 项目扩展与优化建议7.1 功能扩展方向移动端适配开发微信小程序版本使用uniapp跨平台方案增加扫码入库/出库功能智能预警系统基于历史数据预测物资需求机器学习算法优化库存水平多渠道预警通知(短信、邮件、钉钉)供应链管理供应商管理模块采购订单跟踪物流信息对接7.2 性能优化建议后端优化引入Spring Cache抽象层使用Caffeine替代默认缓存数据库读写分离热点数据预加载前端优化组件懒加载路由懒加载图片压缩与CDN加速首屏服务端渲染(SSR)数据库优化大表分库分表历史数据归档查询结果缓存7.3 毕设答辩准备技术亮点提炼前后端分离架构的优势JWT无状态认证机制库存操作的并发控制数据可视化方案答辩常见问题如何保证库存操作的原子性系统如何应对高并发场景权限系统是如何设计的如果让你重新设计系统会做哪些改进项目演示技巧准备两套演示数据(正常流程和异常流程)重点展示核心业务流程对比系统上线前后的效率提升准备一些可视化图表展示数据分析能力8. 常见问题与解决方案8.1 开发环境问题前端依赖安装失败解决方案使用淘宝npm镜像npm install -g cnpm --registryhttps://registry.npmmirror.com cnpm install后端启动报数据库连接错误检查application-dev.yml中的数据库配置确保MySQL服务已启动检查数据库用户权限跨域问题后端添加CORS配置Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(*) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*); } }8.2 业务逻辑问题库存超卖问题使用乐观锁控制Update(UPDATE inventory SET quantity quantity - #{quantity}, version version 1 WHERE material_id #{materialId} AND warehouse_id #{warehouseId} AND version #{version}) int deductQuantityWithVersion(Param(materialId) Long materialId, Param(warehouseId) Long warehouseId, Param(quantity) Integer quantity, Param(version) Integer version);批量导入性能问题使用MyBatis的批量插入Transactional public void batchImport(ListMaterial materials) { SqlSession sqlSession sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH); MaterialMapper mapper sqlSession.getMapper(MaterialMapper.class); try { for (Material material : materials) { mapper.insert(material); } sqlSession.commit(); } catch (Exception e) { sqlSession.rollback(); throw e; } finally { sqlSession.close(); } }JWT令牌过期处理实现无感刷新// 在axios响应拦截器中处理token刷新 service.interceptors.response.use( response { return response }, error { if (error.response.status 401) { if (!isRefreshing) { isRefreshing true return refreshToken().then(res { const { token } res.data setToken(token) error.config.headers[Authorization] Bearer token return service(error.config) }).finally(() { isRefreshing false }) } } return Promise.reject(error) } )8.3 生产环境问题内存泄漏排查使用Arthas工具分析# 启动Arthas java -jar arthas-boot.jar # 查看内存对象 dashboard heapdump /tmp/heapdump.hprof慢SQL优化开启MyBatis-Plus的SQL日志mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl使用EXPLAIN分析执行计划接口性能监控集成Prometheus GrafanaBean MeterRegistryCustomizerMeterRegistry metricsCommonTags() { return registry - registry.config().commonTags(application, covid-material); }在实际开发过程中这个项目遇到了不少挑战特别是在库存管理和分配流程的并发控制上。最初版本没有考虑乐观锁导致在压力测试时出现了库存数据不一致的问题。后来通过引入版本号机制解决了这个问题这也让我深刻理解了在高并发场景下数据一致性的重要性。另一个经验是关于前端性能优化。最初打包后的文件很大导致首屏加载很慢。通过分析发现主要是Element Plus按需加载没配置好后来调整babel.config.js正确配置按需加载后打包体积减少了60%以上。
返回列表