接口设计 - xych5q/online-shop GitHub Wiki

接口设计

编写:王振涛 | 状态:接口清单已完成,Java 版 17 个接口全部实现并通过实测(见 卖家端模块实现、买家端模块实现) 本次迭代成果物:本设计(接口清单与业务规则)。Java 正式实现的接口路径与本文保持一致。


一、接口总览

分组 接口 方法 鉴权 说明
买家 /api/product GET 否 获取当前在售 / 交易中商品
买家 /api/intents POST 否 提交购买意向,返回口令码(仅此一次)
买家 /api/intents/lookup/{code} GET 码 查位次与状态
买家 /api/intents/lookup/{code} PUT 码 改姓名 / 电话
买家 /api/intents/lookup/{code}/cancel POST 码 撤销意向
卖家 /api/seller/login POST 否 登录,返回 token
卖家 /api/seller/password POST token 修改密码
卖家 /api/seller/overview GET token 后台首页数据
卖家 /api/seller/products POST token 发布商品
卖家 /api/seller/products/{id}/freeze POST token 手动冻结
卖家 /api/seller/products/{id}/unfreeze POST token 手动解冻
卖家 /api/seller/products/{id}/deal POST token 与队首进入交易
卖家 /api/seller/products/{id}/deal/result POST token 标记交易结果
卖家 /api/seller/products/{id}/offshelf POST token 手动下架
卖家 /api/seller/intents/{id}/dispose POST token 处置失败意向(作废 / 重排)
卖家 /api/seller/history GET token 历史商品列表(分页)
卖家 /api/seller/history/{id} GET token 历史商品详情与意向流水
静态 /uploads/{file} GET 否 商品图片

卖家鉴权:X-Token 请求头或 Authorization: Bearer <token>。买家侧以"持有口令码"作为唯一凭证,无账号体系。

二、买家侧接口

GET /api/product

出参:{ product: { id, name, description, price, imageUrl, status, publishedAt } | null } 规则:只返回 在售 或 已冻结 的商品;无商品返回 null,前端显示"暂无商品在售"。不返回任何意向或口令码信息。

POST /api/intents

入参:{ name, phone }(均必填,去空格后非空) 出参:{ code } 规则:

  • 商品不存在 → 400「暂无商品在售」
  • 商品状态非 在售 → 400「商品交易中,暂停接收新意向」
  • 姓名或电话为空 → 400「姓名与联系电话均为必填」
  • 生成 8 位大写字母数字口令码(字符集剔除 I/O/0/1 等易混淆字符),全库唯一
  • 口令码只在此响应中返回一次,后续任何接口都不再返回

GET /api/intents/lookup/{code}

出参:{ intent: { name, phone, status, inTransaction, position, submittedAt }, product } 规则:口令码对应意向已处终态(成功 / 撤销 / 失败)或不存在 → 404「口令码无效或已失效」;已进入交易时 position 为 null、inTransaction 为 true。

PUT /api/intents/lookup/{code}

入参:{ name, phone } 规则:同名校验同上;修改不改变排队位次。

POST /api/intents/lookup/{code}/cancel

规则:排队中与交易中均可撤销 → 置为 cancelled(终态,历史留痕);口令码随即失效。

  • 排队中撤销:仅队列少一人,后续位次前移;
  • 交易中(队首)撤销:交易终止 → 队列仍有排队意向则下一位自动递补进入交易、商品保持冻结,队列为空则商品自动恢复在售;(2026-10-07 拍板 + 2026-10-13 回改代码,见 需求规格说明书 4.2 与 测试用例清单 TC-A17 / TC-A18。早期版本此处为「交易中拒绝撤销」,已废弃。)

三、卖家侧接口

POST /api/seller/login

入参:{ username, password };失败 401「用户名或密码错误」;成功返回 { token, username } 并刷新 token(旧 token 立即失效)。

POST /api/seller/password

入参:{ oldPassword, newPassword };原密码错误 400;新密码 < 8 位 400;成功后 换盐重算哈希。

GET /api/seller/overview

出参:{ detail: { product, queue[], currentDeal, pendingFailed[] }, canPublish } 规则:queue 按提交时间正序并带 position;currentDeal 与 pendingFailed 均不含口令码;canPublish 为 true 表示当前无在售商品。

POST /api/seller/products

