第6组 · 杜美芳 · 第1次评审详情 - sebestp/coursewise GitHub Wiki

杜美芳 · 第1次评审详情(第6组 · 组员)

基本信息

  • 报告期次:第1次 | 报告日期:2026-10-08 | 项目组号:第6组 | 项目名称:在线购物系统

工作任务与成果物

成果物 · 《在线购物系统》需求规格说明书

《在线购物系统》需求规格说明书
一、 项目背景与目标
  • 项目背景:本项目为《软件工程实践》课程第一阶段(I)的挑战升级项目。课程以“大模型驱动开发”为背景,强调“定义问题”与“验收结果”。
  • 项目目标:构建一个单卖家、免注册、线下交易的极简在线购物系统(MVP),重点考察需求分析与商品状态流转控制能力。
  • 项目范围:仅开发 Web 网页端,包含卖家后台与买家前台。不包含 Android、iOS、微信小程序客户端。不提供线上支付、物流、商品评价、消息通知功能。
二、 用户角色定义
  1. 卖家:系统的唯一管理员(固定单账号)。使用“用户名+密码”登录后台,可修改密码。负责发布商品、处理交易、查看历史记录。
  2. 买家:免注册用户,无需账号。可匿名浏览商品,提交购买意向时仅需填写姓名和联系电话,凭系统生成的“口令码”管理自己的意向。
三、 子系统划分

根据软件工程模块化设计原则,本系统划分为四个子系统:

  1. 卖家管理子系统:负责卖家登录鉴权、密码修改、商品发布、手动冻结/解冻、交易结果标记、历史商品查看。
  2. 买家交易子系统:负责商品前台展示、买家意向提交、口令码生成与展示、买家意向管理(查询/修改/撤销)。
  3. 交易状态流转子系统(核心):负责商品状态机控制、排队队列管理(先到先得)、交易失败后的自动递补、并发状态校验。
  4. 数据与基础设施子系统:负责数据库表结构设计、图片上传与存储、API 联调、数据持久化。
四、 核心业务规则
1. 商品发布与修改规则
  • 单商品限制:同一时间,系统仅允许 一件 商品处于“在售”状态。要发布新品,必须先将当前商品“交易成功(下架)”或“手动下架”。
  • 不可修改:商品一经发布,名称、描述、图片、价格 均不可修改。如需调整,只能下架后重新发布。
  • 数据约束:
    • 名称:必填,长度无限制。
    • 描述:选填,可为空,长度无限制。
    • 图片:选填,最多上传 1 张主图,格式 JPG/PNG,单张不超过 5MB;不上传则展示占位图。
    • 价格:必填,人民币,保留 2 位小数;必须大于 0,不能为 0 或负数,无上限。
2. 商品状态定义
  • 在售:商品正常展示,可接收买家购买意向。
  • 已冻结(交易中):分为自动冻结与手动冻结,二者共用同一状态。冻结后商品详情仍可浏览,但不再接收新购买意向,买家页面统一提示“商品交易中”。
    • 自动冻结:队首意向进入交易流程时,系统自动触发冻结。
    • 手动冻结:卖家后台主动操作,临时停售商品。
    • 交易中的商品禁止手动解冻,手动解冻仅允许用于卖家手动冻结的商品。
  • 已下架:
    • 路径1:交易成功,商品从冻结直接转为下架,进入历史商品。
    • 路径2:卖家在售状态下手动下架商品,进入历史商品。
3. 排队与“先到先得”机制
  • 排队触发:买家提交购买意向时进入队列,并非“有人提交就冻结商品”。
  • 排序原则:严格按提交时间正序(先到先得)。卖家 无权 手动挑选交易对象。
  • 冻结时机:商品冻结发生在 卖家与队首买家“进入交易” 的那一刻。在此之前,商品仍处于“在售”状态,可继续接收新意向。
  • 队列封闭:一旦进入“已冻结(交易中)”状态,不再接收任何新意向。
  • 自动递补:当队首买家交易失败(或撤销),队列后面的买家 自动按顺序递补 进入交易,商品再次自动冻结,无需卖家手动操作。若队列已空,商品恢复“在售”状态等待新买家。
