173 lines
4.2 KiB
Markdown
173 lines
4.2 KiB
Markdown
# R1 商品目录接口对接说明
|
||
|
||
**提供方:成员 1(R1 商品目录)**
|
||
|
||
**消费者:成员 2(设计清单)、成员 3(订单)、成员 4(设计/生产数据)**
|
||
|
||
**分支:`feat/r1-catalog`**
|
||
|
||
本文档是 R1 向其他路线交付商品数据的简明使用说明。字段、路径与
|
||
[`api-contract-v1.md`](./api-contract-v1.md) 第 3 节一致;发生冲突时以 API 契约为准。
|
||
|
||
## 1. 使用边界
|
||
|
||
- 商品与分类查询都是公开接口,调用时必须传 `auth: false`,不依赖登录态。
|
||
- 消费方只能读取在售商品;下架或不存在的商品详情返回 `404`。
|
||
- `productId` 是跨路线的稳定业务 ID,不能用数据库生成顺序、数组下标或展示名称替代。
|
||
- `price` 是服务端权威价格。前端可展示,**不能**作为下单金额依据。
|
||
|
||
## 2. 稳定商品 ID
|
||
|
||
| productId | 商品 |
|
||
|---|---|
|
||
| `notebook-small` | 微雕笔记本(小) |
|
||
| `notebook-large` | 微雕笔记本(大) |
|
||
| `coaster` | 铜质杯垫 |
|
||
| `penbox` | 竹制笔盒 |
|
||
| `booklamp` | 书本型灯 |
|
||
|
||
每个商品都对应一个稳定分类 ID:`cat-<productId>`,例如 `cat-penbox`。
|
||
|
||
## 3. 查询接口
|
||
|
||
### `GET /api/categories`
|
||
|
||
返回扁平分类数组,已按 `sort` 升序排列。
|
||
|
||
```ts
|
||
type CategoryDTO = {
|
||
id: string
|
||
name: string
|
||
parentId?: string | null
|
||
sort: number
|
||
}
|
||
```
|
||
|
||
### `GET /api/products`
|
||
|
||
查询参数均可选:
|
||
|
||
```ts
|
||
{
|
||
page?: number // 默认 1
|
||
pageSize?: number // 默认 20,最大 100
|
||
categoryId?: string
|
||
keyword?: string // 匹配名称、副标题、描述
|
||
}
|
||
```
|
||
|
||
响应 `data`:
|
||
|
||
```ts
|
||
{
|
||
list: ProductDTO[]
|
||
total: number
|
||
page: number
|
||
pageSize: number
|
||
}
|
||
```
|
||
|
||
### `GET /api/products/:id`
|
||
|
||
返回一个在售商品;商品不存在或不是 `ON_SALE` 时返回 `404`。
|
||
|
||
```ts
|
||
type ProductDTO = {
|
||
id: string
|
||
name: string
|
||
categoryId?: string
|
||
price: number // 元,服务端已转为 number
|
||
originalPrice?: number
|
||
leadTime: string
|
||
subtitle?: string
|
||
description?: string
|
||
story?: string
|
||
scene?: string
|
||
tags: string[]
|
||
specs: [string, string][]
|
||
tone: [number, number, number]
|
||
mask: {
|
||
shape: 'rect' | 'circle'
|
||
width: number
|
||
height: number
|
||
borderRadius?: number
|
||
}
|
||
images: string[]
|
||
iconImg?: string
|
||
status: 'ON_SALE'
|
||
sort: number
|
||
}
|
||
```
|
||
|
||
所有接口沿用统一响应包裹:`{ code: 0, message: 'ok', data }`。
|
||
|
||
## 4. 前端调用方式
|
||
|
||
前端消费者统一从 R1 API 层调用,不能在页面直接拼 HTTP 请求:
|
||
|
||
```ts
|
||
import { fetchCategories, fetchProduct, fetchProducts } from '../../utils/api/product'
|
||
|
||
const { list } = await fetchProducts({ keyword: '笔记本', page: 1, pageSize: 20 })
|
||
const product = await fetchProduct('penbox')
|
||
const categories = await fetchCategories()
|
||
```
|
||
|
||
## 5. 给成员 2:设计清单
|
||
|
||
设计清单保存商品相关数据时使用:
|
||
|
||
```ts
|
||
{
|
||
productId: product.id,
|
||
productName: product.name,
|
||
unitPrice: product.price, // 仅展示/设计清单快照;不是订单结算依据
|
||
count: quantity,
|
||
designData: {
|
||
category: { id: product.id, mask: product.mask, tone: product.tone },
|
||
},
|
||
}
|
||
```
|
||
|
||
`mask` 与 `tone` 必须原样保留,供 R4 构造生产画布;不要把 `productName` 当作关联键。
|
||
|
||
## 6. 给成员 3:订单
|
||
|
||
R3 创建订单时,客户端请求只传:
|
||
|
||
```ts
|
||
{
|
||
items: [{ productId: 'penbox', quantity: 1 }]
|
||
}
|
||
```
|
||
|
||
R3 服务端必须:
|
||
|
||
1. 按 `productId` 查询商品并确认 `status === ON_SALE`;
|
||
2. 以服务端 `price` 重算订单总价;
|
||
3. 将 `productId`、`name`、`price`、`quantity` 写进 `OrderItem` 快照;
|
||
4. 忽略客户端传入的单价、商品名和总金额。
|
||
|
||
## 7. 给成员 4:设计与生产
|
||
|
||
R4 若需要从商品恢复画布尺寸或主题色,使用 `ProductDTO.mask` 和 `ProductDTO.tone`。
|
||
生产任务内保存的是设计快照;不要仅凭商品名称重新定位商品。
|
||
|
||
## 8. 联调前置条件
|
||
|
||
R1 合入或本地联调前,后端需要先执行数据库迁移与种子数据:
|
||
|
||
```bash
|
||
npm run prisma:migrate
|
||
npm run prisma:seed
|
||
```
|
||
|
||
最小 smoke 验收:
|
||
|
||
```text
|
||
GET /api/categories
|
||
GET /api/products?page=1&pageSize=20
|
||
GET /api/products/penbox
|
||
GET /api/products/not-on-sale-or-missing -> 404
|
||
```
|