VibeCoding 实战:用 Qoder 从零开发 Java Spring Boot WMS 系统(八)
第7章 出库管理模块:镜像流程 + 防超卖硬约束
本章目标:开发出库模块。它的结构和入库高度对称(状态机 + 一对多明细 + 执行事务),所以本章重点练两件事:① 如何复用第6章的模式快速开发(VibeCoding 提效关键),② 出库独有的难点——防超卖(库存不足必须拒绝,且要在并发下也守得住)。
学习目标
- 会用"参照已有模块"的提示词方式,让 AI 快速生成对称功能
- 理解并实现出库扣库存的"防超卖"校验
- 认识并发扣减的坑,学会用带条件的 UPDATE 保证库存不为负
- 掌握"拣货"这一出库特有的执行语义(从哪个储位取货)
7.1 用"参照物"提示词,事半功倍
入库已经把"状态机 + 一对多 + 执行事务"这套结构跑通了。出库不要从零描述,而是让 AI 参照入库来生成,这样风格统一、还省时——这是 VibeCoding 里非常重要的提效技巧:给 AI 一个已验证的范例,比写一堆新描述更可靠。
提示词
开发「出库管理」模块。请【参照已完成的入库模块 inbound 的代码结构、分层、状态机风格】来实现,保持一致。
# 引用 docs/api.md 出库接口、#项目规则。
单据结构(对应表):
- wms_outbound_order 出库单主表(单号CK+日期+流水、仓库、状态、制单人、审核人)
- wms_outbound_item 明细(商品、计划数量、已出库数量)
- wms_outbound_record 出库执行记录(拣货:商品、储位、实际拣货数量)
状态机:与入库相同 DRAFT→PENDING→APPROVED→COMPLETED,含 REJECTED/CANCELLED,规则一致。
创建/编辑/提交/审核/驳回/作废:与入库同样的实现方式,把单据前缀改成 CK。
先只生成"主表+明细的创建、分页、详情、状态流转"这部分(对标第6章 6.2、6.3),
mvnw compile 通过后自测一遍创建→提交→审核,贴结果。执行扣库存部分下一轮再做。
预期结果
- 出库的创建、分页、详情、状态流转完成,风格与入库一致
- 自测:创建含明细出库单→提交→审核通过
注意事项
- ⚠️ "参照入库实现"要显式引用入库的代码。用
#把InboundOrderService、InboundController等喂给 AI,它才能真正照着写,而不是凭空又造一套不同风格。 - 💡 把"执行扣库存"单独拆到下一轮,是因为它风险最高(超卖)。先让对称的、低风险的部分落地,再集中精力攻难点。
7.2 核心难点:执行出库的"防超卖"
出库执行 = 拣货。仓管员指定"从哪个储位、取哪个商品、取多少",系统在事务里扣减库存并记流水。与入库最大的不同:扣库存前必须确认库存够,且要防止并发下"两个人同时把同一份库存都抢走"。
提示词
实现出库执行接口 POST /api/outbound/{id}/execute,入参 records[{goodsId, locationId, qty}]。
仅 APPROVED 可执行。在【单个事务】内对每条 record:
1. 防超卖校验(关键):用一条带条件的 UPDATE 原子扣减库存——
UPDATE wms_inventory SET qty = qty - #{qty}
WHERE goods_id=#{goodsId} AND location_id=#{locationId} AND qty >= #{qty}
通过"影响行数是否为 1"判断是否扣减成功;影响行数为 0 说明库存不足或储位无货,
立即抛 BusinessException 并回滚整单。不要用"先 SELECT 查数量再判断后 UPDATE"的写法(有并发超卖风险)。
2. 写 wms_inventory_log:变动类型=OUT、变动数量、变动前/后数量、关联出库单号
(变动前数量可在扣减成功后再查一次,或在 SQL 中处理,说明你的取舍)
3. 累加 wms_outbound_item 已出库数量
全部明细处理完:已出库>=计划 则 COMPLETED,否则保持 APPROVED(支持部分出库)。
@Transactional(rollbackFor=Exception.class)。
校验:拣货数量为正整数;出库明细计划数量不得超过该商品在该仓可用库存(创建或审核时给提示,最终以执行时扣减校验为准)。
预期结果与关键验证
- 正常:库存充足时出库成功,库存正确减少、流水为 OUT
- 库存不足:出库失败、报"库存不足"、且整单回滚(前面已扣的明细也要回滚回去,库存不变脏)
为什么这是重点(务必理解,不只是照做)
错误写法(先查后扣):
if (select qty >= 需求) { update qty = qty - 需求 }
并发场景:两个线程都 select 到 qty=10,都判断"够",都 update -8,
最终 qty = -6 —— 超卖!库存变负数。
正确写法(条件更新):
update ... set qty = qty - 需求 where id=? and qty >= 需求
数据库行锁保证:只有一个能更新成功,另一个 where 条件不满足、影响行数=0 → 判定失败。
这个"用一个带条件的 UPDATE + 判断影响行数"的技巧,是库存/余额/秒杀这类系统的通用防线。AI 不一定会主动这么写(很多默认给"先查后扣"),所以你要在提示词里点名要求。
注意事项
- ⚠️ 一定测并发或至少测"库存不够":让 AI 演示"对库存只有100的商品发起出库200",确认被拒且库存不变。
- ⚠️
wms_inventory.qty建表时是否该加CHECK (qty>=0)或无符号:让 AI 顺带说明数据库层能否再加一道防线(教学可提,不强求)。
7.3 端到端主链路自测(打通"出"这条腿)
提示词
用 curl 完整跑一遍"入库→出库"闭环(复用第6章已入库的数据):
1. 登录
2. 确认某商品G1在储位L1当前库存(假设=100)
3. 创建出库单:G1 计划出库 30
4. 提交→审核通过→执行出库:从 L1 拣货 30
5. 查库存:G1@L1 应变为 70
6. 查流水:新增一条 OUT 记录,变动前100、变动后70、关联出库单号
7. 查出库单:COMPLETED
再额外验证一次"超卖保护":对现在库存70的 G1 发起出库100,应失败且库存仍为70。
每步贴关键结果。
第 5、6 步数字对,第 7 步(额外)失败且库存不动 —— 出库模块就合格了。
7.4 前端:出库单列表 + 创建 + 拣货页
提示词
参照入库前端页面风格,生成出库模块前端(wms-web):
1. src/api/outbound.ts
2. OutboundList.vue:查询区(单号/状态/仓库) + 表格 + 分页,操作列按状态动态显示按钮(同入库规则)
3. OutboundForm.vue:创建出库单,动态明细行(选商品+计划数量)
4. OutboundExecute.vue:录入"商品+储位+拣货数量"执行出库;储位用仓库→库区→储位级联;
当库存不足时,接口会返回失败,页面要正确提示"库存不足"
5. 加菜单「出库管理」,npm run dev 验证 UI 流程。
注意事项
- ⚠️ 拣货页要友好展示后端返回的"库存不足"错误(不要白屏/静默失败)。让 AI 在响应拦截里把失败 message 用
ElMessage.error弹出。 - 💡 拣货时可加个"该商品当前各储位库存"的只读提示,方便仓管选位——这是体验优化,能加则加,不加不影响主流程。
7.5 提交
出库模块后端+前端完成,7.3 闭环与超卖保护自测通过。
git add . && git commit -m "feat: 出库模块完成(复用入库模式+防超卖条件扣减+拣货事务+流水+前端)"
本章检查清单
- [ ] 出库创建/分页/详情/状态流转与入库风格一致
- [ ] 执行出库用"条件 UPDATE + 判断影响行数"防超卖,非"先查后扣"
- [ ] 库存不足时整单回滚,库存不变负
- [ ] 7.3 入库→出库闭环数字正确(100→70),流水 OUT 正确
- [ ] 前端拣货页能提示"库存不足"
- [ ] 已提交 Git
出入库双腿打通,进入 第8章 库存与盘点模块,给库存做"账实核对"。
常见问题
Q:出库和入库这么像,为什么不一次让 AI 两个都生成? A:正因为像,才更要分开验证——出库多了"防超卖"这个入库没有的致命点。一次生成两个,很容易把入库那套"只加不减"的逻辑糊弄过去,超卖 bug 就被掩盖了。分开做,才能针对性地攻每个模块独有风险。
Q:AI 坚持用"先查再改"怎么办?
A:明确拒绝并说明理由:"先查再改在并发下会超卖,必须用带 qty >= #{qty} 条件的单条 UPDATE 并判断影响行数。请重写。" 把它当成 code review,训练你"识别坏模式"的能力。
Q:变动前/后数量在流水里怎么取准确? A:条件 UPDATE 扣减后,"变动后数量"是确定的(扣减成功即知),"变动前"可由 变动后 + 本次数量 反推,或再查一次。让 AI 说明它取的方案、并发下是否准确——这本身就是很好的技术讨论。