4. 进入交易的触发机制
  • 触发方:由卖家在后台手动触发。
  • 触发条件:
    1. 商品当前状态为“在售”;
    2. 排队队列中存在至少一位买家(即已提交购买意向);
    3. 队列中无正在进行的交易(上一次交易已结束)。
  • 触发动作:
    1. 卖家在后台的“意向购买人列表”中,点击“开始交易”按钮;
    2. 系统自动将队列中提交时间最早的买家状态变更为“已进入交易”;
    3. 商品状态从“在售”自动变更为“已冻结(交易中)”;
    4. 队列封闭,不再接收任何新的购买意向。
  • 备注:若队列中有多名买家,卖家只能与队首(最早提交)的买家开始交易,无权挑选其他买家。这保证了“先到先得”的公平性。
5. 口令码机制(买家唯一凭证)
  • 生成与展示:买家提交意向后,系统 即时生成 唯一口令码,仅显示一次 在提交成功页面,提醒买家自行保存。不发送短信,无找回功能。
  • 生命周期:
    • 生效:提交意向成功即生效。
    • 失效条件:① 意向彻底终结(成功/作废/撤销);② 商品下架。失效后,买家无法再查询、修改或撤销。
    • 特殊说明:交易失败后若卖家选择“重新排队”,该意向并未终结,口令码继续有效,买家可凭原口令码查询新的排队位次。
  • 权限范围:买家凭口令码可做三件事——① 查询排队位次/状态;② 修改姓名和电话(不影响排队次序);③ 撤销意向(仅限排队阶段,一旦进入交易状态,买家无法自行撤销,只能由卖家标记交易结果)。
6. 交易结果与恢复机制
  • 判定方式:交易是否成功,由 卖家在后台手动标记(成功/失败),系统不自动判断。
  • 标记成功:商品直接下架,进入历史商品。操作不可撤销,一旦标记永久生效。
  • 标记失败:商品立即自动恢复为“在售”,中间无延迟。卖家处置当前意向(作废/重新排队),随后系统检查队列:若有剩余意向,自动递补队首进入交易,商品再次冻结;若无剩余意向,商品保持“在售”状态等待新买家。
  • 买家撤销与商品状态无关:买家撤销意向只是从队列中退出,后面位次前移。商品状态 不因买家撤销而改变,只由卖家标记交易结果决定。
五、 功能需求清单
1. 卖家端(后台管理)
模块 需求描述 备注
登录 卖家使用固定“用户名+密码”登录后台,不提供注册和退出登录功能。 密码加密存储。
修改密码 需验证原密码,新密码长度≥8位。
发布商品 填写名称、描述、图片、价格。同一时间只能有一件在售。 需校验图片格式/大小、价格精度。
手动冻结/解冻 对“在售”商品可手动冻结,对“已冻结(无交易中)”商品可手动解冻。 禁止在交易中手动解冻。
查看意向 查看当前有效意向列表,按提交时间排序,显示姓名、电话、提交时间、交易结果。 不可修改买家意向。
确认交易结果 在线下交易完成后,手动标记“交易成功”或“交易失败”。 标记成功即商品下架;标记失败即商品自动恢复在售并递补。
查看历史商品 查看所有已下架商品,每个商品下挂完整的意向购买人流水。 历史记录只读,持久化保存,重启不丢。
2. 买家端(前台展示)
模块 需求描述 备注
浏览商品 无需登录即可查看当前在售商品的名称、描述、图片、价格。 无商品时展示“暂无商品在售”占位图。
提交意向 点击“我要购买”,填写姓名和联系电话(必填),提交后进入排队队列。 提交成功后展示口令码。
管理意向 凭口令码查询排队位次、修改姓名/电话(不影响位次)、撤销意向。 撤销仅限排队阶段。
查看状态 商品冻结期间,买家仍可浏览详情,但购买按钮置灰,提示“商品交易中”。
3. 后端接口设计初稿(商品模块)
接口名称 请求方式 接口地址 接口说明
发布商品 POST /api/product/publish 卖家发布商品,商品进入“在售”状态。
手动冻结商品 POST /api/product/freeze 卖家手动冻结在售商品,用于临时停售。
手动解冻商品 POST /api/product/unfreeze 卖家手动解冻自己冻结的商品(禁止解冻交易中的商品)。
确认交易成功 POST /api/product/confirm-sold 卖家标记交易成功,商品直接下架,进入历史记录。
确认交易失败 POST /api/product/confirm-failed 卖家标记交易失败,商品自动恢复在售并递补下一位买家。
六、 状态机定义
1. 商品状态流转图
stateDiagram-v2
    [*] --> 在售: 卖家发布商品
    在售 --> 已冻结: 队首意向进入交易(自动冻结)/ 卖家手动冻结
    在售 --> 已下架: 卖家手动下架
    已冻结 --> 已下架: 卖家标记交易成功(商品永久撤下)
    已冻结 --> 在售: 卖家标记交易失败(商品自动恢复在售)
    已下架 --> [*]
