1. 项目背景与核心价值
福泰轴承股份有限公司进销存系统是一个典型的制造业企业资源管理平台。作为轴承制造行业的代表性企业,其业务流程具有三个显著特点:多品类SKU管理(轴承型号可达上千种)、严格的批次追踪要求(涉及质量追溯)、复杂的供应链协同(从原材料到成品)。传统Excel管理方式已无法满足日均2000+出入库单据的处理需求,这正是本系统要解决的核心痛点。
我去年参与过某汽配企业的ERP改造项目,深刻体会到制造业进销存系统的特殊之处:不仅需要实现基础的商品进出管理,更要解决BOM(物料清单)关联、质量追溯、供应商评估等行业特定需求。这个毕业设计项目虽然规模较小,但完整涵盖了企业级进销存的核心功能模块。
2. 技术架构解析
2.1 SpringBoot后端设计要点
采用SpringBoot 2.7.x + MyBatis Plus组合,这是经过多个生产项目验证的稳定方案。关键配置如下:
// 多数据源配置示例(主库+报表库) @Configuration @MapperScan(basePackages = "com.ftbearing.mapper.primary", sqlSessionTemplateRef = "primarySqlSessionTemplate") public class PrimaryDataSourceConfig { @Bean @ConfigurationProperties(prefix = "spring.datasource.primary") public DataSource primaryDataSource() { return DataSourceBuilder.create().build(); } }特别要注意事务管理的设计。在采购入库-库存更新-财务记账这个典型流程中,需要配置分布式事务:
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED) public void purchaseStockIn(PurchaseOrder order) { // 1. 更新采购单状态 // 2. 增加库存记录 // 3. 生成应付账款 }2.2 Vue前端工程化实践
使用Vue 3 + Element Plus的组合,采用典型的模块划分:
src/ ├── api/ # Axios封装 ├── assets/ # 静态资源 ├── components/ # 通用组件 │ ├── BarcodeScanner.vue # 扫码枪组件 │ └── InventoryTable.vue # 带合并单元格的库存表格 ├── router/ # 动态路由配置 ├── stores/ # Pinia状态管理 ├── utils/ # 工具类 └── views/ ├── inventory/ # 库存管理 ├── purchase/ # 采购管理 └── sales/ # 销售管理重点说下扫码枪集成方案。在仓库模块中,我们通过HID API实现了无感扫码:
// 扫码枪事件监听 window.addEventListener('keypress', (e) => { if(e.target.tagName !== 'INPUT') { this.scanBuffer += String.fromCharCode(e.charCode) clearTimeout(this.scanTimer) this.scanTimer = setTimeout(() => { this.handleBarcode(this.scanBuffer) this.scanBuffer = '' }, 100) } })2.3 MySQL数据库设计规范
遵循制造业进销存系统的三大设计原则:
- 事务一致性:所有库存变更必须通过事务处理
- 历史可追溯:关键表都需要create_time/update_time和操作人记录
- 性能优先:热表(如inventory)需要合理的索引策略
核心表结构示例:
CREATE TABLE `t_inventory` ( `id` bigint NOT NULL AUTO_INCREMENT, `sku_code` varchar(32) NOT NULL COMMENT '轴承型号', `batch_no` varchar(32) NOT NULL COMMENT '生产批次', `warehouse_id` int NOT NULL, `location_code` varchar(16) NOT NULL COMMENT '库位编码', `quantity` decimal(12,3) NOT NULL, `locked_quantity` decimal(12,3) DEFAULT '0.000' COMMENT '锁定数量', `version` int DEFAULT '0' COMMENT '乐观锁版本', PRIMARY KEY (`id`), UNIQUE KEY `uk_sku_batch` (`sku_code`,`batch_no`,`warehouse_id`), KEY `idx_warehouse` (`warehouse_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;3. 核心业务模块实现
3.1 采购入库流程
典型的多层级审批流程实现:
- 采购申请单(PR)→ 2. 采购订单(PO)→ 3. 到货质检 → 4. 入库上架
关键代码片段:
// 带质检的入库服务 public void qualityCheckAndStockIn(InspectionForm form) { // 1. 质检结果判定 if(form.getPassRate() < 0.95) { throw new BizException("质检合格率低于95%"); } // 2. 合格品入库 inventoryService.batchStockIn( form.getItems().stream() .filter(i -> i.isQualified()) .collect(Collectors.toList()) ); // 3. 不合格品处理 rejectionService.createRejectionOrder( form.getItems().stream() .filter(i -> !i.isQualified()) .collect(Collectors.toList()) ); }3.2 销售出库策略
实现先进先出(FIFO)的库存扣减逻辑:
public List<Inventory> deductInventory(String skuCode, BigDecimal quantity) { // 按批次日期排序获取可用库存 List<Inventory> available = inventoryMapper.selectAvailable( skuCode, quantity); if(available.stream() .mapToDouble(i -> i.getQuantity().doubleValue()) .sum() < quantity.doubleValue()) { throw new InventoryShortageException(); } // 按批次顺序扣减 BigDecimal remaining = quantity; List<Inventory> deducted = new ArrayList<>(); for(Inventory item : available) { if(remaining.compareTo(BigDecimal.ZERO) <= 0) break; BigDecimal deduct = item.getQuantity().min(remaining); inventoryMapper.deduct(item.getId(), deduct, item.getVersion()); deducted.add(item); remaining = remaining.subtract(deduct); } return deducted; }3.3 库存预警机制
基于Spring Scheduler的定时库存检查:
@Scheduled(cron = "0 0 9,15 * * ?") // 每天9点和15点执行 public void checkInventoryWarning() { List<InventoryAlert> alerts = inventoryMapper.selectBelowSafetyStock(); alerts.forEach(alert -> { String message = String.format( "型号%s库存低于安全库存(当前:%s, 安全:%s)", alert.getSkuCode(), alert.getCurrent(), alert.getSafety()); // 发送企业微信通知 wechatNotifier.sendToPurchaseGroup(message); // 生成采购建议单 suggestionService.generatePurchaseSuggestion( alert.getSkuCode(), alert.getSafety().subtract(alert.getCurrent()) ); }); }4. 部署与运维实践
4.1 多环境配置方案
使用Spring Profile实现环境隔离:
application.yml # 公共配置 application-dev.yml # 开发环境 application-test.yml # 测试环境 application-prod.yml # 生产环境关键的生产环境配置项:
spring: datasource: url: jdbc:mysql://mysql-prod:3306/ftbearing?useSSL=false&serverTimezone=Asia/Shanghai hikari: maximum-pool-size: 20 connection-timeout: 30000 redis: host: redis-prod password: ${REDIS_PASSWORD} cache: type: redis redis: time-to-live: 1h4.2 前端性能优化
通过以下手段将首屏加载时间控制在1.5秒内:
- 路由懒加载
const Inventory = () => import('./views/Inventory.vue')- 组件级代码分割
- CDN引入Element Plus
- Gzip压缩(nginx配置示例):
gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;4.3 数据库运维要点
建立定期维护任务:
- 每日凌晨自动备份
mysqldump -u root -p ftbearing > /backups/ftbearing_$(date +%F).sql- 每周执行表优化
OPTIMIZE TABLE t_inventory_log;- 每月统计表分析
ANALYZE TABLE t_sales_detail;5. 典型问题排查实录
5.1 并发库存超卖问题
现象:并发扣减库存时出现负数 解决方案:采用乐观锁机制
@Update("UPDATE t_inventory SET quantity=quantity-#{deduct}, version=version+1 WHERE id=#{id} AND version=#{version}") int deductWithVersion(@Param("id") Long id, @Param("deduct") BigDecimal deduct, @Param("version") Integer version);5.2 树形分类性能问题
现象:轴承品类三级分类加载缓慢(>3s) 优化方案:
- 使用嵌套集模型替代邻接表
- 添加缓存层
@Cacheable(value = "categoryTree", key = "#root.methodName") public List<Category> getTreeView() { return categoryMapper.selectNestedSet(); }5.3 报表导出OOM问题
现象:导出半年交易记录时内存溢出 解决方案:
- 采用流式查询
@Select("SELECT * FROM t_sales WHERE create_time BETWEEN #{start} AND #{end}") @Options(resultSetType = ResultSetType.FORWARD_ONLY, fetchSize = 1000) @ResultType(SalesOrder.class) void streamSales(@Param("start") Date start, @Param("end") Date end, ResultHandler<SalesOrder> handler);- 分片处理数据
- 使用SXSSFWorkbook进行Excel导出
6. 项目演进建议
- 引入Redis缓存热点数据(如库存余量)
// 二级缓存配置示例 @Cacheable(value = "inventory", key = "#skuCode+'_'+#warehouseId") public BigDecimal getInventoryQuantity(String skuCode, Integer warehouseId) { return inventoryMapper.selectQuantity(skuCode, warehouseId); }- 增加OpenAPI支持
@Tag(name = "InventoryAPI") @RestController @RequestMapping("/api/inventory") public class InventoryApiController { @Operation(summary = "获取实时库存") @GetMapping("/{skuCode}") public R<InventoryVO> getBySku(@PathVariable String skuCode) { // ... } }- 构建Docker化部署方案
FROM openjdk:11-jre COPY target/ftbearing.jar /app/ ENTRYPOINT ["java","-jar","/app/ftbearing.jar"] EXPOSE 8080这个项目最让我印象深刻的是库存事务的设计。在压力测试时发现,简单的@Transactional注解在分布式环境下并不能完全保证一致性,最终通过"预占库存+异步确认"的机制解决了这个问题。建议在实际部署时,一定要用JMeter模拟至少200并发下的库存操作测试。