第1组 · 王振涛 · 第1次评审详情 - sebestp/coursewise GitHub Wiki

王振涛 · 第1次评审详情(第1组 · 组员)

基本信息

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

工作任务与成果物

  • 已完成工作:领域模型与 REST 接口设计:17 个接口(卖家 12 + 买家 5)的入参、出参与错误码约定

  • 成果物:接口设计

  • 已完成工作:实现 Java 卖家端模块:登录鉴权、发布商品、冻结解冻、进入交易、标记交易结果与失败意向处置、下架、历史流水、修改密码共 12 个 REST 接口(836 行,mvn package 编译通过、curl 逐条实测通过)

  • 成果物:卖家端模块实现

成果物 · 接口设计

接口设计

编写:王振涛 | 状态:接口清单已完成,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 侧实现。

成果物 · 卖家端模块实现

卖家端模块实现

第 1 组 · 在线购物系统 | 完成人:王振涛 | 完成日期:2026-10-07 对应提交:f4d8821 | 代码位置:server/src/main/java/com/group1/onlineshop/

一、交付内容
文件 行数 职责
service/SellerService.java 267 登录鉴权、修改密码、工作台总览、历史列表与意向流水
service/ProductService.java 390 发布商品、冻结/解冻、交易流转、下架、失败意向处置、图片保存
controller/SellerController.java 179 12 个卖家端 REST 接口
repository/ProductRepository.java — 骨架缺失,按 IntentRepository 风格补建(未改动其他 repository)

合计 836 行。

二、12 个接口与验证结果(curl 逐条实测,全部通过)
# 接口 关键验证
1 POST /api/seller/login 正确返回 token;错误密码 401;重新登录后旧 token 失效
2 POST /api/seller/password 原密码错拒绝;新密码 <8 位拒绝;成功后 BCrypt 重编码,旧密码无法登录
3 GET /api/seller/overview 空态返回 canPublish:true;队列带 position 1..n;currentDeal/pendingFailed 不返回口令码
4 POST /api/seller/products 名称必填 ≤100 字、描述 ≤2000 字、价格 >0 两位小数;重复发布拒绝;base64 图片解码落盘
5 POST /api/seller/products/{id}/freeze 在售→冻结(frozenBy=manual);已下架拒绝;非在售拒绝
6 POST /api/seller/products/{id}/unfreeze 手动冻结可恢复在售;交易冻结拒绝手动解冻
7 POST /api/seller/products/{id}/deal 队首进入交易、商品自动冻结(frozenBy=auto);空队列拒绝
8 POST /api/seller/products/{id}/deal/result success→胜出+队列转失败+下架进历史;fail→转失败待处置+下位自动递补
9 POST /api/seller/products/{id}/offshelf 队列剩余置失败;交易中拒绝下架
10 POST /api/seller/intents/{id}/dispose requeue 沿用原码排到队尾;void 作废;重复处置拒绝
11 GET /api/seller/history 只查已下架、closedAt 倒序、每页 10 条
12 GET /api/seller/history/{id} 详情+意向流水;JSON 全文检查无口令码泄漏
三、关键业务规则
  • 商品四态:在售 on_sale →(手动冻结 / 交易冻结)→ 冻结 frozen → 回到在售或(下架)→ 已下架 off_shelf。
  • 进入交易即自动冻结:卖家不能挑人,只能与队首进入;商品自动置冻结且 frozenBy='auto',此时禁止手动解冻,必须先标记交易结果。
  • 标记成功:胜出意向置成功,队列剩余全部转失败并已处置,商品下架进历史,全部口令码随之失效。
  • 标记失败:当前转失败待处置;队列有人则下一位自动递补(商品保持冻结),无人则商品恢复在售。
  • 口令码脱敏:卖家端任何接口都不返回买家口令码,历史流水亦然。
四、验证方式与组长复核
  • 提交后由组长在本地执行 mvn -q package,编译通过并产出可执行 jar。
  • java -jar target/online-shop.jar 启动成功(日志出现 Started OnlineShopApplication)。
  • 接口行为抽样复核一致(未登录返回 401、空商品返回 {product:null})。
五、与其他模块的边界
  • 交易流转的正式实现位于林初俊交付的 TradeService(见 买家端模块实现);本模块通过注入调用。
  • GET /api/product 本模块曾临时实现,已按技术债 TD-3 移交 BuyerController,避免重复映射。
六、相关文档

教师点评

(评审后填写)

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