
很多食堂采购系统做不稳定,不是界面问题,而是底层数据结构和库存算法没设计好。

常见翻车现场你一定见过:
说白了:数据库结构不规范 + 库存算法太粗糙。
真正可商用的食堂采购系统源码,核心就两件事:
第一,表结构要可追溯 第二,库存算法要强一致
下面我用一套可直接落地的设计方案,把关键实现从表结构到代码完整拆开讲清楚。
先统一一个标准流程:
采购申请 → 采购单 → 入库 → 库存累加 领料/消耗 → 出库 → 库存扣减 盘点 → 差异调整 月底 → 成本核算 + 对账
所以数据库至少要支撑:
记住一句话:
库存 = 汇总结果 流水 = 真正依据
库存表只是“缓存”,库存流水才是“真相”。
技术栈示例:
SpringBoot + MySQL + MyBatis
CREATE TABLE goods (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
category_id BIGINT,
unit VARCHAR(20),
spec VARCHAR(100),
enabled TINYINT DEFAULT 1,
created_at DATETIME
);作用:基础物料信息
示例:
CREATE TABLE supplier (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(200),
contact VARCHAR(50),
phone VARCHAR(20),
status TINYINT DEFAULT 1
);CREATE TABLE warehouse (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
location VARCHAR(200)
);支持:
高频查询表
CREATE TABLE inventory (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
goods_id BIGINT,
warehouse_id BIGINT,
quantity DECIMAL(10,2) DEFAULT 0,
amount DECIMAL(12,2) DEFAULT 0,
version INT DEFAULT 0,
UNIQUE KEY uk_goods_wh(goods_id, warehouse_id)
);关键字段:
这是最重要的一张表
CREATE TABLE inventory_log (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
goods_id BIGINT,
warehouse_id BIGINT,
type VARCHAR(20),
quantity DECIMAL(10,2),
price DECIMAL(10,2),
amount DECIMAL(12,2),
ref_no VARCHAR(50),
created_at DATETIME
);type:
所有库存变化必须写这张表。
很多人直接:
update inventory set quantity = quantity - 10
这种做法必出事故。
正确思路:
库存变更三步走:
① 写库存流水 ② 扣减库存(带锁) ③ 校验结果

@Transactional
public void stockIn(Long goodsId, Long warehouseId,
BigDecimal qty, BigDecimal price) {
BigDecimal amount = qty.multiply(price);
// 1 写流水
inventoryLogMapper.insert(new InventoryLog(
goodsId, warehouseId, "IN", qty, price, amount
));
// 2 更新库存
inventoryMapper.addStock(goodsId, warehouseId, qty, amount);
}UPDATE inventory
SET quantity = quantity + #{qty},
amount = amount + #{amount},
version = version + 1
WHERE goods_id = #{goodsId}
AND warehouse_id = #{warehouseId};重点来了。
出库一定要防:
推荐方案:
乐观锁 + 条件扣减
UPDATE inventory
SET quantity = quantity - #{qty},
amount = amount - #{amount},
version = version + 1
WHERE goods_id = #{goodsId}
AND warehouse_id = #{warehouseId}
AND quantity >= #{qty}
AND version = #{version};如果影响行数为 0 → 扣减失败。
@Transactional
public void stockOut(Long goodsId, Long warehouseId,
BigDecimal qty) {
Inventory inv = inventoryMapper.select(goodsId, warehouseId);
if (inv.getQuantity().compareTo(qty) < 0) {
throw new RuntimeException("库存不足");
}
BigDecimal avgPrice =
inv.getAmount().divide(inv.getQuantity(), 2, RoundingMode.HALF_UP);
BigDecimal amount = avgPrice.multiply(qty);
int rows = inventoryMapper.reduceStock(
goodsId, warehouseId, qty, amount, inv.getVersion());
if (rows == 0) {
throw new RuntimeException("库存并发冲突,请重试");
}
inventoryLogMapper.insert(
new InventoryLog(goodsId, warehouseId, "OUT", qty, avgPrice, amount)
);
}食堂场景推荐:
加权平均法
原因:
公式:
新平均价 = (旧金额 + 入库金额) ÷ (旧数量 + 入库数量)
SQL 示例:
amount / quantity直接算即可。
如果是多校区或集团食堂,订单并发高时:
必须加:
1 分库分表(按仓库拆) 2 Redis库存缓存 3 批量入库写入 4 异步流水日志
否则库存表会成为瓶颈。

如果你正在做食堂采购系统源码,记住这三条铁律:
库存只查 inventory 对账只查 inventory_log 任何库存变化必须走事务
这样系统跑几年都不会乱。
真正商用级系统,拼的不是功能多,而是:
数据稳定 + 算法可靠 + 并发安全
底层打牢,上层再怎么扩展都不怕。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。