Loading

注: 交易中的商品禁止卖家手动解冻;手动解冻仅适用于卖家自己手动冻结的商品。

2. 买家意向生命周期图
stateDiagram-v2
    [*] --> 排队中: 买家提交意向
    排队中 --> 已进入交易: 轮到该买家(队首进入交易)
    排队中 --> 已撤销: 买家凭口令码撤销
    已进入交易 --> 交易成功: 卖家标记成功(商品下架)
    已进入交易 --> 交易失败: 卖家标记失败(商品恢复在售)
    交易失败 --> 重新排队中: 卖家选择“重新排队”(排到队尾)
    交易失败 --> 已作废: 卖家选择“作废”
    重新排队中 --> 已进入交易: 再次轮到该买家
    重新排队中 --> 已撤销: 买家凭口令码撤销
    重新排队中 --> 已作废: 卖家选择“作废”
    交易成功 --> [*]
    已撤销 --> [*]
    已作废 --> [*]
Loading

注: 交易失败时,若卖家选择“作废”,该买家口令码失效;若选择“重新排队”,口令码继续有效(排到队尾)。

3. 系统主流程图
flowchart TD
    A[卖家登录] --> B[发布商品]
    B --> C[商品进入在售]
    C --> D[买家提交意向]
    D --> E[进入排队队列]
    E --> F[卖家与队列第一位进入交易]
    F --> G[商品自动冻结,队列封口]
    G --> H[线下交易]
    H --> I{卖家标记结果}
    
    I -->|交易成功| J[商品下架,口令码失效]
    J --> K[进入历史记录]
    
    I -->|交易失败| L[商品恢复在售]
    L --> M{队列是否有人}
    M -->|有人| N[系统自动让下一位递补]
    N --> F
    M -->|无人| C
    
    L --> O[卖家决定失败者:作废 或 重新排队]
Loading

注: 本图仅覆盖交易主流程。卖家后台的“手动冻结/解冻”功能独立于本流程,不影响自动冻结逻辑。

4. 意向终态(历史记录中)
  • 成功:买到商品(全系统最多一个)。
  • 撤销:买家主动撤销。
  • 失败:其余所有情况(含交易失败被作废、被挤掉没轮上、重新排队后仍未成功等)。
  • 注:不再单独列出“重新排队”作为终态,通过“失败次数”可反推。
七、 非功能性需求与边界规则
  1. 并发处理:严格同一时间的多个意向,采取“先到先得排入队列,后到者等待递补”策略,不报错。
  2. 数据持久化:历史记录持久化存储,系统重启不丢失。需求未规定“永久保留”或保留期限。
  3. 通知机制:系统 无主动推送,买家需自行凭口令码查询进度。
  4. 页面刷新:本版本无自动刷新商品状态功能。
  5. 二次确认:冻结、成交、失败恢复操作 无二次确认弹窗,点击即执行。
  6. 操作不可逆:标记“交易成功”后无法撤销,商品直接永久下架。
  7. 隐私与安全:卖家后台可查看买家电话,但前台不展示买家信息。卖家密码需加密存储。
八、 业务流程
1. 卖家流程

登录后台 → 发布商品(商品进入在售) → 等待买家提交意向形成排队队列 → 队首意向自动进入交易,商品自动冻结 → 线下交易 → 卖家后台标记交易结果。

  • 标记成功:商品下架,可发布新商品。
  • 标记失败:商品恢复在售;卖家处置该意向(作废/重排队尾),系统自动递补下一位意向进入交易,商品再次冻结。若队列已空,商品保持“在售”。
2. 买家流程

