CYBERSWAT / 开发部自由练习 · v0.4.1

U05 · 两个人同时借同一件物品,会发生什么

v0.4 · 先修:手册 16–22,尤其关系、事务和资源授权。工作台。参考:Odin Inventory。借用扩展为本部新编。

用虚构演示灯、支架和线缆建立物品库。先做分类和物品,再把“是否借出”变成一个有所有者、会冲突的动作。不要导入真实资产台账。

动手路线:每一步都有文件和结果

先完成 统一启动与恢复手册 的首次准备。以下命令均在自己的练习根目录执行;原文中的 api/web/desktop 概念对应本节列出的现成文件,不需要猜测如何搭空项目。

  1. 打开 u05/starter/model.ts,按 borrow → giveBack → canDeleteCategory 的顺序逐个实现。下面第1节起解释规则;每完成一个函数就重跑样例/测试。
  2. 执行以下三条,分别核对样例、行为和类型。未完成的 TODO 应失败,全部实现后测试应为 fail 0:
node tools/try.mjs u05
node tools/check.mjs u05
npm run check
  1. 打开 runtime/src/u05.ts、runtime/web/u05.js,找到对自己规则函数的调用。先用一句话记录输入从哪里来、结果写到哪里;读不懂的语法回到本节先修对应章节。

  2. 先运行 node tools/lab.mjs db up,等待迁移完成。构建并启动自己的作品:

node tools/build-app.mjs u05
node tools/lab.mjs start u05
  1. 实际操作:登录 Alice→建立分类→添加灯→借用;切到 Bob 再归还应403。接口验收会并发借用,实际成功者不固定;重新归还空闲物品应409。

在第二个终端运行 node tools/probe.mjs u05。脚本会添加合成数据并报告每组检查;不清空自己的数据。 首次通过后停止并重新启动服务,再运行 node tools/probe.mjs u05 --resume;数据库单元同时按统一手册重启数据库。

  1. 解释与再做一次:找到条件更新中的 borrower:null;若删除这个条件,竞争请求会怎样? 在自己的 LEARNING.md 先写答案,再选本节末尾的变式,列一个新失败例子后修改。保持原用例通过。仅跑通配套界面、没有完成规则与解释,记录为“参考演示已观察”,不记录为自己完成作品。

卡在规则时按“函数输入→分支条件→返回值→参考实现”逐级看提示;卡在运行时按 故障表 定位。若想单独观察参考成品,在构建命令后加 --reference;页面会明确标出参考演示。观察完再去掉该参数重建,继续自己的 starter。

1. 从状态转换开始

打开 u05/starter/model.ts,Item.borrower 为 null 表示可借,非空表示某个合成身份占用。完成 borrow:确认身份、找到物品、确认空闲,再返回新状态。giveBack 必须由当前借用者执行;空闲物品再归还也是失败。

运行样例与测试:Alice 借到灯,Bob 不能借,也不能归还。这里的 actor 只是纯函数参数。实际服务必须从认证结果取身份,不允许客户端自由填写 actor。

2. 画出表,再建分类和物品

在作品 README 画 Category→Item 的一对多关系,列本版字段 Category.id、Item.id/category/borrower。category 为分类外键,borrower 为当前借用者;展示名称与编辑功能留作扩展,不要求猜测不存在的字段。打开已提供的 Prisma schema 与迁移确认外键和编号唯一。

本实验规定:分类下仍有物品时拒绝删除,返回 409。canDeleteCategory 只演示规则,实际仍需数据库外键/事务保证。先读物品列表确认分类引用,再用验收脚本直接删除分类;不能只禁用页面按钮。

3. 接入借用与归还接口

写契约:POST /api/items/:id/borrow 成功 200;已借出 409;未登录 401;不存在 404。POST /api/items/:id/return 只允许当前借用者,其他身份 403,已经空闲 409。请求不接收 borrowerId,服务从身份中取得用户。

控制器做输入验证;服务中区分 NOT_FOUND、FORBIDDEN 与 CONFLICT。核心 giveBack 对重复归还给通用拒绝,接口要先读取资源状态,按上述契约分类,不能把函数错误名机械映射成全部 HTTP 状态。

4. 让数据库决定谁先成功

借用操作必须是条件更新:条件同时包含物品 id 与 borrowerId 为空;只有受影响行数为 1 才成功。若还要插入借用日志,把更新与日志写入同一事务。不能先读空闲,再无条件更新。

在 probe.mjs 用两个合成账号令牌,通过 Promise.all 同时发送借用请求。预期一方 200、另一方 409,数据库只有一个实际借用者;不要指定一定 Alice 赢,先到顺序并不固定。失败方不能留下“借用成功”的日志。

5. 接到页面并复查服务端边界

按手册 16 的 Vue 页面显示分类、物品与状态。点击期间显示处理中,成功后重新取服务端状态,失败显示原因。用 Alice 借用后,切换 Bob;即使手动构造请求,也不能归还 Alice 的物品。隐藏按钮只是界面提示。

重启 API 与数据库,仍应保留借用者;归还后才重新可借。数据库故障时页面不能乐观地永久显示借用成功。

6. 自检、变式与暂停

记录分类被引用、无身份、物品不存在、同时借用、他人归还、重复归还、重启持久化七组结果。核心测试只覆盖顺序状态变换,不覆盖数据库并发和真实身份。

独立变式:增加维修状态。先画清“维修中是否可借、借出时是否允许转维修”,写冲突预期后实现迁移与测试。暂停前提交当前迁移并保存合成数据准备脚本,下一次从 README 启动,不手工改生产数据。下一步 U09 或 U10。