入参:{ name, description, price, image? }(image 为 data:image/(jpeg|png);base64,...) 规则:已有在售或交易中商品 → 400「已有商品在售或交易中,需先下架」;名称 ≤100 字、描述 ≤2000 字、价格 > 0 且保留两位小数;图片 JPG/PNG ≤5MB,不传或失败时用占位图。发布成功后商品状态直接为 在售(无独立"上架"动作)。

POST /api/seller/products/{id}/freeze

规则:仅 在售 可手动冻结;置 frozenBy = 'manual'。

POST /api/seller/products/{id}/unfreeze

规则:仅 frozenBy = 'manual' 可解冻 → 400「交易中的商品不能手动解冻」(交易冻结必须先标记交易结果)。

POST /api/seller/products/{id}/deal

规则:仅 在售 可发起;取队列首位(按提交时间,卖家不可挑人)置 in_transaction,商品自动置 已冻结 / frozenBy='auto';队列为空 → 400「当前没有排队的买家」。

口径已记录(2026-10-13):需求方第 8 条答复要求「买家提交意向后队首自动进入交易」,但 FR-B1 / FR-B2 规定「商品交易中不接收新意向」——若做成提交即冻结,第二位及之后的买家将无法提交,排队队列无法形成。本组决定:在需求方答复前保留本接口作为触发入口,队列可正常形成,队首进入交易时才冻结商品。该差异已登记 需求问题库 #10 提交澄清,答复到达后立即对齐。详见 需求规格说明书 4.1。

POST /api/seller/products/{id}/deal/result

入参:{ result: 'success' | 'fail' } 规则:

  • success:当前意向 → success;队列剩余全部 → failed;商品 → 已下架 进入历史;所有口令码失效
  • fail:当前意向 → failed(待处置);队列有人 → 下一位自动递补进入交易(商品保持冻结);队列空 → 商品自动恢复在售

POST /api/seller/products/{id}/offshelf

规则:交易冻结中的商品须先标记结果 → 400;手动下架时队列剩余意向转 failed,商品进入历史。

待需求方确认:手动下架时队列意向的处置方式,见需求问题库第 2 条。

POST /api/seller/intents/{id}/dispose

入参:{ action: 'void' | 'requeue' } 规则:仅处置 failed 且未处置过的意向;requeue 生成新排队记录,沿用原口令码、submittedAt 刷新为当前时间(排到队尾)。

待需求方确认:重新排队由卖家发起还是买家自助,见需求问题库第 9 条。

GET /api/seller/history

出参:{ page, totalPages, total, items[] } 规则:仅含 已下架 商品,按成交时间倒序,每页 10 条。

GET /api/seller/history/{id}

出参:{ product, intents[] } 规则:意向流水按提交时间正序,不含口令码;只读,无修改 / 删除接口。

四、数据模型

Product:id, name, description, price, image, status(on_sale|frozen|off_shelf), createdAt, frozenAt, frozenBy(manual|auto|null), closedAt

Intent:id, productId, name, phone, code, submittedAt, status(queued|in_transaction|success|cancelled|failed), disposed, requeuedFrom?

Seller:username, salt, hash, token

状态机:在售 →(手动冻结/进入交易)→ 已冻结 →(标记成功)→ 已下架;已冻结 →(标记失败且队列空)→ 在售;在售 / 已冻结(手动)→(手动下架)→ 已下架。

意向终态:成功 / 撤销 / 失败(三档)。

五、Java 实现方式

关注点 实现选择
接口层 Spring Boot @RestController,共 17 个 REST 接口
持久化 H2 关系库 + Spring Data JPA(表结构同第四节)
密码存储 Spring Security BCryptPasswordEncoder
单元测试 JUnit 5 + @SpringBootTest / MockMvc(18 组全绿,2026-10-13)
接口冒烟 test-cases/api-smoke.sh(curl,44 项断言全通过)
端到端测试 Selenium(迭代 2 补齐);UI 用例见 测试用例清单 第三节 TC-U01~U08
构建与运行 mvn package + java -jar
容器化部署 Dockerfile(JDK/JRE 21 两阶段)+ docker-compose + deploy/docker-verify.sh;待在装了 Docker 的机器上完成实跑(见 构建与部署手册 第五节)

接口路径、入参出参、业务规则与第三节清单保持一致,均在 Java 侧实现。

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