1. 项目背景与核心需求
在烟草制造行业,库存管理一直是影响企业运营效率的关键环节。传统卷烟厂的库存管理往往面临三大痛点:手工记录导致的数据滞后、多系统并行造成的"信息孤岛"、缺乏有效的批次追踪机制。这些问题直接影响生产计划的准确性,甚至可能因原料过期或成品积压造成数百万的经济损失。
以某中型卷烟厂为例,其日常需要管理的物料超过200种,包括:
- 原料类:烟叶(分不同产地和等级)、香精香料、滤嘴棒
- 辅料类:卷烟纸、铝箔纸、包装盒
- 成品类:不同规格的成品卷烟
这套基于Spring Boot的库存管理系统,正是为解决上述问题而设计。系统实现了从原料入库到成品出库的全流程数字化管理,特别强化了三个核心能力:
- 实时库存可视化:所有物料的当前库存、库龄、存放位置实时更新
- 智能预警机制:对临期物料、安全库存进行自动预警
- 全链路追溯:通过批次号可追溯任意物料的完整流转记录
2. 技术架构设计
2.1 整体技术栈选型
系统采用经典的B/S架构,具体技术选型如下:
前端技术栈:
- 核心框架:Vue.js 2.6 + Element UI
- 图表库:ECharts 4.0(用于库存数据可视化)
- 打印组件:Lodop(用于出货单打印)
后端技术栈:
- 基础框架:Spring Boot 2.3.12.RELEASE
- 安全框架:Spring Security + JWT
- 持久层:MyBatis-Plus 3.4.2
- 缓存:Redis 6.0(库存热点数据缓存)
- 消息队列:RabbitMQ 3.8(异步处理库存变更)
数据库设计:
- 主库:MySQL 8.0(事务型操作)
- 分析库:ClickHouse 21.3(库存历史数据分析)
选择这套技术栈主要基于以下考量:
- Spring Boot的自动配置特性大幅减少了XML配置,配合MyBatis-Plus的代码生成器,开发效率提升40%以上
- Vue+Element UI的组合能够快速构建管理后台界面,特别适合表单密集型的库存管理系统
- 引入ClickHouse作为分析库,可支持TB级库存历史数据的即时分析
2.2 核心表结构设计
系统共设计28张数据表,以下是5个核心表的结构:
1. 物料主表(material)
sql复制CREATE TABLE `material` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '物料ID',
`code` varchar(32) NOT NULL COMMENT '物料编码',
`name` varchar(100) NOT NULL COMMENT '物料名称',
`category_id` int NOT NULL COMMENT '分类ID',
`spec` varchar(200) DEFAULT NULL COMMENT '规格参数',
`unit` varchar(10) NOT NULL COMMENT '计量单位',
`safety_stock` decimal(12,3) DEFAULT '0.000' COMMENT '安全库存',
`shelf_life` int DEFAULT NULL COMMENT '保质期(天)',
`status` tinyint DEFAULT '1' COMMENT '状态(0停用1启用)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_code` (`code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2. 库存批次表(stock_batch)
sql复制CREATE TABLE `stock_batch` (
`batch_id` varchar(32) NOT NULL COMMENT '批次号',
`material_id` bigint NOT NULL COMMENT '物料ID',
`warehouse_id` int NOT NULL COMMENT '仓库ID',
`quantity` decimal(12,3) NOT NULL COMMENT '当前数量',
`produce_date` date NOT NULL COMMENT '生产日期',
`expire_date` date DEFAULT NULL COMMENT '过期日期',
`supplier_id` int DEFAULT NULL COMMENT '供应商ID',
`inbound_time` datetime NOT NULL COMMENT '入库时间',
PRIMARY KEY (`batch_id`),
KEY `idx_material` (`material_id`),
KEY `idx_expire` (`expire_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 入库单表(stock_in)
sql复制CREATE TABLE `stock_in` (
`id` bigint NOT NULL AUTO_INCREMENT,
`order_no` varchar(32) NOT NULL COMMENT '入库单号',
`batch_id` varchar(32) NOT NULL COMMENT '关联批次号',
`operator_id` int NOT NULL COMMENT '操作人',
`in_type` tinyint NOT NULL COMMENT '入库类型(1采购2退料3调拨)',
`total_amount` decimal(12,2) DEFAULT NULL COMMENT '总金额',
`status` tinyint DEFAULT '0' COMMENT '状态(0待审核1已通过2已驳回)',
`create_time` datetime NOT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
关键设计说明:
- 采用批次管理设计,每个入库批次生成唯一batch_id,实现先进先出(FIFO)管理
- 所有数量字段使用decimal(12,3)类型,支持烟草行业特有的克、支等精确计量
- 建立完善的索引策略,特别是在material_id和expire_date上建立组合索引
3. 核心功能实现
3.1 智能入库流程
入库流程包含以下关键步骤:
-
采购预约入库:
- 采购员在ERP生成采购订单后,同步到库存系统
- 系统自动分配预期库位并生成预约入库单(状态为"待收货")
-
到货质检:
java复制// 质检结果处理逻辑 @Transactional public InspectionResult processInspection(InspectionDTO dto) { // 1. 验证采购订单有效性 PurchaseOrder order = orderMapper.selectByOrderNo(dto.getOrderNo()); if(order == null || !OrderStatus.WAIT_RECEIVE.equals(order.getStatus())){ throw new BusinessException("无效的采购订单"); } // 2. 保存质检结果 InspectionRecord record = new InspectionRecord(); BeanUtils.copyProperties(dto, record); inspectionMapper.insert(record); // 3. 更新采购单状态 if(dto.isPassed()){ order.setStatus(OrderStatus.QUALITY_PASSED); }else{ order.setStatus(OrderStatus.QUALITY_REJECTED); // 触发退货流程 returnOrderService.createReturn(order, dto.getRejectReason()); } orderMapper.updateById(order); return buildResult(record); } -
实际入库操作:
- 扫描物料条码自动带出物料信息
- 系统根据保质期规则自动计算过期日期
- 生成库存批次并分配库位
-
库存预占机制:
java复制public boolean tryLockStock(Long materialId, BigDecimal quantity) { // 使用Redis分布式锁 String lockKey = "stock:lock:" + materialId; try { // 尝试获取锁 Boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS); if(Boolean.TRUE.equals(locked)){ // 检查可用库存 BigDecimal available = stockMapper.selectAvailable(materialId); if(available.compareTo(quantity) >= 0){ // 记录预占 stockMapper.lockStock(materialId, quantity); return true; } } return false; } finally { // 释放锁 redisTemplate.delete(lockKey); } }
3.2 出库审批流程
出库流程的特殊性在于严格的审批控制:
-
出库申请:
- 生产部门提交出库需求单
- 系统自动检查物料可用库存
- 触发审批工作流(使用Activiti引擎)
-
多级审批设计:
xml复制<!-- 审批流程定义 --> <process id="outboundApproval" name="物料出库审批流程"> <startEvent id="start"/> <userTask id="deptApprove" name="部门审批" candidateGroups="dept_leader"/> <sequenceFlow sourceRef="start" targetRef="deptApprove"/> <exclusiveGateway id="gateway1"/> <sequenceFlow sourceRef="deptApprove" targetRef="gateway1"/> <!-- 价值超过1万元需要财务审批 --> <sequenceFlow sourceRef="gateway1" targetRef="financeApprove" conditionExpression="${totalAmount > 10000}"/> <userTask id="financeApprove" name="财务审批" candidateGroups="finance"/> <sequenceFlow sourceRef="gateway1" targetRef="warehouse" conditionExpression="${totalAmount <= 10000}"/> <userTask id="warehouse" name="仓库备货" assignee="${stockOperator}"/> <endEvent id="end"/> </process> -
批次出库策略:
- 系统自动选择最早入库的批次(FIFO)
- 支持部分出库(一个出库单关联多个批次)
- 出库后自动更新批次剩余数量
4. 特色功能实现
4.1 库存智能预警
系统实现了三类核心预警:
-
库存水位预警:
- 安全库存预警:当库存量低于安全库存时触发
- 呆滞料预警:超过180天未发生出入库的物料
-
时效性预警:
java复制// 临期预警定时任务 @Scheduled(cron = "0 0 9 * * ?") // 每天上午9点执行 public void checkExpiringStock() { // 查询30天内将过期的批次 List<StockBatch> expiringBatches = batchMapper.selectExpiring( LocalDate.now(), LocalDate.now().plusDays(30)); expiringBatches.forEach(batch -> { // 发送企业微信通知 wechatService.sendAlert( batch.getMaterialName() + "即将过期", "批次号:" + batch.getBatchId() + "\n过期日期:" + batch.getExpireDate() + "\n当前库存:" + batch.getQuantity()); // 记录预警日志 WarningLog log = new WarningLog(); log.setType(WarningType.EXPIRING); log.setRefId(batch.getBatchId()); log.setContent("物料即将过期"); warningLogMapper.insert(log); }); } -
差异预警:
- 定期比对系统库存与盘点结果
- 超过5%的差异自动生成差异报告
4.2 移动端协同
针对卷烟厂现场作业需求,开发了配套微信小程序:
-
扫码快速入库:
- 扫描物料二维码自动带出信息
- 拍照上传实物照片
- GPS定位记录入库位置
-
移动审批:
- 审批人可随时随地处理待办
- 支持电子签名确认
- 审批记录区块链存证
-
库存查询:
javascript复制// 小程序端库存查询 function queryStock() { wx.scanCode({ success: (res) => { const materialCode = res.result; wx.request({ url: 'https://api.xxx.com/miniapp/stock', data: { code: materialCode }, success: (resp) => { this.setData({ stockInfo: resp.data }); } }); } }); }
5. 部署实施要点
5.1 系统部署架构
生产环境采用高可用部署方案:
code复制 +-----------------+
| CDN/对象存储 |
+--------+--------+
|
+------------------+ | +------------------+
| Web服务器集群 | | | API服务器集群 |
| (Nginx + Vue静态) +-------+-------+ (Spring Boot) |
+------------------+ +---------+--------+
|
+-------------------------+----+
| Redis集群(哨兵模式) |
+------------+---------------+
|
+------------------+------------------+
| | |
+------+------+ +------+------+ +------+------+
| MySQL主库 | | MySQL从库1 | | MySQL从库2 |
+-------------+ +-------------+ +-------------+
5.2 性能优化措施
-
库存查询优化:
- 使用Redis缓存热点物料库存数据
- 建立物料的库存快照表,避免实时计算
-
批量操作处理:
java复制// 使用MyBatis批量插入 @Transactional public void batchInsertInDetails(List<InDetail> details) { SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH); try { InDetailMapper mapper = session.getMapper(InDetailMapper.class); for (InDetail detail : details) { mapper.insert(detail); } session.commit(); } finally { session.close(); } } -
数据库调优:
- 配置InnoDB缓冲池(设置为物理内存的70%)
- 优化慢查询:为所有交易表添加操作时间索引
- 使用数据库中间件实现读写分离
5.3 上线迁移方案
-
数据迁移流程:
- 旧系统数据清洗(使用Kettle工具)
- 双系统并行运行1个月
- 灰度切换:按仓库逐步迁移
-
用户培训重点:
- 批次管理概念与操作
- 移动端扫码功能使用
- 预警信息的处理流程
6. 实际应用效果
在某卷烟厂实施后取得显著成效:
-
运营效率提升:
- 入库效率提高60%(从平均15分钟/单到6分钟/单)
- 出库差错率降低至0.2%以下
-
库存优化:
- 库存周转天数从45天降至28天
- 临期物料损失减少75%
-
管理提升:
- 实现100%批次可追溯
- 审批流程从平均8小时缩短至1.5小时
系统特别在以下场景表现出色:
- 生产旺季的高并发出入库处理
- 突发质量问题的快速批次追溯
- 跨厂区的库存调拨协同
7. 常见问题解决方案
7.1 库存不一致问题
现象:系统库存与实际盘点结果存在差异
排查步骤:
- 检查未完成的出入库单据
- 核对库存变更日志
- 验证事务完整性
解决方案:
sql复制-- 库存校正脚本示例
BEGIN;
INSERT INTO stock_adjustment
(material_id, batch_id, old_qty, new_qty, reason)
SELECT
s.material_id, s.batch_id, s.quantity,
p.physical_qty, '周期盘点校正'
FROM stock_batch s
JOIN physical_inventory p ON s.batch_id = p.batch_id
WHERE s.quantity != p.physical_qty;
UPDATE stock_batch s
JOIN physical_inventory p ON s.batch_id = p.batch_id
SET s.quantity = p.physical_qty
WHERE s.quantity != p.physical_qty;
COMMIT;
7.2 高并发下的超卖问题
现象:多个出库单同时扣除库存导致超卖
解决方案:
-
采用乐观锁机制:
java复制@Transactional public boolean deductStock(Long batchId, BigDecimal qty) { // 1. 查询当前版本号 StockBatch batch = batchMapper.selectForUpdate(batchId); // 2. 校验库存 if(batch.getQuantity().compareTo(qty) < 0){ return false; } // 3. 尝试扣减 int updated = batchMapper.deductWithVersion( batchId, qty, batch.getVersion()); return updated > 0; } -
数据库层面设置检查约束:
sql复制ALTER TABLE stock_batch ADD CONSTRAINT chk_quantity CHECK (quantity >= 0);
7.3 系统集成问题
现象:与ERP系统数据不同步
解决方案:
-
建立统一消息格式:
xml复制<!-- 库存变更通知消息 --> <message> <header> <msgId>UUID</msgId> <msgType>STOCK_CHANGE</msgType> <timestamp>2023-07-20T15:30:00</timestamp> </header> <body> <materialCode>A10025</materialCode> <batchNo>B20230715001</batchNo> <changeType>OUTBOUND</changeType> <quantity>-500.000</quantity> <documentNo>OUT20230720001</documentNo> </body> </message> -
采用补偿事务机制:
- 本地事务表记录集成操作
- 定时任务检查未完成的操作
- 自动重试或人工干预
8. 项目演进方向
-
智能化升级:
- 引入机器学习预测库存需求
- 使用计算机视觉实现自动物料识别
-
物联网集成:
- 接入温湿度传感器监控仓库环境
- AGV机器人联动实现智能搬运
-
区块链应用:
- 关键操作上链存证
- 建立供应链协同网络
这套系统在实际开发中,我们特别注重三个原则:操作便捷性(特别是对年龄较大的仓库管理员)、数据准确性(烟草行业对账务要求极高)、系统稳定性(生产系统要求24/7可用)。经过6个月的开发和3个月的试运行,目前已在三家卷烟厂稳定运行,每天处理超过2000笔出入库交易。