第6组 · 杜美芳 · 第1次评审详情 - sebestp/coursewise GitHub Wiki
- 报告期次:第1次 | 报告日期:2026-10-08 | 项目组号:第6组 | 项目名称:在线购物系统
-
已完成工作:整理基线需求与sow文档、核心状态图绘制、子系统划分
-
成果物:《在线购物系统》需求规格说明书
-
已完成工作:编写后端商品模块设计文档(商品表结构、接口设计、状态流转规则)
-
成果物:后端商品模块设计文档(完整版)
- 项目背景:本项目为《软件工程实践》课程第一阶段(I)的挑战升级项目。课程以“大模型驱动开发”为背景,强调“定义问题”与“验收结果”。
- 项目目标:构建一个单卖家、免注册、线下交易的极简在线购物系统(MVP),重点考察需求分析与商品状态流转控制能力。
- 项目范围:仅开发 Web 网页端,包含卖家后台与买家前台。不包含 Android、iOS、微信小程序客户端。不提供线上支付、物流、商品评价、消息通知功能。
- 卖家:系统的唯一管理员(固定单账号)。使用“用户名+密码”登录后台,可修改密码。负责发布商品、处理交易、查看历史记录。
- 买家:免注册用户,无需账号。可匿名浏览商品,提交购买意向时仅需填写姓名和联系电话,凭系统生成的“口令码”管理自己的意向。
根据软件工程模块化设计原则,本系统划分为四个子系统:
- 卖家管理子系统:负责卖家登录鉴权、密码修改、商品发布、手动冻结/解冻、交易结果标记、历史商品查看。
- 买家交易子系统:负责商品前台展示、买家意向提交、口令码生成与展示、买家意向管理(查询/修改/撤销)。
- 交易状态流转子系统(核心):负责商品状态机控制、排队队列管理(先到先得)、交易失败后的自动递补、并发状态校验。
- 数据与基础设施子系统:负责数据库表结构设计、图片上传与存储、API 联调、数据持久化。
- 单商品限制:同一时间,系统仅允许 一件 商品处于“在售”状态。要发布新品,必须先将当前商品“交易成功(下架)”或“手动下架”。
- 不可修改:商品一经发布,名称、描述、图片、价格 均不可修改。如需调整,只能下架后重新发布。
-
数据约束:
- 名称:必填,长度无限制。
- 描述:选填,可为空,长度无限制。
- 图片:选填,最多上传 1 张主图,格式 JPG/PNG,单张不超过 5MB;不上传则展示占位图。
- 价格:必填,人民币,保留 2 位小数;必须大于 0,不能为 0 或负数,无上限。
- 在售:商品正常展示,可接收买家购买意向。
-
已冻结(交易中):分为自动冻结与手动冻结,二者共用同一状态。冻结后商品详情仍可浏览,但不再接收新购买意向,买家页面统一提示“商品交易中”。
- 自动冻结:队首意向进入交易流程时,系统自动触发冻结。
- 手动冻结:卖家后台主动操作,临时停售商品。
- 交易中的商品禁止手动解冻,手动解冻仅允许用于卖家手动冻结的商品。
-
已下架:
- 路径1:交易成功,商品从冻结直接转为下架,进入历史商品。
- 路径2:卖家在售状态下手动下架商品,进入历史商品。
- 排队触发:买家提交购买意向时进入队列,并非“有人提交就冻结商品”。
- 排序原则:严格按提交时间正序(先到先得)。卖家 无权 手动挑选交易对象。
- 冻结时机:商品冻结发生在 卖家与队首买家“进入交易” 的那一刻。在此之前,商品仍处于“在售”状态,可继续接收新意向。
- 队列封闭:一旦进入“已冻结(交易中)”状态,不再接收任何新意向。
- 自动递补:当队首买家交易失败(或撤销),队列后面的买家 自动按顺序递补 进入交易,商品再次自动冻结,无需卖家手动操作。若队列已空,商品恢复“在售”状态等待新买家。
- 触发方:由卖家在后台手动触发。
-
触发条件:
- 商品当前状态为“在售”;
- 排队队列中存在至少一位买家(即已提交购买意向);
- 队列中无正在进行的交易(上一次交易已结束)。
-
触发动作:
- 卖家在后台的“意向购买人列表”中,点击“开始交易”按钮;
- 系统自动将队列中提交时间最早的买家状态变更为“已进入交易”;
- 商品状态从“在售”自动变更为“已冻结(交易中)”;
- 队列封闭,不再接收任何新的购买意向。
- 备注:若队列中有多名买家,卖家只能与队首(最早提交)的买家开始交易,无权挑选其他买家。这保证了“先到先得”的公平性。
- 生成与展示:买家提交意向后,系统 即时生成 唯一口令码,仅显示一次 在提交成功页面,提醒买家自行保存。不发送短信,无找回功能。
-
生命周期:
- 生效:提交意向成功即生效。
- 失效条件:① 意向彻底终结(成功/作废/撤销);② 商品下架。失效后,买家无法再查询、修改或撤销。
- 特殊说明:交易失败后若卖家选择“重新排队”,该意向并未终结,口令码继续有效,买家可凭原口令码查询新的排队位次。
- 权限范围:买家凭口令码可做三件事——① 查询排队位次/状态;② 修改姓名和电话(不影响排队次序);③ 撤销意向(仅限排队阶段,一旦进入交易状态,买家无法自行撤销,只能由卖家标记交易结果)。
- 判定方式:交易是否成功,由 卖家在后台手动标记(成功/失败),系统不自动判断。
- 标记成功:商品直接下架,进入历史商品。操作不可撤销,一旦标记永久生效。
- 标记失败:商品立即自动恢复为“在售”,中间无延迟。卖家处置当前意向(作废/重新排队),随后系统检查队列:若有剩余意向,自动递补队首进入交易,商品再次冻结;若无剩余意向,商品保持“在售”状态等待新买家。
- 买家撤销与商品状态无关:买家撤销意向只是从队列中退出,后面位次前移。商品状态 不因买家撤销而改变,只由卖家标记交易结果决定。
| 模块 | 需求描述 | 备注 |
|---|---|---|
| 登录 | 卖家使用固定“用户名+密码”登录后台,不提供注册和退出登录功能。 | 密码加密存储。 |
| 修改密码 | 需验证原密码,新密码长度≥8位。 | |
| 发布商品 | 填写名称、描述、图片、价格。同一时间只能有一件在售。 | 需校验图片格式/大小、价格精度。 |
| 手动冻结/解冻 | 对“在售”商品可手动冻结,对“已冻结(无交易中)”商品可手动解冻。 | 禁止在交易中手动解冻。 |
| 查看意向 | 查看当前有效意向列表,按提交时间排序,显示姓名、电话、提交时间、交易结果。 | 不可修改买家意向。 |
| 确认交易结果 | 在线下交易完成后,手动标记“交易成功”或“交易失败”。 | 标记成功即商品下架;标记失败即商品自动恢复在售并递补。 |
| 查看历史商品 | 查看所有已下架商品,每个商品下挂完整的意向购买人流水。 | 历史记录只读,持久化保存,重启不丢。 |
| 模块 | 需求描述 | 备注 |
|---|---|---|
| 浏览商品 | 无需登录即可查看当前在售商品的名称、描述、图片、价格。 | 无商品时展示“暂无商品在售”占位图。 |
| 提交意向 | 点击“我要购买”,填写姓名和联系电话(必填),提交后进入排队队列。 | 提交成功后展示口令码。 |
| 管理意向 | 凭口令码查询排队位次、修改姓名/电话(不影响位次)、撤销意向。 | 撤销仅限排队阶段。 |
| 查看状态 | 商品冻结期间,买家仍可浏览详情,但购买按钮置灰,提示“商品交易中”。 |
| 接口名称 | 请求方式 | 接口地址 | 接口说明 |
|---|---|---|---|
| 发布商品 | POST | /api/product/publish |
卖家发布商品,商品进入“在售”状态。 |
| 手动冻结商品 | POST | /api/product/freeze |
卖家手动冻结在售商品,用于临时停售。 |
| 手动解冻商品 | POST | /api/product/unfreeze |
卖家手动解冻自己冻结的商品(禁止解冻交易中的商品)。 |
| 确认交易成功 | POST | /api/product/confirm-sold |
卖家标记交易成功,商品直接下架,进入历史记录。 |
| 确认交易失败 | POST | /api/product/confirm-failed |
卖家标记交易失败,商品自动恢复在售并递补下一位买家。 |
stateDiagram-v2
[*] --> 在售: 卖家发布商品
在售 --> 已冻结: 队首意向进入交易(自动冻结)/ 卖家手动冻结
在售 --> 已下架: 卖家手动下架
已冻结 --> 已下架: 卖家标记交易成功(商品永久撤下)
已冻结 --> 在售: 卖家标记交易失败(商品自动恢复在售)
已下架 --> [*]
注: 交易中的商品禁止卖家手动解冻;手动解冻仅适用于卖家自己手动冻结的商品。
stateDiagram-v2
[*] --> 排队中: 买家提交意向
排队中 --> 已进入交易: 轮到该买家(队首进入交易)
排队中 --> 已撤销: 买家凭口令码撤销
已进入交易 --> 交易成功: 卖家标记成功(商品下架)
已进入交易 --> 交易失败: 卖家标记失败(商品恢复在售)
交易失败 --> 重新排队中: 卖家选择“重新排队”(排到队尾)
交易失败 --> 已作废: 卖家选择“作废”
重新排队中 --> 已进入交易: 再次轮到该买家
重新排队中 --> 已撤销: 买家凭口令码撤销
重新排队中 --> 已作废: 卖家选择“作废”
交易成功 --> [*]
已撤销 --> [*]
已作废 --> [*]
注: 交易失败时,若卖家选择“作废”,该买家口令码失效;若选择“重新排队”,口令码继续有效(排到队尾)。
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[卖家决定失败者:作废 或 重新排队]
注: 本图仅覆盖交易主流程。卖家后台的“手动冻结/解冻”功能独立于本流程,不影响自动冻结逻辑。
- 成功:买到商品(全系统最多一个)。
- 撤销:买家主动撤销。
- 失败:其余所有情况(含交易失败被作废、被挤掉没轮上、重新排队后仍未成功等)。
- 注:不再单独列出“重新排队”作为终态,通过“失败次数”可反推。
- 并发处理:严格同一时间的多个意向,采取“先到先得排入队列,后到者等待递补”策略,不报错。
- 数据持久化:历史记录持久化存储,系统重启不丢失。需求未规定“永久保留”或保留期限。
- 通知机制:系统 无主动推送,买家需自行凭口令码查询进度。
- 页面刷新:本版本无自动刷新商品状态功能。
- 二次确认:冻结、成交、失败恢复操作 无二次确认弹窗,点击即执行。
- 操作不可逆:标记“交易成功”后无法撤销,商品直接永久下架。
- 隐私与安全:卖家后台可查看买家电话,但前台不展示买家信息。卖家密码需加密存储。
登录后台 → 发布商品(商品进入在售) → 等待买家提交意向形成排队队列 → 队首意向自动进入交易,商品自动冻结 → 线下交易 → 卖家后台标记交易结果。
- 标记成功:商品下架,可发布新商品。
- 标记失败:商品恢复在售;卖家处置该意向(作废/重排队尾),系统自动递补下一位意向进入交易,商品再次冻结。若队列已空,商品保持“在售”。
浏览在售商品 → 点击购买 → 填写姓名+电话提交意向 → 获取口令码,进入排队队列。
- 排队阶段:凭口令码查询位次、修改信息、撤销意向。
- 轮到队首:自动进入交易,商品冻结。
- 线下交易:等待卖家标记结果;交易结束或商品下架,口令码失效。
- 单活跃商品约束:同一时间,系统最多允许一件“未下架商品”存在。未下架商品包括:在售、手动冻结、交易中冻结。要发布新品,当前商品必须已下架。
- 买家身份约束:买家无账号体系,口令码是唯一凭证。口令码仅提交成功时显示一次,系统不发送短信、不提供找回。
-
先到先得约束:队列严格按
queued_at正序排列,时间相同按意向 ID 正序。卖家无权挑选交易对象,只能操作队首。 - 开始交易约束:买家提交意向只进入队列,不冻结商品。只有卖家执行“开始交易”后,队首意向才进入交易,商品才进入“交易中冻结”。
- 撤销边界约束:买家撤销仅允许发生在“排队中”或“重新排队中”。撤销只使该意向退出队列,后续位次前移,不改变商品状态,不触发递补。
- 交易失败处置约束:卖家标记交易失败时,必须同时选择当前意向的处置方式:作废或重新排队。系统在同一事务内完成“处置当前意向 + 检查队列 + 自动递补或恢复在售”。
- 下架与队列约束:交易成功或卖家手动下架时,商品下架,所有未终态意向统一转为“最终失败”,对应口令码失效。
- 假设买家姓名、电话真实有效,系统仅做非空和格式校验,不做真伪核验。
- 假设同一手机号同一时间只能存在一条有效意向,不支持同一手机号多队列占位。
- 系统无主动推送,买家需凭口令码自行查询进度。
- 价格“无上限”指业务不人为封顶,实际存储受数据库精度约束。本版按
DECIMAL(10,2)验收,有效范围为0.01 ~ 99,999,999.99。 - 若前文接口清单未列“开始交易”“手动下架”接口,开发时应按本口径补充,否则第十二章验收无法落地。
- 商品“已冻结”必须区分为“手动冻结”和“交易中冻结”。二者前端均可提示“商品交易中”,但后端权限不同。
- 手动冻结:卖家主动停售,可手动解冻,不可接收新意向。
- 交易中冻结:队首进入交易时触发,不可接收新意向,禁止手动解冻,禁止手动下架,只能由卖家标记交易成功或失败。
- 交易失败时,若队列还有下一位,商品不经过“在售”中间态,直接由下一位递补并保持“交易中冻结”。
- 若卖家选择“重新排队”,该意向排到队尾,更新排队时间,不参与本次递补;若队列无其他人,商品恢复“在售”,该意向仍为“重新排队中”,等待卖家下次开始交易。
- 买家在排队阶段撤销,不触发递补;只有交易失败才触发递补。
- 交易成功或手动下架后,队列中未轮到、重新排队中的意向全部转为“最终失败”。
| 值 | 状态 | 说明 |
|---|---|---|
| 0 | 在售 | 可接收买家意向 |
| 1 | 手动冻结 | 卖家主动停售,可手动解冻 |
| 2 | 交易中冻结 | 队首进入交易,禁止解冻、禁止手动下架 |
| 3 | 已下架 | 终态,进入历史商品 |
商品表关键字段:
-
id:商品 ID -
status:商品状态,取值 0~3 -
current_intent_id:当前交易中的意向 ID,非交易中为空 -
name、description、image_url、price -
created_at、off_shelf_at
| 值 | 状态 | 说明 |
|---|---|---|
| 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
- 类型:
DECIMAL(10,2) - 规则:必须大于 0,强制保留两位小数
- 有效范围:
0.01 ~ 99,999,999.99 - “无上限”解释为不设业务封顶,实际以数据库精度为准。
- 全局唯一
- 提交意向成功即生成
- 仅提交成功页面显示一次
- 意向终态或商品下架后失效
- 重新排队时继续有效
- 图片上传超限/格式错误:前端实时校验,拦截非法文件并提示。
- 交易中商品解冻异常:禁止操作,接口拦截,提示“交易中的商品不支持手动解冻”。
- 交易中商品手动下架异常:禁止操作,提示“交易中的商品不支持手动下架,请先标记交易结果”。
-
并发提交意向:使用数据库事务和行锁,按
queued_at、意向 ID 排序,保证先到先得,无插队、无乱序。 - 失效口令码查询:已终结或商品下架后的口令码,提示“口令码已失效,无对应意向信息”。
- 交易中买家撤销异常:拒绝撤销,提示“已进入交易,买家不能自行撤销,请联系卖家处理交易结果”。
-
交易失败递补事务:标记失败时,必须携带
productId、intentId、disposition。系统在同一事务内:- 校验商品为交易中冻结,且
current_intent_id = intentId; - 若
disposition = VOID,当前意向转为已作废; - 若
disposition = REQUEUE,当前意向转为重新排队中,更新queued_at,排到队尾; - 查询剩余有效队列;
- 有下一位:队首转为交易中,商品保持交易中冻结,更新
current_intent_id; - 无下一位:商品恢复在售,清空
current_intent_id。
- 校验商品为交易中冻结,且
- 重新排队且队列为空:商品恢复在售,该意向保持重新排队中,不立即进入交易,等待卖家下次开始交易。
- 交易成功处理:当前意向转为交易成功,商品下架;其他未终态意向全部转为最终失败,口令码失效。
- 手动下架处理:仅允许在售或手动冻结状态下架;交易中冻结禁止下架。下架后,所有未终态意向转为最终失败,口令码失效。
- 开始交易异常:非在售状态拒绝;队列为空拒绝;只能取队首意向,卖家不能指定其他意向。
-
接口参数要求:
confirm-sold必须携带productId、intentId;confirm-failed必须携带productId、intentId、disposition。若前文接口初稿未写,以本处为准。
- 商品发布、开始交易、标记成功、标记失败、手动下架均需在事务中完成。
- 单活跃商品约束通过数据库唯一索引或事务锁保证。
- 队列排序以
queued_at和意向 ID 为准,重新排队时更新queued_at。 - 交易失败递补过程中,商品不允许接收新意向,不出现可插队的中间在售状态。
- 卖家可正常登录、改密、发布单商品、手动冻结/解冻、手动下架。
- 商品发布后名称、描述、图片、价格不可修改,只能下架后重新发布。
- 买家可匿名浏览、提交意向、获取唯一口令码,并凭口令码查询、修改姓名电话、撤销排队意向。
- 排队严格先到先得;买家提交意向不冻结商品;只有卖家执行“开始交易”后,队首进入交易,商品转为交易中冻结,队列封闭。
- 排队阶段买家撤销只影响队列位次,不改变商品状态,不触发递补。
- 交易失败时,系统在同一事务内完成“当前意向作废或重新排队 + 自动递补或恢复在售”,不出现可被新买家插入的中间状态。
- 卖家选择“重新排队”时,该意向排到队尾,不立即再次进入交易;若队列无其他人,商品恢复在售,该意向仍可凭原口令码查询。
- 交易成功时,商品直接下架,当前意向成功,队列中其他未终态意向全部最终失败,相关口令码失效。
- 手动下架仅允许在售或手动冻结状态;交易中冻结禁止手动下架。下架后剩余未终态意向全部最终失败,口令码失效。
- 交易中商品禁止手动解冻,禁止手动下架,提示明确。
- 商品状态、意向状态流转完全匹配第十章数据字典和状态定义。
- 历史商品、意向流水持久化保存,重启不丢失,只读不可改。
- 所有异常场景均有拦截与提示,无逻辑漏洞、无状态错乱。
- 并发提交意向时,排队顺序绝对一致,无插队、无乱序。
本文档为项目阶段性定稿需求规格说明书,在本课程迭代周期内,核心业务规则、状态流转、功能范围不做变更。所有开发严格依据本文档执行,无额外新增需求、无隐性需求。
本文档为《在线购物系统 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。
| 字段 | 类型 | 说明 |
|---|---|---|
| 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。
| 字段 | 类型 | 说明 |
|---|---|---|
| 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)。
[*] --> 在售(0) : 卖家发布商品
在售(0) --> 手动冻结(1) : 卖家手动冻结
手动冻结(1) --> 在售(0) : 卖家手动解冻(交易中禁止)
在售(0) --> 交易中冻结(2) : 卖家开始交易,队首进入交易
交易中冻结(2) --> 已下架(3) : 交易成功
交易中冻结(2) --> 交易中冻结(2) : 交易失败 + 队列有下一位(自动递补)
交易中冻结(2) --> 在售(0) : 交易失败 + 队列为空(恢复在售)
在售(0)/手动冻结(1) --> 已下架(3) : 卖家手动下架
已下架(3) --> [*] : 终态
权限要点:交易中冻结(status=2)禁止手动解冻(40908)、禁止手动下架(40909),只能由卖家标记交易结果。
排队中(0) --> 交易中(1) : 轮到队首
排队中(0) --> 已撤销(4) : 买家撤销
交易中(1) --> 交易成功(2) : 卖家标记成功
交易中(1) --> 已作废(3) : 卖家标记失败 + 作废(VOID)
交易中(1) --> 重新排队中(5): 卖家标记失败 + 重新排队(REQUEUE)
重新排队中(5) --> 交易中(1): 再次轮到
重新排队中(5) --> 已撤销(4): 买家撤销
(0/1/5) --> 最终失败(6) : 商品下架 / 交易成功关闭队列 / 被挤掉
统一前缀 /api,统一响应结构:
{ "code": 0, "message": "success", "data": {} }金额以字符串返回,ID 以字符串返回(Long 转 String),时间 ISO 8601。
| 接口 | 方法 | 地址 | 说明 | 关键入参 |
|---|---|---|---|---|
| 发布商品 | 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(重新排队)。
| 接口 | 方法 | 地址 |
|---|---|---|
| 登录 | 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 |
| 接口 | 方法 | 地址 |
|---|---|---|
| 当前商品 | GET | /api/buyer/product/current |
| 提交意向 | POST | /api/buyer/intent |
| 查询意向 | GET | /api/buyer/intent?code= |
| 修改意向 | PUT | /api/buyer/intent |
| 撤销意向 | POST | /api/buyer/intent/cancel |
| 接口 | 方法 | 地址 |
|---|---|---|
| 图片上传 | POST | /api/upload/image(multipart 字段 file,JPG/PNG 不超 5MB) |
发布商品:
// 请求
{ "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 } } }所有写操作在事务内完成,通过 SELECT ... FOR UPDATE 锁定商品行串行化状态流转。
| 操作 | 事务步骤 |
|---|---|
| 提交意向 | 锁活跃商品行 -> 校验在售 -> 同手机号查重(0/1/5)-> 生成口令码哈希 -> 插入队列。锁商品行保证并发提交排队顺序绝对一致(先到先得) |
| 开始交易 | 锁商品行 -> 校验在售 -> 取队首(queued_at ASC, id ASC)-> 队首 CAS 置交易中 -> 商品置交易中冻结 |
| 交易成功 | 锁商品行 -> 校验交易中且 current_intent_id == intentId -> 当前意向置成功 -> 其余未终态意向置最终失败 -> 商品下架 |
| 交易失败 | 锁商品行 -> 校验交易中且队首一致 -> 按 disposition 处置(作废/重新排队)-> 查询剩余队列 -> 有下一位直接递补(不经过在售中间态),无则恢复在售 |
| 手动下架 | 锁商品行 -> 校验非交易中 -> 未终态意向全部最终失败 -> 商品下架 |
关键保证:
- 单活跃商品:数据库生成列 + 唯一索引兜底,应用层前置校验友好提示。
- 队列排序统一:queued_at ASC, id ASC;重新排队更新 queued_at 排到队尾,且不立即再次进入交易(递补查询排除当前意向)。
- CAS 乐观更新:状态流转用 UPDATE ... WHERE status = 旧值 校验影响行数,防止并发状态错乱。
- 幂等:写接口支持 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 定义供其生成联调产物。
(评审后填写)