第1组 · 王振涛 · 第1次评审详情 - sebestp/coursewise GitHub Wiki
- 报告期次:第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>。买家侧以"持有口令码"作为唯一凭证,无账号体系。
出参:{ product: { id, name, description, price, imageUrl, status, publishedAt } | null }
规则:只返回 在售 或 已冻结 的商品;无商品返回 null,前端显示"暂无商品在售"。不返回任何意向或口令码信息。
入参:{ name, phone }(均必填,去空格后非空)
出参:{ code }
规则:
- 商品不存在 → 400「暂无商品在售」
- 商品状态非
在售→ 400「商品交易中,暂停接收新意向」 - 姓名或电话为空 → 400「姓名与联系电话均为必填」
- 生成 8 位大写字母数字口令码(字符集剔除 I/O/0/1 等易混淆字符),全库唯一
- 口令码只在此响应中返回一次,后续任何接口都不再返回
出参:{ intent: { name, phone, status, inTransaction, position, submittedAt }, product }
规则:口令码对应意向已处终态(成功 / 撤销 / 失败)或不存在 → 404「口令码无效或已失效」;已进入交易时 position 为 null、inTransaction 为 true。
入参:{ name, phone } 规则:同名校验同上;修改不改变排队位次。
规则:排队中与交易中均可撤销 → 置为 cancelled(终态,历史留痕);口令码随即失效。
- 排队中撤销:仅队列少一人,后续位次前移;
- 交易中(队首)撤销:交易终止 → 队列仍有排队意向则下一位自动递补进入交易、商品保持冻结,队列为空则商品自动恢复在售;(2026-10-07 拍板 + 2026-10-13 回改代码,见 需求规格说明书 4.2 与 测试用例清单 TC-A17 / TC-A18。早期版本此处为「交易中拒绝撤销」,已废弃。)
入参:{ username, password };失败 401「用户名或密码错误」;成功返回 { token, username } 并刷新 token(旧 token 立即失效)。
入参:{ oldPassword, newPassword };原密码错误 400;新密码 < 8 位 400;成功后 换盐重算哈希。
出参:{ detail: { product, queue[], currentDeal, pendingFailed[] }, canPublish }
规则:queue 按提交时间正序并带 position;currentDeal 与 pendingFailed 均不含口令码;canPublish 为 true 表示当前无在售商品。
入参:{ name, description, price, image? }(image 为 data:image/(jpeg|png);base64,...)
规则:已有在售或交易中商品 → 400「已有商品在售或交易中,需先下架」;名称 ≤100 字、描述 ≤2000 字、价格 > 0 且保留两位小数;图片 JPG/PNG ≤5MB,不传或失败时用占位图。发布成功后商品状态直接为 在售(无独立"上架"动作)。
规则:仅 在售 可手动冻结;置 frozenBy = 'manual'。
规则:仅 frozenBy = 'manual' 可解冻 → 400「交易中的商品不能手动解冻」(交易冻结必须先标记交易结果)。
规则:仅 在售 可发起;取队列首位(按提交时间,卖家不可挑人)置 in_transaction,商品自动置 已冻结 / frozenBy='auto';队列为空 → 400「当前没有排队的买家」。
口径已记录(2026-10-13):需求方第 8 条答复要求「买家提交意向后队首自动进入交易」,但 FR-B1 / FR-B2 规定「商品交易中不接收新意向」——若做成提交即冻结,第二位及之后的买家将无法提交,排队队列无法形成。本组决定:在需求方答复前保留本接口作为触发入口,队列可正常形成,队首进入交易时才冻结商品。该差异已登记 需求问题库 #10 提交澄清,答复到达后立即对齐。详见 需求规格说明书 4.1。
入参:{ result: 'success' | 'fail' }
规则:
-
success:当前意向 →success;队列剩余全部 →failed;商品 →已下架进入历史;所有口令码失效 -
fail:当前意向 →failed(待处置);队列有人 → 下一位自动递补进入交易(商品保持冻结);队列空 → 商品自动恢复在售
规则:交易冻结中的商品须先标记结果 → 400;手动下架时队列剩余意向转 failed,商品进入历史。
待需求方确认:手动下架时队列意向的处置方式,见需求问题库第 2 条。
入参:{ action: 'void' | 'requeue' }
规则:仅处置 failed 且未处置过的意向;requeue 生成新排队记录,沿用原口令码、submittedAt 刷新为当前时间(排到队尾)。
待需求方确认:重新排队由卖家发起还是买家自助,见需求问题库第 9 条。
出参:{ page, totalPages, total, items[] } 规则:仅含 已下架 商品,按成交时间倒序,每页 10 条。
出参:{ 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
状态机:在售 →(手动冻结/进入交易)→ 已冻结 →(标记成功)→ 已下架;已冻结 →(标记失败且队列空)→ 在售;在售 / 已冻结(手动)→(手动下架)→ 已下架。
意向终态:成功 / 撤销 / 失败(三档)。
| 关注点 | 实现选择 |
|---|---|
| 接口层 | 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 行。
| # | 接口 | 关键验证 |
|---|---|---|
| 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,避免重复映射。
(评审后填写)