362 lines
10 KiB
Markdown
362 lines
10 KiB
Markdown
# 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` 的商品。
|
||
|
||
响应 `data`:`{ list: ProductDTO[], total: number, page: number, pageSize: number }`。
|
||
|
||
### 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、决策#4,2026-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/dispatch(R4 新增,下单后 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`。
|