Files
wxmp_backend/docs/api-contract-v1.md
T

360 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# wxmp_backend API 契约 v1
本文档是小程序前端 `wechat_wc` 与后端 `wxmp_backend` 共同遵守的接口契约。
后端 Swagger 只展示实现,本文档才是前后端开发、评审、联调的依据。
契约变更必须升版本号(v2...),禁止在同一版本内悄悄改字段或语义。
## 1. 通用约定
- 基础路径:`/api`,例如 `POST /api/auth/login`
- 默认鉴权:除 `@Public()` 外所有接口要求 `Authorization: Bearer <accessToken>`
- 统一响应:`{ code, message, data }`,成功 `code: 0`,失败 `code` 等于 HTTP 状态码。
- 请求体校验:ValidationPipe 白名单模式,多余字段直接 400。
- 金额:数据库用 `Decimal(10,2)`,对外 JSON 统一输出 `number`(两位小数精度的整数元)。
- 分页:列表接口统一响应 `{ list, total, page, pageSize }`
- 时间:统一 ISO 8601 UTC 字符串。
- 幂等:下单等写接口支持客户端 `requestId`,同一 `requestId` 只生效一次。
- 错误码约定:
| code | 含义 |
|---|---|
| 400 | 参数校验失败 |
| 401 | 未登录 / token 失效 |
| 403 | 已登录但无权限访问他人资源 |
| 404 | 资源不存在 |
| 409 | 唯一冲突 / 重复操作 |
| 503 | 微信或依赖服务暂不可用 |
## 2. 认证与用户(已实现)
### POST /api/auth/login
公开接口。请求体:`{ code: string }``wx.login` 的 code)。
响应 `data`
```json
{
"accessToken": "jwt",
"isNewUser": true,
"nickname": null,
"avatar": null
}
```
`isNewUser=true` 时前端引导补全昵称/头像。
### POST /api/auth/register
公开接口。企业主体可选:换手机号注册,返回 `{ accessToken }`
### GET /api/users/me
返回当前用户。`data` 至少包含 `id``openid``nickname``avatar`
### PATCH /api/users/me
更新当前用户资料。请求体:`{ nickname?: string, avatar?: string }`
## 3. 商品与分类(R1,待实现)
### GET /api/categories
公开接口。返回扁平分类列表,按 `sort` 升序。
### GET /api/products
公开接口。查询参数:
| 参数 | 类型 | 说明 |
|---|---|---|
| `page` | number | 默认 1 |
| `pageSize` | number | 默认 20,上限 100 |
| `categoryId` | string | 可选,分类过滤 |
| `keyword` | string | 可选,名称/描述模糊搜索 |
仅返回 `status=ON_SALE` 的商品。
### GET /api/products/:id
公开接口。返回单个在售商品;不存在返回 404。
### ProductDTO
```ts
{
id: string
name: string
categoryId?: string
price: 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
}
```
## 4. 收货地址(R2,待实现完整 CRUD)
### GET /api/addresses
返回当前用户地址列表,默认地址在前。
### POST /api/addresses
请求体:
```ts
{
name: string
phone: string
province: string
city: string
district: string
detail: string
isDefault?: boolean
}
```
`isDefault=true` 时事务内取消其他地址默认标记。
### PATCH /api/addresses/:id
更新本人地址。`isDefault` 同样走默认地址事务。
### PATCH /api/addresses/:id/default
设为默认;非本人资源返回 403。
### DELETE /api/addresses/:id
删除本人地址;若删除的是默认地址,自动将最新一条设为默认。
注意:前端 `AddressItem.region: string[]` 与后端 `province/city/district` 的转换
只允许出现在前端 `src/utils/api/address.ts`
## 5. 设计清单(R2,待实现完整 CRUD)
### GET /api/design-list
返回当前用户设计清单,按 `createdAt` 倒序。
### GET /api/design-list/:id
返回单条设计清单(后端已实现 `@Get(':id')`R3 结算页 `fetchDesign` 消费)。仅本人可查,越权 403 / 不存在 404。
### POST /api/design-list
请求体:
```ts
{
title: string
items: {
productId: string
productName: string
unitPrice: number
count: number
designData?: {
version?: 1
category?: { id: string; mask: object; tone?: number[] }
background?: { src: string; color?: string; pos?: { x: number; y: number; scale: number } }
wordcloud?: { jobId?: string; imageUrl: string; names: string[] }
stickers?: unknown[]
imageSrc?: string // 兼容旧版
imagePos?: { x: number; y: number; scale: number } // 兼容旧版
}
}[]
}
```
> **designData 结构遵循 `wechat_wc/docs/design-data-contract-v1.md`R2/R4 冻结契约)。**
> 该契约保证 R4 下单后能据此构造 `.wcd` 投递到词云平台。要点:
> 贴纸图 `src` 在**派单前**必须为 COS 持久 URL;R2 保存/更新清单时允许暂存
> `wxfile://`/`tmp` 本地路径,由 R4 在下单/派单前上传 COS 并回写
> design-data-contract-v1.md 约束#1、决策#42026-08-12 冻结);
> 保留 `wordcloud` 分组、保留 `category.mask`;后端该 JSON 白名单须放行
> `version/background/wordcloud/rotation/zIndex`。
>`items` 为服务端 JSON,需做结构白名单与大小校验(单条 ≤ 1MB)。
>
> **实现补充(R2,非契约变更)**:服务端 JSON body 传输上限为 **2MB**`main.ts`Nest 默认 100KB
> 会使 1MB 业务限制不可达)。三层边界:≤1MB 正常受理;1MB~2MB 由 design-list service 返回
> 400「单条设计数据超过 1MB 上限」;>2MB 返回 413「请求体过大」。文件上传(R4 multipart
> 不走此限制,沿用各模块独立校验(如底图 ≤10MB)。
>
> **实现补充(DIY 修正,非契约变更)**`stickers[].width/height` 语义为「画布显示像素」
> (画布坐标空间 = `category.mask` 尺寸,`x/y` 允许超出画布边界,渲染端裁切、WCD 按原值还原),
> 渲染、碰撞检测、WCD 打包三方按同一语义消费;
> 历史数据中的原始像素值由读取端按 mask 归一化兼容。详见 design-data-contract-v1.md 注记。
### PATCH /api/design-list/:id
更新本人清单:`items``title`、状态迁移。
### DELETE /api/design-list/:id
删除本人清单。批量删除建议 `POST /api/design-list/batch-delete` 请求体 `{ ids: string[] }`
### 状态映射
| 前端显示 | 前端状态码 | 后端 DesignListStatus |
|---|---|---|
| 待设计 | `undesigned` | `DRAFT` |
| 设计中 | `designing` | `SUBMITTED` |
| 生产中 | `processing` | `PROCESSING` |
| 已下单 | `ordered` | `DONE` |
`ordered``orderId != null` 派生,前端不提交该状态。
## 6. 订单(R3,待实现)
### POST /api/orders
请求体:
```ts
{
designListId?: string
addressId: string
requestId?: string
items: [{ productId: string, quantity: number }]
}
```
服务端职责:
- 按 Product 真实价格与设计清单快照重算 `totalAmount`,忽略客户端金额。
- `prisma.$transaction` 创建 Order + OrderItem + Payment(PENDING)。
- 生成唯一 `orderNo``requestId` 幂等,重复请求返回已创建订单。
- 收货地址同步快照到 `addressSnapshot`,订单创建后不受地址变更影响。
响应 `data`
```ts
{
id: string
orderNo: string
status: 'PENDING'
totalAmount: number
items: [{ productId, name, price, quantity }]
addressSnapshot: object
createdAt: string
}
```
### GET /api/orders
参数:`status`(可选)、`page``pageSize`。只返回本人订单。
### GET /api/orders/:id
仅本人可查,越权 403,不存在 404。
### PATCH /api/orders/:id/confirm
确认收货,`SHIPPED -> COMPLETED`
### POST /api/orders/:id/cancel
`PENDING` 可取消,`PENDING -> CANCELLED`
### 订单状态机
`PENDING -> PAID -> PROCESSING -> SHIPPED -> COMPLETED``PENDING -> CANCELLED`
前端只读展示,状态迁移由后端与支付回调驱动。
## 7. 支付(R3,占位)
### POST /api/payments/:orderId/pay
支付密钥未配置时返回:
```json
{ "configured": false, "message": "支付未配置" }
```
不调用微信、不写成功记录、不使用默认商户参数。
### POST /api/payments/notify
公开接口,保留原始 body 与 headers 签名;本版本不实现验签与落库。
## 8. 上传 / 词云 / 线稿(R4,待实现)
### GET /api/upload/credentials?key=...
返回 COS STS 临时凭证或预签名 URL。小程序不得持有永久密钥。
### POST /api/wordcloud/generate
multipart 请求:`image`(底图)+ `names`(文本名单)+ `params?`
返回:
```ts
{ jobId: string }
```
### GET /api/wordcloud/jobs/:id
```ts
{
id: string
status: 'queued' | 'running' | 'success' | 'failed'
progress: number
imageUrl?: string
error?: string
}
```
仅本人可查;小程序使用轮询,不使用 SSE。
### POST /api/sketch
multipart`image`。返回处理后图片 URL;失败时前端降级本地灰度。
### POST /api/orders/:id/dispatchR4 新增,下单后 WCD 派单触发)
幂等触发把订单对应设计的 `.wcd` 投递到词云平台形成生产任务。
请求体:`{}`(幂等键 `requestId` 可选)。
响应 `data`
```ts
{
orderId: string
status: 'queued' | 'running' | 'success' | 'failed' | 'not_configured'
wordcloudJobId?: string
message?: string
}
```
- 同一订单重复调用只投递一次(以 `CustomizationTask.orderId` 唯一或状态约束)。
- `WORDCLOUD_API_URL` 未配置时返回 `status: 'not_configured'` 与可读 `message`,不做假成功。
- 常规路径:订单进入 `PROCESSING` 时由后端队列自动触发;本接口作为支付未配置期的联调/运营手段。
- 词云平台侧契约见 `docs/wordcloud-contract.md``POST /api/jobs` 可选 `wcd_file`)。
### 环境变量(R4 新增)
| 变量 | 必填 | 说明 |
|---|---|---|
| `WORDCLOUD_API_URL` | 否(为空视为未配置) | 词云平台(FastAPI)地址;未配置时派单返回 `not_configured` |
| `WORDCLOUD_TIMEOUT_MS` | 否 | 请求 wordcloud 超时(毫秒),默认 30000 |
## 9. 契约维护规则
- 前端与后端同名分支成对开发:`feat/r1-catalog``feat/r2-address-design``feat/r3-order-pay``feat/r4-upload-wordcloud`
- 每个 PR 必须同步更新本文档对应章节与前端 `src/types`
- 契约字段只能增加(optional),不能改名、改类型、改语义;否则升版本。
- 与 wordcloud 之间的契约见 `docs/wordcloud-contract.md`