浏览在售商品 → 点击购买 → 填写姓名+电话提交意向 → 获取口令码,进入排队队列。

  1. 排队阶段:凭口令码查询位次、修改信息、撤销意向。
  2. 轮到队首:自动进入交易,商品冻结。
  3. 线下交易:等待卖家标记结果;交易结束或商品下架,口令码失效。
九、系统约束与假设条件
9.1 业务约束
  1. 单活跃商品约束:同一时间,系统最多允许一件“未下架商品”存在。未下架商品包括:在售、手动冻结、交易中冻结。要发布新品,当前商品必须已下架。
  2. 买家身份约束:买家无账号体系,口令码是唯一凭证。口令码仅提交成功时显示一次,系统不发送短信、不提供找回。
  3. 先到先得约束:队列严格按 queued_at 正序排列,时间相同按意向 ID 正序。卖家无权挑选交易对象,只能操作队首。
  4. 开始交易约束:买家提交意向只进入队列,不冻结商品。只有卖家执行“开始交易”后,队首意向才进入交易,商品才进入“交易中冻结”。
  5. 撤销边界约束:买家撤销仅允许发生在“排队中”或“重新排队中”。撤销只使该意向退出队列,后续位次前移,不改变商品状态,不触发递补。
  6. 交易失败处置约束:卖家标记交易失败时,必须同时选择当前意向的处置方式:作废或重新排队。系统在同一事务内完成“处置当前意向 + 检查队列 + 自动递补或恢复在售”。
  7. 下架与队列约束:交易成功或卖家手动下架时,商品下架,所有未终态意向统一转为“最终失败”,对应口令码失效。
9.2 开发与运行假设
  1. 假设买家姓名、电话真实有效,系统仅做非空和格式校验,不做真伪核验。
  2. 假设同一手机号同一时间只能存在一条有效意向,不支持同一手机号多队列占位。
  3. 系统无主动推送,买家需凭口令码自行查询进度。
  4. 价格“无上限”指业务不人为封顶,实际存储受数据库精度约束。本版按 DECIMAL(10,2) 验收,有效范围为 0.01 ~ 99,999,999.99。
  5. 若前文接口清单未列“开始交易”“手动下架”接口,开发时应按本口径补充,否则第十二章验收无法落地。
9.3 状态与流程澄清
  1. 商品“已冻结”必须区分为“手动冻结”和“交易中冻结”。二者前端均可提示“商品交易中”,但后端权限不同。
  2. 手动冻结:卖家主动停售,可手动解冻,不可接收新意向。
  3. 交易中冻结:队首进入交易时触发,不可接收新意向,禁止手动解冻,禁止手动下架,只能由卖家标记交易成功或失败。
  4. 交易失败时,若队列还有下一位,商品不经过“在售”中间态,直接由下一位递补并保持“交易中冻结”。
  5. 若卖家选择“重新排队”,该意向排到队尾,更新排队时间,不参与本次递补;若队列无其他人,商品恢复“在售”,该意向仍为“重新排队中”,等待卖家下次开始交易。
  6. 买家在排队阶段撤销,不触发递补;只有交易失败才触发递补。
  7. 交易成功或手动下架后,队列中未轮到、重新排队中的意向全部转为“最终失败”。
十、数据字典简要说明
1. 商品状态字段
值 状态 说明
0 在售 可接收买家意向
1 手动冻结 卖家主动停售,可手动解冻
2 交易中冻结 队首进入交易,禁止解冻、禁止手动下架
3 已下架 终态,进入历史商品

商品表关键字段:

  • id:商品 ID
  • status:商品状态,取值 0~3
  • current_intent_id:当前交易中的意向 ID,非交易中为空
  • name、description、image_url、price
  • created_at、off_shelf_at
2. 意向状态字段
值 状态 说明
0 排队中 已提交,等待轮到
1 交易中 当前队首,商品已交易中冻结
2 交易成功 终态
3 已作废 卖家交易失败后选择作废,终态
4 已撤销 买家主动撤销,终态
5 重新排队中 交易失败后选择重新排队,非终态
6 最终失败 商品下架、交易成功关闭队列、被挤掉未轮上等,终态

说明:不再把“交易失败”作为独立长期状态。卖家标记失败时,该意向立即转为“已作废”或“重新排队中”。

