feat: split DA and API layers by domain, add parallel route docs
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
# 并行开发指南(R0)
|
||||
|
||||
本文档是四名成员并行开发时的代码修改约定。目标是:每个人在自己路线的文件里改动,
|
||||
不与其他人发生文件级冲突;共享文件只由 R0 聚合入口维护,平时不再堆业务代码。
|
||||
|
||||
## 1. 目录约定
|
||||
|
||||
页面不得直接读写 Storage 或跨过聚合入口调用底层模块,统一从下面两个入口导入:
|
||||
|
||||
```ts
|
||||
import { getAddressList, addAddress } from '../../utils/store'
|
||||
import { fetchProducts, createOrder } from '../../utils/api'
|
||||
```
|
||||
|
||||
`src/utils/store/`:DA 层,按域拆分:
|
||||
|
||||
| 文件 | 归属 | 内容 |
|
||||
|---|---|---|
|
||||
| `store/keys.ts` | R0,尽量不改 | openid 作用域 key 与 mock 判断 |
|
||||
| `store/user.ts` | R0/R3 | 用户信息、账号切换、注册表 |
|
||||
| `store/design.ts` | R2 | 设计清单本地数据 |
|
||||
| `store/address.ts` | R2 | 收货地址本地数据 |
|
||||
| `store/order.ts` | R3 | 订单本地数据与 designToOrder |
|
||||
| `store/theme.ts` | R0 | 主题模式读写 |
|
||||
| `store/index.ts` | R0 | 聚合 re-export,只加不删 |
|
||||
|
||||
`src/utils/api/`:HTTP 接口层,按域拆分:
|
||||
|
||||
| 文件 | 归属 | 内容 |
|
||||
|---|---|---|
|
||||
| `api/auth.ts` | R0 | 登录、登出、token |
|
||||
| `api/user.ts` | R0 | 用户资料 |
|
||||
| `api/product.ts` | R1 | 商品、分类 |
|
||||
| `api/address.ts` | R2 | 收货地址(待补) |
|
||||
| `api/design.ts` | R2 | 设计清单(待补) |
|
||||
| `api/order.ts` | R3 | 订单、支付(待补) |
|
||||
| `api/upload.ts` | R4 | 上传、词云、线稿(待补) |
|
||||
| `api/index.ts` | R0 | 聚合 re-export |
|
||||
|
||||
类型统一放在 `src/types/index.ts`,或按域新增 `src/types/<domain>.ts`,
|
||||
不要把页面私有类型散落在各个页面里。
|
||||
|
||||
## 2. 路线归属
|
||||
|
||||
每条路线的完整开发内容、设计 DO/DON'T 与验收标准见 `docs/routes/`:
|
||||
|
||||
| 文档 | 分支 |
|
||||
|---|---|
|
||||
| [R1 商品目录动态化](routes/route-r1-product-catalog.md) | `feat/r1-catalog` |
|
||||
| [R2 地址 + 设计清单](routes/route-r2-address-design.md) | `feat/r2-address-design` |
|
||||
| [R3 订单 + 支付占位](routes/route-r3-order-pay.md) | `feat/r3-order-pay` |
|
||||
| [R4 上传 + 词云 + 线稿](routes/route-r4-upload-wordcloud.md) | `feat/r4-upload-wordcloud` |
|
||||
|
||||
| 路线 | 页面 | 后端 | 词云项目 |
|
||||
|---|---|---|---|
|
||||
| R1 商品目录 | `index`、`shop`、`product` | products/categories、schema 扩展、seed | 不用 |
|
||||
| R2 地址 + 设计清单 | `address`、`designList` | addresses CRUD、design-list CRUD、状态映射 | 不用 |
|
||||
| R3 订单 + 支付占位 | `checkout`、`orders`、`orderDetail`、付款按钮 | orders 重算/事务、payments 占位 | 不用 |
|
||||
| R4 上传 + 词云 + 线稿 | `wordcloud`、`diy/stickerEdit` | upload、词云适配器、sketch、队列 | 复用,需冻结最小契约 |
|
||||
|
||||
文件所有权约定:
|
||||
|
||||
- `checkout` 页归 R3,R2 只做 `address` 和 `designList` 页。
|
||||
- `profile` 页是只读统计消费者,等 R2/R3 合入后再统一收尾。
|
||||
- `wordcloud` 项目只有 R4 会碰,其余路线不依赖它。
|
||||
|
||||
## 3. 新增接口怎么改
|
||||
|
||||
1. 后端先把 Swagger/DTO 定下来,确认字段与状态枚举。
|
||||
2. 在对应的 `api/<domain>.ts` 里写函数,返回类型对齐 `src/types`。
|
||||
3. 不需要改页面里的导入路径:聚合入口已 re-export 所有域文件,
|
||||
新函数会自动从 `../../utils/api` 导出。
|
||||
4. 页面只调用 `api` 层的函数,不在页面里直接写 `http.get`。
|
||||
|
||||
## 4. 新增本地业务怎么改
|
||||
|
||||
1. 判断属于哪个域,写进 `store/<domain>.ts`。
|
||||
2. 若该域已有文件,直接在文件内追加函数;不要新建 `xxx2.ts`。
|
||||
3. 新域需在 `store/index.ts` 增加一行显式 re-export。
|
||||
4. 页面通过 `../../utils/store` 导入,不直接 import 内部模块路径。
|
||||
|
||||
## 5. Git 协作
|
||||
|
||||
每路线在 `wechat_wc` 与 `wxmp_backend` 使用同名分支:
|
||||
|
||||
| 路线 | 分支名 |
|
||||
|---|---|
|
||||
| R1 | `feat/r1-catalog` |
|
||||
| R2 | `feat/r2-address-design` |
|
||||
| R3 | `feat/r3-order-pay` |
|
||||
| R4 | `feat/r4-upload-wordcloud` |
|
||||
|
||||
日常循环:
|
||||
|
||||
```bash
|
||||
git checkout main && git pull
|
||||
git checkout -b feat/r1-catalog
|
||||
|
||||
# 提交并推送自己的分支
|
||||
git add -A
|
||||
git commit -m "feat(catalog): xxx"
|
||||
git push -u origin feat/r1-catalog
|
||||
|
||||
# 主干有更新时,变基到最新
|
||||
git fetch origin
|
||||
git rebase origin/main
|
||||
|
||||
# 合入:只有合入负责人执行
|
||||
git checkout main && git pull
|
||||
git merge --no-ff feat/r1-catalog
|
||||
git push origin main
|
||||
```
|
||||
|
||||
规则:
|
||||
|
||||
- 主干 `main` 保持可运行,禁止直接 push,合入走 Pull Request。
|
||||
- 后端与前端同名分支是一对,评审时成对看,后端 Swagger 先定契约。
|
||||
- 不跨路线互相拉分支;主干更新只通过 `rebase origin/main` 获取。
|
||||
- 合入顺序:R1 先合(R3 服务端金额重算依赖商品表),R2/R4 随后,R3 最后。
|
||||
|
||||
## 6. 验证
|
||||
|
||||
每次提交前至少保证:
|
||||
|
||||
```bash
|
||||
npm run build:weapp
|
||||
```
|
||||
|
||||
R1 合入前额外跑后端接口 smoke;R2/R3 合入前跑对应页面在微信开发者工具里的手测;
|
||||
R4 合入前跑一次词云契约 smoke。
|
||||
@@ -0,0 +1,26 @@
|
||||
# 四条并行开发路线
|
||||
|
||||
四份文档分别对应四个开发分支,覆盖前端 `wechat_wc`、后端 `wxmp_backend` 与词云项目
|
||||
`wordcloud` 的分工、开发内容、设计 DO/DON'T 和验收标准。以本目录文档为准,开发时
|
||||
不要随意扩大分支边界。
|
||||
|
||||
| 文档 | 分支 | 内容 |
|
||||
|---|---|---|
|
||||
| [R1 商品目录动态化](route-r1-product-catalog.md) | `feat/r1-catalog` | 商品/分类 API、seed、首页/商品/详情页切换 |
|
||||
| [R2 地址 + 设计清单](route-r2-address-design.md) | `feat/r2-address-design` | 地址 CRUD、默认地址事务、设计 JSON 与状态映射 |
|
||||
| [R3 订单 + 支付占位](route-r3-order-pay.md) | `feat/r3-order-pay` | 服务端下单、金额重算、订单状态机、支付未配置占位 |
|
||||
| [R4 上传 + 词云 + 线稿](route-r4-upload-wordcloud.md) | `feat/r4-upload-wordcloud` | COS 上传、wordcloud 契约冻结、sketch、任务轮询 |
|
||||
|
||||
## 合并顺序与依赖
|
||||
|
||||
- R1 是交易链路的前置,优先合入。
|
||||
- R2 依赖已有登录体系,可与 R1 并行。
|
||||
- R3 依赖 R1 的商品表和 R2 的地址/设计清单接口,最后合入。
|
||||
- R4 独立并行,但依赖 wordcloud 最小契约冻结。
|
||||
|
||||
## 通用红线
|
||||
|
||||
- 前后端同名分支成对评审,后端 Swagger 先定契约,前端再实现。
|
||||
- 每个分支合入主干前必须通过各自仓库的构建验证。
|
||||
- 页面统一从 `../../utils/store` 与 `../../utils/api` 聚合入口导入。
|
||||
- 不得跨路线修改其他分支的文件所有权。
|
||||
@@ -0,0 +1,93 @@
|
||||
# R1 商品目录动态化(feat/r1-catalog)
|
||||
|
||||
**Goal:** 让首页、商品列表、沉浸式详情、商品详情四个页面改从后端商品/分类 API 取数,
|
||||
前端不再直接依赖静态 `productConfig.ts` 作为线上数据源。
|
||||
|
||||
**Architecture:** 前端新增按域的 `api/product.ts` 与页面级加载状态;后端扩展 Product 表字段、
|
||||
补齐 products/categories 查询能力、写入 5 个真实品类 seed;前后端通过 `docs/api-contract-v1.md`
|
||||
冻结响应结构;静态 `productConfig.ts` 降级为离线兜底与图标映射,R1 合并验证前不删除。
|
||||
|
||||
**Tech Stack:** Taro 3.6 + React 18 + TypeScript;NestJS 11 + Prisma/PostgreSQL。
|
||||
|
||||
## 1. 本分支要开发的内容
|
||||
|
||||
### 前端 `wechat_wc`
|
||||
|
||||
| 文件 | 职责 |
|
||||
|---|---|
|
||||
| `src/types/index.ts` 或 `src/types/product.ts` | ProductCategory 与后端字段对齐,新增 `subtitle/tone/story/scene/tags/specs/mask/originalPrice/leadTime/status` |
|
||||
| `src/utils/api/product.ts` | `fetchProducts(params)`、`fetchProduct(id)`、`fetchCategories()`,返回类型显式声明 |
|
||||
| `src/hooks/useProducts.ts`(新增) | 商品列表/详情加载、loading/error/retry、可选 60s 内存缓存 |
|
||||
| `src/pages/index/index.tsx` | 品类网格与搜索改走 API;成品轮播仍可静态,但价格/图片建议取商品数据 |
|
||||
| `src/pages/shop/index.tsx` | 商品网格改走 API,补加载骨架、空态、失败重试 |
|
||||
| `src/pages/shop/detail/index.tsx` | 按 `id` 拉详情,补 loading/error,保留沉浸式滚动动画 |
|
||||
| `src/pages/product/index.tsx` | 按 `id` 拉详情,`addDesign` 继续复用 `store/design.ts` |
|
||||
| `src/utils/productConfig.ts` | 保留为 seed 参考与离线兜底,不参与线上主流程 |
|
||||
|
||||
### 后端 `wxmp_backend`
|
||||
|
||||
| 模块 | 内容 |
|
||||
|---|---|
|
||||
| `prisma/schema.prisma` | Product 增加 `subtitle/leadTime/originalPrice/tone/tags/specs/mask/story/scene/iconImg`,其中 `tone Int[]`、`specs Json`、`mask Json`、`tags String[]` |
|
||||
| `prisma/seed.ts` | 写入 5 个真实品类(笔记本小/大、杯垫、笔盒、书灯),图片用 OSS URL,mask 与前端现有一致 |
|
||||
| `products` | `GET /api/products`:分页、`categoryId`、`keyword`、`status=ON_SALE`;`GET /api/products/:id`:404 处理 |
|
||||
| `categories` | `GET /api/categories`:稳定排序返回,暂做扁平结构 |
|
||||
| `docs/api-contract-v1.md` | 商品/分类接口、分页结构、Decimal 数字格式、错误码 |
|
||||
|
||||
### 词云项目 `wordcloud`
|
||||
|
||||
不参与本分支。
|
||||
|
||||
## 2. 设计注意事项
|
||||
|
||||
### DO
|
||||
|
||||
- DO 前端只消费后端返回的 `price`,下单链路以后也不允许前端自算金额。
|
||||
- DO 在 API 层把 Decimal 字符串规范成本地 `number`,页面组件里不做字符串拼接运算。
|
||||
- DO 所有列表/详情页补齐 loading、空态、失败重试,失败时给出可操作文案。
|
||||
- DO 图片加载失败使用 `product.iconImg` 或 `四角星.svg` 兜底,单张图失败不阻塞页面。
|
||||
- DO `mask`、`specs`、`tone` 等富字段作为后端 JSON 返回,前端类型用判别联合描述。
|
||||
- DO 后端 seed 和前端 `productConfig.ts` 使用同一套稳定的业务 ID(如 `notebook-small`),方便前后端联调。
|
||||
- DO 在 R1 分支内同步更新前端 `types` 与 Swagger 文档,一个 PR 成对评审。
|
||||
- DO 商品列表接口先给分页参数,前端第一版可以每次取全部,但接口结构要能扩展。
|
||||
- DO 公共接口统一 `auth:false`,不要携带 token。
|
||||
- DO 保持项目事件规范:所有点击使用 `onTap`,不使用 `onClick`。
|
||||
|
||||
### DON'T
|
||||
|
||||
- DON'T 直接删除 `productConfig.ts`,R1 合并前它仍是离线兜底和图标映射来源。
|
||||
- DON'T 在页面里直接写 `http.get('/api/products')`,必须收口到 `api/product.ts`。
|
||||
- DON'T 在前端硬编码新的图片 CDN 地址,继续走 `assetUrl` 与 `OSS_BASE_URL`。
|
||||
- DON'T 让前端根据 `originalPrice` 自行计算促销逻辑,促销后续由后端字段统一表达。
|
||||
- DON'T 依赖数据库插入顺序,列表必须有 `sort/createdAt` 稳定排序。
|
||||
- DON'T 在 seed 中用随机 cuid 造成不同环境商品 ID 漂移。
|
||||
- DON'T 在 R1 里顺手改主题、TabBar、DIY 等无关文件。
|
||||
- DON'T 把 `description` 塞进 JSON 大字段混用,普通段落继续用字符串字段。
|
||||
|
||||
## 3. 补充内容(用户未列但建议纳入)
|
||||
|
||||
### 验收标准
|
||||
|
||||
- 前后端本地联调:首页/商品列表/沉浸式详情/商品详情四个页面数据来自 API,静态配置失效时页面有兜底而不会白屏。
|
||||
- 后端 `npm run build`、前端 `npm run build:weapp` 通过。
|
||||
- Swagger 中商品列表/详情/分类文档完整;前端类型与 Swagger 字段一一对应。
|
||||
- seed 后数据库包含 5 个在售品类,图片 URL 可访问。
|
||||
|
||||
### 合并与依赖
|
||||
|
||||
- 分支名:前端与后端均为 `feat/r1-catalog`。
|
||||
- 合并顺序:R1 是整个交易链路的前提,优先合入;R3 的服务端金额重算依赖本分支的商品表。
|
||||
- 合入前跑一次后端接口 smoke:`GET /api/categories`、`GET /api/products`、`GET /api/products/:id`。
|
||||
|
||||
### 风险
|
||||
|
||||
- OSS 图片域名若未配到微信后台 `downloadFile` 合法域名,商品图会在真机白图,联调时先确认。
|
||||
- `tone`、`mask` 等富字段若后端 JSON 序列化方式不统一,前端类型会悄悄失效,合入前要跑真实返回样例。
|
||||
- 首页成品轮播目前混用静态文案与商品数据,R1 建议只把“热门品类”切 API,轮播数据源单独决策。
|
||||
|
||||
### 测试要求
|
||||
|
||||
- 后端:至少补 products/categories 的 e2e(正常列表、空列表、404、keyword 过滤)。
|
||||
- 前端:三个页面手测加载态、断网重试、空数据、关键词搜索无结果、图片 404 兜底。
|
||||
- 不需要在 R1 引入自动化 UI 测试,保持手测清单即可。
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
# R2 收货地址 + 设计清单(feat/r2-address-design)
|
||||
|
||||
**Goal:** 把收货地址和设计清单从本地 Storage 切换到后端 CRUD,完成字段契约、接口权限、
|
||||
默认地址事务、设计数据 JSON 与状态映射,让 R3 的订单闭环可以直接消费这两组能力。
|
||||
|
||||
**Architecture:** 前端 `api/address.ts`、`api/design.ts` 负责 HTTP 契约,本地
|
||||
`store/address.ts`、`store/design.ts` 在联调期保留为兜底;后端补齐 addresses 与 design-list
|
||||
的完整 CRUD、事务与本人数据校验;`checkout` 页归 R3 所有,R2 只定义接口不修改该页面。
|
||||
|
||||
**Tech Stack:** Taro 3.6 + React 18 + TypeScript;NestJS 11 + Prisma/PostgreSQL。
|
||||
|
||||
## 1. 本分支要开发的内容
|
||||
|
||||
### 前端 `wechat_wc`
|
||||
|
||||
| 文件 | 职责 |
|
||||
|---|---|
|
||||
| `src/utils/api/address.ts` | `fetchAddresses/createAddress/updateAddress/deleteAddress/setDefaultAddress`,负责 `region[]` 与 `province/city/district` 双向转换 |
|
||||
| `src/utils/api/design.ts` | `fetchDesignList/createDesign/updateDesign/deleteDesigns` |
|
||||
| `src/pages/address/index.tsx` | 列表、新增、编辑、删除、设为默认全部走 API;保留表单校验与区域 Picker |
|
||||
| `src/pages/designList/index.tsx` | 列表、状态筛选、批量删除走 API;保留 LoginGuard |
|
||||
| `src/types/index.ts` | AddressItem/DesignItem 与后端结构对齐,新增服务端 id、状态码、时间字段 |
|
||||
| `src/utils/store/address.ts`、`design.ts` | 降级为“本地缓存/离线兜底”,API 调用成功后同步刷新 |
|
||||
|
||||
### 后端 `wxmp_backend`
|
||||
|
||||
| 模块 | 内容 |
|
||||
|---|---|
|
||||
| `addresses` | `GET/POST /api/addresses`、`PATCH /api/addresses/:id`、`PATCH /api/addresses/:id/default`、`DELETE /api/addresses/:id`;全部带 userId 归属校验 |
|
||||
| `addresses` 事务 | 设置默认时事务内先清旧默认再设新默认;删除默认地址后自动指定最新一条为默认 |
|
||||
| `design-list` | `GET/POST /api/design-list`、`PATCH /api/design-list/:id`、`DELETE /api/design-list/:id`、批量删除 |
|
||||
| `design-list` 校验 | `items` JSON 结构与大小校验、状态机转换、本人专属查询 |
|
||||
| `docs/api-contract-v1.md` | 地址字段映射、设计清单状态枚举、错误码 |
|
||||
|
||||
### 词云项目 `wordcloud`
|
||||
|
||||
不参与本分支,但 `designData` 中的本地贴纸图片路径问题会影响后续 R4,见风险一节。
|
||||
|
||||
## 2. 状态映射(必须先定死)
|
||||
|
||||
后端 `DesignListStatus` 目前是 `DRAFT/SUBMITTED/PROCESSING/DONE`,前端是
|
||||
`undesigned/designing/ordered`。R2 不要在两边各造一套值,建议按显示语义映射:
|
||||
|
||||
| 前端显示 | 后端状态 | 前端状态码 |
|
||||
|---|---|---|
|
||||
| 待设计 | DRAFT | `undesigned` |
|
||||
| 设计中 | SUBMITTED | `designing` |
|
||||
| 生产中 | PROCESSING | `processing` |
|
||||
| 已下单 | DONE | `ordered` |
|
||||
|
||||
“已下单”不要用独立状态表达,改为 `orderId != null` 派生;前端只显示,不提交这个状态。
|
||||
状态码收口到 `docs/api-contract-v1.md`,后端校验状态迁移,前端不传自由字符串。
|
||||
|
||||
## 3. 字段契约
|
||||
|
||||
### 地址
|
||||
|
||||
后端 `province/city/district/detail` 对应前端 `region: [province, city, district] + detail`。
|
||||
转换只放在 `api/address.ts`,页面和 store 不得散落第二次转换。
|
||||
|
||||
### 设计数据
|
||||
|
||||
`DesignList.items` 是服务端 JSON,前端提交结构建议与当前 `DesignItem.designData` 对齐:
|
||||
|
||||
```ts
|
||||
interface DesignData {
|
||||
stickers?: StickerItem[]
|
||||
category?: ProductCategory
|
||||
imageSrc?: string
|
||||
imagePos?: { x: number; y: number; scale: number }
|
||||
}
|
||||
```
|
||||
|
||||
服务端只做白名单字段校验,不修改业务 JSON 内容;单条设计数据建议限制在 1MB 以内。
|
||||
|
||||
## 4. 设计注意事项
|
||||
|
||||
### DO
|
||||
|
||||
- DO 地址的增删改查和默认切换全部要求登录态,401 时引导去个人中心登录。
|
||||
- DO 所有按 id 操作的服务端路由同时带 `userId` 条件,404 与 403 区分清楚。
|
||||
- DO 默认地址切换与删除补偿放在同一个 Prisma transaction 里。
|
||||
- DO 前端继续沿用户已熟悉的表单校验,但最终校验提示以后端返回为准。
|
||||
- DO 设计清单状态通过后端枚举返回,前端状态筛选基于返回的 `status` 计算。
|
||||
- DO 批量删除设计条目提供单个接口或循环调用时保证部分失败可恢复;推荐后端一次批量删除。
|
||||
- DO 本地 store 只在 API 失败时做降级读取,成功写回后以服务端数据为准。
|
||||
|
||||
### DON'T
|
||||
|
||||
- DON'T 把前端 `region` 数组原样 POST 给后端,后端契约是四个独立地址字段。
|
||||
- DON'T 让前端直接写 `isDefault` 的两条规则,默认地址唯一性由后端事务保证。
|
||||
- DON'T 在路由/service 层漏掉归属校验;这是本分支最容易出的越权洞。
|
||||
- DON'T 新增前端自造状态 `ordered` 之外的字符串,状态值必须在契约文档里可枚举。
|
||||
- DON'T 在本分支修改 `checkout/index.tsx`,地址选择器与下单集成留给 R3。
|
||||
- DON'T 把 `wxfile://` 临时贴纸路径当作可持久化 URL 直接入库,R4 之前它只是本地预览。
|
||||
- DON'T 用设计清单字段承载订单状态,`orderId` 派生展示即可。
|
||||
|
||||
## 5. 补充内容
|
||||
|
||||
### 验收标准
|
||||
|
||||
- 地址页与设计清单页在登录态下增删改查、默认切换、批量删除全部走真实后端并刷新。
|
||||
- 后端所有变更接口带本人校验,越权访问返回 403,不存在资源返回 404。
|
||||
- 默认地址永远唯一;删除默认地址后列表自动产生新的默认地址。
|
||||
- 前端构建与后端构建通过;Swagger 涵盖地址与设计清单全部接口。
|
||||
- 状态映射表在 `docs/api-contract-v1.md` 中落地,前端页面不再出现魔法字符串状态。
|
||||
|
||||
### 合并与依赖
|
||||
|
||||
- 分支名:前端与后端均为 `feat/r2-address-design`。
|
||||
- R2 依赖已有登录体系(已完成),不依赖 R1;可与 R1 并行开发。
|
||||
- checkout 对地址 API 的调用在 R3 接入,R2 只需保证接口稳定即可。
|
||||
|
||||
### 风险
|
||||
|
||||
- 设计数据中的贴纸是本地临时路径,一旦真机重启或清理缓存,旧设计预览会裂图;在 R4 上传能力完成前,文档明确这是已知限制。
|
||||
- 旧版本本地 `smart_design_list_<openid>` 数据与后端数据可能并存,首次切换登录时避免双写冲突;可做一次性“导入本地清单”或直接忽略旧数据。
|
||||
- 批量删除接口若后端未实现,前端循环删除会遇到部分失败,联调时先约定失败语义。
|
||||
|
||||
### 测试要求
|
||||
|
||||
- 后端:地址 CRUD、默认地址唯一性事务、越权访问、设计清单状态迁移、超大 JSON 拒绝。
|
||||
- 前端:地址新增/编辑/删除/默认切换、设计清单筛选/批量删除、断网降级提示、401 引导登录。
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
# R3 订单闭环 + 支付接口占位(feat/r3-order-pay)
|
||||
|
||||
**Goal:** 打通“设计清单 → 结算确认 → 创建订单 → 订单列表/详情 → 状态流转”的服务端闭环,
|
||||
微信支付只留接口与配置位,不填真实密钥;支付不可用时流程能明确回到“待付款/支付未配置”。
|
||||
|
||||
**Architecture:** 前端 `api/order.ts` 承接下单与查询,`checkout/orders/orderDetail` 三个页面
|
||||
改为服务端数据源;后端订单创建在事务内重算金额、快照地址、生成幂等的订单号并创建 PENDING
|
||||
支付记录;`payments` 在密钥未配置时返回显式占位响应,不做假成功。
|
||||
|
||||
**Tech Stack:** Taro 3.6 + React 18 + TypeScript;NestJS 11 + Prisma/PostgreSQL + Redis/BullMQ(可选)。
|
||||
|
||||
## 1. 本分支要开发的内容
|
||||
|
||||
### 前端 `wechat_wc`
|
||||
|
||||
| 文件 | 职责 |
|
||||
|---|---|
|
||||
| `src/utils/api/order.ts` | `createOrder/fetchOrders/fetchOrderDetail/payOrder`;请求只带商品项、地址 id、designListId,不带金额 |
|
||||
| `src/types/index.ts` | 服务端 Order 类型:`orderNo/status/totalAmount/items/addressSnapshot/createdAt/paidAt` |
|
||||
| `src/pages/checkout/index.tsx` | 从服务端读设计清单,选择地址(用 R2 的地址接口),提交下单;展示服务端总价;支付未配置时提示后回订单列表 |
|
||||
| `src/pages/orders/index.tsx` | 订单列表按状态 Tab 拉取;付款/确认收货等按钮调用对应接口或显示未配置 |
|
||||
| `src/pages/orderDetail/index.tsx` | 按订单 id 拉详情;地址快照、商品、状态条、物流占位 |
|
||||
| `src/utils/store/order.ts` | 标记 deprecated,仅保留为本地兜底,不再被 checkout 主流程调用 |
|
||||
| `src/pages/profile/index.tsx` | 状态统计改为基于服务端订单列表(R2/R3 合入后统一收尾) |
|
||||
|
||||
### 后端 `wxmp_backend`
|
||||
|
||||
| 模块 | 内容 |
|
||||
|---|---|
|
||||
| `orders` | `POST /api/orders`:服务端按 Product/design-list 重算金额、事务创建订单+items+Payment、生成唯一 orderNo、快照地址 |
|
||||
| `orders` 查询 | `GET /api/orders`(状态筛选/分页)、`GET /api/orders/:id`(仅本人)、`PATCH /api/orders/:id/confirm`、`POST /api/orders/:id/cancel`(按需) |
|
||||
| `payments` | `POST /api/payments/:orderId/pay` 与 `POST /api/payments/notify` 保留签名;密钥为空时返回 `{ configured:false, message:'支付未配置' }` |
|
||||
| `wechat` | `createUnifiedOrder/verifyPayNotify` 保持占位,不填入假商户参数 |
|
||||
| `queue` | 可选:订单创建后入队定制任务,处理器先只更新 `CustomizationTask` 状态 |
|
||||
| `docs/api-contract-v1.md` | 订单创建请求/响应、状态机、金额字段精度、错误码、幂等键 |
|
||||
|
||||
### 词云项目 `wordcloud`
|
||||
|
||||
不参与本分支。
|
||||
|
||||
## 2. 订单状态机
|
||||
|
||||
服务端 `OrderStatus` 与前端展示映射:
|
||||
|
||||
| 后端 | 前端 Tab | 说明 |
|
||||
|---|---|---|
|
||||
| PENDING | 待付款 | 允许取消;支付未配置时长期停留 |
|
||||
| PAID | 待发货 | 由支付回调推进,占位期不出现 |
|
||||
| PROCESSING | 待发货 | 定制生产中 |
|
||||
| SHIPPED | 待收货 | 需要发货/物流数据,本分支可展示占位 |
|
||||
| COMPLETED | 已完成 | 用户确认收货推进 |
|
||||
| CANCELLED | 已取消 | 待付款状态用户取消 |
|
||||
|
||||
前端只读状态数组,不做状态转移判断;转移一律走后端接口或支付回调。
|
||||
|
||||
## 3. 接口设计要点
|
||||
|
||||
创建订单请求:
|
||||
|
||||
```ts
|
||||
{
|
||||
designListId: string,
|
||||
addressId: string,
|
||||
items: [{ productId, quantity }]
|
||||
}
|
||||
```
|
||||
|
||||
服务端响应:
|
||||
|
||||
```ts
|
||||
{
|
||||
id: string,
|
||||
orderNo: string,
|
||||
status: "PENDING",
|
||||
totalAmount: number,
|
||||
items: [],
|
||||
addressSnapshot: {},
|
||||
createdAt: string
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 设计注意事项
|
||||
|
||||
### DO
|
||||
|
||||
- DO 订单金额、单价、总价全部由服务端计算,前端提交只给 `productId + quantity`。
|
||||
- DO 创建订单使用 `prisma.$transaction`,订单、明细、支付记录任一步失败整体回滚。
|
||||
- DO 给创建订单接口支持幂等键(客户端 `requestId` 或 `designListId` 防重复下单),双击提交只产生一单。
|
||||
- DO 订单号 `orderNo` 唯一,生成规则包含日期与随机位并在冲突时重试。
|
||||
- DO 收货地址在创建订单时直接快照到 `addressSnapshot`,订单创建后不再跟随地址变更。
|
||||
- DO 支付占位返回结构化 `configured:false`,前端据此显示“暂不支持支付”而不是错误弹窗。
|
||||
- DO 后端从配置读取微信支付密钥,密钥缺失时支付接口直接返回占位,不使用默认值或硬编码。
|
||||
- DO 订单列表/详情都校验当前用户 openid,越权访问返回 403。
|
||||
- DO 金额相关字段用 Decimal 精确类型持久化,序列化时统一成字符串或 number,避免浮点误差。
|
||||
|
||||
### DON'T
|
||||
|
||||
- DON'T 信任前端传的 `totalAmount`/`price`,即使前端为了展示计算过也一律忽略。
|
||||
- DON'T 让客户端参数直接决定 `status`,状态只能由服务端接口和支付回调推进。
|
||||
- DON'T 在代码里填占位商户号、密钥、证书路径;密钥缺失是合法运行状态而非 bug。
|
||||
- DON'T 模拟支付成功,包括开发环境;未配置就是未配置,避免上线前“假流程”掩盖问题。
|
||||
- DON'T 支付回调先解锁订单再验签;本分支只留入口,不实现任何未验签的落库逻辑。
|
||||
- DON'T 在本分支删除 `store/order.ts`,把它标记 deprecated 即可,本地兜底退出要留到全链路验证后。
|
||||
- DON'T 修改 R2 的地址/设计清单接口签名,R3 只消费。
|
||||
|
||||
## 5. 补充内容
|
||||
|
||||
### 验收标准
|
||||
|
||||
- 指定一个已登录用户,从设计清单进入结算页,选地址后创建订单;服务端返回的 `totalAmount` 与商品单价×数量一致。
|
||||
- 双击提交只生成一单,订单号唯一。
|
||||
- 订单列表/详情展示服务端数据,越权用户拿不到他人订单。
|
||||
- 支付按钮在密钥未配置时返回 `configured:false` 并给出明确文案,不调用微信、不写成功记录。
|
||||
- 后端 `npm run build`、前端 `npm run build:weapp` 通过。
|
||||
|
||||
### 合并与依赖
|
||||
|
||||
- 分支名:前端与后端均为 `feat/r3-order-pay`。
|
||||
- 依赖 R1 的商品表和 R2 的地址/设计清单接口,合并顺序放在 R1、R2 之后。
|
||||
- 支付实现作为一个独立后续里程碑,不阻塞本分支交付。
|
||||
|
||||
### 风险
|
||||
|
||||
- 微信小程序对订单支付有场景与类目要求,目前未备案时不要尝试真支付,容易触发审核风险。
|
||||
- 物流信息当前为假数据,SHIPPED/COMPLETED 的物流时间轴要标注占位。
|
||||
- 若 R2 未按约定提供地址接口,checkout 集成会被卡住;R3 开发时先按 `api-contract-v1.md` 编写,联调阶段再对齐实现。
|
||||
|
||||
### 测试要求
|
||||
|
||||
- 后端:订单创建事务、金额重算、重复提交幂等、越权查询、状态流转、支付未配置占位响应。
|
||||
- 前端:结算页创建订单、双击防抖、订单空态、状态 Tab 筛选、越权场景提示、支付未配置提示。
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
# R4 上传 + 词云生成 + 线稿(feat/r4-upload-wordcloud)
|
||||
|
||||
**Goal:** 打通用户图片上传、词云任务生成、DIY 贴纸线稿处理和异步任务状态轮询;
|
||||
这是四条路线中唯一复用 `wordcloud` 项目的路线,必须通过冻结接口契约的方式隔离它的大量变更。
|
||||
|
||||
**Architecture:** 小程序只与 `wxmp_backend` 通信;wxmp_backend 实现 COS 上传凭证、词云适配器、
|
||||
sketch 接口和任务记录/轮询;`wordcloud` FastAPI 服务作为外部引擎,只暴露并冻结最小契约。
|
||||
小程序页面对词云使用轮询进度,不使用 SSE/EventSource。
|
||||
|
||||
**Tech Stack:** Taro 3.6 + React 18 + TypeScript;NestJS 11 + Prisma/PostgreSQL + Redis/BullMQ;
|
||||
腾讯云 COS;wordcloud FastAPI + EfficientWordCloud。
|
||||
|
||||
## 1. 本分支要开发的内容
|
||||
|
||||
### 前端 `wechat_wc`
|
||||
|
||||
| 文件 | 职责 |
|
||||
|---|---|
|
||||
| `src/utils/api/upload.ts` | `getUploadCredentials/uploadToCos/sketchImage/createWordCloudJob/getWordCloudJob/getWordCloudResult` |
|
||||
| `src/pages/wordcloud/index.tsx` | 真实上传底图、提交名字清单、创建任务、轮询进度、展示结果图并保存相册 |
|
||||
| `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 |
|
||||
|
||||
### 后端 `wxmp_backend`
|
||||
|
||||
| 模块 | 内容 |
|
||||
|---|---|
|
||||
| `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 契约请求;任务状态入库,用户归属校验 |
|
||||
| `sketch`(新增) | `POST /api/sketch` 接收图片,返回处理后图片 URL;内部可先做基础处理或接外部 AI |
|
||||
| `queue` | BullMQ 消费者把 wordcloud/sketch 任务状态同步到 `CustomizationTask`/`WordCloudJob` |
|
||||
| `docs/wordcloud-contract.md` | 冻结 wordcloud 三/四个端点、字段、状态与错误语义,含版本号 |
|
||||
|
||||
### 词云项目 `wordcloud`
|
||||
|
||||
- 需要冻结的接口:`POST /api/jobs`(建议增加直接传名字列表/JSON 的方式)、`GET /api/jobs/{id}`、`GET /api/jobs/{id}/files/png`。
|
||||
- 其余 canvas、assets、projects、templates 等接口继续按它自己的节奏演进,小程序后端不依赖。
|
||||
- 若契约不变,wordcloud 内部重构无需通知 R4;契约变更时先升版本号,R4 单独出适配器更新。
|
||||
|
||||
## 2. 词云任务模型
|
||||
|
||||
小程序侧采用异步任务模型:
|
||||
|
||||
```ts
|
||||
interface WordCloudJob {
|
||||
id: string
|
||||
status: 'queued' | 'running' | 'success' | 'failed'
|
||||
progress: number
|
||||
imageUrl?: string
|
||||
error?: string
|
||||
}
|
||||
```
|
||||
|
||||
流程:
|
||||
|
||||
1. 前端上传底图,拿到 COS URL 或临时文件。
|
||||
2. `POST /api/wordcloud/generate` 提交底图 + 名字文本,后端创建任务并返回 `jobId`。
|
||||
3. 前端每 1-2 秒 `GET /api/wordcloud/jobs/:id` 轮询,`success` 后取 `imageUrl`。
|
||||
4. 结果图建议由 wxmp_backend 转存 COS 后返回微信可下载域名链接。
|
||||
|
||||
## 3. 设计注意事项
|
||||
|
||||
### DO
|
||||
|
||||
- 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` 记录契约版本,并在后端适配器代码里注释依赖版本。
|
||||
|
||||
### 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 页的模拟逻辑。
|
||||
|
||||
## 4. 补充内容
|
||||
|
||||
### 验收标准
|
||||
|
||||
- 小程序端上传图片可拿到 COS 可访问 URL,`Upload` 表有记录且归属当前用户。
|
||||
- `POST /api/wordcloud/generate` 可在真实 wordcloud 服务上产出 PNG,前端轮询到结果并保存相册。
|
||||
- `POST /api/sketch` 返回有效图片 URL;接口失败时前端走降级且不白屏。
|
||||
- 词云任务非本人不可访问;上传文件类型/大小校验生效。
|
||||
- `docs/wordcloud-contract.md` 已冻结并带版本号;前端 `wordcloud` 页不再有随机进度。
|
||||
|
||||
### 合并与依赖
|
||||
|
||||
- 分支名:前端与后端均为 `feat/r4-upload-wordcloud`。
|
||||
- 与 R1-R3 并行,不依赖商品/订单链路;后端上传基础设施可为后续 R2 的贴纸图片持久化提供能力。
|
||||
- wordcloud 契约若未冻结,R4 先完成“契约文档 + 后端适配层”,联调阶段再补真实 job。
|
||||
|
||||
### 风险
|
||||
|
||||
- wordcloud 正在大量变更,任务状态/产物路径随时可能变化;这正是契约冻结要解决的问题,联调时先锁定一个部署版本。
|
||||
- COS 与微信 `downloadFile`/`uploadFile` 合法域名必须提前配置,否则真机上传下载会失败。
|
||||
- 词云生成是 CPU 密集型任务,需要限制并发与单账号频率,防止被刷爆资源。
|
||||
- `designData` 中的贴纸图片若在 R4 接入上传,需要 R2 的设计 JSON 结构配合增加 `uploadedUrl` 字段,两分支交接时注意。
|
||||
|
||||
### 测试要求
|
||||
|
||||
- 后端:上传凭证、大小/类型校验、wordcloud 适配器正常/失败/超时、任务归属权限、sketch 接口。
|
||||
- 前端:词云上传、轮询进度、fail 重试、结果保存相册;线稿接口失败降级;无 COS 配置提示。
|
||||
- 集成:用固定 wordcloud 部署版本跑一次真实生成端到端 smoke。
|
||||
|
||||
Reference in New Issue
Block a user