From d39c619c41fa8f3fc0aa3e2e307548cc608fa61d Mon Sep 17 00:00:00 2001 From: obroccolio Date: Mon, 10 Aug 2026 02:08:50 +0800 Subject: [PATCH] docs: add API and wordcloud contract v1 --- docs/api-contract-v1.md | 306 +++++++++++++++++++++++++++++++++++++ docs/wordcloud-contract.md | 109 +++++++++++++ 2 files changed, 415 insertions(+) create mode 100644 docs/api-contract-v1.md create mode 100644 docs/wordcloud-contract.md diff --git a/docs/api-contract-v1.md b/docs/api-contract-v1.md new file mode 100644 index 0000000..14dd87e --- /dev/null +++ b/docs/api-contract-v1.md @@ -0,0 +1,306 @@ +# wxmp_backend API 契约 v1 + +本文档是小程序前端 `wechat_wc` 与后端 `wxmp_backend` 共同遵守的接口契约。 +后端 Swagger 只展示实现,本文档才是前后端开发、评审、联调的依据。 +契约变更必须升版本号(v2...),禁止在同一版本内悄悄改字段或语义。 + +## 1. 通用约定 + +- 基础路径:`/api`,例如 `POST /api/auth/login`。 +- 默认鉴权:除 `@Public()` 外所有接口要求 `Authorization: Bearer `。 +- 统一响应:`{ 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` 倒序。 + +### POST /api/design-list + +请求体: + +```ts +{ + title: string + items: { + productId: string + productName: string + unitPrice: number + count: number + designData?: { + stickers?: unknown[] + category?: unknown + imageSrc?: string + imagePos?: { x: number; y: number; scale: number } + } + }[] +} +``` + +`items` 为服务端 JSON,需做结构白名单与大小校验(单条 ≤ 1MB)。 + +### 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;失败时前端降级本地灰度。 + +## 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`。 diff --git a/docs/wordcloud-contract.md b/docs/wordcloud-contract.md new file mode 100644 index 0000000..f63f69d --- /dev/null +++ b/docs/wordcloud-contract.md @@ -0,0 +1,109 @@ +# wordcloud 外部服务契约 v1 + +本文档冻结 `wxmp_backend` 与 `/Users/broccoli/Project/wordcloud` 之间依赖的最小接口。 +wordcloud 项目内部仍在大量变更,但只要不违反本文档,`wxmp_backend` 的适配层不受影响。 + +## 1. 版本与状态 + +- 契约版本:`v1` +- 状态:*.xlsx 名单模式冻结;`names` 直接文本/JSON 模式为推荐扩展点,尚未冻结。 +- 服务地址:由 `wxmp_backend` 环境变量配置,禁止硬编码到代码或小程序前端。 +- 小程序前端永远不直接访问 wordcloud,只访问 `wxmp_backend`。 + +## 2. 冻结接口 + +### POST /api/jobs + +创建词云任务,`multipart/form-data`: + +| 字段 | 必填 | 说明 | +|---|---|---| +| `name_list` | 是 | `.xlsx` 名单文件,v1 必填 | +| `mask_image` | IMAGE 模式必填 | `.png/.jpg/.jpeg` 掩膜 | +| `font_file` | 否 | `.ttf/.ttc/.otf` 临时字体 | +| `font_id` | 否 | 已上传字体 id | +| `params` | 否 | JSON 字符串,顶层必须是对象 | + +`params` 关键值: + +```json +{ + "MODE": "IMAGE", + "DATA_COL_INDEX": 1, + "SEED": 42, + "N_REPETITIONS": 20, + "ENABLE_STROKE_WEIGHTS": false, + "FONT_COLOR": "#000000" +} +``` + +响应:`{ "job_id": "..." }`。 + +### GET /api/jobs/{job_id} + +返回: + +```json +{ + "job_id": "...", + "status": "queued|running|success|failed", + "stage": "...", + "progress_percent": 0, + "message": "...", + "created_at": "...", + "updated_at": "...", + "artifacts": {}, + "error": "" +} +``` + +### GET /api/jobs/{job_id}/result + +成功时返回产物地址: + +```json +{ + "job_id": "...", + "status": "success", + "image_url": "/api/jobs/{job_id}/files/png", + "svg_url": "/api/jobs/{job_id}/files/svg", + "svg_stroke_url": "/api/jobs/{job_id}/files/svg_stroke", + "db_url": "/api/jobs/{job_id}/files/db", + "metrics_url": "/api/jobs/{job_id}/files/metrics" +} +``` + +### GET /api/jobs/{job_id}/files/{kind} + +`kind` 支持 `png/svg/svg_stroke/db/metrics`。wxmp_backend 只消费 `png` 并把结果转存 COS。 + +## 3. 状态语义 + +| status | 含义 | 小程序映射 | +|---|---|---| +| `queued` | 已入队,未开始 | `queued` | +| `running` | 生成中 | `running` | +| `success` | 完成 | `success` | +| `failed` | 失败 | `failed` | + +进度以 `progress_percent` 为准,`wxmp_backend` 原样透传;小程序轮询间隔 1-2 秒。 + +## 4. 错误语义 + +- 4xx:参数错误,错误信息在 `detail` 或 `message`。 +- 5xx:服务/任务异常,wxmp_backend 将任务标记 `failed` 并在超时后清理。 +- wxmp_backend 适配层应设置请求超时(例如 30s),任务超时上限(例如 10 分钟)。 + +## 5. 稳定性规则 + +- 上述 4 个端点的路径、字段名、状态枚举、分页/轮询语义冻结为 v1。 +- wordcloud 内部重构、新增 canvas/assets/projects/templates 能力不影响本契约。 +- 契约变更流程:定义 v2 -> 更新本文档 -> wxmp_backend 在 R4 分支升级适配器 -> 双版本并存一个发布周期。 +- 若 wordcloud 希望新增“直接传名字列表”能力,默认视为 v1 兼容扩展,字段必须是 optional。 + +## 6. 集成要点 + +- `wxmp_backend` 负责把小程序文本名单转换为 `.xlsx` 再调用 `POST /api/jobs`。 +- `wxmp_backend` 保存 `job_id -> userId` 映射,轮询与结果查询必须校验归属。 +- 结果图由 wxmp_backend 下载并转存 COS,返回给小程序的是 COS 公网 URL。 +- wordcloud 若对外暴露公网,需要加访问 token 或网络白名单,防止被直接刷任务。