意向表关键字段:

  • id:意向 ID
  • product_id:商品 ID
  • code:口令码
  • name、phone
  • status:意向状态,取值 0~6
  • queued_at:排队时间,重新排队时更新
  • fail_count:交易失败次数
  • created_at、updated_at
3. 价格字段
  • 类型:DECIMAL(10,2)
  • 规则:必须大于 0,强制保留两位小数
  • 有效范围:0.01 ~ 99,999,999.99
  • “无上限”解释为不设业务封顶,实际以数据库精度为准。
4. 口令码字段
  • 全局唯一
  • 提交意向成功即生成
  • 仅提交成功页面显示一次
  • 意向终态或商品下架后失效
  • 重新排队时继续有效
十一、风险与异常处理机制
11.1 业务异常处理
  1. 图片上传超限/格式错误:前端实时校验,拦截非法文件并提示。
  2. 交易中商品解冻异常:禁止操作,接口拦截,提示“交易中的商品不支持手动解冻”。
  3. 交易中商品手动下架异常:禁止操作,提示“交易中的商品不支持手动下架,请先标记交易结果”。
  4. 并发提交意向:使用数据库事务和行锁,按 queued_at、意向 ID 排序,保证先到先得,无插队、无乱序。
  5. 失效口令码查询:已终结或商品下架后的口令码,提示“口令码已失效,无对应意向信息”。
  6. 交易中买家撤销异常:拒绝撤销,提示“已进入交易,买家不能自行撤销,请联系卖家处理交易结果”。
  7. 交易失败递补事务:标记失败时,必须携带 productId、intentId、disposition。系统在同一事务内:
    • 校验商品为交易中冻结,且 current_intent_id = intentId;
    • 若 disposition = VOID,当前意向转为已作废;
    • 若 disposition = REQUEUE,当前意向转为重新排队中,更新 queued_at,排到队尾;
    • 查询剩余有效队列;
    • 有下一位:队首转为交易中,商品保持交易中冻结,更新 current_intent_id;
    • 无下一位:商品恢复在售,清空 current_intent_id。
  8. 重新排队且队列为空:商品恢复在售,该意向保持重新排队中,不立即进入交易,等待卖家下次开始交易。
  9. 交易成功处理:当前意向转为交易成功,商品下架;其他未终态意向全部转为最终失败,口令码失效。
  10. 手动下架处理:仅允许在售或手动冻结状态下架;交易中冻结禁止下架。下架后,所有未终态意向转为最终失败,口令码失效。
  11. 开始交易异常:非在售状态拒绝;队列为空拒绝;只能取队首意向,卖家不能指定其他意向。
  12. 接口参数要求:confirm-sold 必须携带 productId、intentId;confirm-failed 必须携带 productId、intentId、disposition。若前文接口初稿未写,以本处为准。
11.2 事务与并发一致性
  1. 商品发布、开始交易、标记成功、标记失败、手动下架均需在事务中完成。
  2. 单活跃商品约束通过数据库唯一索引或事务锁保证。
  3. 队列排序以 queued_at 和意向 ID 为准,重新排队时更新 queued_at。
  4. 交易失败递补过程中,商品不允许接收新意向,不出现可插队的中间在售状态。
十二、需求验收标准
  1. 卖家可正常登录、改密、发布单商品、手动冻结/解冻、手动下架。
  2. 商品发布后名称、描述、图片、价格不可修改,只能下架后重新发布。
  3. 买家可匿名浏览、提交意向、获取唯一口令码,并凭口令码查询、修改姓名电话、撤销排队意向。
  4. 排队严格先到先得;买家提交意向不冻结商品;只有卖家执行“开始交易”后,队首进入交易,商品转为交易中冻结,队列封闭。
  5. 排队阶段买家撤销只影响队列位次,不改变商品状态,不触发递补。
  6. 交易失败时,系统在同一事务内完成“当前意向作废或重新排队 + 自动递补或恢复在售”,不出现可被新买家插入的中间状态。
  7. 卖家选择“重新排队”时,该意向排到队尾,不立即再次进入交易;若队列无其他人,商品恢复在售,该意向仍可凭原口令码查询。
  8. 交易成功时,商品直接下架,当前意向成功,队列中其他未终态意向全部最终失败,相关口令码失效。
  9. 手动下架仅允许在售或手动冻结状态;交易中冻结禁止手动下架。下架后剩余未终态意向全部最终失败,口令码失效。
  10. 交易中商品禁止手动解冻,禁止手动下架,提示明确。
  11. 商品状态、意向状态流转完全匹配第十章数据字典和状态定义。
  12. 历史商品、意向流水持久化保存,重启不丢失,只读不可改。
  13. 所有异常场景均有拦截与提示,无逻辑漏洞、无状态错乱。
  14. 并发提交意向时,排队顺序绝对一致,无插队、无乱序。
