# 平台电商素材生产平台 · 技术栈与架构定稿 > **文档类型**:技术栈与架构设计(定稿) > **版本**:V1.0 | **状态**:已定稿(评审通过后进入 M0) | **日期**:2026-10-30 > **适用范围**:统一 WEB 平台(选品 → 原始物料 → 图片/视频生产 → 命名 → 提审 → 投放配置) > **输入依据**:`docs/PRD/平台电商素材生产平台_PRD_V0.1.md`、两份参考项目调研报告、`docs/评估/平台电商素材生产平台_整合可行性评估_V0.1.md` > **环境基线(已在本机核实)**:macOS arm64、Node **v24.15.0**、pnpm **11.4.0**、PostgreSQL **18.4**(Docker,5432)、Redis **7.4.9**(Docker,6379) > **密级**:内部;本文不含平台签名盐、账号、密钥等敏感值。 --- ## 目录 0. 定稿结论(一页纸) 1. 决策依据与约束 2. 技术栈总表(版本锁定) 3. 架构总览 4. Monorepo 结构与包职责 5. 前端架构 6. 后端架构 7. 数据层(PostgreSQL 18 + Prisma 7) 8. Redis、队列与实时 9. 文件与对象存储 10. 平台适配与 Playwright 11. 图片产线 12. AI 网关 13. 认证、权限、凭据与安全 14. 工程规范与质量门禁 15. 开发环境与命令 16. 构建与部署 17. 版本治理 18. 与评估文档的差异 19. M0 必须验证的风险 20. 明确不采用的技术与理由 21. 附录 A:`.npmrc` 与根配置 22. 附录 B:依赖清单 23. 附录 C:待确认项 --- ## 0 定稿结论(一页纸) | 决策 | 定稿 | 关键理由 | |---|---|---| | 运行时 | **Node.js 24.15.0(精确版本)** | 与开发机一致;Prisma 7 引擎要求 `^24.11`;Vite 8 要求 `>=22.12` | | 包管理 | **pnpm 11.4.0(唯一)** | 与开发机一致;workspace 严格依赖;**不使用 npm** | | 语言 | TypeScript **5.9.3**(strict) | typescript-eslint 8.72 要求 `<6.1`,暂不上 TS 7 | | 前端 | React **19.3.1** + **Semi Design 2.104.0** + **Tailwind CSS 4.3.3** + Vite **8.3.5** | Semi 支持 React 19(dewu 已验证);Tailwind 4 由 scgf 验证;Vite 8 满足 Tailwind/Semi 插件 | | 后端 | **Express 5.3.0** + **Prisma 7.10.1** + **PostgreSQL 18** + **Redis 7** + **BullMQ 6.5.0** | Express 5 可迁移 scgf 中间件;Prisma 7 是当前稳定线(8 仍为 RC);PG/Redis 已在本机运行 | | 数据访问 | **Prisma 7.10.1 + `@prisma/adapter-pg`**(ESM) | 类型安全、迁移一体化;19 表模型需重写(+8~12 人日,见 §18) | | 队列/实时 | BullMQ 5 类队列 + **SSE**(不用 WebSocket) | 任务进度是单向流;SSE 对 nginx/公网更友好 | | 图像 | sharp **0.32.6(锁定)** + ag-psd **30.1.0** + @napi-rs/canvas **0.1.97** + fontkit **2.0.4** + ONNX 抠图 | 与 scgf 图像引擎行为一致,降低存量模板回归风险 | | 平台自动化 | **Playwright 1.64.1**(独立 platform-worker) | 送审必须页面自动化;独立进程隔离;账号级串行锁 | | 部署 | **单机 Docker Compose**(api + 4 worker + web/nginx) | 首期够用;代码保持无本地状态,后续可平滑扩副本 | | 对象存储 | **开发本地磁盘 / 生产 OSS(S3 适配层)** | 开发机不新增容器;生产支持多副本与签名 URL | | 数据库/缓存部署 | **复用开发机已有 Docker(PG 18 / Redis 7),平台不负责安装** | 用户明确要求;`.env` 直连 localhost | > **与评估文档的差异**(详见 §18):ORM 由评估时的 Sequelize 改为 **Prisma**,工作量 **+8~12 人日**(全量 383~569 → **391~581 人日**);UI 由评估的 Tailwind 3 改为 **Tailwind 4**(+1~3 人日验证,在不确定性内);其余方案不变。 --- ## 1 决策依据与约束 ### 1.1 硬约束(用户已确认) | 约束 | 值 | 来源 | |---|---|---| | Node.js | **24.15.0** | 与开发机一致 | | 包管理 | **pnpm**,非必要不用 npm | 用户明确要求 | | PostgreSQL | **18**(开发机 Docker `postgres:18-alpine`,实际 18.4,5432) | 用户明确要求 | | Redis | **7**(开发机 Docker `redis:7-alpine`,实际 7.4.9,6379) | 用户明确要求 | | 部署 | 内部平台,但**公网可访问 + 登录鉴权** | 用户确认 | | 对象存储 | 开发本地磁盘,生产 OSS | 用户确认 | | 数据库/缓存 | 开发前直接开启已有容器,平台不做安装/编排 | 用户明确要求 | | 形态 | 统一 WEB 平台,桌面端停止演进 | 用户确认 | | 工程 | 新建统一工程 + 迁移两个参考项目资产 | 用户确认 | | UI | Semi Design 最新版(支持 React 19)+ Tailwind CSS 4 | 用户确认 | | ORM | Prisma | 用户确认 | ### 1.2 参考项目继承关系 | 资产 | 来源 | 在本工程中的处理 | |---|---|---| | 认证/口令安全/RBAC/客户范围/系统配置/任务事件/通知/导出 | scgf `backend/src` | 迁移业务逻辑;**数据访问层改为 Prisma**,模型重写 | | PSD 解析/合成/抠图/字体/命名引擎 | scgf `utils/*` | 代码级迁移;渲染行为锁定(sharp/ag-psd/canvas 版本不变) | | 星光签名/字段映射/送审命名/混剪规则/DPLink/审核 CSV | dewu `scripts/lib/*`、`electron/platform/*` | 抽为 `packages/domain` / `packages/platform-dewu`;纯逻辑几乎原样迁移 | | 送审批次策略(串行/幂等/回查/审计) | dewu `electron/submit/*` | 迁移逻辑,存储改 DB + Redis | | 只读接口层、商品详情 SSR 直取 | dewu `electron/platform/*`、`shop/detail.ts` | 服务端化,去掉 Electron | | Electron 外壳/更新/签名/窗口/本地文件 | dewu `electron/*` | 废弃 | | 页面信息架构(选品/物料/审核/送审/投放) | dewu `src/pages/*` | 交互参考;组件按 Semi 2.104 重写 | | 页面信息架构(模板/生产/任务/配置) | scgf `frontend/src/pages/*` | 交互参考;按新 IA 重写 | ### 1.3 本机实测结论(定稿依据) | 项 | 实测 | 结论 | |---|---|---| | Node | `v24.15.0` | 与要求一致;Prisma 7 要求 `^24.11`,满足 | | pnpm | `11.4.0` | 锁定;`packageManager` 写 11.4.0 | | PG | `PostgreSQL 18.4`(alpine,5432) | 直连;使用 PG18 原生 `uuidv7()` | | Redis | `redis_version:7.4.9`(6379) | BullMQ 6 支持 Redis 7 | | 图像原生依赖 | sharp 0.32.6、ag-psd 30.1.0、@napi-rs/canvas 0.1.97、fontkit 2.0.4、@imgly/background-removal-node 1.4.5 均可在 Node 24.15 arm64 加载(libvips 8.14.5) | 锁定该组合作为图像基线 | | pnpm registry 最新版本 | Semi 2.104.0、Tailwind 4.3.3、Vite 8.3.5、Prisma 7.10.1、Express 5.3.0、BullMQ 6.5.0、Playwright 1.64.1 | 见 §2 版本总表 | --- ## 2 技术栈总表(版本锁定) > 标记 **🔒** 的为精确锁定(升级需回归/评审);其余使用 caret 范围 + 提交 lockfile。 > 版本以 2026-10-30 查询/实测为准;`pnpm-lock.yaml` 是唯一事实来源。 ### 2.1 运行时与工具链 | 类别 | 技术 | 版本 | 说明 | |---|---|---|---| | 运行时 | Node.js | **24.15.0 🔒** | `.nvmrc`、`engines`、Docker 镜像三处一致 | | 包管理 | pnpm | **11.4.0 🔒** | `packageManager`;corepack;禁止 npm | | 语言 | TypeScript | **5.9.3 🔒** | strict;typescript-eslint 8.72 要求 `<6.1` | | 开发执行 | tsx | 4.23.15 | 后端/脚本热执行 | | 构建 | Vite | **8.3.5 🔒** | 前端;Node `>=22.12` | | Lint | ESLint | **10.13.0 🔒** | flat config | | Lint | typescript-eslint | **8.72.1 🔒** | 支持 ESLint 8/9/10、TS `<6.1` | | 格式 | Prettier | 3.9.10 | 与 ESLint 分离 | | 单测 | Vitest | **5.0.5 🔒** | 支持 Vite 8 | | 覆盖率 | @vitest/coverage-v8 | 5.0.5 | — | | E2E | Playwright | **1.64.1 🔒** | 同时用于平台自动化;浏览器版本随包锁定 | ### 2.2 前端 | 类别 | 技术 | 版本 | 说明 | |---|---|---|---| | 框架 | React / react-dom | **19.3.1 🔒** | Semi 2.104 支持 React 19 | | 路由 | react-router-dom | 7.18.5 | Data Router;路由级 lazy | | 组件库 | @douyinfe/semi-ui | **2.104.0 🔒** | 最新稳定;zh_CN、暗色、Table/Form/Upload/Tree | | 组件库构建 | @douyinfe/semi-vite-plugin | **2.104.0 🔒** | 主题/CSS Layer;peer vite `>=3` | | 图标 | @remixicon/react | 4.9.0 | 与 Semi 搭配 | | CSS | Tailwind CSS | **4.3.3 🔒** | `@tailwindcss/vite` 4.3.3;`@theme` token | | 构建插件 | @vitejs/plugin-react | **6.1.2 🔒** | peer Vite `^8`;React 19 | | 状态 | zustand | 5.0.16 | 会话/UI 状态 | | 服务端状态 | @tanstack/react-query | 5.105.0 | 列表/轮询/缓存 | | HTTP | axios | 1.21.0 | 拦截器、刷新令牌、上传进度 | | 表单 | react-hook-form | 7.91.0 | 复杂表单 | | 校验 | @hookform/resolvers + zod | 6.0.2 / 4.6.7 | 与后端共享 schema | | 图表 | recharts | 3.10.1 | Dashboard | | 日期 | dayjs | 1.11.23 | zh-cn | | Markdown | marked | 18.1.2 | 发版说明/富文本白名单渲染 | | 测试 | @testing-library/react | 16.3.3 | React 19;配合 jsdom 30.1.3 | ### 2.3 后端 | 类别 | 技术 | 版本 | 说明 | |---|---|---|---| | Web 框架 | Express | **5.3.0 🔒** | 迁移 scgf 中间件;不选 Nest/Fastify | | ORM | Prisma | **7.10.1 🔒** | 稳定线;8 仍为 RC,不采用 | | ORM 客户端 | @prisma/client | **7.10.1 🔒** | ESM;`prisma-client` 生成器 | | PG 驱动适配 | @prisma/adapter-pg | **7.10.1 🔒** | 连接 PG18 | | PG 驱动 | pg | 8.23.1 | 适配器/运维脚本/健康检查 | | 数据库 | PostgreSQL | **18(18.4)🔒** | 已有 Docker;PG18 `uuidv7()` | | 缓存/队列 | Redis | **7(7.4.9)🔒** | 已有 Docker | | Redis 客户端 | ioredis | **6.0.0 🔒** | BullMQ 6 推荐 | | 队列 | BullMQ | **6.5.0 🔒** | 5 类队列;Redis 7 兼容 | | 鉴权 | jsonwebtoken | 9.0.3 | access/refresh + pwdv | | 密码 | bcrypt | 6.0.0 | 复用 scgf 策略 | | 上传 | multer | 2.5.0 | 内存/临时盘;大文件直传 OSS | | 校验 | zod | 4.6.7 | DTO/环境变量/表单共享 | | 日志 | pino + pino-http | 10.4.0 / 11.0.0 | 结构化 JSON | | 安全头 | helmet | 8.3.0 | 公网暴露必配 | | CORS | cors | 2.8.6 | 白名单 | | 压缩 | compression | 1.8.2 | 静态/JSON | | 限流 | express-rate-limit + rate-limit-redis | 8.7.2 / 6.0.2 | Redis 共享限流 | | 打包 | archiver | 8.0.0 | ZIP level 9 | | 表格 | exceljs | 4.4.0 | CSV/XLSX 导入导出 | | ID | uuid | 14.0.3 | 仅应用侧特殊场景;主键用 PG `uuidv7()` | | 环境变量 | dotenv | 18.0.9 | dev 加载;prod 由编排注入 | | 指标 | prom-client | 15.1.3 | `/metrics` | | 对象存储 | @aws-sdk/client-s3 + presigner | 3.1160.0 | S3/MinIO | | 阿里云 OSS | ali-oss | 6.23.0 | 生产 OSS 驱动 | | MFA(管理员) | otplib | 13.5.0 | TOTP;公网暴露必配 | ### 2.4 图像 / PSD / 平台 / AI | 类别 | 技术 | 版本 | 说明 | |---|---|---|---| | 图像处理 | sharp | **0.32.6 🔒** | 与 scgf 引擎行为一致;不可随意升级 | | PSD 解析 | ag-psd | **30.1.0 🔒** | 图层读取 | | Canvas | @napi-rs/canvas | **0.1.97 🔒** | 合成画布 | | 字体 | fontkit | **2.0.4 🔒** | 字体名/排版 | | 抠图 | @imgly/background-removal-node | **1.4.5 🔒** | ONNX medium;独立 Worker | | 浏览器自动化 | Playwright | **1.64.1 🔒** | platform-worker;Chromium 版本随包 | | AI Provider | 火山方舟 Seedream(图片)/ 视频模型待选 | 通过 REST 适配 | 不绑定 SDK;密钥只走环境变量 | | AI 校验 | VLM(文字逐字/合规) | 通过 REST 适配 | 可插拔 provider | ### 2.5 为什么是这些版本(关键兼容性证据) | 组合 | 证据 | |---|---| | Node 24.15 + Prisma 7.10.1 | Prisma engines:`^22.18 \|\| ^24.11 \|\| >=26`;`@prisma/client`:`^20.19 \|\| ^22.12 \|\| >=24.0` | | Vite 8.3.5 + Node 24.15 | Vite engines:`^20.19.0 \|\| >=22.12.0` | | Vite 8 + Tailwind 4 | `@tailwindcss/vite` peer:`^5.2 \|\| ^6 \|\| ^7 \|\| ^8` | | Vite 8 + Semi 插件 | `semi-vite-plugin` peer:`vite >=3` | | Vite 8 + plugin-react 6.1.2 | peer `vite ^8`;其 oxc/babel peer 均为 optional | | React 19.3 + Semi 2.104 | Semi peer `react >=16`;dewu 已在 React 19.3 + Semi 2.103 生产验证 | | TS 5.9.3 + typescript-eslint 8.72 | typescript-eslint peer `typescript >=4.8.4 <6.1.0`(故不选 TS 7.0.2) | | ESLint 10 + typescript-eslint 8.72 | peer 支持 `^10.0.0` | | BullMQ 6.5 + Redis 7.4 | BullMQ peer `ioredis >=5`、Node `>=14.18`;Redis 7 兼容 | | 图像栈 + Node 24 arm64 | 本机实测 5 个原生包全部加载成功(libvips 8.14.5) | --- ## 3 架构总览 ### 3.1 运行拓扑(开发环境) > 开发机不新增 PG/Redis 容器:平台进程直接连已有 Docker;文件落本地目录。 ``` ┌───────────────────────────── macOS 开发机 ─────────────────────────────┐ │ │ │ 浏览器 ──http://localhost:5173──▶ Vite dev server(apps/web) │ │ │ /api 代理 │ │ ▼ │ │ apps/api(Express 5,:8300) │ │ │ │ │ │ │ ├──▶ PostgreSQL 18.4 :5432 │ │ │ │ (Docker 已有) │ │ │ ├──▶ Redis 7.4.9 :6379 │ │ │ │ (Docker 已有) │ │ │ └──▶ 本地磁盘 ./var/uploads │ │ │ │ │ 独立进程(pnpm dev:workers): ▼ │ │ worker-image(PSD/合成/抠图) worker-platform(Playwright 有头/无头) │ │ worker-ai(生图/视频) worker-export(ZIP/CSV/打包) │ │ │ │ │ └──▶ BullMQ 队列(Redis) │ └────────────────────────────────────────────────────────────────────────┘ ``` ### 3.2 运行拓扑(生产) ``` 公网(HTTPS) │ ┌────────▼────────┐ │ WAF / 云防护 │ (可选但建议) └────────┬────────┘ ┌────────▼────────┐ │ nginx(web 镜像)│ SPA + /api 反代 + SSE + /files └────────┬────────┘ │ ┌────────────────────┼────────────────────┬────────────────────┐ ▼ ▼ ▼ ▼ api ×1~N worker-image ×1~N worker-platform ×1 worker-export/ai │ │ │ │ └──────────┬─────────┴──────────┬─────────┴──────────┬─────────┘ ▼ ▼ ▼ PostgreSQL 18 Redis 7 对象存储(OSS/S3) (托管/独立容器) (队列/锁/限流) (物料/成片/导出包) ``` **关键设计**: - API 无状态(会话在 JWT/Redis;文件在对象存储)→ 可水平扩副本。 - Worker 按负载类型拆进程:图像/抠图吃 CPU+内存,Playwright 吃内存+浏览器,AI 吃网络等待,导出吃 IO。 - 平台账号写操作在 `worker-platform` 内**按账号串行**,API 不直接开浏览器。 ### 3.3 请求与任务链路 ``` 同步请求:浏览器 → nginx → api → Prisma(PG) → 返回 异步任务:api 建任务行(PG)→ 入 BullMQ(jobId = taskId)→ worker 执行 → 更新任务行 + 写 task_event → Redis Pub/Sub → api SSE → 浏览器 文件:上传 → api 签发上传凭证(或本地直存)→ 对象存储/本地盘 → 记录 storage_key 下载 → api 校验权限 → 302 签名 URL / 流式代理 ``` --- ## 4 Monorepo 结构与包职责 ### 4.1 目录 ``` lingxi/ ├── package.json # 根:scripts、engines、packageManager ├── pnpm-workspace.yaml ├── pnpm-lock.yaml # 唯一事实来源 ├── .nvmrc # 24.15.0 ├── .npmrc # engine-strict=true 等 ├── tsconfig.base.json ├── prisma/ │ ├── schema.prisma # 唯一 schema │ ├── migrations/ # prisma migrate 生成 │ └── seed.ts ├── apps/ │ ├── web/ # React 19 + Semi + Tailwind 4(Vite 8) │ ├── api/ # Express 5(BFF + 业务 API) │ ├── worker-image/ # sharp/ag-psd/canvas/抠图 │ ├── worker-platform/ # Playwright:登录/送审/审核/投放 │ ├── worker-ai/ # 生图/视频 │ └── worker-export/ # ZIP/CSV/XLSX ├── packages/ │ ├── shared/ # zod schema、DTO、错误码、枚举、类型 │ ├── domain/ # 命名引擎、送审规则、DPLink、审核映射(纯逻辑) │ ├── platform-dewu/ # 星光签名/只读接口/页面自动化 │ ├── platform-vipshop/ # 唯品会适配(M3 起) │ ├── db/ # Prisma client 单例、事务工具、Repository │ ├── storage/ # StorageAdapter(local / s3 / oss) │ ├── ai/ # Provider 接口 + Ark 实现 + 成本核算 │ └── ui/ # 设计 token + Semi 封装 + 通用组件 ├── infra/ │ ├── docker/ # 各镜像 Dockerfile │ ├── nginx/ # SPA + API + SSE + /files │ ├── compose/ # dev/prod compose │ └── scripts/ # db-create、备份、健康检查 ├── test/ │ ├── integration/ # PG/Redis 集成测试 │ ├── golden/ # PSD 黄金样本 + 像素 diff │ └── e2e/ # Playwright └── docs/ ``` ### 4.1.1 模块规范 - 全仓库 **ESM**:各包 `package.json` 设 `"type": "module"`,`tsconfig` 用 `module: NodeNext` + `verbatimModuleSyntax`。 - Prisma 7 使用 `prisma-client` 生成器产出 ESM client,统一从 `@lingxi/db` 导入。 - 后端构建用 `tsc`(ESM 输出),开发用 `tsx`;不使用 CommonJS `require`。 - 禁止跨包相对路径引用;统一 `@lingxi/*` workspace 协议。 ### 4.2 包依赖规则 | 规则 | 说明 | |---|---| | `apps/*` 不得被其他包依赖 | 应用是组合根 | | `packages/domain` 只依赖 `shared` | 纯逻辑、可单测、不碰 DB/HTTP | | `packages/platform-*` 依赖 `domain` + `shared` | 平台协议/页面自动化;不反向依赖 API | | `packages/db` 只暴露 Prisma client 与 Repository | 禁止业务直接 import Prisma client 到处用 | | `packages/ui` 只依赖 React/Semi/Tailwind | 不含业务请求 | | 跨包引用走 `@lingxi/*` workspace 协议 | `pnpm` 严格链接,禁止相对路径穿透 | ### 4.3 根 scripts(只用 pnpm) ```json { "packageManager": "pnpm@11.4.0", "engines": { "node": "24.15.0", "pnpm": "11.4.0" }, "scripts": { "dev": "pnpm --parallel -r --filter @lingxi/web --filter @lingxi/api run dev", "dev:workers": "pnpm --parallel -r --filter './apps/worker-*' run dev", "build": "pnpm -r build", "lint": "eslint .", "format": "prettier --write .", "typecheck": "pnpm -r typecheck", "test": "vitest run", "test:integration": "vitest run -c vitest.integration.config.ts", "test:e2e": "playwright test", "db:create": "tsx infra/scripts/db-create.ts", "db:migrate": "prisma migrate dev", "db:deploy": "prisma migrate deploy", "db:seed": "prisma db seed", "db:studio": "prisma studio", "playwright:install": "playwright install chromium" } } ``` --- ## 5 前端架构(React 19 + Semi 2.104 + Tailwind 4) ### 5.1 应用结构 ``` apps/web/src/ ├── app/ # 入口、路由、Provider、错误边界 ├── layouts/ # 侧边栏/顶栏/权限布局 ├── features/ │ ├── platform/ # 平台切换 │ ├── selection/ # 选品/圈选/历史 │ ├── materials/ # 原始物料 │ ├── image/ # 模板/制作/批量/预览 │ ├── video/ # 脚本/画面/成片 │ ├── review/ # 提审/审核 │ ├── delivery/ # 投放配置/DPLink │ ├── assets/ # 模板/Logo/展示/促销/脚本资产 │ └── system/ # 用户/角色/客户/配置/任务日志 ├── shared/ # api client、hooks、utils、权限、i18n └── styles/ # tailwind.css、semi-theme.css、tokens.css ``` - 路由:`react-router-dom` 7 Data Router;按 `features` 懒加载;路由守卫统一在 `app/routes.tsx`。 - 权限:后端 `/auth/me` 返回 `permissions[]`;菜单/按钮用 ``;**前端不作为安全边界**,后端每个接口独立校验。 - 状态:服务端状态全部走 TanStack Query;仅会话/主题/布局用 zustand。禁止把列表数据塞 zustand。 - 请求:单一 `shared/api/client.ts`(axios);自动附加 access token;401 触发一次 refresh;失败统一 toast + 错误码映射。 - 表单:react-hook-form + zod(schema 来自 `@lingxi/shared`),Semi Form 作为展示层。 ### 5.2 Semi 2.104 + Tailwind 4 集成(必须按此顺序,否则静默失效) ```css /* apps/web/src/styles/index.css —— 顺序 = 层叠优先级(M0 Spike 最终确认) */ @layer theme, base, semi, components, utilities; @import "tailwindcss"; @import "@douyinfe/semi-ui/dist/css/semi.min.css" layer(semi); @import "./semi-theme.css" layer(semi); /* Semi CSS 变量覆盖 */ @import "./tokens.css"; /* 品牌 token(@theme) */ ``` **硬性约定**: 1. `@import` 顺序即层叠顺序:Tailwind base → Semi → 主题 token → 页面样式;页面级样式不得写未分层的 `* { margin:0 }`。 2. 品牌色/圆角/字号只允许在 `tokens.css` 的 `@theme` 与 `semi-theme.css` 的 `--semi-color-*` 定义,页面禁止硬编码色值(CI 用脚本校验)。 3. 不使用 `.semi-*` 内部类做覆盖;只通过 Semi 语义 token 与 `ConfigProvider`。 4. 组件统一从 `@lingxi/ui` 导出(薄封装 Semi),页面不直接 import Semi 的内部组件,便于后续换肤/升级。 5. **M0 必须先做构建 Spike**:Semi 2.104 + Tailwind 4.3 + Vite 8 同时引入,验证 modal/drawer/table 样式与 `@layer` 顺序;失败则回退方案见 §19 R1。 ### 5.3 页面与路由(首期) | 路由 | 页面 | 首期 | |---|---|---| | `/` | 工作台/Dashboard | 是 | | `/selection` | 选品(检索/圈选/历史) | 是 | | `/materials` | 原始物料 | 是 | | `/image/templates` | 模板资产 | 是 | | `/image/produce` | 图片制作(三规格/四层合成/批量) | 是 | | `/review` | 提审与审核 | 是(MVP-2) | | `/delivery` | 投放配置/DPLink | 是(MVP-2) | | `/assets` | 资产中心 | M4 | | `/system/*` | 用户/角色/客户/配置/任务日志 | 是 | | `/video/*` | 视频产线 | M5 | ### 5.4 构建与性能 - Vite 8:`build.target = es2022`;路由级 code-split;Semi 按需(插件已处理)。 - 图片列表用缩略图 URL(`?w=320`);大图预览走原图/签名 URL。 - 表格默认分页 20/50;超过 200 行启用虚拟滚动(Semi Table 支持)。 - 首屏预算:JS ≤ 350KB gzip(不含图片);超过则拆包。 --- ## 6 后端架构(Express 5) ### 6.1 分层 ``` apps/api/src/ ├── server.ts # 启动、优雅退出 ├── app.ts # Express 装配(middleware 顺序) ├── routes/ # 路由声明(薄) ├── controllers/ # 参数校验 + 调 service ├── services/ # 业务编排(事务边界) ├── repositories/ # Prisma 查询封装(可测试) ├── middlewares/ # auth、scope、rateLimit、error、requestId ├── events/ # SSE、任务事件发布 └── jobs/ # 入队(只投递,不执行) ``` - **Controller 不写 SQL/Prisma**;Service 不直接读 `req`。 - 事务:Service 层用 `prisma.$transaction`;跨任务/文件操作先落库再入队。 - 错误:统一 `AppError(code, message, httpStatus, details)`;错误码在 `packages/shared/errors.ts`;响应不泄露堆栈。 - 请求 ID:`x-request-id` 透传 pino;审计与日志可关联。 ### 6.2 API 规范 | 项 | 约定 | |---|---| | 前缀 | `/api/v1` | | 成功 | `{ "data": ..., "requestId": "..." }` | | 失败 | `{ "error": { "code": "AUTH_401", "message": "...", "details": ... }, "requestId": "..." }` | | 分页 | `?page=1&pageSize=20`;返回 `{ items, total, page, pageSize }`;大列表用 cursor | | 幂等 | 写操作支持 `Idempotency-Key`;任务创建以 `(batchId, templateId, productId)` 唯一约束兜底 | | 时间 | ISO 8601 UTC;DB `timestamptz` | | 文件 | 上传走 `POST /files/presign`(生产)或 `POST /files/upload`(开发本地) | | 实时 | `GET /api/v1/events?topics=batch:{id}`(SSE) | | 文档 | zod → OpenAPI(dev 环境 `/api/docs`);不维护手写文档 | ### 6.3 中间件顺序(固定) ``` requestId → pino-http → helmet → cors 白名单 → compression(跳过 SSE) → body parser(json 2mb / urlencoded)→ cookie parser → 全局限流(Redis)→ 路由 → 404 → error handler(统一信封) ``` - `helmet` 在公网必须开启;`cors` 只允许 `WEB_ORIGIN`。 - SSE 路由放在 `compression` 之后但显式跳过压缩,并设置 `Cache-Control: no-cache`、`X-Accel-Buffering: no`。 - 上传路由使用 multer 单独限制大小与 MIME,不适用全局 body 限制。 ### 6.4 健康检查与可观测 | 端点 | 用途 | |---|---| | `GET /health/live` | 进程存活(不查依赖) | | `GET /health/ready` | DB/Redis/对象存储探活;失败 503 | | `GET /metrics` | Prometheus(内部网段/鉴权) | - 日志:pino JSON;字段 `requestId/userId/platformId/taskId`;**禁止打印 token/密码/签名盐**。 - 指标:HTTP P95、队列积压、任务成功率/时长、图像处理耗时、AI 调用成本。 --- ## 7 数据层(PostgreSQL 18 + Prisma 7.10.1) ### 7.1 Prisma 约定 - 生成器:Prisma 7 的 `prisma-client`(ESM),输出到 `packages/db/src/generated/prisma`;应用统一从 `@lingxi/db` 导入。 - 连接:`@prisma/adapter-pg`(driver adapter)+ `pg`;连接池 `max=10`(API)/`max=3`(Worker),按实例数核算 PG `max_connections`。 - 迁移:本地 `prisma migrate dev`;CI/生产 `prisma migrate deploy`;**禁止 `db push` 到生产**。 - 命名:`@@map("snake_case")`、`@map("snake_case")`;Prisma 字段 camelCase,DB 列 snake_case。 - 主键:`String @id @default(dbgenerated("uuidv7()")) @db.Uuid`(PG18 原生 `uuidv7()`,时间有序、索引友好)。 - 时间:`DateTime @db.Timestamptz(6)`;统一 UTC。 - JSON:`Json`(映射 JSONB);仅用于配置/快照/扩展字段,不用于高频过滤。 - 软删除:`deletedAt DateTime?`;查询统一走 Repository 的 `notDeleted`。 - 枚举:稳定集合用 Prisma enum;运营可维护的字典用表(如失败原因、类目)。 - 索引:`platform_id/client_id/status/created_at` 组合;任务表 `(status, created_at)`;事件表 `(task_id, created_at)`。 ### 7.2 初始 schema 范围(与评估 §5.6 对齐) | 域 | 表(Prisma model) | |---|---| | 组织 | `Platform`、`Project`、`Client`、`User`、`Role`、`UserClient` | | 凭据 | `PlatformAccount`(能力:soft/hard/review;密文;归属) | | 商品/选品 | `ProductSnapshot`、`SelectionBatch`、`SelectionItem` | | 物料 | `RawAsset` | | 模板/资产 | `Template`、`TemplateCategory`、`TemplateTag`、`TemplateTagRelation`、`TemplateSpec`、`Asset` | | 生产 | `ProductionBatch`、`ProductionTask`、`Material`、`NamingRule` | | 提审 | `ReviewTicket`、`ReviewItem`、`SubmitLedger` | | 投放 | `DeliveryConfig`、`DeliveryLink` | | 任务/通知 | `TaskEvent`、`FailureCatalog`、`Notification`、`NotificationTemplate` | | 配置/审计 | `SystemConfig`、`SystemConfigAudit`、`UserAudit`、`OperationAudit` | > 共约 33 张表;与评估文档的数据模型一致,仅访问层从 Sequelize 改为 Prisma。 ### 7.3 迁移与种子 - 初始化:`pnpm db:create`(Node 脚本连 `postgres` 库,`CREATE DATABASE lingxi`,不存在才建)→ `pnpm db:migrate` → `pnpm db:seed`。 - 种子:管理员账号(口令随机、仅显示一次)、基础角色/权限、得物平台、默认规格(800×800 / 1280×720 / 1080×1920)、字典。根 `package.json` 需声明 `"prisma": { "seed": "tsx prisma/seed.ts" }`。 - 回滚:Prisma 迁移是前向的;破坏性变更采用 expand/contract(先加列/双写,再切读,最后删列),保留 down 说明在迁移目录 README。 - 备份:生产每日 `pg_dump` + WAL;迁移前强制快照。 ### 7.4 数据隔离 - 单租户(内部平台);数据范围:`platformId` + `clientId`。 - 所有列表查询必须带范围条件(Repository 强制参数);`fail-closed`:无范围 = 查不到,而不是全量。 - 审计表不做外键级联,保留历史。 --- ## 8 Redis、队列与实时 ### 8.1 Redis 使用边界 | 用途 | Key 前缀 | 说明 | |---|---|---| | BullMQ | `lingxi:bull:*` | 5 类队列 | | 缓存 | `lingxi:cache:*` | 平台枚举/标签树/配置;TTL | | 限流 | `lingxi:rl:*` | 登录、API、AI 调用 | | 锁 | `lingxi:lock:account:{id}` 等 | `SET NX PX` + Lua 释放 | | Pub/Sub | `lingxi:events` | SSE 广播 | | 会话(可选) | `lingxi:sess:*` | 刷新令牌黑名单/设备 | - Redis 7 已有;**不执行 `FLUSHALL`**;统一前缀,避免与其他项目冲突。 - 禁用 `KEYS`;批量用 `SCAN`。 - 本地开发使用 db 0;测试用 db 15(`REDIS_URL=.../15`)。 ### 8.2 队列拓扑 | 队列 | 生产者 | 消费者 | 并发建议 | 说明 | |---|---|---|---|---| | `image-render` | api | worker-image | 2~4 | PSD/合成/抠图;吃内存 | | `platform-task` | api | worker-platform | 每账号 1 | 登录/送审/审核/投放;串行 | | `ai-image` | api | worker-ai | 2~4 | 外部 API;限流/配额 | | `ai-video` | api | worker-ai | 1~2 | 长任务;M5 | | `export` | api | worker-export | 2 | ZIP/CSV;IO 密集 | 约定:`jobId = DB task id`;`attempts=3`(平台写操作只 1 次 + 回查);指数退避;`removeOnComplete` 保留 1000/1 天;失败进 DLQ(`*-failed`)。DB 是事实来源;每 60s reconciler 扫描 `processing` 超时任务重新入队或标记失败。 ### 8.3 SSE(任务进度) - 端点:`GET /api/v1/events?topics=batch:{id},platform:{code}`;`text/event-stream`。 - 鉴权:登录 cookie/JWT(EventSource 不能带 header → 用短时 ticket:`POST /events/ticket` 换 60s ticket)。 - 服务端:worker 更新任务 → `publish(lingxi:events, {topic, seq, payload})` → 每个 api 实例订阅并 fan-out 给本实例 SSE 连接。 - 事件:`{ type: 'batch.progress' | 'task.status' | 'notification', topic, seq, ts, data }`。 - 心跳 15s;断线重连用 `Last-Event-ID`(seq 游标,保留最近 5 分钟环形缓冲)。 - nginx:单独 location,`proxy_buffering off; proxy_read_timeout 3600s;`。 ### 8.4 分布式锁与幂等 - 账号锁:`SET lock:account:{id} NX PX 900000`;释放用 Lua 比对 token;超时自动释放并告警。 - 任务幂等:DB 唯一约束 + `jobId`;重复投递由 BullMQ 去重。 - 提交幂等:`SubmitLedger`(md5)先查后提;平台返回重复码时按已提交处理。 - 限流:登录 5 次/分钟/IP+用户名;普通 API 300 次/分钟/用户;AI 按日配额。 --- ## 9 文件与对象存储 ### 9.1 StorageAdapter ```ts export interface StorageAdapter { put(key: string, body: Buffer | Readable, opts: { contentType: string; cacheControl?: string }): Promise<{ key: string; size: number; md5: string }>; get(key: string): Promise; delete(key: string): Promise; exists(key: string): Promise; signDownload(key: string, ttlSeconds: number): Promise; copy(srcKey: string, dstKey: string): Promise; } ``` | 驱动 | 使用场景 | 实现 | |---|---|---| | `local` | 开发/单机 | `./var/uploads`;`/files/:key` 鉴权后流式返回;禁止路径穿越 | | `s3` | MinIO/兼容 S3 | `@aws-sdk/client-s3` + presigner | | `oss` | 阿里云 OSS 生产 | `ali-oss`(STS/签名 URL) | - 配置:`STORAGE_DRIVER=local|s3|oss`;启动时 zod 校验必填项。 - Key 规则:`{platformCode}/{clientId}/{domain}/{yyyy}/{mm}/{uuid}.{ext}`;禁止使用客户端文件名作为 key。 - 缩略图:图片上传后由 `worker-image` 生成 `_thumb.webp`(`?w=320/640`);清理任务按引用计数/保留期。 - 大文件(视频 140–190MB):生产走浏览器/客户端直传 OSS(STS 临时凭证),API 只登记元数据;Playwright 送审时 worker 从对象存储下载到本地临时目录再 `setInputFiles`。 - 生命周期:原始物料默认长期保留;导出包 7~30 天;临时文件 24 小时。 --- ## 10 平台适配与 Playwright(worker-platform) ### 10.1 适配器接口 ```ts export interface PlatformAdapter { code: 'dewu' | 'vipshop'; capabilities: { read: boolean; write: boolean; submit: boolean; dplink: boolean }; login(ctx: AccountContext): Promise; searchProducts(q: SearchQuery): Promise; getProduct(spuId: string): Promise; listRawAssets(spuId: string): Promise; listMaterials(q: MaterialQuery): Promise; submitReview(input: SubmitInput): Promise; queryReview(input: ReviewQuery): Promise; buildDeliveryLink(input: DeliveryLinkInput): Promise; } ``` - 得物:读链路走签名 HTTP(`packages/platform-dewu/http.ts`);写链路默认页面自动化(`page.ts`),直连写接口作为 POC 备选。 - 唯品会:M3 开始,先摸登录/接口/提审流程,再实现同一接口。 - **不另开浏览器 context**:同一账号复用持久化 context;token 由凭据服务单写(API 只读,禁止自行重登)。 ### 10.2 Worker 运行约束 | 项 | 约定 | |---|---| | 浏览器 | Chromium 版本随 Playwright 1.64.1 锁定;`pnpm playwright:install` 安装 | | 账号并发 | 同一 `platformAccountId` 同时只允许 1 个写任务(Redis 锁) | | 只读并发 | 全局上限 4;按平台限速 | | 失败证据 | 截图 + DOM 快照 + reasonCode 存对象存储,DB 记录 key | | 选择器 | 全部走配置(`selectors.dewu.json`),禁止散落硬编码;关键元素缺失触发健康告警 | | 上传 | 大文件先落本地临时目录,`setInputFiles` 后清理;超时 15 分钟 | | 重试 | 网络/5xx 重试 1 次;提交超时**只回查不重试** | --- ## 11 图片产线(worker-image) - 输入:模板(PSD)+ 商品图 + 展示方式资产 + Logo + 促销模板 + 字段;输出:三规格图片 + 命名 + 元数据。 - 流程:`readPsd` → 资产快照 → 图层合成(保持 PSD z 序,按 `assetType` 取资产)→ 抠图(仅白底)→ 压缩(JPG 二分质量 / PNG 调色板)→ 写对象存储 → `Material` 落库。 - 版本:sharp 0.32.6 / ag-psd 30.1.0 / canvas 0.1.97 / fontkit 2.0.4 **锁定**;升级必须过黄金样本。 - 回归:`test/golden/` 存 ≥5 个真实模板 + 期望图;像素 diff 阈值(默认 mean diff < 0.5%,单像素阈值 8/255);CI 跑 `pnpm test:golden`。 - 字体:内置字体清单与授权登记;换字体必须跑回归;缺字形时显式失败(不许"豆腐块但任务成功")。 - 并发:worker-image 独立进程,默认 2 并发;内存上限监控,OOM 重启由容器策略兜底。 --- ## 12 AI 网关(worker-ai) ### 12.1 接口 ```ts export interface AiImageProvider { generate(input: ImageGenInput): Promise; // 返回 storageKey/成本/耗时 checkText(input: TextCheckInput): Promise; // VLM 逐字校验 } ``` - Provider:`ark`(火山方舟 Seedream,默认);可插拔其他厂商。 - 模型分层:成品默认 `pro`,草稿/批量初筛用 `flash`;模型名/单价/限额全部配置化。 - 队列:`ai-image` / `ai-video`;按账号级并发与 RPM 限流;429 退避。 - 成本:每次调用写 `AiUsage`(provider/model/tokens/单价/金额/任务);日/月预算告警;超限降级或拒绝。 - 合规:文字逐字校验 + 人工复核闸门 + 品牌 Logo 程序贴回;生成内容留痕(prompt/输出/审核人)。 - 密钥:只读环境变量/KMS;**禁止进 DB/系统配置/日志**。 --- ## 13 认证、权限、凭据与安全 ### 13.1 认证 | 项 | 约定 | |---|---| | Access Token | JWT,15 分钟,前端仅内存保存(不写 localStorage) | | Refresh Token | httpOnly + Secure + SameSite=Strict Cookie,7 天,旋转 + 复用检测 | | 口令 | bcrypt 12;策略复用 scgf(8–20 位、字母+数字、弱口令库、不得含用户名) | | 强制改密 | 首次登录/管理员重置;服务端 403 `PASSWORD_CHANGE_REQUIRED` | | 失效 | JWT 携带 `pwdv`;改密/重置递增,旧令牌立即失效 | | MFA | 管理员与高权限角色强制 TOTP;公网暴露的前置 | | 限流 | 登录 5 次/分钟/IP+用户名;失败锁定 15 分钟 | ### 13.2 权限 - RBAC:`admin / designer / optimizer / operator`;权限 key(如 `template.write`、`review.submit`、`delivery.manage`)。 - 数据范围:`platformId` × `clientId`;Repository 强制注入;越权返回 404(不泄露存在性)。 - 平台账号:`PlatformAccount.capabilities`(soft/hard/review);使用账号需授权;敏感操作二次确认 + 审计。 ### 13.3 凭据加密 - 算法:AES-256-GCM;密钥 `PLATFORM_CRED_KEY`(32 字节 base64,环境变量/KMS 注入);AAD 绑定 `accountId`。 - 存储:`PlatformAccount.credentialCipher`(密文)+ `keyVersion`;轮换时后台重加密。 - 禁止:写入日志、审计 old/new 明文、系统配置表、前端响应。 ### 13.4 公网暴露面 | 控制 | 要求 | |---|---| | HTTPS | TLS 1.2+;HSTS | | WAF | 云 WAF/高防(建议);至少 nginx + 限流 | | 安全头 | helmet:CSP、X-Frame-Options、nosniff、Referrer-Policy | | CORS | 仅 `WEB_ORIGIN` | | MFA | 管理员强制;运营可配置 | | 依赖/镜像 | CI `pnpm audit --prod`;镜像扫描 | | 渗透测试 | 上线前 1 次;每年 1 次 | | 备份 | PG 每日 + WAL;对象存储版本化 | | 审计 | 登录、权限变更、凭据使用、提审/投放、导出 | --- ## 14 工程规范与质量门禁 ### 14.1 TypeScript / Lint - `strict: true`、`noUncheckedIndexedAccess`、`exactOptionalPropertyTypes`(新代码);禁止 `any`(`unknown` + 收窄)。 - ESLint 10 flat config:`@typescript-eslint`、`react-hooks`、`import` 排序;`pnpm lint` 零 warning。 - Prettier 3.9:单引号、无分号、printWidth 100;`pnpm format:check` 入 CI。 - commitlint + cz-git(沿用 scgf 规范):`feat/fix/docs/refactor/test/chore`。 ### 14.2 测试分层 | 层 | 工具 | 范围 | 门禁 | |---|---|---|---| | 单元 | Vitest 5 | domain/命名/校验/成本 | 覆盖率 ≥ 70%(domain ≥ 85%) | | 集成 | Vitest + PG/Redis 测试库 | Repository/事务/队列/SSE | 每次 PR | | 契约 | Vitest + 抓包 fixture | 星光只读接口/字段映射 | 每次 PR | | 黄金图 | Vitest + pixelmatch | PSD 合成/压缩 | 每次改图像代码 | | E2E | Playwright 1.64 | 登录→选品→出图→提审 | 每日/M1 后 | - 测试库:`lingxi_test`(PG)+ Redis db 15;每个测试文件事务回滚或 truncate。 - 不 mock 数据库(集成层);外部平台用 fixture/mock server。 ### 14.3 CI(pnpm only) ``` pnpm install --frozen-lockfile pnpm lint pnpm typecheck pnpm test pnpm db:deploy(测试库) pnpm test:integration pnpm test:golden pnpm build pnpm test:e2e(可选,需浏览器) ``` - 服务:`postgres:18-alpine`、`redis:7-alpine`(GitHub Actions services 或 GitLab services)。 - 缓存:pnpm store;Node 24.15.0 由 `.nvmrc`/action 指定。 - **任何步骤禁止 `npm install`**;如遇工具要求 npm,用 `pnpm dlx` 替代 `npx`。 --- ## 15 开发环境与命令(本机) ### 15.1 前置(已具备) | 项 | 本机 | 说明 | |---|---|---| | Node | 24.15.0 | `.nvmrc` 固定 | | pnpm | 11.4.0 | `corepack enable` 后由 `packageManager` 锁定 | | PG | 18.4 @ 127.0.0.1:5432 | Docker 已有;**平台不启动/不安装** | | Redis | 7.4.9 @ 127.0.0.1:6379 | Docker 已有;**平台不启动/不安装** | ### 15.2 首次启动 ```bash corepack enable pnpm install cp .env.example .env # 填 PG 密码/JWT 密钥/凭据密钥 pnpm db:create # 连接 postgres 库,创建 lingxi(不存在才建) pnpm db:migrate # prisma migrate dev pnpm db:seed # 管理员/角色/平台/规格 pnpm playwright:install # 安装 Chromium(平台 Worker 需要) pnpm dev # web:5173 + api:8300 # 另开终端: pnpm dev:workers # 4 类 Worker ``` ### 15.3 `.env.example` ```dotenv NODE_ENV=development PORT=8300 WEB_ORIGIN=http://localhost:5173 DATABASE_URL=postgresql://postgres:CHANGE_ME@127.0.0.1:5432/lingxi?schema=public REDIS_URL=redis://127.0.0.1:6379/0 REDIS_PREFIX=lingxi JWT_ACCESS_SECRET=CHANGE_ME_32+_CHARS JWT_REFRESH_SECRET=CHANGE_ME_32+_CHARS PLATFORM_CRED_KEY=base64:CHANGE_ME_32_BYTES STORAGE_DRIVER=local STORAGE_LOCAL_ROOT=./var/uploads # STORAGE_DRIVER=s3 # S3_ENDPOINT=http://127.0.0.1:9000 # S3_REGION=us-east-1 # S3_BUCKET=lingxi # S3_ACCESS_KEY_ID= # S3_SECRET_ACCESS_KEY= # STORAGE_DRIVER=oss # OSS_REGION=oss-cn-shanghai # OSS_BUCKET= # OSS_ACCESS_KEY_ID= # OSS_ACCESS_KEY_SECRET= PLAYWRIGHT_BROWSERS_PATH=./.cache/ms-playwright AI_PROVIDER=ark ARK_BASE_URL=https://ark.cn-beijing.volces.com/api/v3 ARK_API_KEY= LOG_LEVEL=info ``` ### 15.4 端口与目录 | 服务 | 端口 | 说明 | |---|---|---| | web | 5173 | Vite dev;`/api` 代理到 8300 | | api | 8300 | 避开常见 3000,降低冲突 | | PG | 5432 | 已有容器 | | Redis | 6379 | 已有容器 | | 本地文件 | `./var/uploads` | 已 gitignore | | Playwright | `./.cache/ms-playwright` | 已 gitignore | ### 15.5 pnpm 构建脚本白名单 pnpm 11 默认拦截依赖的安装脚本;以下必须放行(与 scgf `pnpm-workspace.yaml` 一致): ```yaml # pnpm-workspace.yaml packages: - apps/* - packages/* allowBuilds: bcrypt: true sharp: true onnxruntime-node: true msgpackr-extract: true ``` > 若 pnpm 11 提示 `allowBuilds` 未知字段,改用 `onlyBuiltDependencies`(同上清单)。CI 以 `pnpm install --frozen-lockfile` 成功为准。 --- ## 16 构建与部署 ### 16.1 镜像 | 镜像 | 基础 | 内容 | |---|---|---| | `lingxi/web` | `nginx:1.29-alpine` | 构建后的 SPA + nginx 配置 | | `lingxi/api` | `node:24.15.0-bookworm-slim` | api 构建产物 + prisma 运行时 | | `lingxi/worker-*` | 同上 | 各自 worker 产物 | - 构建阶段:`corepack enable` → `pnpm install --frozen-lockfile` → `pnpm --filter build`;运行阶段只拷贝 `dist` + `node_modules`(pnpm deploy/`--prod`)。 - 非 root 用户运行;`NODE_ENV=production`。 - Node 镜像若暂无 `24.15.0-bookworm-slim` tag:锁定 `node:24-bookworm-slim` 的 digest,并在构建阶段执行 `node -v` 断言 `v24.15.0`,不通过则失败。 ### 16.2 Compose(生产/联调) ```yaml services: web: { image: lingxi/web, ports: ["80:80", "443:443"] } api: { image: lingxi/api, env_file: .env } worker-image: { image: lingxi/worker-image, env_file: .env } worker-platform: { image: lingxi/worker-platform, env_file: .env } worker-ai: { image: lingxi/worker-ai, env_file: .env } worker-export: { image: lingxi/worker-export, env_file: .env } # PG/Redis 使用外部实例(已有/托管),不写入 compose ``` - 发布顺序:备份 → `prisma migrate deploy`(一次性 job)→ api → workers → web → 健康检查。 - 迁移采用 expand/contract,保证旧版本可回滚;镜像保留上一版。 - 备份:每日 `pg_dump` + WAL 归档;对象存储版本化;恢复演练每季度。 ### 16.3 nginx 关键配置 | 路径 | 配置 | |---|---| | `/` | SPA,`try_files $uri $uri/ /index.html` | | `/api/` | 反代 api:8300;`client_max_body_size 300m`;超时 300s | | `/api/v1/events` | `proxy_buffering off; proxy_read_timeout 3600s;` | | `/files/` | 反代 api(本地驱动)或直连对象存储(生产用签名 URL 跳过) | --- ## 17 版本治理 | 类别 | 策略 | |---|---| | Node/pnpm | 精确锁定;`.nvmrc` + `engines` + 镜像三处一致 | | Playwright | 精确锁定;浏览器 revision 随包;升级需回归送审选择器 | | 图像栈 | 精确锁定;升级需黄金样本 + 像素 diff 通过 | | Prisma | 精确锁定 minor;升级需跑迁移与集成测试 | | UI 库 | caret + lockfile;每月升级窗口 | | 依赖 | `pnpm-lock.yaml` 提交;`pnpm audit --prod` 入 CI | | 废弃 | 不保留 Electron/桌面端构建链 | --- ## 18 与评估文档的差异(需同步) | 项 | 评估(ORM/UI 定稿前) | 本定稿 | 影响 | |---|---|---|---| | ORM | Sequelize 6(复用 scgf) | **Prisma 7.10.1**(模型/迁移重写) | **+8~12 人日**;总量 383~569 → **391~581**;首期 280~414 → **288~426** | | UI CSS | Tailwind 3.4(dewu 验证) | **Tailwind 4.3.3 + Semi 2.104** | +1~3 人日集成验证(在 ±25% 内) | | 运行时 | Node 24 LTS | **Node 24.15.0 精确** | 无 | | 数据/缓存 | PG/Redis 由部署提供 | **复用开发机已有 Docker,不负责安装** | 省去部署工作 | | 部署 | 单机 Compose | 同 | 公网安全加固 4~8 人日已列 | | 对象存储 | S3 适配 | **local(dev)/OSS(prod)** | 同 | > 评估文档已同步更新为 **391~581 人日 / 首期 288~426 人日**(rev.2);本定稿以 Prisma + Tailwind 4 为准。 --- ## 19 M0 必须验证的风险(Go/No-Go) | 编号 | 风险 | 验证方式 | 通过标准 | 回退方案 | |---|---|---|---|---| | R1 | Semi 2.104 + Tailwind 4.3 + Vite 8 样式层叠冲突 | 建一个含 Table/Modal/Drawer/Form 的 Spike 页 | 组件样式正确、暗色/中文正常、`@layer` 顺序可控 | 回退 Tailwind 3.4.19 + Semi 2.104(dewu 已验证) | | R2 | Prisma 7.10.1 + PG18 + Node 24.15 | 建 5 张表、迁移、seed、事务、uuidv7 默认值、adapter-pg 连接 | 迁移/回滚/连接池/类型生成全通过;`select uuidv7()` 返回 v7(本机已验证 PG18 支持) | 回退 Prisma 7.9.x 或评估 Drizzle(需重新评审) | | R3 | sharp 0.32.6 / ag-psd 30.1 / canvas 0.1.97 在 Node 24 arm64 + Linux 镜像 | 本机 + Docker 各跑一次合成 | 输出一致、无崩溃 | 逐项升级并重跑黄金样本 | | R4 | Playwright 1.64 在 macOS/Linux 行为差异 | 本机跑登录/送审空跑 | 选择器/上传可用 | 锁定 1.60(dewu 已验证) | | R5 | Node 24.15.0 官方镜像 tag | `docker build` 构建 API 镜像 | `node -v` = 24.15.0 | 用 `node:24-bookworm-slim` + digest + 版本断言 | | R6 | OSS SDK 选择(ali-oss vs S3 兼容) | 用测试 bucket 跑 put/sign/get | 签名 URL 可下载、分片上传可用 | 统一改用 S3 兼容网关 | | R7 | Prisma 迁移 19 表的一次性搬迁 | 从 scgf 导出数据导入新库 | 行数/文件/命名对账通过 | 分批迁移 + 只读并行 | --- ## 20 明确不采用的技术与理由(ADR) | 不采用 | 理由 | |---|---| | Sequelize 6 | 用户选择 Prisma;类型/迁移体验更好;代价是模型重写 +8~12 人日 | | Tailwind 3 | 用户选择 Tailwind 4;需 M0 验证 Semi 共存 | | Arco Design / shadcn/ui | 用户选择 Semi;Arco 与 scgf 页面绑定,shadcn 需自建后台组件层 | | WebSocket | 任务进度单向,SSE 足够;少一套鉴权/心跳/广播实现 | | Next.js | 内部 SPA,不需要 SSR/SEO;Vite SPA 更简单 | | NestJS / Fastify | Express 5 可迁移 scgf 中间件;换框架收益低于成本 | | MongoDB | 主数据强关系 + 事务 + 报表;PG 合适 | | Redis 8 / PG 17 | 用户环境已固定 Redis 7 / PG 18 | | K8s(首期) | 单机 Compose 足够;代码保持无状态,后续可迁移 | | MinIO(开发必选) | 用户未提供;开发用 local 驱动,生产用 OSS | | npm | 用户要求统一 pnpm | | Electron | 桌面端停止演进 | --- ## 21 附录 A:`.npmrc` 与根配置 ```ini engine-strict=true auto-install-peers=true strict-peer-dependencies=false resolution-mode=highest ``` > `auto-install-peers=true` 用于兼容 Semi/Tailwind 生态的 peer 依赖;锁定 lockfile 后再收紧。 ## 22 附录 B:依赖清单(按包) > 完整版本以 `pnpm-lock.yaml` 为准;下表为定稿基线。 | 包 | dependencies 摘要 | |---|---| | `apps/web` | react 19.3.1、react-dom 19.3.1、react-router-dom 7.18.5、@douyinfe/semi-ui 2.104.0、@remixicon/react 4.9.0、zustand 5.0.16、@tanstack/react-query 5.105.0、axios 1.21.0、react-hook-form 7.91.0、@hookform/resolvers 6.0.2、zod 4.6.7、recharts 3.10.1、dayjs 1.11.23、marked 18.1.2 | | `apps/api` | express 5.3.0、@lingxi/db、@lingxi/shared、@lingxi/domain、@lingxi/storage、@lingxi/ai、bullmq 6.5.0、ioredis 6.0.0、jsonwebtoken 9.0.3、bcrypt 6.0.0、multer 2.5.0、zod 4.6.7、pino 10.4.0、pino-http 11.0.0、helmet 8.3.0、cors 2.8.6、compression 1.8.2、express-rate-limit 8.7.2、rate-limit-redis 6.0.2、archiver 8.0.0、exceljs 4.4.0、uuid 14.0.3、dotenv 18.0.9、prom-client 15.1.3、otplib 13.5.0 | | `packages/db` | @prisma/client 7.10.1、@prisma/adapter-pg 7.10.1、pg 8.23.1 | | `packages/domain` | zod 4.6.7、dayjs 1.11.23(纯逻辑,不依赖 DB/HTTP) | | `packages/platform-dewu` | 原生 fetch(Node 24 内置)、playwright 1.64.1、zod 4.6.7 | | `packages/storage` | @aws-sdk/client-s3 3.1160.0、@aws-sdk/s3-request-presigner 3.1160.0、ali-oss 6.23.0 | | `packages/ai` | 原生 fetch、zod 4.6.7 | | `apps/worker-image` | sharp 0.32.6、ag-psd 30.1.0、@napi-rs/canvas 0.1.97、fontkit 2.0.4、@imgly/background-removal-node 1.4.5、@lingxi/domain、@lingxi/storage | | `apps/worker-platform` | playwright 1.64.1、@lingxi/platform-dewu、@lingxi/platform-vipshop、@lingxi/db | | `apps/worker-ai` | @lingxi/ai、@lingxi/storage | | `apps/worker-export` | archiver 8.0.0、exceljs 4.4.0、@lingxi/storage | | 根 devDependencies | typescript 5.9.3、tsx 4.23.15、vite 8.3.5、@vitejs/plugin-react 6.1.2、tailwindcss 4.3.3、@tailwindcss/vite 4.3.3、@douyinfe/semi-vite-plugin 2.104.0、eslint 10.13.0、typescript-eslint 8.72.1、prettier 3.9.10、vitest 5.0.5、@vitest/coverage-v8 5.0.5、@testing-library/react 16.3.3、jsdom 30.1.3、playwright 1.64.1、pino-pretty 13.2.0、prisma 7.10.1 | ## 23 附录 C:待确认项 | 编号 | 问题 | 影响 | 建议 | |---|---|---|---| | C-01 | 生产 OSS 的 region/bucket/账号是否已就绪? | 决定 `oss` 驱动配置与直传凭证 | M0 前确认 | | C-02 | 是否有云 WAF/高防?还是仅 nginx 限流? | 公网安全方案 | M0 前确认 | | C-03 | CI 平台是 GitHub Actions 还是内网 GitLab? | CI 配置模板 | M0 前确认 | | C-04 | 生产是否为单台 Linux + Docker? | Compose/备份/监控方案 | M0 前确认 | | C-05 | 管理员 MFA 是否首期强制? | 登录流程与 `otplib` 依赖 | 建议首期强制 | | C-06 | Tailwind 4 + Semi 的 Spike 谁负责/何时完成? | 前端开工前置 | M0 第 1 周 | --- *定稿基于 2026-10-30 的开发机实测、pnpm registry 版本与两个参考项目源码;任何版本调整必须更新本文档与 lockfile。*