后端商品模块设计文档 - 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。


三、数据库设计

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** ⚠️