十三、 需求变更说明

本文档为项目阶段性定稿需求规格说明书,在本课程迭代周期内,核心业务规则、状态流转、功能范围不做变更。所有开发严格依据本文档执行,无额外新增需求、无隐性需求。

成果物 · 后端商品模块设计文档(完整版)

后端商品模块设计文档(完整版)

本文档为《在线购物系统 MVP》后端完整设计说明,在 Wiki 初稿基础上补充了状态机、事务、并发、鉴权与全量接口定义。可直接替换 Wiki 中《后端商品模块设计文档》初稿页面。

对应分工:杜美芳(后端商品模块:商品表、status 字段、发布/冻结/恢复/成交接口 + SOW 技术部分),并与 李炫苇(意向表/卖家表/图片上传)协作联调。


一、模块概述

本模块负责在线购物系统中商品与交易状态流转的核心后端逻辑,包括:

  • 商品状态管理(在售 / 手动冻结 / 交易中冻结 / 已下架)
  • 发布 / 冻结 / 解冻 / 开始交易 / 成交 / 失败递补 / 手动下架接口
  • 排队队列(先到先得)与口令码机制
  • 并发控制、事务一致性、幂等

边界:本项目仅 Web 端,单卖家、买家免注册,不做支付/物流/评价/推送。


二、技术架构与选型
层 选型 说明
语言/框架 Java 17 + Spring Boot 3.3 单体后端
持久层 MyBatis-Plus 3.5 + MySQL 8 Long 转 String 序列化防 JS 精度丢失
接口文档 springdoc-openapi Swagger UI:/swagger-ui.html
鉴权 卖家 JWT + BCrypt;买家口令码 SHA-256 口令码不存明文
金额 DECIMAL(10,2) 对外以字符串返回
时间 DATETIME(3) + ISO 8601(+08:00) 统一东八区

关键依赖:spring-boot-starter-web、mybatis-plus-spring-boot3-starter、mysql-connector-j、spring-security-crypto、jjwt、springdoc-openapi-starter-webmvc-ui。


三、数据库设计
3.1 商品表 product
字段 类型 说明
id BIGINT 自增 主键
name VARCHAR(255) 商品名称,必填,发布后不可修改
description TEXT 描述,选填
image_url VARCHAR(500) 主图地址(最多 1 张,JPG/PNG 不超 5MB)
price DECIMAL(10,2) 价格,大于 0,两位小数
status TINYINT 0 在售 / 1 手动冻结 / 2 交易中冻结 / 3 已下架
current_intent_id BIGINT 当前交易中的意向 ID,非交易中为 NULL
created_at DATETIME(3) 发布时间
off_shelf_at DATETIME(3) 下架时间
active_flag 生成列 status IN (0,1,2) 时为 1 否则 NULL,配合唯一索引实现单活跃商品约束

单活跃商品约束:active_flag 为 MySQL 生成列,加 UNIQUE KEY uk_product_active(active_flag)。MySQL 唯一索引忽略 NULL,故同一时刻至多一行 active_flag=1(至多一件未下架商品)。并发发布第二件由数据库直接拒绝,应用层捕获 DuplicateKeyException 返回 40901。

3.2 意向表 intent
字段 类型 说明
id BIGINT 自增 主键
product_id BIGINT 所属商品
code_hash CHAR(64) 口令码 SHA-256 哈希(唯一,不存明文)
name / phone VARCHAR(50)/VARCHAR(20) 买家姓名 / 电话
status TINYINT 0~6,见意向状态机
queued_at DATETIME(3) 排队时间,重新排队时更新
fail_count INT 交易失败次数

关键索引:UNIQUE(code_hash)、INDEX(product_id, status, queued_at)、INDEX(product_id, phone)。

3.3 卖家表 seller

id、username(唯一)、password_hash(BCrypt)、时间字段。固定单账号,启动时自动初始化。

