docs: R4 WCD 生产任务契约(designData→词云)与路线文档
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## 项目概览
|
||||
|
||||
智绘微刻(Smart-Engraving)微信小程序:词云生成 + 个性化激光雕刻定制。基于 **Taro 3.6.31 + React 18 + TypeScript**,SCSS 变量驱动主题,构建产物运行在微信小程序(webpack5 编译到 `dist/`)。
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
npm install # 安装依赖
|
||||
npm run dev:weapp # 微信小程序开发模式(watch,配合微信开发者工具)
|
||||
npm run build:weapp # 生产构建;也是提交前 / 验收的标准验证命令
|
||||
npm run dev:h5 # H5 预览调试(H5 下事件用 onClick,见下文)
|
||||
node --test tools/homeTopMask.test.mjs # 运行 node:test 单测(目前唯一测试,无需 jest)
|
||||
```
|
||||
|
||||
没有配置 lint/test 脚本。提交前验证 = `npm run build:weapp` 通过(见 `docs/parallel-development-guide.md` §6)。
|
||||
|
||||
## 数据访问层(核心架构,务必先读)
|
||||
|
||||
页面**不得**直接读写 Storage 或直接发请求。所有数据操作取道两个聚合入口,二者都只 re-export、不堆业务:
|
||||
|
||||
- `../../utils/store`(DA 层,本地 Storage,按域拆文件):页面从 `src/utils/store/index.ts` 导入 `getDesignList()`、`addAddress()`、`designToOrder()` 等。
|
||||
- `../../utils/api`(HTTP 层,按域拆文件):页面从 `src/utils/api/index.ts` 导入 `login()`、`fetchProducts()` 等。
|
||||
|
||||
每个域文件(`store/keys|user|design|address|order|theme.ts`、`api/auth|user|product|address|design|order|upload.ts`)按领域职责划分,新增逻辑加到对应域文件里(不要新建 `xxx2.ts`),新文件需在聚合入口显式 re-export。后端替换 = 把 `store/<domain>.ts` 的函数换成 `api/<domain>.ts` 的 HTTP 调用,页面层零改动。
|
||||
|
||||
**openid 作用域隔离**(`store/keys.ts`):storage key 形如 `${scope}_${openid}`,通过 `key(scope)`(当前用户)与 `keyFor(scope, openid)`(指定用户)生成,实现多用户数据隔离。
|
||||
|
||||
类型统一放 `src/types/index.ts` 或 `src/types/<domain>.ts`,不要散落在页面里。
|
||||
|
||||
## 协作约定(R0–R4 并行路线)
|
||||
|
||||
仓库按四人并行开发组织,主干为 `master`,禁止直接推主干,合入走 PR。每人一条端到端路线(前端 + 后端、同名分支):
|
||||
|
||||
| 路线 | 分支 | 页面归属 | 数据域 |
|
||||
|---|---|---|---|
|
||||
| R1 商品目录 | `feat/r1-catalog` | `index`、`shop`、`shop/detail`、`product` | `api/product` |
|
||||
| R2 地址+设计清单 | `feat/r2-address-design` | `address`、`designList` | `store/design`、`store/address`、`api/address`、`api/design` |
|
||||
| R3 订单+支付占位 | `feat/r3-order-pay` | `checkout`、`orders`、`orderDetail` | `store/order`、`api/order` |
|
||||
| R4 上传+词云+线稿 | `feat/r4-upload-wordcloud` | `wordcloud`、`diy/stickerEdit` | `api/upload` |
|
||||
|
||||
`store/index.ts` 与 `api/index.ts` 由 R0(本分支 `feat/r0-dev-foundation`)维护,**只加不删、不堆业务**。完整规则见 `docs/team-collaboration-guide.md` 与 `docs/parallel-development-guide.md`(含合并顺序:R1 → R2/R4 → R3)。
|
||||
|
||||
## 主题系统
|
||||
|
||||
`ThemeMode`:`light` / `dark` / `auto`(`store/theme.ts`)。`ThemeContext`(`src/context/ThemeContext.tsx`)产出最终生效的 `resolvedTheme: 'light' | 'dark'`。
|
||||
|
||||
- 页面根节点 `className={theme-${resolvedTheme}}`;所有颜色/字号来自 `app.scss` 里的 CSS 变量(浅色 `:root` + `.theme-dark`),**禁止在页面里硬编码颜色/字号/组件形态**,token 语义见 `DESIGN.md`,落地规则见 `设计变更文档.md`。
|
||||
- 每个页面都挂 `<ThemedPageMeta />`(`PageMeta + NavigationBar`),按 `resolvedTheme` 强制覆盖顶部/状态栏颜色;`useStatusBar(resolvedTheme)` 在页面显示时再同步一次。`app.config.ts` 里的 `darkmode: true` 只作为系统主题探测通道(`onThemeChange`/`getAppBaseInfo`),窗口颜色不依赖它。
|
||||
- 自定义 tabBar 无法继承 React Context,通过 `Taro.eventCenter` 订阅 `themeChange` 事件 (`THEME_CHANGE_EVENT`) 同步主题。
|
||||
- 图标一律用 `src/icon/` 下的 PNG/SVG,**禁止用 emoji 当图标**。
|
||||
|
||||
## 事件与生命周期(重要兼容坑)
|
||||
|
||||
- **一律用 `onTap`,不要用 `onClick`**。微信基础库 3.17.0 + Taro React 下动态更新 handler 会触发 `TaroElement.removeEventListener(undefined)`,报 `Cannot read properties of undefined (reading '_num')`。仅在 H5 构建里可用 `onClick`。
|
||||
- 页面显示刷新用 `useDidShow`(`@tarojs/taro`),不要手动覆盖 `page.onShow`(热更新会重复叠加导致异常)。
|
||||
|
||||
## 请求与登录
|
||||
|
||||
- `src/utils/request.ts`:`BASE_URL = https://wxbackend.tokenleaping.com`;后端统一响应 `{ code, message, data }`,`code === 0` 成功;token 存 `smart_access_token`,走 `Authorization: Bearer`。HTTP 401 会自动触发一次会话续登(`app.tsx` 注册的 `refreshSession`),失败则清 token 并调 `onUnauthorized`。
|
||||
- `session.ts` 的 `refreshSession`:`wx.login` 换新 code → `/api/auth/login` 续签 → `/api/users/me` 校验 openid 与本地账号一致才放行(防串号)。`mock_*` 开头的 openid 是历史残留,`isMockOpenid` 会识别并拒绝。
|
||||
- 登录态校验有缓存(`src/utils/authState.ts`),`LoginGuard` 监听 `authStateChanged`/`tabBarChange`/`themeChange` 事件自动刷新。
|
||||
|
||||
## 静态资源与 OSS
|
||||
|
||||
- 产品大图已迁到阿里云 OSS(bucket `wordcloudwechat`,杭州),包体积需保持在微信 2MB 限制内(当前 `dist/` 约 1MB)。
|
||||
- 基址在根目录 `.env` 的 `OSS_BASE_URL`(git 忽略),由 `config/index.js` 注入为 `__OSS_BASE_URL__` 常量。
|
||||
- 所有资源路径经 `assetUrl()`(`src/utils/asset.ts`)处理:`/img/*` 大图拼 OSS 远程地址,`/icon/*` 小图标与 `src/icon`、`theme.json` 通过 `config/index.js` 的 copy patterns 打包进 `dist/`。
|
||||
- 商品/品类配置集中在 `src/utils/productConfig.ts`(`PRODUCTS` 数组 + icon 映射 + `tone` 主题色),遮罩尺寸说明见 `docs/mask-config-guide.md`。
|
||||
|
||||
## 页面结构说明
|
||||
|
||||
路由与 tabBar 见 `src/app.config.ts`(全局 `navigationStyle: custom`,5 个 tab 的自定义 tabBar)。几个非平凡的页面:
|
||||
|
||||
- `pages/shop/index`:单页双容器——2 列商品网格 + 固定 overlay 沉浸式详情(滚动视差、产品 `tone` 主题色联动),点击卡片在当前页内过渡,不跳转;底部 CTA 跳 `/pages/product/index?id=xxx` 衔接购买链路。
|
||||
- `pages/diy/index`:DIY 工作台(贴纸拖拽/缩放/碰撞检测),后续 `diy/stickerEdit` 为贴纸编辑画板。
|
||||
- 共享组件在 `src/components/`(`ThemedPageMeta`、`ScrollTopMask`、`TopBarGradient`、`BottomActionBar`、`LoginGuard`、`LoginModal` 等)。
|
||||
|
||||
## 已知遗留问题
|
||||
|
||||
README「已知遗留问题」表记载(改动相关页面时注意):
|
||||
|
||||
- checkout 页在设计中新增地址后无法及时同步(design 数据写入与读取生命周期不一致)。
|
||||
- `diy/stickerEdit` 页面无法运行:Canvas 2D `createImage` 在部分基础库版本返回 `undefined`,且线稿 AI 接口未接入。
|
||||
@@ -0,0 +1,120 @@
|
||||
# 设计数据 → WCD 生产任务契约 v1
|
||||
|
||||
> 跨路线契约:**R2(数据生产者)与本路线 R4(WCD 打包消费者)共同遵守**。
|
||||
> 冻结目的:R4 下单后要把设计清单以 `.wcd` 包投递到词云平台形成生产任务
|
||||
> (见 `docs/routes/route-r4-upload-wordcloud.md` §3)。R4 能打包,取决于 R2
|
||||
> 保存的 `designData` 携带哪些字段。本文档把这些字段**先定死**。
|
||||
>
|
||||
> 版本规则:字段只能新增 optional 字段;改名/改语义/改类型必须升版本并同步本文件。
|
||||
|
||||
## 1. 为什么需要本契约
|
||||
|
||||
词云平台用 `.wcd`(Zip:`manifest.json` + `document.json` + `assets/`)还原整套画布。
|
||||
还原需要三样东西全部可用:
|
||||
|
||||
1. **画布尺寸 / 背景** ← 来自 `category.mask`。
|
||||
2. **贴纸布局**(位置、大小、旋转、层级)← 来自 `stickers[]`。
|
||||
3. **贴纸图片字节** ← 来自每个贴纸的**持久 URL**(COS)。
|
||||
|
||||
第 3 点是关键约束:如果贴纸图是 `wxfile://`/`tmp` 临时路径,后端打包时下载不到字节,
|
||||
WCD 生产任务直接失败。因此本契约把"图片必须持久化"从 R2 的已知限制升级为**冻结约束**。
|
||||
|
||||
## 2. 冻结的 `designData` 结构(v1)
|
||||
|
||||
与现有前端 `DesignItem.designData` 对齐,保留 `imageSrc/imagePos` 兼容旧版,**新增**如下字段:
|
||||
|
||||
```ts
|
||||
/** 设计数据 v1 —— 支持 R4 WCD 生产任务打包 */
|
||||
interface DesignDataV1 {
|
||||
/** 结构版本,缺省按 v1 处理;旧数据可由后端/前端按版本迁移 */
|
||||
version?: 1
|
||||
|
||||
/** 商品品类(已有)。mask 是画布尺寸来源,保存清单时必须保留 */
|
||||
category?: {
|
||||
id: string
|
||||
mask: { shape: 'rect' | 'circle'; width: number; height: number; borderRadius?: number }
|
||||
tone?: [number, number, number]
|
||||
}
|
||||
|
||||
/** 底图(已有 imageSrc 语义的规范化):src 必须是持久 URL */
|
||||
background?: {
|
||||
src: string // COS 持久 URL;禁止 wxfile:// / tmp 临时路径
|
||||
color?: string // 画布底色(缺省由 mask/主题决定)
|
||||
pos?: { x: number; y: number; scale: number } // 兼容旧 imagePos
|
||||
}
|
||||
|
||||
/**
|
||||
* 词云信息(R4 在词云生成后写入;R2 保存/更新清单时原样保留这组字段,
|
||||
* 后端 items JSON 白名单放行)。
|
||||
*/
|
||||
wordcloud?: {
|
||||
jobId?: string // 生成该词云的 wxmp_backend 侧任务 id
|
||||
imageUrl: string // 词云结果图持久 URL(COS)
|
||||
names: string[] // 名单快照(≤200),生产/重建用,可为空
|
||||
}
|
||||
|
||||
/** 贴纸列表(已有)。每个贴纸的 src 升级为持久 URL 约束 */
|
||||
stickers?: Array<{
|
||||
id: string
|
||||
src: string // ★ 必须为 COS 持久 URL(https),禁止 wxfile:// / tmp / 空
|
||||
x: number
|
||||
y: number
|
||||
scale: number
|
||||
width: number
|
||||
height: number
|
||||
rotation?: number // 已冻结(2026-08-12 决策#2):本期 DIY 持久化;无旋转则省略
|
||||
zIndex?: number // 已冻结(2026-08-12 决策#2):图层顺序;WCD 按 zIndex 排列元素
|
||||
isOverlapping?: boolean
|
||||
edits?: {
|
||||
brightness?: number // -100~100
|
||||
hue?: number // -180~180
|
||||
contrast?: number // -100~100
|
||||
sketchSrc?: string
|
||||
}
|
||||
}>
|
||||
}
|
||||
```
|
||||
|
||||
关系说明:
|
||||
|
||||
- `DesignItem.designData`(前端类型文件 `src/types/index.ts`)与后端 `design-list.items[].designData`
|
||||
(服务端 JSON)为**同一份**结构;前端类型以本契约为准,后端只做白名单校验不改业务 JSON。
|
||||
- `imageSrc/imagePos` 保留为兼容旧版字段;新写入统一走 `background.src/pos`,
|
||||
WCD 打包器同时兜底读旧字段。
|
||||
|
||||
## 3. 冻结约束(R2 需落地)
|
||||
|
||||
| # | 约束 | 说明 |
|
||||
|---|---|---|
|
||||
| 1 | **贴纸图持久化(R4 负责)** | 决策#4:**贴纸持久化由 R4 完成**。R2 允许保存/更新清单时贴纸 `src` 暂为本地路径(`wxfile://`/`tmp`);**R4 在下单/派单前**把本地图上传为 COS 持久 URL 并回写 `designData.stickers[].src`。WCD 打包只消费持久 URL。R2 需保证该字段可被 R4 回写 |
|
||||
| 2 | **词云字段保留** | `designData.wordcloud` 由 R4 写入后,R2 的 PATCH/列表接口不得丢弃该分组 |
|
||||
| 3 | **mask 必须存在** | `designData.category.mask` 是画布尺寸来源;缺 mask 的旧数据 WCD 打包按产品默认尺寸兜底 |
|
||||
| 4 | **布局字段持久化** | 决策#2:`rotation`/`zIndex` 本期由 DIY 随贴纸持久化(当前 `StickerItem` 缺 rotation,需补) |
|
||||
| 5 | **白名单放行** | 后端 `design-list` 的 items JSON 白名单放行 `version/background/wordcloud/rotation/zIndex` 字段;单条 ≤1MB 上限以新结构复核(图片在远端 URL,JSON 本体仍应很小) |
|
||||
| 6 | **状态映射不变** | `ordered` 继续由 `orderId != null` 派生,本契约不改变 R2 状态机 |
|
||||
|
||||
## 4. 边界(本期不做)
|
||||
|
||||
- 贴纸 `edits`(亮度/色相/对比度/`sketchSrc` 线稿)无法由词云平台 `.wcd`
|
||||
`document.json` 表达,本期不随 WCD 携带;仅记录在 `manifest.meta`。若后续生产需要
|
||||
线稿还原,需单独升 WCD 契约(wordcloud 侧支持后再谈)。
|
||||
- 字体不随包携带(wordcloud 第一阶段不做字体打包)。
|
||||
- `.wcd` 只做"订单 → wordcloud"单向投递;暂不做"词云平台 → 小程序"的反向导出。
|
||||
|
||||
## 5. 落地位置
|
||||
|
||||
| 文件 | 内容 |
|
||||
|---|---|
|
||||
| 前端类型 | `wechat_wc/src/types/index.ts` 的 `DesignItem.designData` 按本契约补齐字段 |
|
||||
| 前端页面 | `diy`(保存 designData)、`checkout`(下单前确认 designData 完整) |
|
||||
| 后端白名单 | `wxmp_backend/src/design-list/*` 的 items 校验放行新字段 |
|
||||
| 契约发行 | `wxmp_backend/docs/api-contract-v1.md` §5 引用本文档 |
|
||||
|
||||
## 6. 决策记录(2026-08-12 冻结)
|
||||
|
||||
| 评审点 | 决策 |
|
||||
|---|---|
|
||||
| 1. `background`/`wordcloud` 命名 | **接受**,按 §2 结构冻结 |
|
||||
| 2. `rotation`/`zIndex` 是否本期持久化 | **本期持久化**,DIY 随贴纸保存(见约束 #4) |
|
||||
| 3. 名单快照 `wordcloud.names` 是否必须 | **小程序可不必须**:`names` 保持 optional,小程序可不带;R4 生成词云后按实际写入,生产侧不强依赖 |
|
||||
| 4. 贴纸图持久化由谁触发 | **R4 负责**:下单/派单前把本地贴纸图上传为 COS 持久 URL 并回写 `src`(见约束 #1) |
|
||||
@@ -1,10 +1,12 @@
|
||||
# R4 上传 + 词云生成 + 线稿(feat/r4-upload-wordcloud)
|
||||
# R4 上传 + 词云生成 + 线稿 + 下单后 WCD 生产任务(feat/r4-upload-wordcloud)
|
||||
|
||||
**Goal:** 打通用户图片上传、词云任务生成、DIY 贴纸线稿处理和异步任务状态轮询;
|
||||
**Goal:** 打通用户图片上传、词云任务生成、DIY 贴纸线稿处理、异步任务状态轮询,
|
||||
并在**下单后把订单对应的设计以 WCD 格式投递到词云平台**形成生产任务;
|
||||
这是四条路线中唯一复用 `wordcloud` 项目的路线,必须通过冻结接口契约的方式隔离它的大量变更。
|
||||
|
||||
**Architecture:** 小程序只与 `wxmp_backend` 通信;wxmp_backend 实现 COS 上传凭证、词云适配器、
|
||||
sketch 接口和任务记录/轮询;`wordcloud` FastAPI 服务作为外部引擎,只暴露并冻结最小契约。
|
||||
sketch 接口、任务记录/轮询,以及**由设计数据构造 `.wcd` 包并投递给词云平台**的派单服务;
|
||||
`wordcloud` FastAPI 服务作为外部引擎,只暴露并冻结最小契约。
|
||||
小程序页面对词云使用轮询进度,不使用 SSE/EventSource。
|
||||
|
||||
**Tech Stack:** Taro 3.6 + React 18 + TypeScript;NestJS 11 + Prisma/PostgreSQL + Redis/BullMQ;
|
||||
@@ -21,6 +23,10 @@ sketch 接口和任务记录/轮询;`wordcloud` FastAPI 服务作为外部引
|
||||
| `src/pages/diy/stickerEdit/index.tsx` | 把占位域名 `https://your-api-domain.com/api/sketch` 换成 `BASE_URL/api/sketch`;保留前端灰度降级 |
|
||||
| `src/types/index.ts` 或 `src/types/upload.ts` | `WordCloudJob/WordCloudStatus/SketchResult/CosCredentials` |
|
||||
| `src/utils/request.ts` 或新增 `upload.ts` | 上传文件统一带 token 与业务 header |
|
||||
| `src/types/index.ts`(designData) | 按 `docs/design-data-contract-v1.md` 冻结结构补充字段(详见下文 R2 依赖) |
|
||||
|
||||
> 下单后的 WCD 投递完全发生在后端,**前端不新增页面与接口**;前端只需保证
|
||||
> `designData` 按冻结契约携带持久图片 URL、布局与名单快照。
|
||||
|
||||
### 后端 `wxmp_backend`
|
||||
|
||||
@@ -29,13 +35,17 @@ sketch 接口和任务记录/轮询;`wordcloud` FastAPI 服务作为外部引
|
||||
| `upload` | `GET /api/upload/credentials?key=...` 返回 STS 临时凭证或 COS 预签名 URL;`Upload` 表记录 |
|
||||
| `wordcloud`(新增) | `POST /api/wordcloud/generate`、`GET /api/wordcloud/jobs/:id`、`GET /api/wordcloud/jobs/:id/result` |
|
||||
| `wordcloud` 适配器 | 把小程序传入的底图+名字列表转成 wordcloud 契约请求;任务状态入库,用户归属校验 |
|
||||
| **`wordcloud` 派单服务(新增)** | `buildWcdPackage(designData)` 构造 `.wcd` → `dispatchToWordcloud(orderId)` 投递 → 落 `CustomizationTask` → 轮询产物并转存 COS(见 §3) |
|
||||
| `sketch`(新增) | `POST /api/sketch` 接收图片,返回处理后图片 URL;内部可先做基础处理或接外部 AI |
|
||||
| `queue` | BullMQ 消费者把 wordcloud/sketch 任务状态同步到 `CustomizationTask`/`WordCloudJob` |
|
||||
| `docs/wordcloud-contract.md` | 冻结 wordcloud 三/四个端点、字段、状态与错误语义,含版本号 |
|
||||
| `queue` | BullMQ 消费者把 wordcloud/sketch/生产派单任务状态同步到 `CustomizationTask`/`WordCloudJob` |
|
||||
| `docs/wordcloud-contract.md` | 冻结 wordcloud 接口,含**可选的 WCD 任务输入**,含版本号 |
|
||||
| `config` | `.env` 增加 `WORDCLOUD_API_URL`(词云平台地址),见 §3.4 |
|
||||
| `prisma` | `CustomizationTask` 增加 `orderId`/`wordcloudJobId`(migration) |
|
||||
|
||||
### 词云项目 `wordcloud`
|
||||
|
||||
- 需要冻结的接口:`POST /api/jobs`(建议增加直接传名字列表/JSON 的方式)、`GET /api/jobs/{id}`、`GET /api/jobs/{id}/files/png`。
|
||||
- 需要冻结的接口:`POST /api/jobs`(现有:`name_list(.xlsx)` 必填;**新增可选 `wcd_file`**)、
|
||||
`GET /api/jobs/{id}`、`GET /api/jobs/{id}/result`、`GET /api/jobs/{id}/files/{kind}`。
|
||||
- 其余 canvas、assets、projects、templates 等接口继续按它自己的节奏演进,小程序后端不依赖。
|
||||
- 若契约不变,wordcloud 内部重构无需通知 R4;契约变更时先升版本号,R4 单独出适配器更新。
|
||||
|
||||
@@ -60,57 +70,157 @@ interface WordCloudJob {
|
||||
3. 前端每 1-2 秒 `GET /api/wordcloud/jobs/:id` 轮询,`success` 后取 `imageUrl`。
|
||||
4. 结果图建议由 wxmp_backend 转存 COS 后返回微信可下载域名链接。
|
||||
|
||||
## 3. 设计注意事项
|
||||
## 3. 下单后生产任务:WCD 投递(本分支新增功能)
|
||||
|
||||
### DO
|
||||
> 场景:用户在 DIY 过程中生成的词云图、贴纸布局最终落在一条设计清单(`design-list`)里。
|
||||
> 下单之后,订单对应的整套设计必须能**原样恢复到词云平台**,形成可追溯、可复用的
|
||||
> **生产任务**(用于后续加工/激光雕刻)。传输格式采用词云平台已定义的 `.wcd`
|
||||
> 画布导入导出包(Zip:`manifest.json` + `document.json` + `assets/`)。
|
||||
|
||||
- DO 小程序只调用 `wxmp_backend`,`wordcloud` 的地址、密钥、内部参数对小程序完全不可见。
|
||||
- DO 上传走临时凭证或预签名 URL,前端拿不到主账号 SecretKey。
|
||||
- DO 文件名与 `key` 由服务端生成或校验(如 `uploads/{userId}/{uuid}.jpg`),杜绝用户传路径穿越。
|
||||
- DO 限制文件类型与大小(底图建议 png/jpg ≤ 10MB、名单 ≤ 200 个名字),前后端双重校验。
|
||||
- DO 词云任务在数据库建记录并按用户隔离,用户只能查询自己的 job。
|
||||
- DO 轮询采用普通 HTTP GET,不做 SSE;小程序端 EventSource 支持不稳定。
|
||||
- DO 进度字段以 wordcloud 返回为准,前端只负责展示,不再用随机数模拟进度。
|
||||
- DO sketch 请求带登录态和文件大小校验,失败时前端降级本地灰度,并明确提示“已使用本地线稿”。
|
||||
- DO 长期运行的任务设置超时与失败清理,避免 COS 对象和任务记录无限堆积。
|
||||
- DO 在 `docs/wordcloud-contract.md` 记录契约版本,并在后端适配器代码里注释依赖版本。
|
||||
### 3.1 端到端链路
|
||||
|
||||
### DON'T
|
||||
```
|
||||
设计清单 designData(R2 按 design-data-contract-v1.md 冻结结构保存)
|
||||
→ R3 POST /api/orders 创建订单
|
||||
→ 订单进入生产(PENDING → PROCESSING;支付未配置期可走幂等触发,见 3.3)
|
||||
→ queue 入队 customization 生产任务
|
||||
→ WordCloudService.dispatchToWordcloud(orderId)
|
||||
1. 读订单关联 design-list 的 designData
|
||||
2. 贴纸图片持久化(R4 职责,契约束 #1):本地 `wxfile://`/`tmp` 图 → 上传 COS → 回写 `stickers[].src` 为持久 URL
|
||||
3. buildWcdPackage(designData) → 内存构造 .wcd(布局 → document.json,图片字节 → assets/)
|
||||
4. POST {WORDCLOUD_API_URL}/api/jobs multipart wcd_file + params
|
||||
5. 落 CustomizationTask{ orderId, wordcloudJobId, status },开轮询
|
||||
6. 轮询 GET /api/jobs/{id}/result → 产物(PNG/SVG)转存 COS
|
||||
→ 更新 CustomizationTask.status / resultUrl
|
||||
```
|
||||
|
||||
小程序前端永不接触 wordcloud;WCD 完全由 wxmp_backend 构造与投递。
|
||||
|
||||
### 3.2 WCD 包结构(wxmp_backend 构造,wordcloud 侧已能消费)
|
||||
|
||||
后端按 `designData` 构造以下 Zip:
|
||||
|
||||
```
|
||||
{orderNo}.wcd
|
||||
├── manifest.json // format: "wordcloud-canvas", version: 1
|
||||
│ // + canvas{width,height,background} + assets[ id/name/type/mimeType/sha256/size ]
|
||||
├── document.json // CanvasDocument:width/height/background/layers/elements
|
||||
└── assets/<assetId>.<ext> // 每个 sticker/底图的图片字节(从持久 URL 下载)
|
||||
```
|
||||
|
||||
映射规则(以词云平台 `/api/design-templates/import` 现有实现为准):
|
||||
|
||||
- `document.canvas` ← 商品 `category.mask` 尺寸 + 背景色。
|
||||
- `elements[]` ← 底图(`background.src`)作为一个 element;每个贴纸
|
||||
`{ type:'sticker', assetId: asset-N, x, y, width, height, rotation?, opacity? }`,
|
||||
`assetId` 用包内临时 ID。
|
||||
- `assets/` ← 每个贴纸图片 + 底图的字节,文件名以包内 `assetId` 开头
|
||||
(wordcloud 解包根据 `assets/<assetId>*` 匹配)。
|
||||
- 名单快照(`designData.wordcloud.names`)写入 `manifest` 的自定义 `meta` 字段,
|
||||
仅作记录,不影响导入还原。
|
||||
|
||||
### 3.3 触发与幂等
|
||||
|
||||
- 推荐:订单进入 `PROCESSING` 时(支付回调确认后)入队 `customization` 任务,
|
||||
由处理器调用派单服务。
|
||||
- 支付未配置期:提供 `POST /api/orders/:id/dispatch`(幂等)作为联调/运营触发手段,
|
||||
同一订单只投递一次(以 `CustomizationTask.orderId` 唯一或状态机约束)。
|
||||
|
||||
### 3.4 环境变量(wxmp_backend)
|
||||
|
||||
```dotenv
|
||||
# ── 词云平台(WCD 生产任务)────────────────────────────
|
||||
# 词云服务(FastAPI)地址;未配置时下单后的 WCD 派单返回“未配置”,不做假成功
|
||||
WORDCLOUD_API_URL=
|
||||
# 请求 wordcloud 超时(毫秒);可选
|
||||
WORDCLOUD_TIMEOUT_MS=30000
|
||||
```
|
||||
|
||||
`config/configuration.ts` 增加 `wordcloud: { apiUrl, timeoutMs }`;
|
||||
`config/validation.schema.ts` 增加 `WORDCLOUD_API_URL: Joi.string().allow('').default('')`
|
||||
(允许为空,与"支付密钥未配置是合法状态"一致)。
|
||||
|
||||
### 3.5 词云平台需新增的能力
|
||||
|
||||
- `POST /api/jobs` 支持可选 `wcd_file`(multipart `.wcd`):存在时跳过 `.xlsx` 名单
|
||||
模式,进入"还原设计 → 生成生产任务"模式;状态机与产物管线复用现有 jobs。
|
||||
- wordcloud 平台改动点(供其 R4 实现参考,契约先行冻结):
|
||||
1. `name_list` 与 `wcd_file` 二选一(`wcd_file` 对已有调用向后兼容,纯新增)。
|
||||
2. 解包 `.wcd` 校验 `format/version`,`assets` 按 SHA-256 去重(复用现有导入逻辑)。
|
||||
3. `document.json` 归一化后落 `design_documents`,`sticker.assetId` 重映射为真实素材 ID。
|
||||
4. 创建生产 job,产物沿用 `GET /api/jobs/{id}/result` + `/files/{kind}`。
|
||||
|
||||
### 3.6 设计注意事项
|
||||
|
||||
#### DO
|
||||
|
||||
- DO 由 wxmp_backend 构造并投递 WCD,wordcloud 地址、密钥、内部参数对小程序完全不可见。
|
||||
- DO `wordcloudJobId → orderId/userId` 关联入库,轮询与结果查询带归属校验。
|
||||
- DO `WORDCLOUD_API_URL` 未配置时返回结构化“未配置”错误(状态字面量 `not_configured`,
|
||||
见 `api-contract-v1.md` §8),前端给出可理解提示。
|
||||
- DO 派单幂等:同一订单重复触发只产生一次投递。
|
||||
- DO 产物由 wxmp_backend 下载转存 COS 后返回公网 URL,不向小程序暴露 wordcloud 内部地址。
|
||||
- DO 名单/布局等设计快照写入 `designData`(冻结契约),保证下单后可重建 WCD。
|
||||
- DO WCD 内部的贴纸图片必须来自持久 URL(COS),打包前先下载字节。
|
||||
- DO 包大小与单账号投递频率限制(wordcloud 为 CPU 密集任务)。
|
||||
|
||||
#### DON'T
|
||||
|
||||
- DON'T 把 wordcloud 项目 fork 进 wxmp_backend,也不要把它的内部 Python/C++ 代码搬进 NestJS。
|
||||
- DON'T 在小程序前端硬编码 wordcloud 基础地址或直接调用它。
|
||||
- DON'T 上传逻辑使用完整云厂商密钥;密钥只存在于服务端环境变量。
|
||||
- DON'T 接受本地临时路径作为最终结果地址,wordcloud 返回的内部 `/api/jobs/...` 必须转成 COS 公网 URL。
|
||||
- DON'T 让用户通过猜 `jobId` 读取他人任务结果,所有查询都带 userId 条件。
|
||||
- DON'T 在密钥未配置时静默跳过上传/词云;返回结构化“未配置”错误,前端给出可理解提示。
|
||||
- DON'T 用假的 `setInterval` 进度假装生成完成,R4 结束前必须移除当前 wordcloud 页的模拟逻辑。
|
||||
- DON'T 在密钥/`WORDCLOUD_API_URL` 未配置时静默跳过或假装成功。
|
||||
- DON'T 用假的 `setInterval` 进度伪装下单后的任务结果;同样不模拟生产派单成功。
|
||||
- DON'T 把 `.wcd` 文件当最终产物直接返回小程序,它只是 wordcloud 的交换容器。
|
||||
- DON'T 接受本地临时路径(`wxfile://`/`tmp`)作为 `designData` 中的贴纸图,R4 打包拿不到字节。
|
||||
|
||||
## 4. 补充内容
|
||||
### 3.7 验收标准
|
||||
|
||||
### 验收标准
|
||||
- 订单进入生产后,wordcloud 平台出现对应 job:`queued → running → success`,
|
||||
产物 PNG 可下载并被 wxmp_backend 转存 COS。
|
||||
- 同一订单重复触发只投递一次。
|
||||
- `WORDCLOUD_API_URL` 未配置(留空)时,派单返回结构化“未配置”,不落假成功记录。
|
||||
- 由 `designData` 构造的 `.wcd` 能在 wordcloud `/api/design-templates/import`
|
||||
同套逻辑下原样还原(素材去重、布局一致)。
|
||||
- 后端 `npm run build`、前端 `npm run build:weapp` 通过;Swagger 与契约文档一致。
|
||||
|
||||
- 小程序端上传图片可拿到 COS 可访问 URL,`Upload` 表有记录且归属当前用户。
|
||||
- `POST /api/wordcloud/generate` 可在真实 wordcloud 服务上产出 PNG,前端轮询到结果并保存相册。
|
||||
- `POST /api/sketch` 返回有效图片 URL;接口失败时前端走降级且不白屏。
|
||||
- 词云任务非本人不可访问;上传文件类型/大小校验生效。
|
||||
- `docs/wordcloud-contract.md` 已冻结并带版本号;前端 `wordcloud` 页不再有随机进度。
|
||||
### 3.8 风险
|
||||
|
||||
- wordcloud 侧 `POST /api/jobs` 现强制 `name_list`;支持 `wcd_file` 二选一需要
|
||||
wordcloud 排期改动。契约先冻结,wordcloud 实现可后置,wxmp_backend 按契约先写适配层。
|
||||
- `designData` 中贴纸图片若仍为本地/临时 URL,WCD 打包会失败——**依赖 R2 冻结持久 URL 规则**。
|
||||
- 支付未接入期没有自然触发点,需要一个幂等 `dispatch` 接口用于联调。
|
||||
- `CustomizationTask` 缺 `orderId`/`wordcloudJobId` 字段,需一次 migration。
|
||||
- 贴纸 `edits`(亮度/色相/对比度/线稿)暂无法由 WCD `document.json` 表达,本期先不随包携带,
|
||||
在 `manifest.meta` 记录并在契约中标注边界。
|
||||
|
||||
## 4. 对 R2 的依赖(接口先冻结)
|
||||
|
||||
R4 的 WCD 打包依赖 R2 设计清单里保存的 `designData` 结构。**接口需在本分支开工前冻结**,
|
||||
相关契约见新文档 `docs/design-data-contract-v1.md`(跨路线共享,R2 为数据生产者,R4 为消费者)。
|
||||
|
||||
需 R2 配合冻结的要点:
|
||||
|
||||
| 项 | 要求 |
|
||||
|---|---|
|
||||
| `designData` 结构 | 按 `docs/design-data-contract-v1.md` 补 `version/background/wordcloud` 字段;`category.mask` 必须保存 |
|
||||
| 贴纸图片持久化 | **R4 负责**:下单/派单前把贴纸本地路径上传为 COS 持久 URL 并回写 `src`;R2 允许保存时 `src` 暂为本地路径 |
|
||||
| 名单快照 | `designData.wordcloud.names` **为 optional,小程序可不带**;R4 生成词云后按实际写入,R2 保存/更新清单时保留该组字段,后端白名单放行 |
|
||||
| 布局完整性 | 贴纸 `rotation`/`zIndex` 本期持久化(现有 `StickerItem` 缺 rotation,需补) |
|
||||
| `design-list` 白名单 | 后端 items JSON 校验放行上述新字段,单条 ≤1MB 上限按新结构复核 |
|
||||
|
||||
决策记录与细则:见 `docs/design-data-contract-v1.md` §6。
|
||||
|
||||
## 5. 补充内容
|
||||
|
||||
### 合并与依赖
|
||||
|
||||
- 分支名:前端与后端均为 `feat/r4-upload-wordcloud`。
|
||||
- 与 R1-R3 并行,不依赖商品/订单链路;后端上传基础设施可为后续 R2 的贴纸图片持久化提供能力。
|
||||
- 下单后 WCD 派单依赖 R2 冻结的 designData 结构与 R3 的订单状态机。
|
||||
- wordcloud 契约若未冻结,R4 先完成“契约文档 + 后端适配层”,联调阶段再补真实 job。
|
||||
|
||||
### 风险
|
||||
|
||||
- wordcloud 正在大量变更,任务状态/产物路径随时可能变化;这正是契约冻结要解决的问题,联调时先锁定一个部署版本。
|
||||
- COS 与微信 `downloadFile`/`uploadFile` 合法域名必须提前配置,否则真机上传下载会失败。
|
||||
- 词云生成是 CPU 密集型任务,需要限制并发与单账号频率,防止被刷爆资源。
|
||||
- `designData` 中的贴纸图片若在 R4 接入上传,需要 R2 的设计 JSON 结构配合增加 `uploadedUrl` 字段,两分支交接时注意。
|
||||
|
||||
### 测试要求
|
||||
|
||||
- 后端:上传凭证、大小/类型校验、wordcloud 适配器正常/失败/超时、任务归属权限、sketch 接口。
|
||||
- 前端:词云上传、轮询进度、fail 重试、结果保存相册;线稿接口失败降级;无 COS 配置提示。
|
||||
- 集成:用固定 wordcloud 部署版本跑一次真实生成端到端 smoke。
|
||||
|
||||
- 后端:上传凭证、大小/类型校验、wordcloud 适配器正常/失败/超时、**WCD 打包与派单、任务归属权限**、sketch 接口。
|
||||
- 前端:词云上传、轮询进度、fail 重试、结果保存相册;线稿接口失败降级;**下单后派单状态展示**;无 COS/无 WORDCLOUD 配置提示。
|
||||
- 集成:用固定 wordcloud 部署版本跑一次真实生成端到端 smoke + 一次下单派单 smoke。
|
||||
Reference in New Issue
Block a user