这个果蔬仓储管理系统是我去年为本地一家中型农产品配送公司开发的实战项目。当时他们还在用Excel表格记录进出库,经常出现库存不准、保质期过期、订单延误等问题。系统上线后,库存准确率从78%提升到99.6%,损耗率降低了43%,今天我就把整个开发过程的关键设计思路和实现细节分享给大家。
系统采用SpringBoot+MyBatisPlus+Vue的主流技术栈,包含采购入库、库存预警、智能分拣、配送管理、数据分析等核心模块。特别在生鲜商品的效期管理和批次追踪上做了深度优化,后面会重点讲解这几个特色功能的实现方案。
选择SpringBoot作为后端框架主要基于三个实际需求:
数据库选用MySQL 8.0而不是MongoDB的原因:
前端用Vue3+Element Plus的组合,主要是考虑到:
核心实体关系特别注意了生鲜商品的特殊性:
java复制// 商品SKU实体
public class Product {
private Long id;
private String code; // 国际商品编码
private String name;
private Integer category; // 1-常温 2-冷藏 3-冷冻
private Integer shelfLife; // 保质期(天)
private Integer alertDays; // 临期预警天数
}
// 批次库存实体
public class Inventory {
private Long id;
private String batchNo; // 批次号
private LocalDate productionDate; // 生产日期
private LocalDate expiryDate; // 过期日期
private Integer status; // 1-正常 2-临期 3-过期
private Integer quantity;
private Long warehouseId; // 所属仓库
}
采购入库流程的防错设计:
生鲜商品最怕过期损耗,我们实现了三级预警机制:
java复制// 定时任务(每天凌晨2点执行)
@Scheduled(cron = "0 0 2 * * ?")
public void checkExpiry() {
// 临期预警(还剩3天)
updateStatusWhere("status = 1 AND DATEDIFF(expiry_date, NOW()) <= alert_days", 2);
// 过期处理
updateStatusWhere("status != 3 AND expiry_date < CURDATE()", 3);
// 生成预警报告
generateAlertReport();
}
配合前端展示优化:
vue复制<template>
<el-table :data="inventoryList">
<el-table-column prop="status" label="状态">
<template #default="{row}">
<el-tag :type="statusTagType[row.status]">
{{ statusText[row.status] }}
</el-tag>
</template>
</el-table-column>
</el-table>
</template>
<script>
export default {
data() {
return {
statusTagType: ['', 'success', 'warning', 'danger'],
statusText: ['', '正常', '临期', '过期']
}
}
}
</script>
不同于固定阈值预警,我们实现了动态算法:
java复制public class DynamicAlertService {
// 基于历史销售数据的动态计算
public Integer calculateAlertThreshold(Long productId) {
// 获取近30天日均销量
Double avgSales = salesMapper.selectAvgSales(productId, 30);
// 获取供应商平均送货周期
Integer deliveryDays = supplierMapper.selectAvgDeliveryDays(productId);
// 安全库存 = 日均销量 × (送货周期 + 缓冲天数2)
return (int) Math.ceil(avgSales * (deliveryDays + 2));
}
}
通过复合索引优化查询性能:
sql复制CREATE INDEX idx_trace ON inventory_log (
product_code,
batch_no,
operation_time DESC
) USING BTREE;
追溯接口实现:
java复制@GetMapping("/trace/{productCode}/{batchNo}")
public List<InventoryLog> traceBatch(
@PathVariable String productCode,
@PathVariable String batchNo) {
return logMapper.selectTrace(
productCode,
batchNo,
LocalDate.now().minusMonths(3) // 最多追溯3个月
);
}
对比了三种方案后选择乐观锁:
java复制@Transactional
public boolean deductInventory(Long inventoryId, Integer quantity) {
Inventory inventory = inventoryMapper.selectById(inventoryId);
if (inventory.getQuantity() < quantity) {
return false;
}
int rows = inventoryMapper.updateQuantity(
inventoryId,
quantity,
inventory.getVersion() // 版本号
);
return rows > 0;
}
对应的SQL语句:
xml复制<update id="updateQuantity">
UPDATE inventory
SET quantity = quantity - #{quantity},
version = version + 1
WHERE id = #{id} AND version = #{version}
</update>
采用多级缓存方案:
缓存更新策略特别注意了"先删缓存再更新DB"的顺序:
java复制public void updateProduct(Product product) {
// 1. 删除缓存
redisTemplate.delete("product:" + product.getId());
// 2. 更新数据库
productMapper.updateById(product);
// 3. 异步刷新本地缓存
cacheRefreshExecutor.execute(() -> {
localCache.put(product.getId(), product);
});
}
最低生产环境配置:
关键JVM参数:
bash复制java -jar -Xms2g -Xmx2g -XX:MaxMetaspaceSize=512m \
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 \
-Dspring.profiles.active=prod \
your-application.jar
从Excel迁移数据的注意事项:
使用Spring Batch的配置示例:
java复制@Bean
public Step excelImportStep() {
return stepBuilderFactory.get("excelImport")
.<ProductDTO, Product>chunk(100)
.reader(excelItemReader())
.processor(productItemProcessor())
.writer(productItemWriter())
.faultTolerant()
.skipLimit(100)
.skip(DataIntegrityViolationException.class)
.listener(new ImportSkipListener())
.build();
}
生鲜商品经常午夜到货,要特别注意时区问题:
java复制// 错误做法(会少算一天)
expiryDate = productionDate.plusDays(shelfLife);
// 正确做法(考虑时间分量)
expiryDate = productionDate.atStartOfDay()
.plusDays(shelfLife)
.toLocalDate();
初期实现的批量入库接口经常超时,优化方案:
xml复制<insert id="batchInsert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO inventory (...)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.field1}, #{item.field2}, ...)
</foreach>
</insert>
yaml复制spring:
datasource:
url: jdbc:mysql://...?rewriteBatchedStatements=true
系统目前已经稳定运行9个月,客户反馈最实用的三个功能是:
开发过程中最大的体会是:生鲜仓储系统要特别关注时效性和防错设计,任何一个数据错误都可能导致实际货物损失。建议大家在类似项目中一定要多到现场观察实际作业流程,不能只靠需求文档来开发。