四、状态机设计
4.1 商品状态机
[*] --> 在售(0)                 : 卖家发布商品
在售(0) --> 手动冻结(1)          : 卖家手动冻结
手动冻结(1) --> 在售(0)          : 卖家手动解冻(交易中禁止)
在售(0) --> 交易中冻结(2)        : 卖家开始交易,队首进入交易
交易中冻结(2) --> 已下架(3)      : 交易成功
交易中冻结(2) --> 交易中冻结(2)   : 交易失败 + 队列有下一位(自动递补)
交易中冻结(2) --> 在售(0)        : 交易失败 + 队列为空(恢复在售)
在售(0)/手动冻结(1) --> 已下架(3) : 卖家手动下架
已下架(3) --> [*]                : 终态

权限要点:交易中冻结(status=2)禁止手动解冻(40908)、禁止手动下架(40909),只能由卖家标记交易结果。

4.2 意向状态机
排队中(0) --> 交易中(1)   : 轮到队首
排队中(0) --> 已撤销(4)   : 买家撤销
交易中(1) --> 交易成功(2) : 卖家标记成功
交易中(1) --> 已作废(3)   : 卖家标记失败 + 作废(VOID)
交易中(1) --> 重新排队中(5): 卖家标记失败 + 重新排队(REQUEUE)
重新排队中(5) --> 交易中(1): 再次轮到
重新排队中(5) --> 已撤销(4): 买家撤销
(0/1/5) --> 最终失败(6)   : 商品下架 / 交易成功关闭队列 / 被挤掉

有效意向状态(可查询、可修改、可撤销):0 排队中、5 重新排队中;1 交易中仅可查询(禁止撤销 40910)。

五、接口设计(全量 19 个)

统一前缀 /api,统一响应结构:

{ "code": 0, "message": "success", "data": {} }

金额以字符串返回,ID 以字符串返回(Long 转 String),时间 ISO 8601。

5.1 商品模块(卖家,需鉴权,Header: Authorization: Bearer )
接口 方法 地址 说明 关键入参
发布商品 POST /api/product/publish 进入在售,校验单活跃 name, price, description?, imageUrl?, requestId?
手动冻结 POST /api/product/freeze 在售 -> 手动冻结 productId, requestId?
手动解冻 POST /api/product/unfreeze 手动冻结 -> 在售 productId, requestId?
开始交易 POST /api/product/start-trade 队首进入交易,商品交易中冻结 productId, requestId?
确认成功 POST /api/product/confirm-sold 交易成功,商品下架 productId, intentId, requestId?
确认失败 POST /api/product/confirm-failed 处置当前意向 + 递补/恢复 productId, intentId, disposition, requestId?
手动下架 POST /api/product/off-shelf 在售/手动冻结 -> 下架 productId, requestId?

disposition 取值:VOID(作废)/ REQUEUE(重新排队)。

5.2 卖家视图(需鉴权)
接口 方法 地址
登录 POST /api/seller/login
修改密码 POST /api/seller/change-password
当前商品 GET /api/seller/product/current
意向列表 GET /api/seller/intents?productId=
历史商品 GET /api/seller/history/products?page=&pageSize=
历史意向 GET /api/seller/history/products/{id}/intents
5.3 买家(免登录)
接口 方法 地址
当前商品 GET /api/buyer/product/current
提交意向 POST /api/buyer/intent
查询意向 GET /api/buyer/intent?code=
修改意向 PUT /api/buyer/intent
撤销意向 POST /api/buyer/intent/cancel
5.4 公共
接口 方法 地址
图片上传 POST /api/upload/image(multipart 字段 file,JPG/PNG 不超 5MB)
5.5 核心接口请求/响应示例

发布商品:

// 请求
{ "name": "二手书", "description": "九成新", "price": 99.90, "imageUrl": "/uploads/xxx.jpg" }
// 响应 data(卖家当前商品视图)
{
  "id": "1", "name": "二手书", "price": "99.90", "status": 0, "statusText": "在售",
  "canFreeze": true, "canUnfreeze": false, "canStartTrade": false,
  "canOffShelf": true, "canConfirmTrade": false, "currentIntent": null, "intentCount": 0
}

提交意向(买家),响应含一次性口令码(仅此一次返回):

