后端商品模块设计文档 - H15058218452-rgb/Software-Engineering-Experimental-Group GitHub Wiki
本文档为《在线购物系统 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 定义供其生成联调产物。