From 00ae12435e70add8c9a00f116fb38d3dc716e08d Mon Sep 17 00:00:00 2001 From: Stev_Wang <304865932@qq.com> Date: Mon, 2 Nov 2026 15:09:51 +0800 Subject: [PATCH] docs(specs): add AGENTS.md and engineering guidelines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 项目尚无统一工程规范,AI 助手与研发各自发挥导致返工风险。建立 总纲 + 细则两层结构:AGENTS.md 只写硬约束与索引(14 条红线、技术栈 基线、仓库结构、命令契约),细则拆为 10 份文档覆盖 Git、代码风格、 格式化、API 与路由、数据库、测试与 CI、Review、日志、密钥、资产命名。 影响面:仅文档。约束力从本轮起对新建代码生效;与现状冲突处以文档为准。 回滚方式:git revert 本提交。 --- AGENTS.md | 215 ++++++++++++++++++++++ docs/工程规范/01-Git规范.md | 122 ++++++++++++ docs/工程规范/02-代码风格.md | 131 +++++++++++++ docs/工程规范/03-格式化与Lint.md | 141 ++++++++++++++ docs/工程规范/04-API与路由规范.md | 160 ++++++++++++++++ docs/工程规范/05-数据库规范.md | 121 ++++++++++++ docs/工程规范/06-测试与CI规范.md | 120 ++++++++++++ docs/工程规范/07-CodeReview与PR规范.md | 129 +++++++++++++ docs/工程规范/08-日志与可观测性规范.md | 112 +++++++++++ docs/工程规范/09-环境变量与密钥规范.md | 131 +++++++++++++ docs/工程规范/10-资产命名与PSD图层规范.md | 137 ++++++++++++++ docs/工程规范/README.md | 43 +++++ 12 files changed, 1562 insertions(+) create mode 100644 AGENTS.md create mode 100644 docs/工程规范/01-Git规范.md create mode 100644 docs/工程规范/02-代码风格.md create mode 100644 docs/工程规范/03-格式化与Lint.md create mode 100644 docs/工程规范/04-API与路由规范.md create mode 100644 docs/工程规范/05-数据库规范.md create mode 100644 docs/工程规范/06-测试与CI规范.md create mode 100644 docs/工程规范/07-CodeReview与PR规范.md create mode 100644 docs/工程规范/08-日志与可观测性规范.md create mode 100644 docs/工程规范/09-环境变量与密钥规范.md create mode 100644 docs/工程规范/10-资产命名与PSD图层规范.md create mode 100644 docs/工程规范/README.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..706e1f4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,215 @@ +# AGENTS.md · 灵犀项目整体规范 + +> 本文件是本仓库的**唯一总纲**,面向所有研发与 AI 编程助手(Claude Code / Cursor / Codex / Copilot 等)。 +> 只写**硬约束与索引**,细则一律见 [`docs/工程规范/`](docs/工程规范/README.md),本文不重复展开。 + +| 项 | 内容 | +| --- | --- | +| 适用对象 | 前端 / 后端 / 测试 / 运维 / AI 工程 / 产品(技术边界) | +| 适用产物 | 代码、配置、脚本、迁移、文档、Prompts | +| 文档版本 | V1.0(2026-11-02) | +| 事实基线 | PRD `V1.6`、技术架构 `V0.4` | + +--- + +## 0 效力与冲突处理 + +1. **优先级(高 → 低)**:现行 PRD → 技术架构 → 工程规范 → 本文示例 → 个人偏好。 +2. 规范与实现冲突时:**先改文档,再改代码**;禁止"代码先行、文档后补"。 +3. 本文与细则冲突:以细则为准;细则未覆盖:以技术架构为准。 +4. 文档未定义且影响面大于单个模块的决策,**必须提 ADR**(`docs/adr/ADR-xxx-标题.md`)评审后再写代码。 +5. 无法确认的业务口径:标 `【待确认 Q-xx】` 并停下,**禁止猜测**(Q 编号见 PRD 第 10 章)。 + +--- + +## 1 项目边界(一句话) + +灵犀 = 信息流广告素材生产平台:**行业包 → 要素卡 → 脚本 → 素材(图片确定性合成 / 视频逐镜生成)→ 快筛 → 导出即归档**。 +内核不分行业,行业差异用「行业包 + schema」表达,**禁止写成代码分支**。 + +**本期不做**(越界即评审驳回):通用视频剪辑器、投放执行、效果归因、客户自助登录、计费结算、图片线 AI 生图(M5 前)、通用低代码/审批引擎、模型训练微调。 + +--- + +## 2 文档基线(唯一事实来源) + +| 文档 | 路径 | 版本 | 作用 | +| --- | --- | --- | --- | +| PRD | `docs/PRD/灵犀-信息流广告素材一站式生产平台PRD.md` | V1.6 | 需求、业务规则(R-01~R-30)唯一来源 | +| 技术架构 | `docs/技术架构/灵犀_信息流素材生产平台_技术架构_V0.1.md` | V0.4 | 技术选型、模块、数据、接口唯一来源 | +| 立项建议 | `docs/立项建议/灵犀_信息流素材生产平台_立项建议书_V0.1.md` | V0.1 | 论证背景,**不约束实现** | +| 工程规范 | `docs/工程规范/` | — | 落地细则(本文件的下游) | +| 参考项目 | `项目参考/`、`sucaigongfang` | — | 仅查阅,**不入库、禁止复制业务代码** | + +- 新增技术选型 / 架构变更,必须先更新技术架构并评审,**禁止在代码里"先试再说"**。 +- 引用业务规则时写编号(如 `// R-04 客户数据隔离`),便于反查。 + +--- + +## 3 硬红线(违反即打回,不接受"下个版本再改") + +1. **包管理只用 pnpm 10**,Node 锁定 `24.15.0`(`.nvmrc`);禁 npm / yarn / bun,禁 `npm link`,内部包用 `workspace:*`。 +2. **全栈 TypeScript strict**;禁 `any`(用 `unknown` + 收窄),禁用 `as` 绕过类型检查。 +3. **UI 只用 `@douyinfe/semi-ui-19`**;图标只用 Remix Icon 且只能从 `packages/ui/icons.ts` 按语义名导入。禁混装其他组件库/图标库,禁业务代码内联 SVG(品牌 Logo 除外)。 +4. **模块化单体**:模块间只走 Service 接口或领域事件;**禁止跨模块 `prisma.*`**;`identity` / `client` 是被依赖方,不得反向依赖。 +5. 任何数据访问必须携带 **`ScopeContext`**,查询必须带 `customer_id`;**禁止"先查全量再过滤"**。 +6. **出网 AI 调用唯一入口 `platform.ai`**;业务模块不得直连任何模型 SDK。 +7. **图片线只做确定性合成**(PSD 套版 + 抠图 + 合成);**禁止**在图片线接入文生图 / 图生图(PRD R-29)。 +8. **迁移一律 expand → migrate → contract**;禁止同一次发布内"删字段 + 改代码"。 +9. **密钥 / Token 不入库、不写日志、不进前端 bundle**,只从环境变量读。 +10. **审计表 append-only(仅 INSERT)**;导出全留痕(PRD R-05)。 +11. **阈值 / 上限类数字只存系统配置一处**(PRD R-26);禁前后端硬编码。 +12. **低质量产出必须显式打标**(`low_quality` + 原因码),不得按成功静默交付(PRD R-30)。 +13. 任务提交必须带 **`Idempotency-Key`**;产物一律短时效签名 URL,**不塞进 JSON**。 +14. **被引用的资产只能停用、不能删除**(PRD R-02);已入库资产修改须**新版本 + 重新审核**(R-03)。 + +--- + +## 4 技术栈基线(版本以技术架构 §2.10 为准) + +| 层 | 选型 | +| --- | --- | +| 运行时 | Node.js `24.15.0` · pnpm `10.x` · TypeScript `5.7+` | +| 前端 | React `19` · Vite `6` · React Router `7` · TanStack Query `5` · Zustand `5` · Tiptap · dnd-kit · ECharts `5` · HLS.js(P2) | +| UI | Semi Design `2.x`(`@douyinfe/semi-ui-19`)· Remix Icon `4.x` | +| 后端 | NestJS `11` + Fastify · Prisma `6` · Zod(静态 DTO)+ Ajv(动态 schema) | +| 数据 | PostgreSQL `18` · Redis `7.4` + BullMQ `5` · MinIO(S3 协议) | +| 媒体 | ffmpeg `7` · sharp `0.33+` · `@napi-rs/canvas` · `ag-psd` · ONNX 抠图 | +| 质量 | Vitest · Testcontainers · Playwright · k6 · oasdiff | +| 观测 | Pino · OpenTelemetry | + +> 版本升级单独 PR + 修订记录,**禁止顺手升级**。新增运行时依赖需在 PR 说明必要性(影响面 + 替代方案)。 + +--- + +## 5 仓库结构(目标态;当前仅 `docs/`) + +``` +apps/api NestJS HTTP + SSE + AI 回调 +apps/worker 同一镜像,按 QUEUE 环境变量启动 +apps/web React SPA +packages/shared 领域事件、错误码、权限点常量、Zod schema、ULID、命名工具 +packages/form-engine 受控 schema 表单渲染器(14 类字段白名单) +packages/render PSD 解析 / 抠图 / 合成 / 编码 +packages/ai-sdk Provider 适配器、路由、限流、记账 +packages/ui Semi 二次封装 + Remix 图标统一出口 + locale +prisma/ schema.prisma 与 migrations/ +infra/ compose、Nginx、监控、备份 +docs/ PRD / 技术架构 / 立项建议 / 工程规范 +``` + +- **依赖方向**:`apps → packages`;`packages` 单向;`shared` 不依赖任何包;**禁止循环依赖**。 +- 工作区包 scope 统一 `@lingxi/*`(`@lingxi/shared`、`@lingxi/ui`、`@lingxi/render`…),保持私有、不发布公网。 +- 后端 8 模块:`identity` `client` `catalog` `industry` `script` `media` `delivery` `platform`。 +- 新目录 / 新包必须先评审,**禁止在 `apps/web` 内私建"共享"目录**充当包。 + +--- + +## 6 命令契约(建仓后必须与下表一致) + +| 场景 | 命令 | +| --- | --- | +| 依赖启动 | `pnpm compose:up` / `pnpm compose:down` | +| 开发 | `pnpm dev` | +| 构建 | `pnpm build` | +| 质量 | `pnpm lint` · `pnpm lint:fix` · `pnpm format` · `pnpm format:check` · `pnpm typecheck` | +| 测试 | `pnpm test` · `pnpm test:int` · `pnpm test:e2e` | +| 数据库 | `pnpm db:migrate` · `pnpm db:reset`(禁止 `docker compose down -v`) | +| 契约 | `pnpm openapi:diff` | + +> 文档、脚本、CI 中出现的命令必须与上表一致,禁止自造别名。 + +--- + +## 7 变更流程(提测 / 合并前必须全绿) + +1. 从 `main` 拉短分支 → 小步提交(Conventional Commits)。 +2. 本地门禁:`pnpm lint && pnpm typecheck && pnpm test`。 +3. PR 描述必含:**背景 / 改动 / 影响面 / 回滚方式 / 验证证据**;迁移、权限、AI、导出类变更须**双人 Review**。 +4. CI 全绿 + Review 通过 → **Squash Merge**;禁止 force push `main`。 +5. 发布:staging 验证 → 生产灰度;**迁移先行、代码后行**。 + +细则:[Git 规范](docs/工程规范/01-Git规范.md) · [Code Review 与 PR](docs/工程规范/07-CodeReview与PR规范.md) · [测试与 CI](docs/工程规范/06-测试与CI规范.md) + +--- + +## 8 文档规范(摘要) + +- 文档与代码**同 PR** 更新;只改文档也要走 PR。 +- 新业务规则先进 PRD 并编号(`R-xx`),再进代码。 +- 文档版本 `V<主>.<次>`:主版本 = 业务口径不兼容;次版本 = 增量补充。 +- 索引三处必须同步:根 `README.md`、`docs/工程规范/README.md`、本文件 §9。 +- 命名沿用 `灵犀_<主题>_<类型>_V<版本>.md`;**新建文档**必须遵循,历史文件保持原名并登记。 + +--- + +## 9 细则索引 + +| 主题 | 文档 | 一句话 | +| --- | --- | --- | +| Git / 分支 / 提交 | [01-Git规范](docs/工程规范/01-Git规范.md) | 主干 + 短分支,Conventional Commits,Squash 合并 | +| 代码风格 | [02-代码风格](docs/工程规范/02-代码风格.md) | 命名、类型、错误处理、异步、React 约定 | +| 格式化与 Lint | [03-格式化与Lint](docs/工程规范/03-格式化与Lint.md) | Prettier + ESLint 9 flat,钩子与 CI 门禁 | +| API 与路由 | [04-API与路由规范](docs/工程规范/04-API与路由规范.md) | REST /api/v1、错误码、鉴权链、任务协议 | +| 数据库 | [05-数据库规范](docs/工程规范/05-数据库规范.md) | ULID、customer_id、游标分页、expand/contract | +| 测试与 CI | [06-测试与CI规范](docs/工程规范/06-测试与CI规范.md) | 分层测试、权限三态、覆盖率只升不降 | +| Code Review | [07-CodeReview与PR规范](docs/工程规范/07-CodeReview与PR规范.md) | PR 模板、双人场景、DoD、合并后观察 | +| 日志与可观测 | [08-日志与可观测性规范](docs/工程规范/08-日志与可观测性规范.md) | Pino 字段、trace_id、审计与日志分离、告警 | +| 环境变量与密钥 | [09-环境变量与密钥规范](docs/工程规范/09-环境变量与密钥规范.md) | `.env.example` 入库、启动校验、轮换与泄露处置 | +| 资产命名与图层 | [10-资产命名与PSD图层规范](docs/工程规范/10-资产命名与PSD图层规范.md) | 命名占位符、PSD 图层前缀、规格矩阵、低质量标记 | + +--- + +## 10 禁止自行决定(须产品 / 技术负责人确认) + +- 新增第三方运行时依赖、新增外部 AI 供应商、改动 `ai_routes` 路由表。 +- 字段类型白名单之外的新字段类型;权限点新增 / 删除 / 改名。 +- 数据保留期、AI 预算阈值、爆量判定阈值。 +- 公开分享 / 免登录审核链接开关(PRD R-28)。 +- 跨客户资产复用授权(R-02 之外的显式授权流程)。 +- 任何会让客户数据跨客户可见的实现方式。 + +--- + +## 11 AI 助手协作约定 + +1. 读上下文顺序:本文件 → 相关细则 → 技术架构对应章节 → PRD 对应章节,**禁止凭记忆写版本号 / 包名 / 表名 / 路由**。 +2. 产出代码必须能过 `pnpm lint`、`pnpm typecheck`、`pnpm test`;不得新增未声明依赖。 +3. 禁止修改已合并的 `prisma/migrations/`;禁止 `--no-verify` 提交。 +4. 涉及业务口径不确定时,输出 `【待确认 Q-xx】` 并说明影响面,**不要编造**。 +5. 改动跨 >3 个模块或触及 §10 清单时,先给出方案与影响面,等确认后再动手。 + +--- + +## 12 待建设规范(建议,尚未落地) + +当前已覆盖 Git / 代码 / 格式 / 接口 / 数据库 / 测试 / Review / 日志 / 密钥 / 资产命名。以下规范建议在对应里程碑前补齐,**补齐前不得以"没有规范"为由自行发挥**: + +| 规范 | 为什么需要 | 建议时点 | +| --- | --- | --- | +| **Prompt 工程规范** | 提示词模板是最容易被低估的资产;需约定模板结构、变量槽、版本与评测集 | M1 前 | +| **AI 成本与预算规范** | 预占 / 结算 / 账本 / 闸门已定,但预算口径与对账流程未文档化 | M2 前 | +| **安全与合规专项** | 广告法极限词、肖像权授权、AI 生成内容标识(R-09 形式仍待确认) | M1 前 | +| **运维 Runbook** | 部署、备份恢复、故障处理、告警处置(技术架构待办已列) | T1 前 | +| **前端交互与 UX 规范** | 快筛快捷键、空态 / 加载态 / 错误态、表格与表单交互统一 | M1 中 | +| **性能预算规范** | 首屏 ≤ 2s、接口 P95 ≤ 500ms 需拆到包体积 / 请求数 / 渲染预算 | M1 后 | +| **ADR 目录** | `docs/adr/` 尚未创建;首次架构决策时建立并纳入索引 | 首次 ADR 时 | +| **文档写作规范** | 术语统一、图表规范、评审流程 | M1 前 | + +--- + +## 附:待同步事项(2026-11-02) + +- [x] 根 `README.md` 已与 PRD V1.6 对齐(三主线 + 一底座、文档索引、命令契约)。 +- [ ] `docs/技术架构/`、`docs/立项建议/`、PRD V1.6、`docs/工程规范/` 尚未纳入 git,应合并为一个 `docs:` 提交入库。 +- [ ] PRD 文件名 `灵犀-信息流广告素材一站式生产平台PRD.md` 不符合 §8 命名;因已被外部引用,暂保留原名,重命名时同步更新全部引用。 +- [ ] 技术架构文档**文件名**为 `_V0.1.md` 但**内部版本**为 V0.4,正文修订记录中 V0.4 / V0.3 顺序倒置,建议下一版修正。 + +## 附:待确认口径(工程侧发现,需产品 / 架构确认) + +| 编号 | 待确认项 | 本文现行约定 | +| --- | --- | --- | +| `C-01` | JSON body 字段命名:架构文档示例为 camelCase,而 query 用 `next_cursor` 等 snake_case | body 用 camelCase,query 用 snake_case(见 [04](docs/工程规范/04-API与路由规范.md) §2) | +| `C-02` | 列表响应是否统一信封 | 列表 `{ items, next_cursor }`,单资源不套 `data` | +| `C-03` | 工作区包 scope 命名 | 暂定 `@lingxi/*` | +| `C-04` | AI 生成内容标识的具体形式与位置(PRD Q-06 / R-09) | 角标 + MP4 元数据(技术架构 §7.4),形式待法务确认 | diff --git a/docs/工程规范/01-Git规范.md b/docs/工程规范/01-Git规范.md new file mode 100644 index 0000000..fc5cfc2 --- /dev/null +++ b/docs/工程规范/01-Git规范.md @@ -0,0 +1,122 @@ +# 01 · Git 规范 + +| 项 | 内容 | +| --- | --- | +| 文档版本 | V1.0(2026-11-02) | +| 依据 | 技术架构 §4.6(CI/CD)、§10.1(仓库结构) | +| 适用范围 | 全仓库(代码、文档、配置、迁移) | +| 强制级别 | 强制 | + +--- + +## 1 分支模型 + +采用 **trunk-based(主干 + 短生命周期分支)**,不建 `develop`、不建长期 `release` 分支。 + +| 类型 | 命名 | 用途 | 生命周期 | +| --- | --- | --- | --- | +| 主干 | `main` | 始终可发布 | 永久 | +| 功能 | `feat/-<简述>` | 新功能 | ≤ 5 天,超过须拆分 | +| 修复 | `fix/-<简述>` | 缺陷修复 | ≤ 2 天 | +| 文档 | `docs/<简述>` | 仅文档 | ≤ 1 天 | +| 重构 | `refactor/-<简述>` | 行为不变的结构调整 | ≤ 3 天 | +| 其他 | `chore/` `test/` `perf/` `build/` `ci/` | 对应类型 | ≤ 2 天 | + +规则: + +- `GIT-01` 分支名全小写 kebab-case,≤ 50 字符,**不含中文**;`scope` 用模块或包名(`api` / `web` / `worker` / `media` / `db` / `ci` / `docs`)。 +- `GIT-02` 分支必须从最新 `main` 拉出,合并前 rebase 到 `main` 最新。 +- `GIT-03` 禁止直接 push `main`;分支保护开启:必须 PR + CI 绿 + ≥1 批准。 +- `GIT-04` 合并后立即删除远端分支。 + +--- + +## 2 提交信息(Conventional Commits 1.0) + +``` +(): + + + + +``` + +| `type` | 用途 | +| --- | --- | +| `feat` | 新功能 | +| `fix` | 缺陷修复 | +| `docs` | 文档 | +| `style` | 仅格式(不影响逻辑) | +| `refactor` | 重构(不改行为) | +| `perf` | 性能 | +| `test` | 测试 | +| `build` | 构建 / 依赖 | +| `ci` | CI / 脚本 | +| `chore` | 杂项 | +| `revert` | 回滚 | + +规则: + +- `GIT-05` `subject` 用英文祈使句,≤ 72 字符,**不加句号**;中文放正文。 +- `GIT-06` `scope` 必填,取值见 `GIT-01`。 +- `GIT-07` 正文说明「**为什么**」与「**影响面 / 回滚方式**」;纯重命名类可仅一行 subject。 +- `GIT-08` 破坏性变更:`type` 后加 `!` 且 footer 写 `BREAKING CHANGE: <说明与迁移方式>`。 +- `GIT-09` 关联业务规则写 `Refs: R-04`;关联 issue 写 `Closes #123`。 +- `GIT-10` 一个提交只做一件事;**禁止**在功能提交里顺带格式化无关文件。 +- `GIT-11` 禁止 `WIP`、`fix bug`、`update` 等无信息量信息;禁止 `--no-verify` 绕过钩子。 + +示例: + +``` +feat(media): 图片批次支持按规格矩阵展开任务项 + +批量生产原先按「模板 × 商品图」展开,导致同素材多规格重复建素材, +违反 PRD R-25「素材为主对象、规格为属性」。改为展开到 task_item, +规格作为属性写入 material_specs。 + +影响面:production_tasks 新增 spec_key 列(expand 迁移,兼容旧数据)。 +回滚方式:关闭 FEATURE_MATRIX_SPEC 开关,旧路径仍可用。 +Refs: R-25 +``` + +--- + +## 3 PR 与合并 + +- `GIT-12` PR 标题遵循提交规范(Squash 后即主干提交信息)。 +- `GIT-13` 合并方式统一 **Squash Merge**,保持 `main` 线性;禁止 Merge commit。 +- `GIT-14` 单个 PR 变更 ≤ 400 行(含测试,不含 lock 文件与生成代码);超出须拆分并在描述说明依赖关系。 +- `GIT-15` 禁止 force push `main` / 已有人 Review 的公共分支;个人分支允许 `--force-with-lease`。 +- `GIT-16` PR 描述模板与双人 Review 场景见 [07-CodeReview与PR规范](07-CodeReview与PR规范.md)。 + +--- + +## 4 Tag 与发布 + +| 项 | 规则 | +| --- | --- | +| 版本号 | 语义化 `v<主>.<次>.<补>` | +| 打 Tag 时机 | 生产发布前,由发布负责人在 `main` 打 annotated tag | +| 里程碑 Tag | `m0` / `m1` / `m2`…(对应 PRD 分期),仅标记不进版本链 | +| 发布记录 | 每个 Tag 必须写变更摘要(可复用 PR 标题集合) | +| 回滚 | 回滚用新 Tag(`v1.2.1` 回滚 `v1.2.0` 内容),**禁止删除已发布 Tag** | + +--- + +## 5 大文件与二进制 + +- `GIT-17` 禁止入库 > 10 MB 的二进制(PSD 模板、成片、视频、字体包),一律走对象存储;确需入库(如开源字体)用 **Git LFS** 并在 PR 说明。 +- `GIT-18` 禁止入库:`.env*`、密钥、证书私钥、构建产物、`node_modules/`、日志、`项目参考/`。 +- `GIT-19` `pnpm-lock.yaml` 必须提交且与 `package.json` 同步;**禁止**手工编辑 lock 文件。 +- `GIT-20` 误提交敏感信息:立即轮换密钥 → 用 `git filter-repo` 清理历史 → 全员重新 clone → 记录复盘。 + +--- + +## 6 检查清单(提交前 / 合并前) + +- [ ] 分支名符合 `GIT-01`,从最新 `main` 拉出 +- [ ] 提交信息符合 Conventional Commits,含 `scope` +- [ ] 提交粒度单一,无无关文件 +- [ ] 无 `.env` / 密钥 / 大文件 +- [ ] PR 描述含背景 / 影响面 / 回滚方式 / 验证证据 +- [ ] CI 全绿,Review 通过,Squash 合并 diff --git a/docs/工程规范/02-代码风格.md b/docs/工程规范/02-代码风格.md new file mode 100644 index 0000000..78531cf --- /dev/null +++ b/docs/工程规范/02-代码风格.md @@ -0,0 +1,131 @@ +# 02 · 代码风格 + +| 项 | 内容 | +| --- | --- | +| 文档版本 | V1.0(2026-11-02) | +| 依据 | 技术架构 §2.2 / §2.3 / §10.1 | +| 适用范围 | `apps/` `packages/` `prisma/` `infra/` 全部 TypeScript / JavaScript | +| 强制级别 | 强制(标注「建议」的除外) | + +> 格式问题交给 Prettier(见 [03](03-格式化与Lint.md)),本文只约定**格式化管不到**的部分。 + +--- + +## 1 命名 + +| 对象 | 规则 | 示例 | +| --- | --- | --- | +| 目录 / 文件 | kebab-case | `ai-routes/`、`script-task.service.ts` | +| React 组件文件 | PascalCase.tsx | `ScriptCard.tsx` | +| 变量 / 函数 | camelCase | `nextCursor`、`resolveScope()` | +| 类型 / 接口 / 类 | PascalCase | `MediaTask`、`StorageProvider` | +| 常量(模块级不可变) | UPPER_SNAKE | `MAX_UPLOAD_MB` | +| 布尔 | `is/has/can/should` 前缀 | `isArchived`、`hasPermission` | +| 事件回调 | 属性 `onXxx`,处理器 `handleXxx` | `onSelect` / `handleSelect` | +| 异步动作 | 动词开头,返回 Promise | `createBatch()` | +| 数据库表 / 列 | snake_case(Prisma `@@map` / `@map`) | `media_tasks`、`customer_id` | +| 权限点常量 | `UPPER_SNAKE`,与库内 code 一致 | `SCRIPT_APPROVE` | + +- `CS-01` 禁止拼音命名(`shangpin`);业务术语用 PRD 术语表英文(`briefCard`、`videoDirection`、`shot`)。 +- `CS-02` 单字母变量只允许循环下标 `i/j` 与泛型 `T/K/V`。 +- `CS-03` 缩写词按单词大小写:`s3Key`、`httpClient`、`ulid`,不写 `S3Key` / `HttpClient` 混排。 + +--- + +## 2 类型 + +- `CS-04` `strict: true`,禁止 `any`、隐式 `any`、`@ts-ignore`;确需忽略用 `@ts-expect-error` 并写原因。 +- `CS-05` 禁止用 `as` 做类型断言绕过检查;只允许「收窄断言」(`as const`、`as unknown as X` 需注释理由)。 +- `CS-06` 对外导出的函数 / 方法**必须显式标注返回类型**,内部函数可推断。 +- `CS-07` 对象契约用 `interface`,联合 / 工具 / 映射类型用 `type`;二者不混用于同一实体。 +- `CS-08` 业务枚举用 `as const` 对象 + 派生联合类型,**禁止 TS `enum`**;库内枚举由字典表提供(Prisma enum 仅用于永不扩展的封闭集合,需迁移评审说明)。 +- `CS-09` 泛型参数 ≥ 2 个或含义不清时必须命名(``),禁止全用 `T`。 +- `CS-10` 禁止在业务代码中出现 `object`、`Function`、`{}` 作为类型;用具体形状或 `Record`。 + +--- + +## 3 函数与结构 + +- `CS-11` 函数单一职责,建议 ≤ 50 行;超过则拆分。 +- `CS-12` 参数 > 3 个必须用对象参数;可选参数不用位置占位。 +- `CS-13` 嵌套 ≤ 3 层,用**早返回**替代 `else` 链。 +- `CS-14` 禁止魔法值:数字 / 字符串常量抽到 `constants` 或读取系统配置(PRD R-26)。 +- `CS-15` 单文件 ≤ 400 行;超出按职责拆分(Controller / Service / Repository 各自成文件)。 +- `CS-16` 禁止循环依赖;跨包引用只从包 `index.ts` 出口导入。 + +--- + +## 4 错误处理 + +- `CS-17` 统一抛 `AppError(code, message, details?)`,`code` 取错误码表(见 [04](04-API与路由规范.md) §4);**禁止 `throw new Error('...')` 直接面向用户**。 +- `CS-18` 禁止 `catch` 后空吞;要么处理、要么包装后重抛,并保留 `cause`。 +- `CS-19` `async` 函数必须有 `await` 或显式 `void`;禁止 floating promise。 +- `CS-20` 外部调用(AI / 存储 / ffmpeg)必须设置超时 + 分类重试;**内容违规类错误不重试**。 +- `CS-21` 任务(BullMQ)处理器必须**幂等**:以 `taskItemId` 为幂等键,重复执行结果一致。 +- `CS-22` 前端错误:路由级 ErrorBoundary + 请求层统一 toast,禁止页面内零散 `alert`。 + +--- + +## 5 注释与文档 + +- `CS-23` 注释解释**为什么**,不复述代码;业务规则必须引用 PRD 编号:`// R-04 查询必须带 customer_id`。 +- `CS-24` 导出 API 写 JSDoc(用途、参数、错误码、副作用);内部实现按需。 +- `CS-25` TODO 格式:`// TODO(@owner, 2026-12-01): 原因与后续`;禁止无主 TODO。 +- `CS-26` 禁止提交注释掉的整段代码;历史查 git。 +- `CS-27` 不写「本次改动」「新增」这类时间性注释,注释描述当下状态。 + +--- + +## 6 导入顺序 + +按组排序,组间空行,组内按字母序(由 ESLint `simple-import-sort` 自动修复): + +``` +1. Node 内置(node:fs) +2. 第三方(@nestjs/*、zod) +3. workspace 包(@lingxi/shared、@lingxi/ui) +4. 应用内绝对路径(~/ 或 @/) +5. 相对路径(./、../) +6. 样式 / 资源(*.css、*.svg?raw) +``` + +- `CS-28` 禁止 `import * as X` 整包导入(图标包、UI 包尤甚,会破坏 tree-shaking)。 +- `CS-29` 图标只从 `packages/ui/icons.ts` 导入语义名(`import { IconScript } from '@lingxi/ui/icons'`),禁直接引 `remixicon` 具体路径。 +- `CS-30` 禁 `export *` 聚合无审阅的公共 API;`index.ts` 显式列出导出。 + +--- + +## 7 React / 前端 + +- `CS-31` 只用函数组件 + Hooks;禁 class 组件、禁 `React.FC`(返回类型写 `ReactNode` / `JSX.Element`)。 +- `CS-32` 服务端数据只用 TanStack Query(`useQuery` / `useMutation`);**禁止**在 `useEffect` 里手写 fetch + 自建缓存。 +- `CS-33` 全局状态只放跨页共享且非服务端数据(Zustand);页面内状态用 `useState` / `useReducer`,禁止为省 props 上全局。 +- `CS-34` 列表 `key` 用业务 ID,禁止 `index`。 +- `CS-35` 组件文件导出一个主组件;子组件同文件时用内部函数,跨文件复用必须提为独立组件。 +- `CS-36` 样式只用 Semi design token(`cssVar`)/ CSS Modules;**禁止散落十六进制色值**、禁止行内 style 做主题。 +- `CS-37` 文案集中在 `packages/ui/locale`(zh-CN),组件内不写死中文用户可见文案(日志与注释除外)。 +- `CS-38` 表单走 `packages/form-engine` 的 schema 渲染;**禁止**在业务页面手写动态字段映射(会绕过白名单校验)。 + +--- + +## 8 后端 + +- `CS-39` 分层职责:Controller 只解析入参 / 组装响应;Service 承载业务规则与状态机;Repository 是唯一访问 Prisma 的层。 +- `CS-40` Repository 方法**必须接收 `ScopeContext`** 作为首个参数,禁止在 Repository 内部从全局取当前用户。 +- `CS-41` 模块间只通过 Service 接口或领域事件通信;禁止跨模块注入 `PrismaService` 直接查表。 +- `CS-42` 状态迁移集中在 Service 的状态机函数内,禁止 Controller 直接改 `status` 字段。 +- `CS-43` 事务边界在 Service;事务内**禁止**外部 IO(AI 调用、HTTP、对象存储)。 +- `CS-44` 队列任务只做「可重试的纯执行」,业务校验与成本预占在 API 层完成。 + +--- + +## 9 检查清单 + +- [ ] 命名符合 §1,无拼音、无缩写混排 +- [ ] 无 `any` / `@ts-ignore` / 滥用 `as` +- [ ] 函数 ≤ 50 行、文件 ≤ 400 行、嵌套 ≤ 3 层 +- [ ] 错误统一 `AppError`,无空 catch +- [ ] 队列任务幂等,外部调用有超时 +- [ ] 图标 / UI 从统一出口导入 +- [ ] 业务规则注释带 R-xx +- [ ] 无魔数、无硬编码阈值 diff --git a/docs/工程规范/03-格式化与Lint.md b/docs/工程规范/03-格式化与Lint.md new file mode 100644 index 0000000..4ba281b --- /dev/null +++ b/docs/工程规范/03-格式化与Lint.md @@ -0,0 +1,141 @@ +# 03 · 格式化与 Lint + +| 项 | 内容 | +| --- | --- | +| 文档版本 | V1.0(2026-11-02) | +| 依据 | 技术架构 §2.2(前端)、§10.1(工程约定)、§4.6(CI/CD) | +| 适用范围 | 全仓库可执行文件 | +| 强制级别 | 强制(CI 门禁,不允许「先合并后修」) | + +--- + +## 1 一句话原则 + +**格式交给工具,评审只看逻辑。** 任何格式争论都应由配置解决,禁止在 Review 中讨论空格与换行。 + +--- + +## 2 Prettier(唯一格式化器) + +| 配置项 | 值 | 说明 | +| --- | --- | --- | +| `printWidth` | `100` | 与技术架构示例行宽一致 | +| `tabWidth` | `2` | | +| `useTabs` | `false` | | +| `semi` | `true` | | +| `singleQuote` | `true` | | +| `trailingComma` | `all` | | +| `arrowParens` | `always` | | +| `endOfLine` | `lf` | 跨平台一致 | +| `plugins` | `prettier-plugin-tailwindcss`(仅 `apps/web`) | 若引入 Tailwind;未引入则不加 | + +规则: + +- `FMT-01` 只允许一个 Prettier 版本、一个根配置;子包**不得**覆盖。 +- `FMT-02` 禁止在文件中写 `prettier-ignore`,除非是生成代码或大段 markdown 表格,并注明原因。 +- `FMT-03` CI 执行 `pnpm format:check`,失败即阻断。 +- `FMT-04` 禁止把格式化产生的无关改动混进功能 PR(会产生冲突与 Review 噪音)。 + +--- + +## 3 ESLint(ESLint 9 flat config) + +| 项 | 约定 | +| --- | --- | +| 配置位置 | 根 `eslint.config.mjs`,按 `apps/*`、`packages/*` 分层 export | +| 基础 | `@eslint/js` recommended + `typescript-eslint` 的 `strictTypeChecked` + `stylisticTypeChecked` | +| 前端附加 | `eslint-plugin-react-hooks`、`eslint-plugin-react-refresh`、`jsx-a11y` | +| 导入排序 | `eslint-plugin-simple-import-sort` | +| 命名检查 | `@typescript-eslint/naming-convention`(对应 [02](02-代码风格.md) §1) | + +### 3.1 必须为 `error` 的规则 + +| 规则 | 目的 | +| --- | --- | +| `@typescript-eslint/no-explicit-any` | 禁 `any` | +| `@typescript-eslint/no-floating-promises` | 禁未处理 Promise | +| `@typescript-eslint/no-misused-promises` | 禁 async 传入非异步回调位 | +| `@typescript-eslint/consistent-type-imports` | 类型导入用 `import type` | +| `@typescript-eslint/no-unnecessary-condition` | 消除冗余判断(strict 下有效) | +| `no-restricted-imports` | **UI / 图标白名单**(见 §3.2) | +| `no-console` | 业务代码禁用,`scripts/**` 与 `worker` 的 logger 除外 | +| `import/no-cycle` | 禁循环依赖 | +| `react-hooks/rules-of-hooks` / `exhaustive-deps` | Hooks 正确性 | +| `jsx-a11y/*` 关键项 | 可访问性底线 | + +### 3.2 白名单(`no-restricted-imports` 强制) + +**禁止导入**(含子路径): + +``` +@douyinfe/semi-ui ← 只能用 @douyinfe/semi-ui-19 +antd @ant-design/* @arco-design/* +@douyinfe/semi-icons @ant-design/icons react-icons lucide-react @heroicons/* +element-plus naive-ui @mui/* @chakra-ui/* +``` + +**规则**: + +- `FMT-05` 图标只能 `import { IconXxx } from '@lingxi/ui/icons'`;禁止 `import * as Icons`、禁止直接引 `remixicon/icons/...`。 +- `FMT-06` SVG 内联仅允许品牌 Logo(`packages/ui/brand/`),其他一律走图标出口。 +- `FMT-07` 颜色字面量(`#rrggbb`、`rgb(`)在 `apps/web` 内禁用,除非在 `packages/ui/theme/` 的 token 定义文件。 + +### 3.3 允许 `warn` 的规则 + +`@typescript-eslint/no-non-null-assertion`(须注释证明非空)、`complexity`(> 15 警告)、`max-lines-per-function`(> 50 警告)。 + +--- + +## 4 提交钩子(本地) + +| 钩子 | 工具 | 行为 | +| --- | --- | --- | +| `pre-commit` | husky + lint-staged | 仅对暂存文件跑 `eslint --fix` + `prettier --write` | +| `commit-msg` | commitlint | 校验 Conventional Commits(见 [01](01-Git规范.md) §2) | +| `pre-push` | husky | 跑 `pnpm typecheck`(本地拦截类型错误,避免浪费 CI) | + +- `FMT-08` `lint-staged` 只处理暂存文件,**禁止**在钩子里跑全量 lint / 全量测试(太慢会诱发 `--no-verify`)。 +- `FMT-09` 禁止 `git commit --no-verify`;如确因钩子故障,须在 PR 描述说明并附 CI 结果。 + +--- + +## 5 CI 门禁(阻断项) + +| 阶段 | 命令 | 失败后果 | +| --- | --- | --- | +| 安装 | `pnpm install --frozen-lockfile` | 阻断(lock 不一致直接失败) | +| 格式 | `pnpm format:check` | 阻断 | +| Lint | `pnpm lint --max-warnings=0` | 阻断 | +| 类型 | `pnpm typecheck` | 阻断 | +| 单测 | `pnpm test -- --coverage` | 阻断(覆盖率不得低于基线) | +| 迁移 | `prisma migrate diff` 校验 schema 与迁移一致 | 阻断 | +| 构建 | `pnpm build` | 阻断 | +| 安全 | 依赖审计 + 镜像高危漏洞 | 阻断(高危 0) | + +- `FMT-10` CI 与本地必须使用**同一份配置**,禁止 CI 单独放宽规则。 +- `FMT-11` 覆盖率基线只升不降;下调须在 PR 说明理由并由技术负责人批准。 + +--- + +## 6 例外与豁免 + +| 场景 | 做法 | +| --- | --- | +| 生成代码(Prisma Client、OpenAPI 类型) | 在 `eslint.config.mjs` 中按路径 `ignores`,禁止在生成文件内加注释 | +| 第三方类型缺陷 | 文件级 `/* eslint-disable rule -- 原因 */`,必须写原因并在 PR 说明 | +| 单行特殊语法 | 行内 `// eslint-disable-next-line rule -- 原因` | +| 全文件 disable | **禁止**;确需则改为局部 disable 或调整规则配置 | + +- `FMT-12` 任何 `eslint-disable` 都必须写明原因与到期处理方式(升级依赖 / 提 issue)。 +- `FMT-13` 规则调整(新增 / 降级 / 关闭)必须单独 PR,说明影响文件数与整改计划。 + +--- + +## 7 检查清单 + +- [ ] `pnpm format:check` 通过,无格式化噪音混入 +- [ ] `pnpm lint --max-warnings=0` 通过 +- [ ] `pnpm typecheck` 通过 +- [ ] 无新增 `eslint-disable`;已有的都有原因 +- [ ] 未引入禁用包 / 禁用图标库 +- [ ] 无散落色值,样式走 token diff --git a/docs/工程规范/04-API与路由规范.md b/docs/工程规范/04-API与路由规范.md new file mode 100644 index 0000000..96e4066 --- /dev/null +++ b/docs/工程规范/04-API与路由规范.md @@ -0,0 +1,160 @@ +# 04 · API 与路由规范 + +| 项 | 内容 | +| --- | --- | +| 文档版本 | V1.0(2026-11-02) | +| 依据 | 技术架构 §6(接口设计)、§3.2(模块划分) | +| 适用范围 | `apps/api` 全部 HTTP / SSE 接口、`apps/web` 全部前端路由 | +| 强制级别 | 强制 | + +--- + +## 1 URL 与版本 + +- `API-01` 服务端统一前缀 `/api/v1`;版本在 **URL** 中,不使用 header 版本。 +- `API-02` 资源名用**复数 kebab-case**:`/brief-cards`、`/media-tasks`、`/industry-packs`。 +- `API-03` 嵌套 ≤ 2 层:`/clients/:clientId/scripts`;更深的归属用 query 过滤。 +- `API-04` 动作优先用子资源表达(`POST /scripts/:id/approve` → 可接受),**禁止**在 URL 用动词拼接业务流(`/doGenerate`)。 +- `API-05` 破坏性变更(删字段 / 改语义 / 改状态码)必须升 `/api/v2`;新增可选字段不升版本。 +- `API-06` 对外契约由 OpenAPI 生成,`pnpm openapi:diff` 检出破坏性变更即阻断(见 [06](06-测试与CI规范.md))。 + +--- + +## 2 请求约定 + +| 项 | 约定 | +| --- | --- | +| 认证 | `Authorization: Bearer `;refresh 走独立端点 | +| 幂等 | 写接口支持 `Idempotency-Key`(24h 内同 key 返回首次结果);**任务提交必带** | +| 内容类型 | `application/json; charset=utf-8`;上传走预签名直传,不经 API 中转 | +| 时间 | ISO 8601 带时区(`2026-11-02T14:30:00+08:00`),**存储统一 UTC** | +| ID | ULID 字符串,26 字符;请求体里的 ID 一律字符串 | +| 字段命名 | JSON body 用 **camelCase**;query 用 **snake_case**(`cursor` / `limit` / `sort` / `next_cursor`,与架构文档示例一致) | +| 未知字段 | 严格模式拒绝(Zod `.strict()`),避免静默丢弃 | + +- `API-07` 所有入参用 Zod schema 校验,Controller 不写手写校验;动态 schema(要素卡)用 Ajv + 白名单。 +- `API-08` 分页:`?cursor=&limit=50`,返回 `next_cursor`;`limit` 默认 50、上限 200。 +- `API-09` 排序:`?sort=-created_at`(`-` 为降序);仅允许白名单字段,非法字段 400。 +- `API-10` 过滤:显式白名单参数;禁止把任意 query 透传给 Prisma。 +- `API-11` 列表**禁止返回大字段**(JSONB 富文本、base64、完整分镜);产物只返回签名 URL。 + +--- + +## 3 响应约定 + +**成功** + +- 单个资源:直接返回对象(不套 `data` 信封)。 +- 列表:`{ "items": [...], "next_cursor": "..." }`;无下一页时 `next_cursor` 为 `null`。 +- 创建:`201` + 资源;异步任务提交:`202` + `{ taskId, estimatedItems, estimatedCost, estimatedSeconds }`。 +- 删除:`204` 无 body。 + +**失败**(统一结构,禁止其他形状) + +```json +{ "code": 40302, "message": "无权访问该客户数据", "details": {}, "trace_id": "01JA..." } +``` + +- `API-12` `message` 面向用户、中文、不含内部实现细节;`details` 放字段级错误;`trace_id` 必填。 +- `API-13` 禁止把堆栈、SQL、密钥、供应商原始报文写进响应。 + +--- + +## 4 错误码表 + +| 段 | 含义 | 常用码 | +| --- | --- | --- | +| `400xx` | 参数校验 | `40001` 参数缺失 / 格式错误 | +| `401xx` | 认证 | `40101` 未登录、`40102` token 过期、`40103` 签名失败 | +| `403xx` | 授权 | `40301` 无权限点、`40302` 数据范围外、`40303` 缺特殊权限、`40304` 违反职责互斥 | +| `404xx` | 不存在 | `40401` 资源不存在 / 已删除 | +| `409xx` | 冲突 | `40901` 版本冲突、`40902` 重复提交、`40903` 状态不允许 | +| `422xx` | 业务规则 | `42201` 业务校验失败、`42202` 合规拦截(禁忌词 / 极限词)、`42203` 成本超预算 | +| `429xx` | 限流 | `42901` 请求过频、`42902` 并发任务超限 | +| `500xx` | 系统 | `50001` 内部错误、`50002` 依赖不可用、`50003` 超时 | + +- `API-14` 新增错误码必须集中登记在 `packages/shared` 的错误码常量,禁止在业务代码里写裸数字。 +- `API-15` 授权类错误**一律 403 且不区分「不存在」与「无权限」的原因**(避免探测他人数据;日志里保留真实原因)。 + +--- + +## 5 鉴权与权限校验链 + +顺序固定,不得跳步: + +``` +JwtGuard → PermissionGuard(权限点)→ SpecialPermissionGuard(特殊权限) + → ScopeGuard(数据范围)→ 业务校验 → Repository(ScopeContext) → 审计埋点 +``` + +- `API-16` 权限点由代码注册(常量),角色是权限点组合(数据化),见技术架构 D-07。 +- `API-17` 特殊权限(导出 / AI 配置 / 审计查看 / 行业包发布 / 跨客户复用授权)必须单独授予,不得打包进普通角色。 +- `API-18` 数据范围解析失败**一律 403**,禁止降级为「全量可见」。 +- `API-19` `/public/review/*`、`/public/share/*` 是**唯一免 JWT** 路由组,必须:HMAC 签名 + 有效期 + 范围(只读 / 可改稿)+ 限流 + 访问留痕;不得在其中暴露任何其他资源。 +- `API-20` 每个端点必须有 3 类权限测试用例(有权限 / 无权限 / 范围外),见 [06](06-测试与CI规范.md)。 + +--- + +## 6 异步任务协议(所有耗时操作统一) + +| 步骤 | 约定 | +| --- | --- | +| 提交 | `POST /xxx-tasks`,带 `Idempotency-Key`,返回 `202` + `{ taskId, estimatedItems, estimatedCost, estimatedSeconds }` | +| 订阅 | `GET /xxx-tasks/{id}/events`(SSE),事件 `item` / `progress` / `done` / `error`,支持 `Last-Event-ID` 续推 | +| 兜底 | `GET /xxx-tasks/{id}` 轮询(前端 SSE 失败 2 次后自动降级) | +| 取消 | `POST /xxx-tasks/{id}/cancel`;仅未开始项取消,已开始项等待结束 | +| 重试 | `POST /xxx-tasks/{id}/retry`;**只重跑失败项**,成功项不得重算成本 | +| 产物 | 一律签名 URL(5–15 分钟),随 `item` / `done` 事件下发 | + +- `API-21` 长耗时操作**禁止**放在同步请求里(> 2s 一律异步)。 +- `API-22` `estimatedCost` 必填;为 0 也要返回(前端展示"预估成本"是 PRD R-06 硬要求)。 +- `API-23` 任务终态写入后事件必须可重放(存事件表或 Redis Stream),断线可恢复。 + +--- + +## 7 上传与下载 + +| 场景 | 约定 | +| --- | --- | +| 上传 | 预签名 PUT 直传对象存储;前端先算 `sha256` 走秒传;> 100 MB 用分片(`listMultipart`) | +| 校验 | 上传完成后服务端 `headObject` 校验大小 / 类型;不一致则任务失败并清理 | +| 下载 | `302` 到签名 URL(5–15 分钟),支持 `Range`(视频预览断点续播) | +| 导出包 | 服务端打包 → `exports` 桶 → 签名 URL,产物保留 3 天 | +| 限制 | 单文件上限读环境变量 `MAX_UPLOAD_MB`,**禁止**代码里写死 | + +- `API-24` 禁止 API 进程中转文件流(内存与带宽不可控)。 +- `API-25` 签名 URL 不得记录到日志(含 query 签名参数)。 + +--- + +## 8 前端路由 + +- `API-26` 路由常量集中 `apps/web/src/routes.ts`,禁止散落字符串路径。 +- `API-27` 路径反映「行业 → 客户」归属:`/clients/:clientId/scripts`、`/clients/:clientId/media/batches/:batchId`。 +- `API-28` 统一布局路由:登录后进入 `AppLayout`(侧边导航 + 面包屑),未登录访问自动跳 `/login` 并带回跳地址。 +- `API-29` 权限守卫组件 ``;无权限渲染 403 页,**不隐藏路由**(避免"菜单有、点进去白屏")。 +- `API-30` 页面级懒加载(`React.lazy` + `Suspense`),首屏包体积纳入性能预算。 +- `API-31` URL 是唯一状态源:筛选 / 分页 / 选中态可分享的必须进 query(`useSearchParams`),禁止只存 Zustand。 +- `API-32` 404 / 403 / 500 三个页面必须存在,且 500 页展示 `trace_id` 供排查。 + +--- + +## 9 契约与兼容 + +- `API-33` OpenAPI 由 NestJS 装饰器生成,提交到仓库,CI 用 `oasdiff` 检测破坏性变更。 +- `API-34` 新增字段必须可选且前端有默认值;删除字段先标记 `deprecated` 一个版本。 +- `API-35` 前端不得依赖未在 OpenAPI 声明的字段。 + +--- + +## 10 检查清单 + +- [ ] URL 复数 kebab-case,版本 `/api/v1`,嵌套 ≤ 2 层 +- [ ] 入参 Zod 校验,未知字段拒绝 +- [ ] 列表走游标分页,不返回大字段 +- [ ] 错误体 `{ code, message, details, trace_id }`,错误码取自常量 +- [ ] 守卫链完整,特殊权限单独校验 +- [ ] 任务提交带 `Idempotency-Key` 与预估成本 +- [ ] 产物只给签名 URL,不打日志 +- [ ] 前端路由常量集中,权限守卫 + 三张错误页齐全 +- [ ] OpenAPI 已更新,`openapi:diff` 通过 diff --git a/docs/工程规范/05-数据库规范.md b/docs/工程规范/05-数据库规范.md new file mode 100644 index 0000000..d936570 --- /dev/null +++ b/docs/工程规范/05-数据库规范.md @@ -0,0 +1,121 @@ +# 05 · 数据库规范 + +| 项 | 内容 | +| --- | --- | +| 文档版本 | V1.0(2026-11-02) | +| 依据 | 技术架构 §5(数据模型)、§8.3 / §8.4(ADR) | +| 适用范围 | `prisma/`、所有 SQL、数据迁移脚本 | +| 强制级别 | 强制 | + +--- + +## 1 命名与主键 + +| 对象 | 规则 | 示例 | +| --- | --- | --- | +| 表 | 复数 snake_case | `media_tasks`、`brief_card_index` | +| 列 | snake_case | `customer_id`、`created_at` | +| 主键 | `id`,**应用侧生成 ULID**,`char(26)` | `01JA8Z...` | +| 外键 | `<单数实体>_id` | `script_id`、`customer_id` | +| 布尔 | `is_` / `has_` 前缀 | `is_archived` | +| 时间 | `_at` 后缀,`timestamptz` | `reviewed_at` | +| 金额 / 成本 | `_cost`、`_amount`,`numeric(18,6)` | `unit_cost` | +| JSON | `_json` 或语义名(`params`、`snapshot`) | `output_params` | +| 索引 | `idx_<表>_<列>`;唯一 `uq_*`;部分 `idx_*_partial` | `idx_media_tasks_customer_created` | + +- `DB-01` **禁止库内生成主键**(不用 `gen_random_uuid()` / `uuidv7()`);主键统一由应用生成 ULID,便于排序与分片。 +- `DB-02` 每张业务表必须含 `created_at`、`updated_at`;需要软删的加 `deleted_at`。 +- `DB-03` Prisma 模型用 PascalCase + `@@map` / `@map` 映射到 snake_case,**禁止**直接在库里用 camelCase 列名。 + +--- + +## 2 客户隔离(最高优先级) + +- `DB-04` 所有业务表 `customer_id NOT NULL` + 外键;同时冗余 `industry_id` 便于按行业聚合。 +- `DB-05` Repository 方法必须接收 `ScopeContext` 并在 `where` 中注入 `customer_id`;**禁止**「先查全量再在内存过滤」(PRD R-04)。 +- `DB-06` 唯一约束必须包含 `customer_id`(如客户内脚本编号唯一),跨客户唯一只允许编码类字段。 +- `DB-07` 跨客户写操作(跨客户复用授权)必须走显式授权表并写审计,**禁止**在业务 SQL 里放宽 `customer_id`。 +- `DB-08` P1 起启用 PG 行级安全(RLS)兜底;启用前必须在 staging 验证「范围外查询返回 0 行」。 + +--- + +## 3 字段与类型 + +| 场景 | 规则 | +| --- | --- | +| 枚举 | 优先字典表 + 数据行;表内保存 `*_snapshot`(名称快照),避免字典变更影响历史 | +| Prisma enum | 仅用于**永不扩展**的封闭集合,且需迁移评审说明 | +| 动态字段 | JSONB + `schema_version`;可检索字段物化到 `brief_card_index`(D-03) | +| 大文本 | `text`;禁止 `varchar(255)` 装富文本 | +| 时长 | 毫秒整数(`duration_ms`),禁止小数秒 | +| 比例 | `numeric(6,4)`;禁止 `float` 存比率 | +| 标签 | 关联表或 `text[]`(仅查询简单场景);**禁止** JSONB 存关联 ID(PRD R-25) | +| 文件 | 只存对象存储 key(`storage_key`)+ 元数据,**禁止存二进制或外链签名 URL** | +| 密码 / 密钥 | 只存哈希 / 密文;AI 供应商密钥用 AES-256-GCM 加密列 | + +- `DB-09` JSONB 上默认不建 GIN 索引;仅在确有按字段查询且 `EXPLAIN` 证明需要时建。 +- `DB-10` 禁止 `SELECT *` 返回含大 JSONB 的列表;查询必须显式 `select`。 + +--- + +## 4 索引与查询 + +- `DB-11` 所有外键列建索引。 +- `DB-12` 列表页统一复合索引:`(customer_id, created_at DESC, id DESC)`;游标分页基于 `(created_at, id)`(技术架构 §5.7)。 +- `DB-13` 唯一约束配合软删用**部分索引**:`WHERE deleted_at IS NULL`。 +- `DB-14` 禁止无条件的 `COUNT(*)` 大表统计;计数走预聚合表或 `EXPLAIN` 验证代价。 +- `DB-15` 禁止 OFFSET 深翻页;分页一律游标。 +- `DB-16` 禁止 N+1:列表查询用 `include`/`select` 显式取关联,或批量 `IN` 查询。 +- `DB-17` 批量写入单批 ≤ 1000 行,超出分片;`createMany` 需注意不触发单条钩子。 +- `DB-18` 慢查询(> 200ms)必须记录到日志并带 `trace_id`,纳入每周巡检。 + +--- + +## 5 迁移(Prisma Migrate) + +- `DB-19` 迁移文件命名 `YYYYMMDDHHmmss_<动词>_<对象>`,如 `20261102153000_add_material_specs`。 +- `DB-20` **禁止修改已合并的迁移文件**;修正用新迁移。 +- `DB-21` 采用 **expand → migrate → contract** 三步;**禁止**同一次发布内「删字段 + 改代码」(技术架构 §4.6)。 +- `DB-22` 生产迁移必须幂等、可重入;大表加列用可空列或带默认值的在线加列,禁止锁表长事务。 +- `DB-23` 迁移前必须备份,并在 staging 演练;回滚脚本随迁移一起提交(`down` 说明)。 +- `DB-24` `prisma db push` 仅限本地探索,**禁止**用于任何共享环境。 +- `DB-25` 迁移涉及数据回填时,回填脚本独立于 schema 迁移,可重复执行。 + +--- + +## 6 事务与并发 + +- `DB-26` 事务边界在 Service 层;跨表写必须同事务。 +- `DB-27` 事务内**禁止外部 IO**(AI 调用、HTTP、对象存储、ffmpeg)。 +- `DB-28` 编号 / 序号类唯一值(脚本编号、批次号)必须用**原子取号**(`UPDATE ... RETURNING` 或序列表 + 行锁),禁止「先查后写」。 +- `DB-29` 状态迁移用条件更新(`WHERE status = 'pending'`)避免并发覆盖;受影响行数为 0 视为冲突并返回 409。 +- `DB-30` 避免长事务(> 5s);批量任务状态更新按批提交。 + +--- + +## 7 删除与保留 + +| 数据 | 策略 | +| --- | --- | +| 业务实体 | 软删 `deleted_at`;被引用的资产**只能停用不能删除**(PRD R-02) | +| 审计 / 账本 / 任务事件 | **append-only**,禁止 UPDATE / DELETE | +| 任务临时产物 | `temp` 桶 30 天自动清理 | +| 导出包 | `exports` 桶 3 天 | +| 归档 | `archive` 桶 ≥ 12 个月 | +| 客户数据删除 | 只按 PRD 约定的保留期与流程执行,**禁止**开发者自行清理 | + +- `DB-31` 硬删只允许:临时文件、失败任务产物、测试数据;且必须有清理任务与日志。 +- `DB-32` 审计表权限:应用账号仅 `INSERT` + `SELECT`,无 `UPDATE` / `DELETE` 授权。 + +--- + +## 8 检查清单 + +- [ ] 表 / 列 snake_case,主键 ULID,`created_at` / `updated_at` 齐全 +- [ ] 业务表有 `customer_id` + 外键 + 冗余 `industry_id` +- [ ] Repository 接收 `ScopeContext`,SQL 带客户维度 +- [ ] 列表查询走复合索引 + 游标分页,无 `SELECT *`、无 N+1 +- [ ] 迁移 expand/contract,未改历史迁移,staging 已演练 +- [ ] 事务内无外部 IO;编号原子取号;状态迁移带条件 +- [ ] 审计 append-only;临时产物有清理策略 +- [ ] 新增 JSONB 字段有 `schema_version` 与检索策略 diff --git a/docs/工程规范/06-测试与CI规范.md b/docs/工程规范/06-测试与CI规范.md new file mode 100644 index 0000000..e6ad7de --- /dev/null +++ b/docs/工程规范/06-测试与CI规范.md @@ -0,0 +1,120 @@ +# 06 · 测试与 CI 规范 + +| 项 | 内容 | +| --- | --- | +| 文档版本 | V1.0(2026-11-02) | +| 依据 | 技术架构 §10.4(测试策略)、§4.6(CI/CD)、§4.1.1(本地环境) | +| 适用范围 | 全仓库 | +| 强制级别 | 强制 | + +--- + +## 1 测试分层 + +| 层级 | 工具 | 范围 | 运行时机 | +| --- | --- | --- | --- | +| 单元 | Vitest | Service、工具函数、状态机、命名 / 表单引擎 | 每次提交(CI + 本地) | +| 集成 | Vitest + Testcontainers(PG 18 / Redis 7.4 / MinIO) | Repository、队列、迁移、事务 | 每次 PR(CI) | +| 契约 | OpenAPI + `oasdiff` | 接口兼容性 | 每次 PR | +| E2E | Playwright | 主链路(登录 → 生成 → 预览 → 勾选 → 导出) | 主干 + 发布前 | +| 性能 | k6 | 列表 / 任务提交 / SSE | 发布前(仅 staging) | +| 视觉回归 | 图片像素比对 | PSD 合成产物 | 改动 `packages/render` 时 | + +- `TST-01` 单测与源码同目录,命名 `.spec.ts`;集成 `*.int.spec.ts`;E2E 放 `e2e/`。 +- `TST-02` 关键包(`shared` / `form-engine` / `render` / `ai-sdk`)行覆盖率 **≥ 70%**;整体基线只升不降。 +- `TST-03` 禁止提交 `.skip` / `.only`;CI 检测到即失败。 +- `TST-04` 禁止依赖执行顺序;每个测试自建数据、自清理(用独立 `customer_id` 前缀)。 + +--- + +## 2 必测清单(缺一项即 Review 不通过) + +| 编号 | 场景 | +| --- | --- | +| `TST-05` | **权限三态**:每个端点至少「有权限 / 无权限 / 范围外」3 条用例 | +| `TST-06` | **客户隔离**:跨客户读取必须 403 或空集,且不得泄漏存在性 | +| `TST-07` | **状态机**:合法迁移 + 非法迁移(返回 409)各一条 | +| `TST-08` | **幂等**:同 `Idempotency-Key` 重复提交只产生一次副作用 | +| `TST-09` | **成本前置**:任务提交未带预估成本 / 超预算时被拒 | +| `TST-10` | **审计**:导出、权限变更、AI 配置变更均落审计 | +| `TST-11` | **降级路径**:抠图失败 → 原图 + `low_quality` 标记;AI 内容违规 → 不重试 | +| `TST-12` | **迁移**:升级后可回滚;expand 阶段旧代码仍可用 | +| `TST-13` | **任务断点**:SSE 断线后 `Last-Event-ID` 可续推 | +| `TST-14` | **命名规则**:非法字符、超长、同名重名各一条(PRD R-01) | + +--- + +## 3 测试数据与环境 + +- `TST-15` 禁止连接生产库 / 生产对象存储;CI 用 Testcontainers 起同版本依赖。 +- `TST-16` 测试数据用 factory 构造,禁止依赖线上导出数据。 +- `TST-17` 测试内置时钟可注入(`clock` 包),禁止 `new Date()` 直接断言时间。 +- `TST-18` AI 调用在测试中一律 mock Provider,**禁止**真实外网调用(防止费用与不稳定)。 +- `TST-19` 涉及文件的测试用内存 / 临时目录,结束后清理。 + +--- + +## 4 CI 流水线 + +``` +install --frozen-lockfile + → format:check → lint --max-warnings=0 → typecheck + → unit test(coverage)→ build + → integration(Testcontainers)→ openapi:diff + → e2e(main / 发布分支)→ 镜像构建 + 漏洞扫描 +``` + +| 阶段 | 阻断条件 | +| --- | --- | +| 安装 | lock 文件与 `package.json` 不一致 | +| 格式 / Lint | 任一错误或警告 | +| 类型 | 任一错误 | +| 单测 | 失败或覆盖率低于基线 | +| 集成 | 失败或超时(单测总时长 ≤ 10 分钟) | +| 契约 | `oasdiff` 检出破坏性变更但未升版本 | +| 构建 | 失败 | +| 安全 | 高危漏洞 > 0 | + +- `TST-20` 流水线并行化按包拆分(Turborepo),但门禁结论必须在聚合 job 上判定。 +- `TST-21` 禁止通过 `--passWithNoTests`、跳过集成测试等方式「修绿」;豁免须技术负责人批准并留 PR 记录。 +- `TST-22` flaky 测试 24 小时内修复或移除并登记,**禁止**重试 3 次掩盖。 + +--- + +## 5 特性开关与灰度 + +| 变量 | 用途 | +| --- | --- | +| `FEATURE_VIDEO` | 视频线开关(未完成不得外露) | +| 其他 `FEATURE_*` | 按里程碑临时开关,上线稳定后删除 | + +- `TST-23` 未完成功能必须藏在开关后,**禁止**半成品直接可见。 +- `TST-24` 开关必须有默认值、有 owner、有清理期限(写进 PR 或配置注释)。 +- `TST-25` 开关组合不得导致数据不一致(关闭后已产生的数据仍可读)。 + +--- + +## 6 发布与回滚 + +``` +staging 部署 → 冒烟(登录 / 生成 / 预览 / 导出) + → 生产迁移(expand)→ 灰度 10% → 观察 30 分钟 → 全量 → 收缩(contract,下个版本) +``` + +- `TST-26` 迁移先行、代码后行;收缩步骤必须在**确认无旧版本流量**后执行。 +- `TST-27` 每次发布必须有回滚预案:代码回滚 + 数据回滚(或前向修复)分别说明。 +- `TST-28` 发布后观察:错误率、P95、队列积压、AI 费用;异常 15 分钟内决策回滚。 +- `TST-29` 发布记录写入 `docs/releases/`(版本、变更、影响面、回滚方式)。 + +--- + +## 7 检查清单 + +- [ ] 新增 / 修改逻辑有对应单测,关键包覆盖率达标 +- [ ] 端点有权限三态用例,客户隔离用例存在 +- [ ] 状态机非法迁移、幂等、成本前置已覆盖 +- [ ] 集成测试用 Testcontainers,无外网依赖 +- [ ] CI 全绿且未使用豁免 +- [ ] 迁移可回滚,staging 已演练 +- [ ] 未完成功能藏开关,开关有清理期限 +- [ ] 发布记录与回滚预案已写 diff --git a/docs/工程规范/07-CodeReview与PR规范.md b/docs/工程规范/07-CodeReview与PR规范.md new file mode 100644 index 0000000..c85ca1a --- /dev/null +++ b/docs/工程规范/07-CodeReview与PR规范.md @@ -0,0 +1,129 @@ +# 07 · Code Review 与 PR 规范 + +| 项 | 内容 | +| --- | --- | +| 文档版本 | V1.0(2026-11-02) | +| 依据 | 技术架构 §10.1(工程约定)、§4.6(CI/CD) | +| 适用范围 | 所有进入 `main` 的变更 | +| 强制级别 | 强制 | + +--- + +## 1 PR 描述模板(缺项即打回) + +```markdown +## 背景 +为什么要改(关联 PRD R-xx / 技术架构章节 / issue) + +## 改动 +做了什么(按文件或模块列 3–6 条,不贴大段 diff) + +## 影响面 +- 受影响模块 / 端点 / 表 / 队列 +- 是否影响存量数据、是否需要回填 +- 是否影响权限、审计、成本 + +## 回滚方式 +代码回滚 / 开关关闭 / 数据回滚,写清具体操作 + +## 验证证据 +- 本地命令与结果(lint / typecheck / test) +- 关键截图或录屏(UI)/ 接口示例(API) +- 迁移在 staging 的执行结果 +``` + +- `PR-01` 标题遵循 Conventional Commits(Squash 后即主干提交)。 +- `PR-02` 变更 ≤ 400 行(不含 lock / 生成代码);超出拆分为多个 PR 并在描述写依赖顺序。 +- `PR-03` 禁止在功能 PR 中混入无关重构、格式化、依赖升级。 +- `PR-04` Draft PR 可用于早期讨论,但**不得**在 CI 未绿时请求 Review。 + +--- + +## 2 Review 分工 + +| 变更类型 | 要求 | +| --- | --- | +| 普通业务代码 | ≥ 1 名同模块 Owner | +| 数据库迁移 / 索引 / 数据回填 | **2 人**(含后端负责人) | +| 权限点 / 角色 / 数据范围 / RLS | **2 人**(含安全或技术负责人) | +| AI 路由 / 供应商 / 密钥 / 成本闸门 | **2 人**(含 AI 工程) | +| 导出 / 分享链接 / 对外公开路由 | **2 人**(含安全) | +| 发布配置 / CI / 基础设施 | **2 人**(含运维) | +| 仅文档 | 1 人(可为作者同组) | + +- `PR-05` 作者不得自我批准;禁止「LGTM」式无内容批准。 +- `PR-06` 首轮 Review 时效 24 小时内;作者负责推动,超过 48 小时升级到模块负责人。 +- `PR-07` Review 意见分「必须改(blocking)/ 建议(non-blocking)」,作者逐条回复;未解决不得合并。 + +--- + +## 3 Review 检查清单(按序检查) + +**正确性** + +- [ ] 逻辑与 PRD 规则一致,边界(空 / 超限 / 并发 / 重试)已处理 +- [ ] 状态机迁移合法,非法路径返回明确错误 +- [ ] 异步任务幂等,失败可重试且不重复计费 + +**数据与隔离** + +- [ ] 所有查询带 `customer_id`,Repository 接收 `ScopeContext` +- [ ] 无跨模块直接 `prisma.*` +- [ ] 迁移 expand/contract,索引与查询匹配,无 `SELECT *` / N+1 + +**安全与合规** + +- [ ] 守卫链完整,特殊权限单独校验 +- [ ] 无密钥 / Token / 签名 URL 进日志或响应 +- [ ] 导出 / 分享 / 公开路由有签名、有效期、限流、留痕 +- [ ] 审计埋点齐全(谁、何时、对什么、改前改后) + +**接口与前端** + +- [ ] 入参 Zod 校验,错误码取自常量 +- [ ] UI 只用 Semi + 统一图标出口,无散落色值 +- [ ] 权限守卫 + 403/404 页;URL 承载可分享状态 + +**质量** + +- [ ] 命名、类型、错误处理符合 [02](02-代码风格.md) +- [ ] 测试覆盖必测清单([06](06-测试与CI规范.md) §2) +- [ ] 无 `eslint-disable` 新增;无 TODO 无主 +- [ ] 文档 / OpenAPI / 变更记录同步更新 + +--- + +## 4 DoD(合并即完成的定义) + +同时满足才可合并: + +1. CI 全绿(含契约与集成),无豁免。 +2. Review 通过,blocking 意见全部解决。 +3. 文档同步:PRD 规则编号、技术架构、OpenAPI、本目录细则(如涉及)。 +4. 迁移可回滚,或在描述说明为何不可回滚与前向修复方案。 +5. 可观测性就位:关键路径有日志、有指标、失败有告警(如适用)。 +6. 影响面与回滚方式已写清,release 记录已补。 + +--- + +## 5 合并与合并后 + +- `PR-08` 统一 **Squash Merge**,提交信息用 PR 标题 + 描述要点。 +- `PR-09` 合并后删除分支;关注 CI 与错误率 15 分钟。 +- `PR-10` 合并后发现缺陷:优先前向修复;影响面大则按回滚预案执行,并补一次复盘(写 `docs/releases/` 或 issue)。 +- `PR-11` 禁止「合并后再补测试 / 再补文档」作为常态;确因紧急修复,须 24 小时内补齐并在 PR 记录追踪链接。 + +--- + +## 6 反模式(Review 直接打回) + +| 反模式 | 原因 | +| --- | --- | +| 巨型 PR(> 800 行) | 无法有效 Review,缺陷率高 | +| 「顺手」重构无关模块 | 混淆影响面,增加回滚难度 | +| 用 `any` / `@ts-ignore` 绕过类型 | 把问题推到运行时 | +| 在 Controller 写业务规则 | 破坏分层,无法复用与测试 | +| 直接改 `status` 字段 | 绕过状态机,产生非法状态 | +| 同步接口里跑长任务 | 违反异步任务协议,超时不可控 | +| 硬编码阈值 / 客户 ID | 违反 PRD R-26 与客户隔离 | +| 无回滚方案的迁移 | 生产事故无法收敛 | diff --git a/docs/工程规范/08-日志与可观测性规范.md b/docs/工程规范/08-日志与可观测性规范.md new file mode 100644 index 0000000..e066a9e --- /dev/null +++ b/docs/工程规范/08-日志与可观测性规范.md @@ -0,0 +1,112 @@ +# 08 · 日志与可观测性规范 + +| 项 | 内容 | +| --- | --- | +| 文档版本 | V1.0(2026-11-02) | +| 依据 | 技术架构 §2.7(可观测性)、§9.7、§5.4.5(审计表) | +| 适用范围 | `apps/api`、`apps/worker`、`apps/web`(错误上报) | +| 强制级别 | 强制 | + +--- + +## 1 日志(Pino,结构化 JSON) + +**必带字段**(每条日志) + +| 字段 | 说明 | +| --- | --- | +| `ts` | ISO 8601 UTC | +| `level` | `error` / `warn` / `info` / `debug` | +| `msg` | 稳定短句(可被聚合),变量放字段里 | +| `trace_id` | 贯穿请求链路;HTTP 入口生成,响应头 `x-trace-id` 回传 | +| `module` | 模块名(`media` / `script` / `identity`…) | +| `action` | 动作(`media.task.submit`、`script.generate`) | + +**按需字段**:`user_id`、`customer_id`、`task_id`、`item_id`、`duration_ms`、`error.code`、`error.stack`(仅后端内部日志)、`retry_count`、`provider`、`model`。 + +- `LOG-01` 禁止 `console.log`;后端用注入的 logger,前端用统一 logger + 错误上报。 +- `LOG-02` `msg` 用英文短句 + 常量,禁止拼业务变量(`"task 123 failed"`),便于聚合统计。 +- `LOG-03` 禁止记录:密码、Token、密钥、Cookie、签名 URL、`Authorization` 头、完整手机号 / 身份证、客户素材原始内容。 +- `LOG-04` 大对象(请求体 / 模型响应)必须先裁剪:≤ 2 KB,且标注 `truncated: true`。 +- `LOG-05` AI 调用日志记录:供应商、模型、任务类型、耗时、token 数、费用、是否降级;**不记录 prompt 全文**(敏感客户信息),需要复现时用引用 ID。 +- `LOG-06` 日志级别语义:`error` = 需人处理;`warn` = 已降级 / 重试;`info` = 业务里程碑与状态变更;`debug` = 仅开发环境默认关闭。 +- `LOG-07` 生产默认 `info`,可按模块动态调级(不重启);禁止长期开 `debug`。 + +--- + +## 2 链路追踪(OpenTelemetry) + +- `LOG-08` HTTP 入口创建 span,`trace_id` 写入响应头、错误体、日志三处,便于用户报错时定位。 +- `LOG-09` 跨队列传递 `traceparent`(BullMQ job data 中携带),worker 侧继续同一 trace。 +- `LOG-10` 外部调用(AI / 存储 / ffmpeg)必须建子 span,记录目标、耗时、结果状态。 +- `LOG-11` 采样率:生产默认 10%,错误与慢请求 100%;采样配置走环境变量。 + +--- + +## 3 审计(与日志严格分离) + +| 维度 | 日志 | 审计 | +| --- | --- | --- | +| 存储 | 日志系统(可轮转、可丢弃) | `audit_logs` 表(append-only) | +| 用途 | 排障 | 追责、合规、回溯「为什么这样」 | +| 内容 | 技术细节 | actor / action / target / before / after / ip / ua / trace_id | +| 修改 | 可删可改 | **仅 INSERT**,任何角色不可改(PRD 9.2) | + +- `LOG-12` 必须审计的操作:登录 / 登出、权限点与角色变更、客户创建与状态变更、导出、分享链接创建与作废、AI 配置变更、行业包发布、字段变更与审批、资产删除 / 停用、跨客户复用授权。 +- `LOG-13` 审计写入与业务写在同一事务内(或 outbox 保证最终一致),**禁止**业务成功但审计丢失。 +- `LOG-14` 审计查询本身也要留痕(谁查了审计)。 + +--- + +## 4 指标(Prometheus 命名) + +命名:`__`,如 `media_task_finished_total`、`queue_depth`、`ai_cost_cny_total`。 + +| 类别 | 必含指标 | +| --- | --- | +| HTTP | 请求量、错误率、P50/P95 延迟(按路由模板,不按原始 URL) | +| 队列 | 各队列深度、等待时长、失败率、重试次数 | +| 业务 | 任务成功率、单条成本、图片有效率、抠图降级率、低质量产出率 | +| AI | 调用次数、失败 / 降级率、token 用量、费用(预算占比) | +| 存储 | 桶容量、上传失败率、签名 URL 下发量 | +| 依赖 | PG 连接数 / 慢查询、Redis 内存 / 命中率 | + +- `LOG-15` 标签基数受控:禁止把 `user_id`、`customer_id`、`task_id` 作为指标标签(用日志 / trace 查)。 +- `LOG-16` 派生指标(汇总 / 均值 / 占比)运行时计算,**不落库**(技术架构 §5.7)。 + +--- + +## 5 告警 + +| 告警 | 阈值(起步,可调) | 处置 | +| --- | --- | --- | +| 接口错误率 | 5 分钟内 > 5% | 查 trace → 定位依赖 → 必要时回滚 | +| 队列积压 | 单队列深度 > 1000 或等待 > 10 分钟 | 扩 worker / 查卡死任务 | +| AI 预算 | 达软限 80% 告警、硬限 100% 阻断 | 降级路由 / 通知负责人 | +| 任务失败率 | 10 分钟内 > 10% | 查供应商 / 依赖 → 切路由 | +| 磁盘 / 存储 | 使用率 > 80% | 清理 temp / 扩容 | +| 证书到期 | < 14 天 | 续签 | +| 抠图降级率 | 单批次 > 30% | 排查模型 / 输入质量 | + +- `LOG-17` 每条告警必须可执行:附 runbook 链接(`docs/runbook/`)与负责人。 +- `LOG-18` 禁止「告警风暴」:同一根因聚合,5 分钟内同一告警只发一次。 + +--- + +## 6 前端可观测 + +- `LOG-19` 前端错误上报必须带:`release` 版本、`trace_id`(若有)、路由、浏览器、是否客户数据脱敏。 +- `LOG-20` 禁止上报用户隐私与客户素材内容;上报前统一脱敏。 +- `LOG-21` 关键交互(生成、预览、勾选、导出)埋点事件名稳定,带 `customer_id` / `batch_id` / `user_id` 三维(PRD 8.1)。 + +--- + +## 7 检查清单 + +- [ ] 日志结构化,必带 `trace_id` / `module` / `action` +- [ ] `msg` 稳定可聚合,变量进字段 +- [ ] 无敏感信息、无签名 URL、无 prompt 全文 +- [ ] 跨队列传递 `traceparent` +- [ ] 审计与日志分离,关键操作全部入 `audit_logs` +- [ ] 新增关键路径有指标与告警 +- [ ] 指标标签基数受控,派生指标不落库 diff --git a/docs/工程规范/09-环境变量与密钥规范.md b/docs/工程规范/09-环境变量与密钥规范.md new file mode 100644 index 0000000..94d3ce2 --- /dev/null +++ b/docs/工程规范/09-环境变量与密钥规范.md @@ -0,0 +1,131 @@ +# 09 · 环境变量与密钥规范 + +| 项 | 内容 | +| --- | --- | +| 文档版本 | V1.0(2026-11-02) | +| 依据 | 技术架构 附录 C(关键环境变量)、§2.8(安全)、§4.1 / §4.4 | +| 适用范围 | 所有环境(dev / staging / prod)、CI、本地开发 | +| 强制级别 | 强制 | + +--- + +## 1 基本规则 + +- `ENV-01` 只有 `.env.example` 入库;`.env`、`.env.local`、`.env.*.local` 一律不入库(`.gitignore` 已覆盖)。 +- `ENV-02` 变量名 `UPPER_SNAKE_CASE`,按前缀分组;新增变量必须同时更新 `.env.example`、本文附录、部署配置三处。 +- `ENV-03` 应用启动时用 Zod 校验环境变量,**缺失或非法直接 fail fast**,禁止带默认值「悄悄跑起来」。 +- `ENV-04` 代码中只能通过集中配置模块读取(`config/env.ts`),**禁止**到处 `process.env.XXX`。 +- `ENV-05` 禁止在仓库、PR、聊天工具、日志中传递真实密钥;示例值用 `xxx` / `changeme`。 +- `ENV-06` 三个环境密钥**完全独立**:dev / staging / prod 不得复用同一套数据库、Redis、S3、JWT、AI 密钥。 + +--- + +## 2 变量清单(前缀分组) + +### 运行与数据库 + +| 变量 | 说明 | 示例 | +| --- | --- | --- | +| `NODE_ENV` | 运行环境 | `development` / `production` | +| `DATABASE_URL` | PG 连接串 | `postgresql://user:pass@host:5432/lingxi` | +| `REDIS_URL` | Redis 连接串 | `redis://host:6379/0` | +| `QUEUE` | worker 订阅队列名 | `image:render` | + +### 对象存储(S3 协议) + +| 变量 | 说明 | +| --- | --- | +| `S3_ENDPOINT` | MinIO / 云 OSS 端点 | +| `S3_ACCESS_KEY` / `S3_SECRET_KEY` | 访问密钥(按环境独立) | +| `S3_BUCKET_ASSETS` / `S3_BUCKET_RENDERS` / `S3_BUCKET_TEMP` | 资产 / 产物 / 临时桶 | +| `S3_BUCKET_ARCHIVE` / `S3_BUCKET_UPLOADS` / `S3_BUCKET_EXPORTS` | 归档 / 上传 / 导出桶 | + +### 认证与安全 + +| 变量 | 说明 | +| --- | --- | +| `JWT_SECRET` / `JWT_REFRESH_SECRET` | 长度 ≥ 32,按环境独立 | +| `ENCRYPTION_KEY` | AES-256-GCM 主密钥(加密 AI 供应商密钥等) | +| `PUBLIC_LINK_HMAC_SECRET` | 免登录审核 / 分享链接签名密钥 | + +### AI 接入 + +| 变量 | 说明 | +| --- | --- | +| `AI_PROVIDER__API_KEY` | 按供应商分(如 `AI_PROVIDER_JIMENG_API_KEY`) | +| `AI_PROVIDER__BASE_URL` | 自定义端点(可选) | +| `AI_ROUTE_REFRESH_MS` | 路由表缓存刷新间隔 | + +> 供应商 API Key 以加密形式存库(`ai_configs`)时,环境变量只用于**首次注入 / 灾备**;运行时读取库内配置,避免多处真相。 + +### 媒体处理 + +| 变量 | 说明 | +| --- | --- | +| `FFMPEG_PATH` / `FFPROBE_PATH` | 二进制路径(镜像内固定) | +| `MAX_UPLOAD_MB` | 单文件上限 | +| `WORKER_CONCURRENCY` | 单 worker 并发 | + +### 成本与开关 + +| 变量 | 说明 | +| --- | --- | +| `COST_SOFT_LIMIT` / `COST_HARD_LIMIT` | 预算软 / 硬限(默认 0.8 / 1.0) | +| `FEATURE_VIDEO` | 视频线开关 | +| `OTEL_EXPORTER_OTLP_ENDPOINT` | 链路追踪导出端点 | + +--- + +## 3 密钥管理 + +| 项 | 规则 | +| --- | --- | +| 存储 | 生产用云 KMS / Vault / Secrets Manager;**禁止**明文写进 compose 或镜像 | +| 注入 | CI/CD 注入环境变量;本地用 `.env.local`(不入库) | +| 加密 | AI 供应商密钥等入库字段用 AES-256-GCM,密钥不落同一库 | +| 权限 | 应用 DB 账号非 superuser;MinIO 按桶最小权限;CI 只读必要范围 | +| 轮换 | JWT / HMAC / 加密密钥至少每 6 个月或人员变动时轮换;轮换需兼容期(新旧并存) | +| 审计 | 密钥变更记审计(谁、何时、哪个 key 的版本号,**不记值**) | + +- `ENV-07` 前端只能暴露 `VITE_PUBLIC_*` 前缀变量;构建产物必须扫描确认无服务端密钥。 +- `ENV-08` 禁止把密钥拼进 URL / query / 日志 / 异常信息。 +- `ENV-09` 本地开发连的 MinIO / PG 凭据只能是 dev 值,且与生产不一致。 + +--- + +## 4 泄露处置(30 分钟内) + +1. **立即轮换**对应密钥(先轮换再排查,不等待确认)。 +2. 撤销相关 token / session,作废签名链接。 +3. 检查访问日志与 `ai_usage`,评估是否被滥用(尤其 AI 费用)。 +4. 用 `git filter-repo` 清理历史并强制全员重新 clone(见 [01](01-Git规范.md) `GIT-20`)。 +5. 24 小时内出复盘:泄露路径、影响面、改进项(通常补 CI 扫描 / pre-commit)。 + +- `ENV-10` 泄露事件必须记录在案,**禁止**「悄悄换掉当没发生」。 + +--- + +## 5 本地开发 + +```bash +cp .env.example .env.local # 填 dev 值(不得填生产值) +pnpm compose:up # PG 18 / Redis 7.4 / MinIO +pnpm db:migrate && pnpm db:seed +pnpm dev +``` + +- `ENV-11` 禁止 `docker compose down -v` 清库;重置数据用 `pnpm db:reset`。 +- `ENV-12` 本地不得连生产数据库 / 生产对象存储 / 生产 AI 供应商(费用与数据风险)。 +- `ENV-13` 开发机 Node 版本由 `.nvmrc` 固定,`package.json` 声明 `engines`,`preinstall` 用 `only-allow pnpm` 拦截其他包管理器。 + +--- + +## 6 检查清单 + +- [ ] 新增变量已写 `.env.example` 与本文 +- [ ] 启动校验覆盖新变量,缺失即失败 +- [ ] 未在代码中散读 `process.env` +- [ ] 三环境密钥独立,未复用 +- [ ] 无密钥进日志 / URL / 前端 bundle +- [ ] 生产密钥来自 KMS / Secrets Manager +- [ ] 轮换与泄露处置流程已知晓 diff --git a/docs/工程规范/10-资产命名与PSD图层规范.md b/docs/工程规范/10-资产命名与PSD图层规范.md new file mode 100644 index 0000000..6868975 --- /dev/null +++ b/docs/工程规范/10-资产命名与PSD图层规范.md @@ -0,0 +1,137 @@ +# 10 · 资产命名与 PSD 图层规范 + +| 项 | 内容 | +| --- | --- | +| 文档版本 | V1.0(2026-11-02) | +| 依据 | PRD §5.3(资产)、§5.2(素材线)、R-01 / R-19 / R-25 / R-29 / R-30;技术架构 §7.3 / §7.5 / §8.11 | +| 适用范围 | 资产上传、模板制作、批次命名、成片导出、对象存储 | +| 强制级别 | 强制(`PSD-*` 为图片线硬前提,不满足即不可入库) | + +--- + +## 1 归属与层级 + +- `AST-01` 全部资产必须挂在「**行业 → 客户**」两级目录下;行业是客户的业务属性,**客户不可跨行业移动**(PRD R-20)。 +- `AST-02` 资产三级:**行业级(公共)→ 客户级(私有)→ 批次级(一次性)**;同名时客户级覆盖行业级。 +- `AST-03` 跨客户复用必须显式授权并留痕,**禁止**默认共享(PRD 5.3.2)。 +- `AST-04` 15 类资产固定为:行业包、脚本模板、视频方向、已入库脚本、图片模板、Logo、产品位置、警示语、人物池、场景池、光影/色调预设、BGM/音效、提示词模板、要素卡模板/卖点库、成片。新增类型须先改 PRD 与架构。 +- `AST-05` 资产状态:草稿 → 启用 → 停用;**被引用的资产只能停用不能删除**(R-02);模板与提示词修改产生新版本并记录被引用的版本号(R-03)。 + +--- + +## 2 命名通用规则 + +| 项 | 规则 | +| --- | --- | +| 允许字符 | 中文、字母、数字、下划线 `_`、短横线 `-` | +| 禁止字符 | 空格、`\ / : * ? " < > |`、`# % & { } $ ! ' @ + =`、控制字符 | +| 长度 | ≤ 100 字符(含扩展名) | +| 占位符 | `{行业}` `{客户}` `{视频方向}` `{日期}` `{序号}`(PRD R-01) | +| 默认格式 | `{行业}-{客户}-{视频方向}-{序号}` | +| 重名 | 自动追加序号(`-02`、`-03`),**禁止覆盖** | +| 大小写 | 语义名大小写敏感;扩展名统一小写 | + +- `AST-06` 占位符由服务端在**批次创建时快照**,之后修改命名规则不影响历史批次。 +- `AST-07` 文件名中的客户名使用**客户简称**(必填字段),不使用全称,避免超长与敏感信息。 + +--- + +## 3 各类编号与成片命名 + +| 对象 | 规则 | 示例 | +| --- | --- | --- | +| 脚本编号 | `{客户编码}-YYMMDD + 当日序号`,**过审入库时原子取号**;草稿不占号;一经分配不重用、不可改(R-21) | `LX001-261102-007` | +| 素材批次号 | `{YYMMDD}{3 位当日序号}` | `261102003` | +| 图片成片 | `{行业}-{客户}-{脚本方向}-{日期}-{序号}-{规格}` | `电商-某服饰-通勤穿搭-261102-01-9x16` | +| 视频成片 | `{行业}-{客户}-{视频方向}-{序号}` | `电商-某服饰-达人到店-03` | +| 导出目录 | `客户 / 交付日期 / 类型 / 批次` | `某服饰/2026-11-02/图片/261102003` | + +- `AST-08` 规格后缀用 `宽x高` 或平台别名(`9x16`、`1x1`),由字典统一,**禁止**自由书写。 +- `AST-09` 成片命名与脚本编号必须可对账(交付清单同 key,PRD 5.5)。 +- `AST-10` 对象存储 key 与展示名分离:`storage_key = assets/{industry_id}/{customer_id}/{asset_id}/v{version}/{filename}`;展示名可改,key 不可改。 + +--- + +## 4 PSD 模板与图层约定(图片线核心) + +### 4.1 图层前缀(唯一合法语义声明方式) + +| 前缀 | 语义 | 数量要求 | +| --- | --- | --- | +| `image_replacement_` | 商品图替换位(主体) | ≥ 1 | +| `clipping_mask_` | 剪贴容器 / 裁切区域 | 0..n | +| `text_replacement_` | 文字替换位(可配置字号 / 对齐 / 颜色) | 0..n | + +- `PSD-01` 命名结构:`<前缀><语义名>`,如 `image_replacement_main`、`text_replacement_price`;**前缀全小写且大小写敏感**。 +- `PSD-02` 同一模板内语义名唯一;重复命名视为非法。 +- `PSD-03` **无命名图层 / 解析失败 / 缺少 `image_replacement_` → 模板不得入库**,返回明确错误并提示设计师修正。 +- `PSD-04` 禁止用图层组名冒充命名图层;前缀必须落在**实际图层**上。 +- `PSD-05` 剪贴蒙版必须挂在 `clipping_mask_` 容器下,商品图替换后仍保持裁切关系。 +- `PSD-06` 新增图层前缀类型须先更新技术架构 §7.3 与本文,**禁止**在代码里私自识别新前缀。 +- `PSD-07` 模板入库时校验:图层语义完整性、画布尺寸与目标规格比例、字体是否在白名单、是否存在隐藏图层(隐藏图层默认忽略并记录)。 +- `PSD-08` 字体:镜像内置中文字体,启动自检;模板引用的字体不在白名单 → 拒绝入库(避免豆腐块,风险 R-T-18)。 + +### 4.2 抠图与合成 + +- `PSD-09` 抠图失败必须降级为原图并标 `low_quality`(原因码 `matting_fallback`),**默认不勾选**(R-30)。 +- `PSD-10` 合成结果必须过质量校验:安全区、Logo 完整性、主体不变形、警示语四项;未过则标 `low_quality` 并记录原因码。 +- `PSD-11` 视觉回归:`packages/render` 改动需跑像素级比对,阈值与基线图入库管理。 + +--- + +## 5 规格矩阵(字典化,禁止硬编码) + +| 平台 | 比例 | 分辨率 | +| --- | --- | --- | +| 抖音 | 9:16 | 1080×1920 | +| 小红书 | 3:4 | 1080×1440 | +| 朋友圈 | 1:1 / 4:5 | 1080×1080 / 1080×1350 | +| 横版 | 16:9 | 1920×1080 | +| 电商主图 | 1:1 | 800×800 / 1200×1200 | + +- `AST-11` 规格来自字典表,前后端与任务判定读同一来源(R-26);新增规格改字典不发版。 +- `AST-12` 素材为主对象、规格为属性:**同源多规格不各建素材**,用 `material_specs` 承载(R-25);素材与脚本多对多用关联表,**禁止 JSON 列存 ID**。 + +--- + +## 6 输出编码与存储 + +| 类型 | 参数 | +| --- | --- | +| 视频 | MP4 / H.264 / AAC / 1080×1920 / 30fps / `+faststart` | +| 图片 | JPG(质量 92)或 PNG;导出质量 ≥ 90 | +| 兜底 | 超限降质必须标 `low_quality`(原因码 `downscaled`),不得静默 | + +| 桶 | 内容 | 保留 | +| --- | --- | --- | +| `assets` | 长期资产 | 永久(版本化) | +| `renders` | 生成产物 | 按客户策略 | +| `temp` | 任务中间文件 | 30 天 | +| `archive` | 归档 | ≥ 12 个月 | +| `uploads` | 原始上传 | 按资产生命周期 | +| `exports` | 打包导出 | 3 天 | + +- `AST-13` 下载一律短时效签名 URL(5–15 分钟),**禁止**公开读权限、禁止把签名 URL 存库。 +- `AST-14` 临时目录按任务隔离:`/tmp/task/{taskItemId}`,任务结束清理;失败任务保留 1 天便于排查后清理。 + +--- + +## 7 低质量原因码(统一枚举) + +`matting_fallback`(抠图降级)· `missing_font`(缺字体)· `downscaled`(超限降质)· `logo_incomplete`(Logo 不完整)· `warning_missing`(警示语缺失)· `safe_area_violated`(安全区越界)· `subject_distorted`(主体变形) + +- `AST-15` 原因码只增不改语义;前端与看板按码聚合,**禁止**在文案里写自由文本。 +- `AST-16` 低质量条目计入质量看板与指标(PRD 8.1),可被人工复核转为正常,但**必须留痕**。 + +--- + +## 8 检查清单(模板入库前) + +- [ ] PSD 至少 1 个 `image_replacement_`,剪贴关系正确 +- [ ] 所有可替换位均有合法前缀,语义名唯一 +- [ ] 画布比例与目标规格匹配 +- [ ] 字体在白名单内,无隐藏图层未说明 +- [ ] 模板归属「行业 → 客户」正确,版本号已生成 +- [ ] 命名符合 §2,无非法字符,长度 ≤ 100 +- [ ] 低质量降级路径可触发,原因码已定义 +- [ ] 产物编码参数符合 §6,签名 URL 生效 diff --git a/docs/工程规范/README.md b/docs/工程规范/README.md new file mode 100644 index 0000000..3dbc12d --- /dev/null +++ b/docs/工程规范/README.md @@ -0,0 +1,43 @@ +# 工程规范索引 + +> 本目录是 [`AGENTS.md`](../../AGENTS.md) 的下游细则。**总纲只写红线与索引,细则在这里展开**。 +> 阅读顺序:先读总纲 §3 硬红线,再按任务查对应细则。 + +| 编号 | 文档 | 覆盖范围 | 强制级别 | +| --- | --- | --- | --- | +| 01 | [Git 规范](01-Git规范.md) | 分支模型、提交信息、合并与发布、Tag、大文件 | 强制 | +| 02 | [代码风格](02-代码风格.md) | 命名、类型、错误处理、异步、React、注释、导入顺序 | 强制 | +| 03 | [格式化与 Lint](03-格式化与Lint.md) | Prettier / ESLint 配置、钩子、CI 门禁、豁免流程 | 强制 | +| 04 | [API 与路由规范](04-API与路由规范.md) | REST、版本、错误码、鉴权链、任务协议、上传下载、前端路由 | 强制 | +| 05 | [数据库规范](05-数据库规范.md) | 命名、主键、客户隔离、索引、迁移、事务、保留期 | 强制 | +| 06 | [测试与 CI 规范](06-测试与CI规范.md) | 分层测试、必测清单、门禁、发布与回滚 | 强制 | +| 07 | [Code Review 与 PR 规范](07-CodeReview与PR规范.md) | PR 模板、双人场景、Review 清单、DoD | 强制 | +| 08 | [日志与可观测性规范](08-日志与可观测性规范.md) | 日志字段、trace、审计、指标、告警 | 强制 | +| 09 | [环境变量与密钥规范](09-环境变量与密钥规范.md) | env 命名、校验、密钥管理、轮换、泄露处置 | 强制 | +| 10 | [资产命名与 PSD 图层规范](10-资产命名与PSD图层规范.md) | 资产归属、命名占位符、图层前缀、规格矩阵、低质量标记 | 强制 | + +## 规范文档自身的约定 + +| 项 | 约定 | +| --- | --- | +| 编写语言 | 中文说明 + 英文标识符 / 命令 / 路径 | +| 结构 | 作用与边界 → 规则(表格 / 编号)→ 检查清单 → 违规示例 | +| 变更 | 修改规则须单独 PR,说明「为什么改」+「存量代码如何处理」 | +| 版本 | `V<主>.<次>`;主版本 = 破坏性(存量代码需整改),次版本 = 补充 | +| 冲突 | 与本目录细则冲突时以技术架构为准;技术架构未覆盖以 PRD 为准 | +| 有效性 | 规则必须可被检查(lint / CI / review 清单 / 脚本),否则降为「建议」 | +| 废止 | 删除规则必须在 PR 描述里写清理计划,禁止直接删条目 | + +## 与总纲的关系 + +- 总纲 `AGENTS.md` §3 的 14 条硬红线**不得在本目录被弱化**,只能细化。 +- 本目录新增的「强制」规则,须在总纲 §9 索引同步登记。 +- 规则编号规则:`<文档号>-<序号>`,如 `DB-07`、`API-03`,便于代码注释与 Review 引用。 + +## 当前状态说明(2026-11-02) + +仓库处于**文档阶段**,尚无代码。本目录是**建仓前置规范**:代码落地时必须按此执行,不允许「先写代码、后补规范」。 + +待代码落地后需要补充/校准的配置类文件(届时以实际配置为准,规范条目不变): + +`.editorconfig` · `.nvmrc` · `.npmrc` · `prettier.config.mjs` · `eslint.config.mjs` · `commitlint.config.js` · `.lintstagedrc` · `turbo.json` · `docker-compose.dev.yaml` · `.env.example`