{ "code": 0, "message": "success",
  "data": { "code": "K7XQ2N9MZR4P", "intent": { "id": "3", "status": 0, "statusText": "排队中", "queuePosition": 1, "canModify": true, "canCancel": true } } }

确认交易失败(disposition=VOID 且队列有下一位):当前意向 -> 已作废,下一队首 -> 交易中,商品保持交易中冻结并更新 currentIntentId。

六、事务与并发一致性设计

所有写操作在事务内完成,通过 SELECT ... FOR UPDATE 锁定商品行串行化状态流转。

操作 事务步骤
提交意向 锁活跃商品行 -> 校验在售 -> 同手机号查重(0/1/5)-> 生成口令码哈希 -> 插入队列。锁商品行保证并发提交排队顺序绝对一致(先到先得)
开始交易 锁商品行 -> 校验在售 -> 取队首(queued_at ASC, id ASC)-> 队首 CAS 置交易中 -> 商品置交易中冻结
交易成功 锁商品行 -> 校验交易中且 current_intent_id == intentId -> 当前意向置成功 -> 其余未终态意向置最终失败 -> 商品下架
交易失败 锁商品行 -> 校验交易中且队首一致 -> 按 disposition 处置(作废/重新排队)-> 查询剩余队列 -> 有下一位直接递补(不经过在售中间态),无则恢复在售
手动下架 锁商品行 -> 校验非交易中 -> 未终态意向全部最终失败 -> 商品下架

关键保证:

  1. 单活跃商品:数据库生成列 + 唯一索引兜底,应用层前置校验友好提示。
  2. 队列排序统一:queued_at ASC, id ASC;重新排队更新 queued_at 排到队尾,且不立即再次进入交易(递补查询排除当前意向)。
  3. CAS 乐观更新:状态流转用 UPDATE ... WHERE status = 旧值 校验影响行数,防止并发状态错乱。
  4. 幂等:写接口支持 requestId,服务端以 操作+requestId 缓存首次结果;处理中重复请求返回 40901。

七、鉴权设计
角色 机制
卖家 固定账号登录 -> 返回 JWT(24h)-> 后续请求 Authorization: Bearer -> 拦截器校验 /api/product/、/api/seller/(除 login)、/api/upload/**;密码 BCrypt 存储
买家 无账号,口令码为唯一凭证;提交时生成 12 位随机码,仅显示一次;库存 SHA-256 哈希;查询/修改/撤销凭口令码;终态或商品下架后失效

八、错误码表(与契约第五章一致)
码 含义 码 含义
0 成功 40905 口令码无效
40001 参数错误 40906 意向状态不允许操作
40101 未登录 40907 非队首买家
40301 无权限 40908 交易中商品不可解冻
40401 资源不存在 40909 交易中商品不可下架
40901 并发冲突 40910 交易中买家不可撤销
40902 商品已冻结 50000 系统错误
40903 商品已下架 40904 重复提交意向

前端仅依据 code 展示提示;后端返回可操作标志(canFreeze / canStartTrade / canSubmitIntent / canModify / canCancel 等)。


九、验收标准映射
验收项(需求 14 条) 实现位置
单商品在售 / 发布后不可修改 product 唯一索引 + publish 校验
提交意向不冻结商品、开始交易才冻结 submit 不改商品状态;start-trade 才置交易中冻结
先到先得 / 并发无插队 商品行锁 + queued_at,id 排序
撤销不触发递补、不改商品状态 cancel 仅改意向状态
交易失败同事务处置+递补/恢复 confirmFailed 单事务内完成
交易成功下架、其余意向最终失败 confirmSold + closeOthersFinalFailed
交易中禁止解冻/下架 40908 / 40909
历史只读持久化 history/products、history/products/{id}/intents
口令码生命周期 哈希存储 + 终态/下架失效校验

十、部署与联调
  • 端口:后端 8080,前端 5173/5174,CORS 已放行。
  • 启动:mvn spring-boot:run;建表自动执行(幂等)。
  • Swagger:/swagger-ui.html。
  • 默认卖家:seller / seller123456。

由 胡芷源 负责 Dockerfile、CI、Postman 集合;本模块提供标准 REST 接口与 Swagger 定义供其生成联调产物。

教师点评

(评审后填写)

⚠️ **GitHub.com Fallback** ⚠️