ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Go 后台进销存做多租户,库存流水和 tenant_id 怎么一起验?

Go 后台进销存做多租户,库存流水和 tenant_id 怎么一起验? Go 后台进销存做多租户库存流水和 tenant_id 怎么一起验Go 后台进销存做多租户不能只给商品表加一个tenant_id也不能只看商品 CRUD 能不能跑。真正要验的是整条链路商品、库存余额、库存流水、出入库单、导出任务和批量操作都要按租户和权限读回。库存扣减还要放进事务否则很容易出现余额变了、流水没写或者低权限账号夹带别的租户商品 id。XYGo Admin 在这里不是现成进销存 SaaS而是一个 GoFrame Vue3 RBAC CRUD 生成器源码样本Issue #9 问到 SaaS、多租户和进销存Issue #8 问到多租户支持适合拿来说明后台生成器接到真实业务时哪些地方必须人工验。本文核验时点是 2026-08-23。当天 brief 读回仓库z312193608/xygo-adminversion.json为 1.4.9GitHub Release API latest 仍是 v1.4.6最新提交为3861551 release: v1.4.9 对象存储增强与 CDN 预览。这些只作为源码样本背景不把多租户进销存写成已经完整交付的成品能力。主查询和边界主查询Go 后台进销存做多租户库存流水和 tenant_id 怎么一起验我把它拆成 5 个开发时一定会碰到的问题进销存多租户只给商品表加tenant_id够不够库存余额、库存流水、出入库单和导出任务怎样一起带租户条件批量扣减库存时低权限账号能不能夹带别的租户商品 idGo 后端应该用 SQL、事务、权限中间件还是日志证明没有跨租户代码生成器能生成 CRUD但哪些库存流水规则必须人工补适用场景多门店、SaaS 进销存、商品库存台账、入库/出库/调拨记录、后台导出和运营管理端权限验收。不适用场景支付、财务凭证、云打印、WMS 仓储调度、完整成本核算和法务审计。那些系统要更重的单据模型、状态机和对账机制不能指望一个后台 CRUD 生成器兜住全部业务语义。第一件事别只给商品表加 tenant_id很多表一开始会这么设计CREATE TABLE erp_product ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT NOT NULL, sku VARCHAR(64) NOT NULL, name VARCHAR(128) NOT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, UNIQUE KEY uk_tenant_sku (tenant_id, sku) );商品表带租户看起来没问题。但库存系统真正出问题的地方往往不在商品表而在库存余额和流水之间。只隔离商品不隔离库存流水后面查账会很痛苦。一个更接近真实业务的拆法至少要有余额表和流水表CREATE TABLE erp_stock_balance ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT NOT NULL, product_id BIGINT NOT NULL, warehouse_id BIGINT NOT NULL, quantity INT NOT NULL DEFAULT 0, updated_at DATETIME NOT NULL, UNIQUE KEY uk_tenant_product_wh (tenant_id, product_id, warehouse_id) ); CREATE TABLE erp_inventory_flow ( id BIGINT PRIMARY KEY AUTO_INCREMENT, tenant_id BIGINT NOT NULL, product_id BIGINT NOT NULL, warehouse_id BIGINT NOT NULL, biz_no VARCHAR(64) NOT NULL, direction VARCHAR(16) NOT NULL, quantity INT NOT NULL, before_qty INT NOT NULL, after_qty INT NOT NULL, operator_id BIGINT NOT NULL, created_at DATETIME NOT NULL, UNIQUE KEY uk_tenant_biz_no (tenant_id, biz_no) );这里有两个点容易被 AI 或生成器漏掉。第一tenant_id要进余额表和流水表不能只在商品表里出现。第二流水的业务单号要按租户做幂等比如uk_tenant_biz_no否则重试一次出库接口就可能多扣一次库存。库存扣减要放在一个事务里库存扣减不是普通编辑表单。余额和流水必须一起成功不能一个成功一个失败。排障时我会先看这种伪代码是不是在一个事务里func DeductStock(ctx context.Context, in DeductInput) error { user : CurrentUser(ctx) if user nil { return ErrUnauthorized } if !user.HasPermission(inventory:deduct) { return ErrForbidden } return db.Transaction(ctx, func(tx Tx) error { balance, err : tx.QueryStockForUpdate(ctx, user.TenantID, in.ProductID, in.WarehouseID) if err ! nil { return err } if balance.Quantity in.Quantity { return ErrStockNotEnough } nextQty : balance.Quantity - in.Quantity if err : tx.UpdateStock(ctx, user.TenantID, in.ProductID, in.WarehouseID, nextQty); err ! nil { return err } return tx.InsertInventoryFlow(ctx, InventoryFlow{ TenantID: user.TenantID, ProductID: in.ProductID, WarehouseID: in.WarehouseID, BizNo: in.BizNo, Direction: out, Quantity: in.Quantity, BeforeQty: balance.Quantity, AfterQty: nextQty, OperatorID: user.ID, }) }) }QueryStockForUpdate这个名字只是示例重点是语义读余额时要锁住当前租户、当前商品、当前仓库这一行。更新余额和写流水必须在同一个事务里。没有事务的库存扣减压力一上来就会出现负库存、重复扣减或流水对不上。只测管理员成功扣减还不够后台权限最容易被误判的地方是“页面按钮隐藏了”。页面按钮隐藏只能减少误点不能当安全边界。接口必须自己判断权限和租户。可以用这组 curl 做最小验收# 1. 未登录应该是 401 curl -i -X POST https://api.example.com/admin/inventory/deduct \ -H Content-Type: application/json \ -d {product_id:1001,warehouse_id:1,quantity:2,biz_no:OUT-001} # 2. 登录了但没有库存扣减权限应该是 403 curl -i -X POST https://api.example.com/admin/inventory/deduct \ -H Authorization: Bearer low_permission_token \ -H Content-Type: application/json \ -d {product_id:1001,warehouse_id:1,quantity:2,biz_no:OUT-001} # 3. 有权限但夹带别的租户 product_id不能扣到数据 curl -i -X POST https://api.example.com/admin/inventory/deduct \ -H Authorization: Bearer tenant_a_admin_token \ -H Content-Type: application/json \ -d {product_id:9001,warehouse_id:1,quantity:2,biz_no:OUT-002} # 4. 同一个 biz_no 重试应该幂等失败或返回已有结果不能重复扣减 curl -i -X POST https://api.example.com/admin/inventory/deduct \ -H Authorization: Bearer tenant_a_admin_token \ -H Content-Type: application/json \ -d {product_id:1001,warehouse_id:1,quantity:2,biz_no:OUT-001}这四个结果比“管理员点按钮成功”重要。尤其是第三个很多后台接口只按product_id查商品没有同时带tenant_id前端正常用时看不出来恶意或误操作请求一来就穿透。批量操作和导出要单独看进销存后台经常有批量调整、批量出库、导出库存流水。这里不要写成SELECT * FROM erp_inventory_flow WHERE product_id IN (1001, 1002, 9001) ORDER BY id DESC;至少要带租户条件必要时还要带仓库权限、时间范围和操作权限SELECT id, tenant_id, product_id, warehouse_id, biz_no, direction, quantity, before_qty, after_qty, operator_id, created_at FROM erp_inventory_flow WHERE tenant_id ? AND product_id IN (?, ?, ?) AND warehouse_id IN (?, ?) AND created_at ? AND created_at ? ORDER BY id DESC;批量扣减也一样不要只把前端传来的 id 拼进IN (...)。先按当前登录人的租户和仓库权限筛一遍再比较请求数量和实际命中数量。实际命中少了要能返回原因至少要在日志里留痕。一条库存扣减日志可以长这样{ event: admin.inventory.deduct, tenant_id: 1, user_id: 17, permission: inventory:deduct, product_id: 1001, warehouse_id: 1, biz_no: OUT-001, quantity: 2, before_qty: 30, after_qty: 28, request_id: req_20260823_001 }日志不是为了好看。线上出现“库存少了两件”时能不能按tenant_id biz_no request_id找回完整链路决定了排障是 5 分钟还是半天。代码生成器能帮什么不能帮什么代码生成器能把商品、仓库、库存余额、流水这些表快速生成列表、表单、基础接口和权限码。它还可以把验收点写进模块清单inventory:deduct 权限码是否生成 扣减按钮是否绑定 inventory:deduct POST /admin/inventory/deduct 是否走后端权限中间件 余额 SQL 是否带 tenant_id warehouse_id 流水表是否写入 before_qty / after_qty / biz_no 重复 biz_no 是否禁止重复扣减 导出接口是否带 tenant_id、仓库权限和时间范围 日志是否包含 user_id、tenant_id、permission、biz_no、request_id但生成器不能替你决定业务语义。库存不足时是禁止出库、允许负库存还是进入审核队列调拨单是否要两阶段确认盘点差异能不能直接改余额这些都不是 CRUD 生成器该自动猜的。这也是我把 XYGo Admin 只放在证据位置的原因。它的server/internal/logic/gencodes/generate.go可以作为 CRUD 生成器主逻辑入口server/internal/middleware/admin_permission.go可以作为后台 API 权限兜底样本。项目关系说清楚就够了它是 GoFrame Vue3 RBAC CRUD 生成器组合不是进销存成品。仓库入口放一个即可GitHub 仓库。上线前再做一次对账扣减接口测完以后最好再跑一组对账 SQL。它不复杂但能很快发现“余额和流水不是一回事”的问题。SELECT b.tenant_id, b.product_id, b.warehouse_id, b.quantity AS balance_qty, COALESCE(SUM( CASE f.direction WHEN in THEN f.quantity WHEN out THEN -f.quantity ELSE 0 END ), 0) AS flow_qty FROM erp_stock_balance b LEFT JOIN erp_inventory_flow f ON f.tenant_id b.tenant_id AND f.product_id b.product_id AND f.warehouse_id b.warehouse_id WHERE b.tenant_id ? GROUP BY b.tenant_id, b.product_id, b.warehouse_id, b.quantity HAVING b.quantity flow_qty;这条 SQL 不一定能直接搬到生产库因为真实系统可能有期初库存、盘点差异、冻结库存和在途库存。但排障思路是一样的余额表是当前状态流水表是变化过程两边要能解释得上。上线前至少抽一个租户、一个仓库、几条商品做 smoke test。跑不通时不要急着改页面先看单据、事务、幂等键和权限范围。还有一个容易忽略的点导出任务也要写入审计日志。很多系统列表接口做了权限导出接口却另走一套逻辑。用户在页面只能看到本租户库存点导出却拿到了全库 CSV这种事故不是前端问题是后端查询条件和权限中间件没有复用。导出接口至少要记录tenant_id、筛选条件、导出人、导出行数和文件 id后面有人追数据泄露时才有证据。最后给一张验收表验收点怎么测常见漏项商品表租户查tenant_id sku唯一约束只给商品表隔离余额和流水没隔离库存余额并发扣减后查余额没有事务或没有行锁库存流水每次扣减都有 before/after只改余额不写流水幂等单号同一个biz_no重试重复扣减RBAC低权限 token 直接 curl页面隐藏接口放行跨租户租户 A 夹带租户 B 商品 id少了tenant_id条件导出导出流水带时间和仓库范围导出接口绕过列表权限日志查tenant_id biz_no request_id只有“操作成功”如果你只是在做一个内部小仓库可能不需要这么重的模型。但只要系统开始往 SaaS、多门店或进销存靠tenant_id就不能只出现在商品表里。库存余额、流水、出入库单、批量操作、导出和日志都要一起验。否则第一版 CRUD 看起来很快真正上线后返工的一定是库存和权限边界。
返回列表