灵犀 · 信息流素材生产平台 技术架构设计
| 项 |
内容 |
| 文档版本 |
V0.4(图片线按 PSD 套版 + 抠图合成落地) |
| 文档状态 |
待评审 |
| 编写日期 |
2026-11-02 |
| 需求基线 |
docs/PRD/灵犀-信息流广告素材一站式生产平台PRD.md V1.6 |
| 关联文档 |
docs/立项建议/灵犀_信息流素材生产平台_立项建议书_V0.1.md(论证)、docs/PRD/灵犀_信息流素材生产平台_PRD_V0.3.md(同事基线) |
| 适用对象 |
研发(前端 / 后端 / 测试 / 运维)、AI 工程、技术负责人、产品(用于确认技术边界) |
| 文档范围 |
技术栈选型、分层与模块、部署拓扑、数据模型、接口设计、关键链路、关键决策、非功能落地、交付路径、风险与未决 |
| 不包含 |
排期承诺与人力预算的最终审批、AI 模型的训练与微调方案、投放平台对接、代码实现细节 |
一句话:这不是"用什么框架"的清单,而是用一套可维护的模块化单体 + 异步任务管线,把 PRD 里的 15 类资产、两条素材生产线、行业包与 schema 动态表单、客户级数据隔离、全量审计与成本可见性,落在公有云上、让 2–4 个研发能在 3 个月内跑通 M0–M1。
0 阅读说明
0.1 这份文档回答什么
- 选什么:前端、后端、数据库、队列、对象存储、媒体处理、AI 接入、观测、部署方式,以及为什么。
- 怎么排:进程与模块怎么划分、一次图片生产与一次视频生产在系统里怎么流动、数据写在哪。
- 怎么兜:PRD 的性能、隔离、审计、成本、合规要求,分别由哪个技术机制承接。
- 不做什么:哪些架构花样(微服务、K8s、自建推理、通用低代码)本期明确不做,以及什么条件下再谈。
0.2 结论摘要(先看这张表)
| 层 |
选型 |
一句话理由 |
| 前端 |
React 19 + TypeScript 5 + Vite 6 + Semi Design 2.x(@douyinfe/semi-ui-19) |
后台型产品,表单/表格/权限组件密度高;Semi 中后台组件齐全、设计语言贴合运营后台 |
| 图标 |
Remix Icon 4.x(@remixicon/react) |
唯一图标库,线性风格统一;禁止其他图标源(见 2.2.1) |
| 后端 |
NestJS 11(Fastify 适配器)+ TypeScript |
模块化边界、DI、守卫/拦截器天然对应"权限点 + 审计 + 数据范围"三件套 |
| 数据库 |
PostgreSQL 18(自建,容器镜像 / 云主机,开发机 Docker) |
JSONB 承载动态表单值、强事务、行级审计;自建可完全掌控版本、参数、扩展与备份策略;18 的异步 I/O 与虚拟生成列降低大表扫描与冗余列维护成本 |
| 缓存 / 队列 |
Redis 7.4 + BullMQ 5 |
一套 Redis 同时做缓存、分布式锁、限流与任务队列,M0–M2 足够 |
| 对象存储 |
S3 兼容(M0 自建 MinIO,可切云 OSS/COS) |
成片与中间产物按"对象 + 签名 URL"治理;S3 接口避免绑死厂商 |
| 媒体处理 |
Node 24(24.15.0)+ ffmpeg 7 + sharp(CPU worker) |
日产 < 500 条时,拼接/转码/缩略图用 CPU 足够;不引入 Python 服务 |
| AI 接入 |
自建 Provider 适配层(HTTP/异步任务 + Webhook 轮询) |
多供应商并存、按用途路由、故障切换、逐次记账,都在这一层收口 |
| 部署 |
单地域公有云,Docker Compose 起步(预留 K8s 触发条件) |
日均 < 500 条不需要 K8s;先把可观测与备份做对 |
| 观测 |
OpenTelemetry + Prometheus + Grafana + Loki + Sentry |
任务链路跨 API/Worker,必须能从"一条任务"追到"一次模型调用" |
| 文档与接口 |
OpenAPI 3.1 + Zod 单一真源 |
前后端共享类型,减少"字段对不上"的返工 |
0.3 一页架构图
0.4 阅读路径
- 只想看结论:读 0.2 与第 2 章(选型)→ 第 8 章(关键决策)。
- 要评审技术方案:第 1 章(目标与约束)→ 3 章(架构)→ 5 章(数据模型)→ 7 章(关键链路)→ 8 章(决策取舍)。
- 要排期与开工:第 4 章(部署)→ 10 章(交付路径与里程碑)→ 附录 B(PRD 追溯矩阵)。
- 要评估风险:第 11 章(技术风险与未决)。
0.5 与 PRD 的关系
| PRD 章节 |
本文承接方式 |
| 5.1 脚本模块 |
5.4 数据模型(要素卡 / 模板 / 脚本版本)+ 6.4 接口 + 7.2 时序 |
| 5.2 素材模块 |
5.4(任务 / 产物 / 分镜)+ 5.3 队列组 + 7.3 / 7.4 时序 |
| 5.3 资产模块 |
5.4(资产 / 版本 / 引用关系)+ 5.6 动态字段 + 7.5 归档 |
| 5.4 平台管理 |
5.4(权限 / 行业包 / 表单 schema)+ 6.2 鉴权 + 7.1 / 7.6 时序 |
| 5.5 导出与交付 |
6.7 导出接口 + 7.5 时序 |
| 6 关键规则 R-01~R-20 |
8 关键决策 + 5.4 约束(如资产删除保护用外键 + 应用校验双层) |
| 9 非功能与合规 |
9 非功能落地(逐条给指标拆解与实现机制) |
| 10 待确认 Q-01~Q-21 |
11.2 技术侧待确认 QT-01~QT-14(只列技术相关的) |
1 架构目标与约束
1.1 必须被架构承接的 14 条硬需求
下面每一条都不是"功能",而是会改变架构形状的约束。评审时逐条对齐,避免"PRD 写了、技术没落"。
| # |
PRD 出处 |
硬要求 |
架构应对 |
| 1 |
5.4.2、R-20 |
行业 → 客户两级目录是全部资产的根节点,客户不可跨行业移动 |
所有业务表强制带 customer_id,industry_id 由客户派生并冗余;外键与唯一约束在库层兜底 |
| 2 |
R-04、9.2 |
客户级数据隔离:任何查询必须带客户维度,禁止"先查全量再过滤" |
Repository 层强制注入数据范围片段;CI 静态检查禁止裸查询;P1 叠加 PG 行级安全(RLS) |
| 3 |
5.4.3 |
仅内置超管,其余角色可配置(权限点 + 数据范围 + 特殊权限 + SoD) |
权限点在代码中声明式注册(装饰器 + 常量表),角色为数据;鉴权 = 守卫(功能)+ 范围解析器(数据)+ 特殊权限校验 |
| 4 |
5.4.7 / 5.4.8 |
行业包 9 槽位 + 要素卡 schema 动态渲染,换行业不发版 |
schema 以 JSON Schema 形式版本化存储;后端 Ajv 校验;前端轻量渲染器;可检索字段物化到索引表 |
| 5 |
5.1.1、R-11 / R-14 |
已发布字段只可弃用不可改名;历史要素卡按当时 schema 解释 |
字段 key 不可变、版本快照不可变;要素卡存 schema_version_id,读时按该版本解释 |
| 6 |
5.2.2 |
视频逐镜生成、单镜重试、拼接包装 |
任务拆到"镜头(shot)"粒度,每镜独立子任务与幂等键;拼接是独立任务,可重入 |
| 7 |
5.2.2、5.2.3 |
异步、进度可见、失败重试、中断可恢复(100 张到 87 张不必从头) |
任务头 + 任务项两级表;每项独立状态机;队列重入 + 幂等键 + 断点续跑 |
| 8 |
5.2.2、5.5 |
在线预览(首帧 ≤ 2s、断点续播)、命名下载、导出即归档 |
成片转 H.264 faststart MP4 + 签名 URL + Range 请求;缩略图预生成;导出为服务端打包 zip |
| 9 |
5.4.4、R-06 |
多供应商、按用途路由、额度与预算、失败切换、成本看板 |
AI 适配层统一收口;模型/路由/额度全部数据化;每次调用落 ai_usage 账本 |
| 10 |
5.4.6、9.2 |
审计只读、不可修改、≥ 12 个月、可检索 |
审计表 append-only(禁 UPDATE/DELETE 权限)+ 按月分区 + 归档到对象存储 |
| 11 |
5.4.5、R-10 |
字典变更只影响新数据,历史保留快照 |
字典项带版本;业务记录存字典快照值而非仅外键 |
| 12 |
5.3.3、R-02 |
被引用资产禁止删除、版本可回溯、引用关系双向可查 |
资产表 + 版本表 + 引用关系表;删除前外键检查 + 应用层提示;软删除 |
| 13 |
9.1 |
列表 ≤ 2s、脚本 ≤ 30s、图片 ≤ 10s、50 张预览不卡 |
查询走覆盖索引 + 分页游标;生成走异步;缩略图独立小图;前端虚拟滚动 |
| 14 |
9.3、R-09 |
AI 生成内容显式 + 隐式标识、肖像授权、分行业合规 |
合成阶段写入可见角标与 MP4 元数据;人物池存授权材料与到期时间;合规规则集数据化 |
1.2 规模假设与容量口径
用户给定:日均成片 < 500 条、全部走外部 AI API、无自建 GPU、公有云部署。以下推算是选型与规格的依据,评审时如有出入按比例调整。
| 维度 |
假设 |
推算 |
影响 |
| 用户规模 |
30–100 人,峰值在线 ≤ 60 |
API 2 实例足够(QPS < 50) |
不需要 K8s / 服务网格 |
| 成片量 |
图片 + 视频合计 < 500 条/日,峰值批次 200 条 |
任务项 < 5000/日 |
单 Redis + 4 个队列组足够 |
| 视频并发 |
单条 15s ≈ 4–6 镜;单镜图生视频 60–180s |
500 条 × 5 镜 × 90s ≈ 62.5 模型小时/日,均值 3 并发、峰值 12–15 |
Worker 并发 20,按供应商限流分池 |
| 图片合成 |
单张 ≤ 10s(sharp + 模板合成) |
2000 张/日 ≈ 5.6 小时单核 |
2 台 8C worker 足够 |
| 存储 |
视频 1080×1920/15s ≈ 6–12 MB;中间产物约成片的 3–5 倍 |
成片 5 GB/日 ≈ 1.8 TB/年;含中间产物 ≈ 8–10 TB/年 |
对象存储分层 + 中间产物 30 天生命周期 |
| 数据库 |
任务项 + 审计 + 成本账是主要增长 |
首年 < 200 GB |
200 GB ESSD 数据盘 + 按月分区,足够 |
| 出网 |
AI 调用全部出公网;下载走公网 |
峰值出网 100 Mbps 量级 |
单地域 + 弹性带宽即可,暂不做 CDN |
| 成本 |
模型调用是主要变动成本 |
单条视频 5 镜 × 图生视频 + 1 次 TTS |
必须逐任务记账,否则无法报价 |
1.3 架构原则
- 模块化单体优先,不等于"一个文件":按业务域切模块(Nest 模块 + 独立 Prisma 访问层),模块间只走服务接口与事件,不共享表写权限。微服务只在出现"独立扩容 / 独立团队 / 独立发布"三个信号之一时才拆。
- 异步是一等公民:凡是超过 2 秒、会调外部模型、会产生文件的操作,一律"提交 → 任务 ID → 进度通道 → 结果",前端不阻塞等待。
- 配置驱动,代码分支最后:行业差异、字典、规格、路由、提示词、权限点全部数据化;新增行业不改代码路径。
- 数据隔离在数据层强制:应用层注入范围之外,库层再加一道(外键 + 强制
customer_id NOT NULL + P1 RLS)。
- 每一分钱可追溯:每次模型调用都写账本(谁、哪个任务、哪个模型、多少 token/秒、多少钱),成本看板从账本聚合,不从日志猜。
- 可恢复优于可重试:任务项级断点 + 幂等键,让"跑到 87 张失败"能从 88 张继续,而不是重跑 100 张。
- 观测先于优化:接口耗时、队列积压、模型成功率、单条成本四个指标从 M0 就埋,否则性能与成本问题无法定位。
- 数据层自管,版本统一:数据库与缓存不自建云托管服务,PostgreSQL 18 与 Redis 7.4 统一用容器镜像部署(开发机 Docker Compose、生产云主机),大版本、参数、扩展、备份策略都由我们掌握;外部只依赖 AI 模型 API。这样本地与生产的差异只剩「规格」和「数据」,不剩「版本」。
1.4 明确不做(以及什么条件下再谈)
| 不做 |
原因 |
重评条件 |
| 微服务拆分 |
日均 < 500 条、团队 < 10 人,拆分带来的是运维成本而非收益 |
单个模块需要独立扩容 3 倍以上,或出现独立团队独立发布 |
| Kubernetes |
Compose + 2–4 台机器足够,K8s 的学习与运维成本高于收益 |
Worker 峰值需要 10 台以上弹性,或多环境多租户隔离要求 |
| 自建模型推理 / GPU |
生成能力是公共品,采购优于自建(见 PRD 附录 B 第 1 条) |
单月 API 费用超过自建 TCO,或出现无法采购的模型能力 |
| 通用低代码 / 流程引擎 |
PRD 5.4.8 明确"受控设计器",只解决要素卡字段 |
出现第二个需要动态表单的业务域 |
| 实时协同编辑 |
内容以"生成 + 审核"为主,不是多人同文档编辑 |
审核环节出现明确的多人在线编辑诉求 |
| CDN |
下载量 < 100 GB/日,源站带宽足够 |
出网带宽成本超过对象存储费用,或出现跨地域访问 |
| 跨地域多活 |
单地域 + 每日备份 + 可接受 RTO 4 小时 |
业务要求 RTO < 1 小时或强合规要求异地双活 |
| 云托管数据库(RDS / 云 Redis) |
需要完全掌控 PG 18 新特性、扩展与备份策略;托管实例可定制空间小、成本高,而本场景规模(< 200 GB)自建运维量可控 |
可用性靠主备 + 自建备份与演练;若团队无人愿承担数据层运维,或 SLA 要求提升,再评估托管 |
1.5 术语
| 术语 |
含义 |
| 任务(Task) |
一次用户提交的批量作业,如"生成 20 条脚本" |
| 任务项(Task Item) |
任务下可独立成败的最小单元,如 1 条脚本、1 张图片、1 个镜头 |
| 产物(Artifact) |
任务项产出的文件对象(图片 / 视频 / 中间帧 / 音频) |
| 幂等键(Idempotency Key) |
由"任务 ID + 项序号 + 版本"生成的唯一键,保证重投不重复计费 |
| 范围片段(Scope Clause) |
由角色数据范围解析出的 SQL 条件,如 customer_id IN (...) |
| 物化索引(Materialized Index) |
把动态表单中声明"可检索"的字段落到独立表,供筛选与报表 |
| 账本(Ledger) |
每次外部模型调用的用量与费用记录,append-only |
2 技术选型
2.1 选型总表
| 层 |
选型 |
版本基线 |
关键理由 |
主要代价 |
| 语言 |
TypeScript(全栈统一) |
5.7+ |
前后端共享类型与 schema 校验,减少字段错位 |
媒体处理生态弱于 Python(用 ffmpeg CLI 弥补) |
| 运行时 |
Node.js 24 LTS |
24.15.0(锁定小版本) |
与 TS 生态一致;worker 并发模型适合 I/O 密集;24 为当前 LTS |
CPU 密集任务需多进程(worker 独立进程池) |
| 包管理 |
pnpm |
10.x |
monorepo 原生、安装快、磁盘省、无幽灵依赖;唯一包管理器,禁 npm / yarn |
团队需统一 pnpm 版本(packageManager 锁定),历史脚本需改写 |
| 前端框架 |
React |
19.x |
生态最大;Semi Design 已支持 React 19 |
需在入口最顶部引入 react19-adapter |
| 构建 |
Vite |
6.x |
启动快、配置少 |
与 SSR 无关(本期纯 SPA) |
| UI 组件 |
Semi Design(React 19 构建 @douyinfe/semi-ui-19) |
2.x |
中后台组件齐全(Table / Form / Modal / Toast / Tree),设计语言统一;19 包 peer 锁定 react ^19,避免 peer 冲突 |
无 ProComponents 等价物,复杂表格页 / 查询表单需在 packages/ui 二次封装(约 3–4 人周);入口仍需导 adapter(见 2.2.1) |
| 图标 |
Remix Icon(@remixicon/react,统一出口 packages/ui/icons.ts) |
4.x |
唯一图标来源,线性风格统一、体积可控 |
禁止其他图标库与内联 SVG(见 2.2.1) |
| 后端框架 |
NestJS(Fastify 适配) |
11.x |
模块化 + DI + 守卫 / 拦截器,天然承载权限与审计 |
装饰器较多,团队需约定分层规范 |
| ORM |
Prisma |
6.x |
类型安全、迁移可审阅、JSONB 支持好 |
复杂 SQL 需 $queryRaw 兜底 |
| 数据库 |
PostgreSQL |
18 |
JSONB + GIN、分区、强事务、RLS;18 的异步 I/O 提升大表扫描吞吐 |
动态表单查询需索引设计 |
| 缓存 / 队列 |
Redis + BullMQ |
7.4 / 5.x |
缓存、限流、锁、队列一套搞定 |
队列持久化弱于 Kafka(任务可重建,可接受) |
| 对象存储 |
S3 兼容(MinIO 起步) |
MinIO 最新稳定 |
签名 URL、版本化、生命周期、可迁云 |
需自运维磁盘与副本 |
| 媒体处理 |
ffmpeg 7 + sharp |
7.x / 0.33+ |
拼接 / 转码 / 抽帧 / 缩略图 / 合成 |
需做进程隔离与超时控制 |
| 鉴权 |
自建 JWT(access + refresh)+ OIDC 适配 |
— |
PRD 要求 SSO 可插拔,JWT 先落地 |
SSO 接入点需预留接口 |
| 接口文档 |
OpenAPI 3.1(Nest Swagger 生成) |
— |
前后端契约 + 测试依据 |
需与 Zod 校验保持同步(用单一真源生成) |
| 校验 |
Zod + Ajv(动态 schema) |
3.x / 8.x |
静态 DTO 用 Zod,动态表单用 Ajv |
两套校验器需约定边界 |
| 观测 |
OpenTelemetry + Prometheus + Grafana + Loki + Sentry |
— |
跨 API/Worker 链路追踪 |
自建 Loki 需磁盘规划 |
| 部署 |
Docker + Docker Compose |
— |
3 个月内最快落地 |
弹性扩容需手动 |
| CI/CD |
GitHub Actions(或 GitLab CI) |
— |
仓库已托管,流程简单 |
无(当前仓库私有) |
2.2 前端
| 能力 |
选型 |
说明 |
| 路由 |
React Router 7 |
后台型 SPA,按模块懒加载 |
| 服务端状态 |
TanStack Query 5 |
列表 / 详情缓存、失效、轮询任务进度 |
| 客户端状态 |
Zustand 5 |
轻量,避免 Redux 样板代码 |
| 表格 |
Semi Table(virtualized) |
任务项、资产库动辄上千行,必须虚拟化;Semi Table 自带虚拟滚动 |
| 图标 |
@remixicon/react + packages/ui/icons.ts 语义出口 |
全站唯一图标来源,业务不直接引具体图标名 |
| 动态表单渲染 |
自研轻量渲染器(基于行业包 schema) |
只支持 PRD 5.4.8 的 14 类字段白名单;比通用表单引擎可控 |
| 富文本 |
Tiptap |
要素卡中的"多行文本 / 富文本"字段 |
| 拖拽排序 |
dnd-kit |
资产排序、模板字段排序(仅列表内,不做自由布局) |
| 图表 |
ECharts 5 |
产能 / 质量 / 成本看板 |
| 视频预览 |
原生 <video> + HLS.js(P2 再上) |
M0–M2 用 progressive MP4,按需 HLS |
| 图片快筛 |
自研网格虚拟列表 + 键盘快捷键 |
50 张并排、勾选状态本地缓存并回写 |
| 代码质量 |
ESLint + Prettier + Vitest + Playwright |
与后端共用 lint 配置 |
为什么自研表单渲染器而不是用 Formily / RJSF:PRD 的 schema 是"JSON Schema + UI 元数据 + 变量映射 + 进入生成标记",与通用表单库的数据模型有偏移;自研渲染器约 2 人周,换来字段联动、校验、可检索标记与提示词变量的统一,后续接表单设计器也更顺。
2.2.1 UI 组件库与图标硬约定
组件库:只用 Semi Design
| 项 |
约定 |
| 唯一组件库 |
React 19 用 @douyinfe/semi-ui-19(底座 @douyinfe/semi-foundation 按需);不得与 @douyinfe/semi-ui 混装(两包 lib/es 内容完全一致,混装会重复打包组件与样式);也不引入第二套组件库(Ant Design / Arco / Element 等) |
| React 19 适配 |
入口文件最顶部(任何 Semi 组件之前)import '@douyinfe/semi-ui-19/react19-adapter';。原因:React 19 的 react-dom 已不再导出 createRoot(19.3.1 实测只剩 createPortal),Semi 的 Modal / Toast / Notification 靠注入的 createRoot 动态挂载,未注入时报 [Semi UI] createRoot is not available。注:官方 getting-started 只说「v19 换 -19 包」,react19 生态页要求导 adapter,两件事都要做(-19 包内已带 react19-adapter 入口) |
| 已知限制 |
React 19 下 Tooltip / Popover / Dropdown 的 children 若是类组件可能定位异常,用 DOM 元素包裹即可(项目内统一用函数组件 + forwardRef) |
| 中后台补齐 |
Semi 没有 ProComponents 等价物:查询表格、详情页、权限树、批量选择等封装在 packages/ui(约 3–4 人周),宁可自研也不引第二套库 |
| 视觉统一 |
只改 Semi 的 design token(cssVar)与主题变量,不在业务里写散落色值与圆角 |
| 表单分工 |
静态表单用 Semi Form;要素卡等动态表单用自研 schema 渲染器,底层控件仍是 Semi 的 Input / Select / DatePicker |
| 国际化 |
先 zh-CN,文案集中在 packages/ui/locale,预留 LocaleProvider 切换能力 |
图标:只用 Remix Icon
| 规则 |
落地方式 |
| 唯一图标库 |
@remixicon/react 4.x;业务代码只从 packages/ui/icons.ts 按语义名导入(如 IconExport、IconRefresh),不直接引包名、不直接写 RiDownloadLine 这类具体图标名 |
| 禁止清单 |
@douyinfe/semi-icons、@ant-design/icons、react-icons、lucide-react、@heroicons/*、iconfont 字体图标、内联手写 SVG(品牌 Logo 除外)一律禁用 |
| 强制执行 |
ESLint no-restricted-imports 覆盖上述包名,并禁止导入 @douyinfe/semi-ui(只允许 -19);CI 额外扫描仓库内 *.svg 与 @font-face 引入;PR 自检清单加一条(图标来源) |
| 风格统一 |
只用 Remix Icon 的 Line 风格,不混用 Fill;尺寸用 size={16} / size={20} 语义值,颜色继承 currentColor |
| 组件内置图标 |
Semi 组件内部的小图标(下拉箭头、勾选框)无法逐个替换,作为已登记例外保留;业务侧新增图标一律用 Remix Icon,P2 再评估样式覆盖 |
| 体积控制 |
按需引入单个图标(import { RiExportLine } from '@remixicon/react'),禁止 import * as Icons 全量引入 |
2.3 后端
| 能力 |
选型 |
说明 |
| HTTP 层 |
NestJS + Fastify |
吞吐高于 Express,拦截器/管道模型完整 |
| 模块划分 |
8 个业务模块(见 3.2) |
模块内 controller → service → repository 三层 |
| 参数校验 |
Zod(静态 DTO)+ Ajv(动态 schema) |
动态表单按版本编译 Ajv 校验器并缓存 |
| 鉴权 |
JWT(短 access 15min + refresh 7d)+ 权限守卫 |
特殊权限单独装饰器 @RequireSpecial('export') |
| 数据访问 |
Prisma + 少量原生 SQL |
复杂报表用 $queryRaw + 视图 |
| 事务 |
Prisma $transaction + 领域事件表(Outbox) |
任务入队与业务写入同一事务,避免"扣了额度没建任务" |
| 幂等 |
请求级 Idempotency-Key 头 + 任务项唯一键 |
重复提交返回原任务 ID |
| 审计 |
拦截器 + 领域事件,异步落库 |
只记录"变更类"操作,读操作仅记录敏感访问 |
| 定时任务 |
BullMQ repeatable job |
复审提醒、月度预算结算、清理任务 |
| 日志 |
Pino(结构化 JSON)+ traceId |
与 OTel 打通 |
分层约定(写进工程规范,评审时对照):
- Controller 只做参数解析与响应组装,不写业务判断;
- Service 承载业务规则(含权限点校验、状态机迁移),不直接拼 SQL;
- Repository 是唯一能访问 Prisma 的层,必须接收
ScopeContext(数据范围上下文),否则类型层面不允许编译;
- 跨模块调用走 Service 接口或领域事件,禁止跨模块直接读写对方表。
第 3 条是 R-04「任何查询必须带客户维度」的工程落点:把数据范围做成 Repository 方法的必传参数,比写规范文档更有效。
2.4 异步任务与媒体处理
队列分组(BullMQ,按"资源类型 + 优先级"分,不是按业务模块分):
| 队列 |
并发 |
典型耗时 |
重试策略 |
说明 |
script:generate |
8 |
5–30s |
3 次,指数退避 |
LLM 调用,按供应商限流分池 |
image:matting |
2(按实测内存夹取,默认 2) |
2–15s |
3 次 |
本地 ONNX 抠图(@imgly/background-removal-node),CPU 密集;失败降级原图并标「低质量」,必须落库 |
image:render |
2–4(默认 2,再按可用核数夹取) |
1–10s |
3 次 |
@napi-rs/canvas + sharp 套版合成,CPU 密集,独立进程池;禁止与 ZIP 打包混跑 |
delivery:zip |
1 |
视批量 |
2 次 |
流式 archiver(level 9),压缩是纯 CPU,与合成互相抢核,故单独排队 |
video:keyframe |
10 |
10–40s |
3 次 |
图生图,受供应商限流 |
video:shot |
12 |
60–180s |
2 次(超时/限流可重试,内容违规不重试) |
图生视频,最贵,必须记账 |
video:compose |
4 |
20–90s |
3 次 |
ffmpeg 拼接 + 字幕 + Logo + 警示语 + 音频 |
media:verify |
4 |
5–20s |
2 次 |
时长 / 比例 / 黑帧 / 静音 / 字幕错字 / AI 标识 |
export:package |
2 |
10–120s |
2 次 |
打包 zip、命名、清单、归档 |
media:post |
6 |
5–60s |
3 次 |
外部上传成片的抽帧、转码 HLS、水印、缩略图、元数据提取(非生成类,失败不影响原文件) |
embed:index |
2 |
2–10s |
3 次 |
脚本语义块向量化与索引(M3 起启用,M1–M2 关闭) |
媒体处理原则:
- 进程隔离:ffmpeg / sharp 在 worker 子进程中执行,设超时(视频 10 分钟、图片 60 秒),超时杀进程并标记失败,不拖垮 worker。
- 临时目录:worker 本地
/tmp/task/{taskItemId} 存放中间文件,成功后上传对象存储并清理;失败保留 24 小时便于排查。
- 统一编码参数:视频 H.264 / AAC / 1080×1920 / 30fps / faststart;图片 JPG q=92 或 PNG;参数写入
render_profile 配置,不散落代码。
- 可中断:每个任务项完成即落库(含产物对象 key),重投时跳过已完成项(
task_items.status 判断)。
- 不重生成已成功镜头:拼接任务只读已完成镜头对象,单镜替换只重跑该 shot。
2.5 数据与存储
| 用途 |
选型 |
关键配置 |
| 业务库 |
PostgreSQL 18(自建:云主机上跑 postgres:18,开发机同镜像) |
每日全备 + WAL 连续归档到 MinIO(PITR 7 天);审计表按月分区;参数与初始化脚本进仓库(infra/postgres/) |
| 缓存 / 队列 |
Redis 7.4(自建:redis:7.4,主从 + AOF everysec) |
缓存逻辑库 db0、队列 db1、限流/锁 db2,避免互相干扰;队列消息可重建、缓存丢失可接受,但队列库不设淘汰策略 |
| 对象存储 |
MinIO(2 节点,纠删码)+ 云 OSS 冷备 |
桶:assets(源资产)、renders(成片)、temp(中间产物)、archive(审计/导出归档) |
| 文件下载 |
签名 URL(15 分钟)+ 服务端 zip 流 |
大包走导出任务,不在请求内同步打包 |
| 备份 |
pgBackRest(或 WAL-G)归档到 MinIO + MinIO 每日跨桶复制 |
RPO:数据库 ≤ 5 分钟,对象 ≤ 24 小时;RTO 4 小时 |
PostgreSQL 18 说明:18 提供的 uuidv7()、虚拟生成列与异步 I/O 可直接用于新表与既有慢查询;但主键仍统一用应用侧生成的 ULID(便于前端乐观渲染、未来分库与导入导出),不依赖库内函数,避免出现「两套主键来源」。jsonb 的 GIN 索引、分区与 RLS 用法与 17 一致,迁移无额外改造。
桶与生命周期:
| 桶 |
内容 |
生命周期 |
assets |
参考物料、模板文件、Logo、人物/场景素材 |
永久 + 版本化(保留 5 个版本) |
renders |
成片(图片 / 视频) |
永久(删除走业务逻辑,不靠生命周期) |
temp |
关键帧、单镜视频、TTS 音频、合成中间文件 |
30 天自动删除 |
archive |
审计日志归档、导出清单快照 |
≥ 12 个月 |
uploads |
外部上传的原文件(成片 / 图片)与分片临时区 |
分片 7 天自动清理;完整对象随素材永久 |
exports |
打包产物(zip) |
3 天自动删除 |
秒传与直传:material_files.hash(sha256)建唯一索引,预签名阶段先查 hash → 命中则直接引用已有对象(秒传);未命中返回分片预签名。下载一律短时效签名 URL(5–15 分钟),禁止桶公开读;对象存储统一经 StorageProvider 接口(presignPut / presignGet / head / copy / delete / listMultipart),便于在 MinIO / OSS / S3 之间切换。
2.6 AI 接入层
PRD 5.4.4 要求"多供应商并存、按用途路由、额度预算、健康降级、成本看板",因此不能在各业务模块里散着调 SDK,必须收口为一个适配层。
| 组件 |
职责 |
实现要点 |
| Provider 适配器 |
文本 / 图生图 / 图生视频 / TTS 四类能力,每类一个统一接口 |
generate(req) -> jobId + poll(jobId) 或 webhook 回调;供应商差异在适配器内消化 |
| 路由表 |
用途 → 模型链(主 / 备)→ 可按客户覆盖 |
数据表 ai_routes,带生效范围与优先级 |
| 限流池 |
按供应商 QPS / 并发数限流 |
Redis 令牌桶,worker 取令牌后再调用 |
| 重试与降级 |
限流/超时/5xx 重试 → 主模型失败切备用 |
内容违规(4xx 语义)不重试,直接失败并记录原因 |
| 成本账本 |
每次调用写 ai_usage(token / 秒 / 张 / 金额) |
由适配器统一写入,业务层不重复记账 |
| 内容安全 |
调用前关键词与规则校验、调用后结果抽检 |
与行业包合规规则集联动;命中即拦截并留痕 |
| 配置四层 |
供应商 → 模型 → 任务映射 → 调用日志,参数逐层覆盖 |
表 ai_providers / ai_models / ai_task_mappings / ai_call_logs;合并顺序:任务级 > 模型级 > 供应商默认 |
| 提示词与行业指南 |
系统提示词 / 负面词 / 行业指南版本化,可回滚 |
存 ai_configs + ai_config_versions;任务记录引用版本号 |
| 方言适配 |
thinking 开关、JSON 模式、联网搜索的供应商差异 |
适配器内映射:thinking.type ↔ enable_thinking(走 extra_body);提示词强制含 JSON;联网作为独立前置步骤 |
| 密钥与并发 |
密钥加密、模块级并发上限 |
AES-256-GCM 加密(出参只给 hasApiKey);Redis 信号量限制全局限并发 |
异步模型调用的两种形态(必须在适配层统一):
- 提交 + 轮询:适用于多数图生视频 / 图生图 API;worker 提交后进入退避轮询,最长等待时长写入配置。
- 提交 + Webhook:供应商支持回调时优先用;回调入口
/api/v1/callbacks/ai/{provider} 做签名校验与幂等。
四层合并与降级(实现顺序):
| 层 |
举例 |
覆盖关系 |
| 供应商 Provider |
火山方舟 / DeepSeek / 阿里百炼 |
Base URL、密钥、平台类型;最底层的默认值 |
| 模型 Model |
doubao-* / deepseek-* / qwen-* |
上下文、最大输出、默认参数(JSONB);覆盖供应商默认 |
| 任务映射 TaskMapping |
脚本生成 / 解析 / 打标 / 校核 / 图生图 / 图生视频 / TTS |
主模型必填 + 备用可选 + 任务级参数;覆盖模型默认 |
| 调用日志 CallLog |
每次调用一行 |
只写不改;记录模型 ID、参数快照、用量、估算金额、错误码 |
方言差异必须收口在三处:① 深度思考参数名(豆包 / DeepSeek 用 thinking.type,通义用 enable_thinking 且需 extra_body);② 默认值相反(有的默认开思考);③ JSON 模式(通义提示词须含 JSON 字样,DeepSeek 偶发空 content)。业务代码只传语义参数(开 / 关 / 强度),映射与兜底都在适配器内。
成本口径:调用前预占、调用后按实际用量结算,估算价与真实账单分列,月度对账;参考项目实测约解析 0.0007 元 / 条、生成 0.003 元 / 次,仅用于预估,不能当报价。
2.7 可观测性与运维
| 维度 |
工具 |
关键指标 / 动作 |
| 指标 |
Prometheus + Grafana |
接口 P95、队列积压与等待时长、任务成功率、单条成本、模型 P95 耗时、供应商错误率 |
| 日志 |
Loki(结构化 JSON) |
按 traceId / taskId 检索;敏感字段脱敏 |
| 链路追踪 |
OpenTelemetry |
API → 队列 → worker → 外部 API 全链路;traceId 随任务透传 |
| 异常 |
Sentry |
前后端错误聚合;按模块与版本聚合 |
| 告警 |
Grafana Alerting → 企业微信 / 飞书 |
队列积压 > 10 分钟、任务失败率 > 5%、单日成本超预算 80%、模型可用性 < 95% |
| 巡检 |
每日定时任务 |
孤儿对象、失败任务滞留、审计分区、备份结果 |
2.8 安全与合规组件
| 要求(PRD 出处) |
机制 |
| 客户数据隔离(R-04) |
customer_id 强制 + Repository 范围注入 + P1 RLS |
| 导出全量审计(R-05) |
导出任务写 export_records + 审计事件;下载链接签名且 15 分钟过期 |
| 被引用资产禁止删除(R-02) |
引用关系表 + 删除前检查 + 软删除 |
| 批量前预估成本(R-06) |
提交接口强制返回预估;超预算需二次确认 |
| 审计只读(9.2) |
审计表仅 INSERT 权限;按月分区 + 归档到 archive 桶 |
| AI 生成标识(R-09) |
合成阶段写入角标与 MP4 元数据;校验任务检查标识存在性 |
| 肖像与素材授权(9.3) |
人物池资产带授权材料与到期日;到期前 30 天提醒 |
| 密钥管理 |
模型密钥存 KMS / 环境变量加密;不落库、不进日志 |
| 上传安全 |
预签名直传 + 类型/大小校验 + 病毒扫描(ClamAV 可选)+ 图片重编码 |
2.9 放弃的选项及原因
| 选项 |
为什么不用 |
代价 |
| Java / Spring Boot |
团队选择了 TS 全栈;后台治理能力 Nest 已够 |
复杂并发与内存管理不如 JVM,但本场景无重 CPU 业务 |
| Python(FastAPI)全栈 |
AI 生态好,但后台治理(RBAC / 表单引擎 / 强类型)工程化成本更高 |
媒体与 AI 仍可后续加 Python 旁路 |
| Python 独立媒体服务 |
< 500 条/日时 CPU 够用,多一套服务多一份运维 |
若出现复杂视频模板(多层合成 / 动画),再评估 Remotion 或 Python 服务 |
| Kafka / RabbitMQ |
任务可重建、无跨团队消费方,Redis 队列足够 |
队列持久化与堆积能力弱,需监控兜底 |
| MongoDB |
业务是强关系(客户 / 资产 / 引用),JSONB 已解决动态字段 |
— |
| Temporal |
工作流模型好,但引入学习成本;本期任务链路用 BullMQ + 状态机即可 |
未来若任务编排复杂化(多分支审批流)再评估 |
| 通用表单引擎(Formily / RJSF) |
与行业包 schema 的变量映射、可检索标记、进入生成标记不匹配 |
自研渲染器约 2 人周 |
| 云函数(FC / SCF) |
视频处理需要长时任务与本地临时盘,函数形态不合适 |
— |
| S3 云服务直接用 OSS(不做 S3 抽象) |
避免绑死厂商;MinIO 可本地化验证 |
运维多一套组件(可用 OSS 取代,接口不变) |
| Ant Design 5 + ProComponents |
团队确定统一用 Semi Design;双组件库会让设计 token、包体、交互规范分裂,ProComponents 的收益不足以引入第二套体系 |
查询表格 / 详情页等需在 packages/ui 自研封装(约 3–4 人周) |
2.10 版本基线
| 组件 |
版本 |
锁定方式 |
| Node.js |
24.15.0(24 LTS) |
.nvmrc 写 24.15.0 + 镜像基础镜像 node:24.15.0-bookworm-slim |
| pnpm |
10.x |
根 package.json 的 packageManager 字段 + CI 启动时校验 |
| TypeScript |
5.7+ |
根 package.json |
| React / Vite / Semi UI |
19 / 6 / 2.x(@douyinfe/semi-ui-19) |
pnpm lockfile |
| Remix Icon |
4.x |
pnpm lockfile(唯一图标库) |
| NestJS / Fastify |
11 / 5 |
pnpm lockfile |
| Prisma |
6 |
lockfile + prisma migrate |
| PostgreSQL |
18 |
postgres:18 镜像(自建);参数文件与初始化脚本进仓库 |
| Redis |
7.4 |
redis:7.4 镜像(自建) |
| BullMQ |
5 |
lockfile |
| ffmpeg |
7.x |
镜像内固定版本 |
| sharp |
0.33+ |
lockfile |
| MinIO |
最新稳定(RELEASE 标签) |
镜像 digest 固定 |
兼容性检查:Node.js 24.15.0 属 24 LTS 线,NestJS 11 要求 Node ≥ 20、Prisma 6 要求 Node ≥ 18.18、sharp 通过 N-API 预编译包(不随 Node ABI 变化);上线前在 CI 用锁定镜像跑一次 pnpm install --frozen-lockfile + 全量单测,并以 engines 字段兜底。PostgreSQL 18 为自建部署:需确认 postgres:18 镜像与备份工具(pgBackRest / WAL-G)版本兼容、Prisma 对 18 的支持(走标准 pg 协议,通常仅需版本声明);另注意开发机 Docker 的 shared_buffers / shm_size 远小于生产,本地跑出的性能结论不可直接外推。
版本基线是"起步基线",不是"永远不动":第三方大版本升级走单独工单,与业务迭代解耦。
3 总体架构
3.1 分层职责
| 层 |
职责 |
不做什么 |
| 接入层 |
TLS、静态资源、反向代理、限流(IP 级)、请求体大小限制 |
不做业务鉴权、不缓存业务数据 |
| 应用层(API) |
鉴权与权限点、数据范围解析、参数校验、事务边界、审计埋点、任务投递 |
不做长耗时处理、不直接调外部模型 |
| 异步任务层 |
脚本生成、图片合成、视频逐镜与拼接、校验、导出打包 |
不直接对外提供 HTTP 接口(回调除外) |
| 领域层 |
8 个业务模块的业务规则与状态机 |
不感知 HTTP、不感知队列 |
| 数据访问层 |
Prisma / 原生 SQL、范围注入、事务 |
不写业务判断 |
| 集成层 |
AI Provider 适配、对象存储、短信/SSO、观测 Export |
不做业务决策,只做协议转换与重试 |
| 数据层 |
PostgreSQL、Redis、对象存储 |
— |
3.2 模块划分(8 个业务模块)
| 模块 |
对应 PRD |
职责 |
拥有的表 |
对外能力 |
identity |
5.4.1 / 5.4.3 |
组织、用户、角色、权限点、数据范围、登录、SoD 校验 |
users / roles / permissions / role_permissions / user_roles / data_scopes / departments |
鉴权守卫、范围解析、权限变更事件 |
client |
5.4.2 |
行业目录、客户、客户组、客户级字段扩展与审批 |
industries / customers / customer_groups / customer_group_members / customer_fields |
客户上下文、资产归属校验 |
catalog |
5.4.5 / 5.3 |
数据字典、资产(15 类)、版本、引用关系、生命周期与复审 |
dict_types / dict_items / assets / asset_versions / asset_refs / asset_reviews |
资产检索、引用校验、版本回溯 |
industry |
5.4.7 / 5.4.8 |
行业包 9 槽位、schema 版本、发布审批、表单设计器后端、客户覆盖 |
industry_packs / industry_pack_versions / form_schemas / compliance_rules / prompt_templates |
schema 编译与校验、发布流水线 |
script |
5.1 |
要素卡、卖点库、脚本模板、批量生成、质量校核、审核入库、版本 |
brief_cards / brief_card_index / selling_points / script_templates / scripts / script_versions |
脚本生成、校核、入库 |
media |
5.2 |
图片线、视频线、任务与任务项、分镜、快筛、预览、失败与成本 |
media_tasks / media_items / shots / render_profiles / preview_marks |
任务编排、产物管理 |
delivery |
5.5 |
导出、命名规则、交付清单、归档、下载签名 |
exports / export_items / delivery_manifests |
打包、命名、审计 |
platform |
5.4.4 / 5.4.6 |
AI 配置(模型/路由/提示词/额度)、AI 调用账本、审计、系统配置、成本看板 |
ai_providers / ai_routes / ai_prompts / ai_usage / audit_logs / system_configs / system_config_versions / cost_budgets |
AI 适配层入口、审计写入、成本聚合 |
共享内核(packages/shared,不拥有表):领域事件定义、错误码、权限点常量、Zod schema、分页/游标、ID 生成、时间与命名规则工具。
边界纪律(评审检查项):
- 模块之间只通过 Service 接口或领域事件交互,禁止跨模块
prisma.xxx 直接读写(用 ESLint 规则 + code review 兜底)。
identity 与 client 是被依赖方,不允许反向依赖 script / media。
platform.ai 是唯一的出网模型调用入口,其他模块不得直接 fetch 供应商接口。
3.3 进程拓扑
| 进程 |
实例(生产) |
职责 |
扩缩方式 |
web(Nginx) |
1(可与 API 同机) |
静态资源、反代、TLS |
垂直 |
api |
2 |
HTTP 接口、SSE 进度、回调入口 |
水平(无状态) |
worker-script |
1(并发 8) |
脚本生成 + 校核 |
按队列积压水平扩 |
worker-image |
1(并发 16) |
图片合成 |
按 CPU 水平扩 |
worker-video |
2(并发 12) |
关键帧、镜头生成、拼接 |
按队列积压水平扩 |
worker-export |
1(并发 2) |
打包、命名、归档 |
按导出量水平扩 |
scheduler |
1 |
定时任务(复审、预算结算、清理、巡检) |
单实例 + Redis 锁 |
minio |
2 节点 |
对象存储 |
加节点/加盘 |
postgres |
自建 1 主(M1 起 +1 从,流复制) |
业务库 |
升配 + 读副本(P2) |
redis |
自建 1 主 1 从(AOF) |
缓存/队列/限流 |
升配 |
worker 可以合并在同一台机器上跑(M0 用 2 台 8C16G 即可),但队列分组必须分开:视频任务一旦积压,不能把脚本生成一起拖死。
3.4 一次图片生产的端到端
以"20 个商品 × 2 个模板 × 3 个规格 = 120 张"为例:
| 步 |
发生位置 |
动作 |
落库 / 落对象 |
| 1 |
API |
校验权限点(素材生成)+ 数据范围(客户可见) |
— |
| 2 |
API |
读客户资产包(模板 / 规格 / Logo / 警示语),解析行业包 schema |
读缓存(Redis,5 分钟) |
| 3 |
API |
生成任务头 + 120 个任务项 + 幂等键,写入 Outbox |
media_tasks / media_items(状态=排队) |
| 4 |
API |
返回 taskId 与"预计 6 分钟、预估成本 ¥X" |
— |
| 5 |
Outbox→队列 |
投递 120 个 image:render 消息(按客户分片,避免单客户占满) |
Redis 队列 |
| 6a |
Worker |
image:matting:抠图(ONNX CPU)→ 透明底 PNG;失败退原图并打 low_quality |
media_items.matting(JSONB:模型版本、耗时、是否降级) |
| 6b |
Worker |
image:render:读 PSD 模板版本 → canvas 按命名图层落位(image_replacement_ / clipping_mask_ / text_replacement_)→ sharp 编码 → 上传 renders |
media_items.status=生成中→待预览,artifacts.object_key |
| 7 |
Worker |
质量校验(安全区 / Logo / 变形 / 警示语 / 缺图层 / 字体回退 / 尺寸超限) |
media_items.checks(JSONB) |
| 8 |
API/SSE |
前端订阅进度,网格占位逐步替换为缩略图 |
— |
| 9 |
API |
用户勾选 → 提交导出(export:package) |
exports / export_items |
| 10 |
Worker |
delivery:zip:按命名规则(批次快照)生成文件名 → 流式 zip → 上传 → 写交付清单 → 归档成片资产 |
exports.object_key、assets(成片) |
| 11 |
API |
返回 15 分钟签名下载 URL;写审计 |
audit_logs |
异常路径:
- 单张失败 → 该任务项置失败并记录分类(模板缺图层 / 物料损坏 / 合成超时),其余继续;支持"仅重跑失败项"。
- worker 崩溃 → 消息重回队列;任务项有幂等键与"已完成"判断,不重复计费。
- 用户中途取消 → 任务头置取消,已投递消息在 worker 取到后快速返回。
3.5 一次视频生产的端到端
以"3 条脚本 × 3 个人物 = 9 条 15s 视频"为例:
| 步 |
发生位置 |
动作 |
落库 / 落对象 |
| 1 |
API |
校验权限、数据范围;校验脚本状态=已入库 |
— |
| 2 |
API |
分镜:读脚本的 shot 结构,展开 9 条 × 5 镜 = 45 个镜头项 |
media_tasks / media_items(每条)/ shots(每镜) |
| 3 |
API |
成本预估(45 次图生视频 + 9 次拼接 + 可选 TTS)与预算校验 |
超预算需二次确认 |
| 4 |
队列 |
投递 video:keyframe(45 个) |
Redis |
| 5 |
Worker |
关键帧:图生图,带参考物料(白底图)与人物、场景、光影提示词 |
shots.keyframe_object_key |
| 6 |
队列 |
投递 video:shot(45 个,取供应商令牌) |
Redis |
| 7 |
Worker |
图生视频(提交 → 轮询/webhook → 下载) |
shots.clip_object_key、ai_usage |
| 8 |
Worker |
口播(如需):TTS → lip sync → shots.audio_object_key |
ai_usage |
| 9 |
队列 |
全部镜头完成后投递 video:compose(9 个) |
Redis |
| 10 |
Worker |
ffmpeg 拼接 + 字幕(逐字对齐)+ Logo + 警示语 + BGM + AI 标识 |
renders(成片) |
| 11 |
Worker |
media:verify:时长 / 比例 / 黑帧 / 静音 / 字幕错字 / 标识 |
media_items.checks |
| 12 |
API |
在线预览:签名 URL + Range;缩略图;勾选 |
preview_marks |
| 13 |
API/Worker |
命名(占位符 + 固定文本)→ 下载或导出 → 归档 |
exports / assets |
成本控制点:
- 任何一次
video:shot 调用前必须有账本记录(调用前预占、调用后结算),防止"提交了 45 个任务但预算只够 20 个"。
- 单镜失败可换模型重试(路由表切换),但换模型产生的额外费用单独记录,便于评估"便宜的模型是否真的省"。
3.6 状态机
| 对象 |
状态集合 |
迁移约束 |
| 任务(media_tasks) |
排队 / 生成中 / 部分失败 / 待预览 / 已勾选 / 已导出 / 已入库 / 失败 / 已取消 |
只有"待预览 / 部分失败"可重试;已导出不可回退 |
| 任务项(media_items) |
排队 / 生成中 / 待预览 / 失败 / 已跳过 |
失败原因必填(枚举,来自字典) |
| 镜头(shots) |
排队 / 关键帧完成 / 生成中 / 完成 / 失败 |
单镜替换只影响本镜 |
| 脚本(scripts) |
草稿 / 生成中 / 待审核 / 已入库 / 使用中 / 已停用 / 失败 |
已入库修改 → 新版本 + 重新审核 |
| 资产(assets) |
草稿 / 已发布 / 已停用 / 已归档 / 只读 |
被引用不可删除,只能停用 |
| 行业包(versions) |
草稿 / 校验中 / 待复核 / 已发布 / 已弃用 |
发布后不可改,只能发新版本 |
| 素材(materials) |
上传中 / 待完善 / 待送审 / 送审中 / 已过审 / 已驳回 / 投放中 / 已下线 |
驳回后重传生成新版本并回待送审;已下线不可回投放中 |
| 送审批次(review_records) |
待提交 / 送审中 / 已通过 / 已驳回 / 已撤回 |
结论绑定提交时冻结版本;重审需新建批次 |
3.7 领域事件与 Outbox
问题:任务入队与业务写入必须"要么都成功、要么都不发生",否则会出现"任务记录建了但消息没投出去"(任务永远排队)或"消息投了但任务没建"(worker 找不到任务)。
方案:事务内写 outbox_events,提交后由 dispatcher(API 内定时任务或 scheduler)投递队列,成功后标记已投递;投递失败按退避重试。
| 事件 |
生产方 |
消费方 |
用途 |
media.task.created |
media |
队列投递器 |
拆任务项入队 |
media.item.completed |
media |
media(聚合进度) |
判断整条任务是否完成 |
script.generated |
script |
script(校核) |
生成后自动校核 |
asset.ref.changed |
catalog |
检索 / 报表 |
维护引用计数缓存 |
ai.usage.recorded |
platform |
成本聚合 |
实时预算告警 |
permission.changed |
identity |
会话失效 |
在线用户下次请求权限失效(PRD 5.4.3) |
industry.pack.published |
industry |
表单渲染缓存失效 |
保证新 schema 立即生效 |
3.8 为什么是模块化单体(而不是微服务)
| 维度 |
模块化单体(本期) |
微服务 |
| 部署复杂度 |
1 个 compose 文件,3–5 个进程 |
8 个服务 + 注册中心 + 网关 + 链路治理 |
| 事务 |
本地事务可覆盖"任务 + 账本 + 资产" |
需 Saga / 补偿,复杂度翻倍 |
| 一致性 |
强一致(Outbox 只解决入队) |
最终一致,需处理中间态 |
| 团队规模 |
2–4 人研发 |
至少每服务 1–2 人 |
| 扩容粒度 |
按进程类型(API / 图片 / 视频)分别扩 |
按服务扩 |
| 拆分时机 |
单个域 QPS 或计算量独立增长 3 倍以上,或有独立团队 |
— |
关键点:扩容粒度由"进程类型"提供,而不是由"服务拆分"提供。视频 worker 可以独立扩到 10 台,而 API 仍是 2 台——这已经解决了 90% 的扩容诉求。
4 部署架构(公有云,单地域)
4.1 环境划分
| 环境 |
用途 |
数据 |
AI 调用 |
规格 |
| dev |
日常开发联调 |
种子数据 + 1 个测试客户 |
沙箱 / 低价模型 |
单机 4C8G |
| staging |
回归、演示、发布前验收 |
脱敏样本(不含真实客户素材) |
与生产同一路由但限额度 |
2 台 8C16G |
| prod |
生产 |
真实客户数据 |
生产路由 |
见 4.3 |
隔离要求:三个环境使用独立 VPC 子网、独立数据库实例、独立对象存储桶与独立密钥;生产密钥只在生产环境注入。
4.1.1 本地开发环境(Docker Compose,开发前先启动)
本地依赖全部容器化,与生产同大版本。开发前先起依赖容器;容器没起时后端/迁移会直接连不上 5432 / 6379,报连接失败而不是代码错误。
| 依赖 |
镜像 |
端口 |
数据卷 |
启动要点 |
| PostgreSQL 18 |
postgres:18 |
5432 |
pgdata |
POSTGRES_DB=lingxi_dev;初始化脚本建 pg_trgm、unaccent 等扩展;shared_buffers 按开发机内存给 512 MB 即可 |
| Redis 7.4 |
redis:7.4 |
6379 |
redisdata |
appendonly yes;仅监听 127.0.0.1,本地不设密码 |
| MinIO |
minio/minio |
9000 / 9001 |
miniodata |
控制台 9001;初始化容器用 mc 自动建 assets / renders / temp / archive 四桶 |
四条约定:
- 数据卷持久化,禁止
down -v(会清空本地库);需要干净环境用 pnpm db:reset(先备份再重建)。
.env.local 示例:DATABASE_URL=postgresql://lingxi:lingxi@127.0.0.1:5432/lingxi_dev、REDIS_URL=redis://127.0.0.1:6379、S3_ENDPOINT=http://127.0.0.1:9000;真实密码只出现在 staging / prod 的密钥里。
- CI 用 Testcontainers 起同版本 PG 18 + Redis + MinIO,与本地一致,避免「本地能跑、CI 挂」。
- 开发机
shared_buffers / shm_size / 磁盘 IOPS 都远小于生产,性能结论不在本地下,压测一律在 staging 跑。
4.2 部署拓扑
4.3 资源规格(生产,起步配置)
| 组件 |
规格 |
数量 |
说明 |
| 负载均衡 / EIP |
共享型 SLB,100 Mbps 弹性带宽 |
1 |
出网与下载共用 |
| 应用节点(api + web) |
4C8G,ESSD 100 GB |
2 |
无状态,挂 SLB |
| Worker 节点 |
8C16G,ESSD 200 GB(临时盘用本地盘更佳) |
2 |
图片 / 视频 / 导出混合部署,按队列并发控制 |
| PostgreSQL 18(自建) |
4C8G,200 GB ESSD 数据盘,每日全备 + WAL 归档(PITR 7 天) |
1 主(M1 起 +1 流复制从库) |
业务库;审计表分区;独立数据节点,不与 worker 混部 |
| Redis 7.4(自建) |
2G,主从 + AOF everysec |
1 主 1 从 |
缓存 + 队列 + 限流;独立数据节点 |
| MinIO |
4C8G + 2 × 1 TB ESSD |
2 节点 |
纠删码;archive 桶可转 OSS 低频 |
| OSS |
标准存储 + 低频 |
按量 |
冷备与审计归档 |
| NAT 网关 |
按量 |
1 |
AI 调用出网,固定出口 IP 便于供应商白名单 |
| 监控 |
可观测套件(托管或 2C4G 自建) |
1 |
Prometheus + Grafana + Loki |
量级估算(起步配置下的余量):
| 资源 |
日均消耗 |
余量判断 |
| vCPU(图片合成) |
2000 张 × 3s ≈ 1.7 核时 |
16 核可用,余量 > 80% |
| vCPU(视频拼接) |
500 条 × 40s ≈ 5.6 核时 |
同上 |
| 出网(AI 调用下载中间产物) |
约 10–30 GB/日 |
100 Mbps 弹性足够 |
| 出网(用户下载) |
5 GB/日 |
足够 |
| 数据库 IOPS |
< 500 |
ESSD 起步盘(3000 IOPS)足够 |
明确写"起步配置"而不是"最终配置":真正的规格应在 M1 上线后用 2 周真实数据校准,避免按峰值过度采购。
4.4 网络与安全组
| 项 |
配置 |
| VPC |
1 个 VPC,2 个子网(应用子网 / 数据子网) |
| 安全组 |
web:仅 80/443 入站;api:仅来自 web 安全组;worker:无入站;db/redis/minio:仅来自应用与 worker 安全组 |
| 出网 |
NAT 网关统一出网;固定 EIP,便于 AI 供应商 IP 白名单 |
| 域名与证书 |
内部域名 + 公网域名各一;证书用云托管证书自动续期 |
| 备案 |
公网域名需 ICP 备案(QT-01) |
| 数据库 |
不开放公网;仅内网访问;开启审计日志 |
| 对象存储 |
桶私有,仅签名 URL 可读;管理台仅内网访问 |
4.5 容器与编排
M0 用 Docker Compose,服务清单:
| 服务 |
镜像 |
启动要点 |
web |
nginx:alpine + 前端产物 |
反代 /api 到 api:3000;/files 直接回源签名 URL(不代理大文件) |
api |
node:24.15.0-bookworm-slim + 构建产物 |
prisma migrate deploy 由独立 init 容器执行,不在 api 启动时跑 |
worker-script / worker-image / worker-video / worker-export |
同 api 镜像,不同入口 + ffmpeg 层 |
每个容器限制 CPU/内存;--concurrency 由环境变量控制 |
scheduler |
同 api 镜像 |
Redis 分布式锁保证单实例 |
minio |
minio/minio + 独立数据盘 |
纠删码模式;健康检查 /minio/health/live |
postgres |
postgres:18 + 独立数据盘 |
健康检查 pg_isready;shared_buffers 取内存 25%;WAL 归档目录挂载到独立卷;不与 worker 同机(M0 可同机但不推荐) |
redis |
redis:7.4 + 数据卷 |
appendonly yes;队列逻辑库 maxmemory-policy noeviction(避免淘汰任务消息);健康检查 redis-cli ping |
otel-collector / prometheus / loki / grafana |
官方镜像 |
仅内网暴露 |
运行约束:
- 所有容器
restart: unless-stopped,健康检查必须真实(API 检查 DB 与 Redis,不只看端口)。
- worker 容器设置
shm_size 与临时目录挂载,避免 ffmpeg 写满磁盘。
- 日志统一输出 stdout(JSON),由 Loki 采集;不在容器内落文件。
- 镜像内固定 ffmpeg 与字体(中文字体必须预置,否则字幕乱码——常见坑)。
4.6 CI/CD
| 阶段 |
动作 |
门禁 |
| PR |
lint + typecheck + 单测 + 迁移文件审查 |
必须通过;迁移文件需 DBA 视角 review |
| 合并到 main |
构建镜像(tag = commit sha)+ 推镜像仓库 |
— |
| 部署 staging |
compose pull && up -d + 迁移 + 冒烟测试 |
冒烟失败自动回滚上一镜像 |
| 部署 prod |
手动触发(首次),灰度:先 api-1,再 api-2 |
观察 10 分钟错误率与 P95 |
| 回滚 |
镜像回退 + 迁移只做"可向前兼容"的变更 |
见下方迁移策略 |
数据库迁移策略(expand / contract):
- 先加字段(可空或带默认),发布读新字段的代码;
- 回填数据(独立脚本 / 任务);
- 确认无旧代码使用后,再删旧字段(下一个版本)。
- 禁止在同一个发布里"删字段 + 改代码",否则回滚即数据事故。
4.7 备份与容灾
| 对象 |
策略 |
RPO |
RTO |
| PostgreSQL(自建) |
每日全备 + WAL 连续归档到 MinIO(PITR 7 天)+ 每周异地快照 |
≤ 5 分钟 |
≤ 4 小时 |
| Redis(自建) |
每日 RDB 快照 + AOF 保留 7 天(队列消息可重建、缓存可丢) |
≤ 1 小时 |
≤ 1 小时 |
| MinIO(assets / renders) |
每日跨桶复制到 OSS;版本化保留 5 版 |
≤ 24 小时 |
≤ 4 小时 |
| MinIO(temp) |
不备份(可重生成) |
— |
— |
| 配置与密钥 |
配置存 Git(脱敏),密钥存 KMS |
— |
≤ 1 小时 |
| 审计日志 |
分区表 + 月度归档到 archive 桶 |
≤ 1 天 |
≤ 8 小时 |
演练:每季度一次"从备份恢复一个客户的数据"演练,验证 RTO;演练记录进审计。
4.8 扩容与成本控制
| 信号 |
阈值(建议) |
动作 |
| 队列等待 P95 |
> 10 分钟 |
worker 水平扩 1 台 |
| API P95 |
> 800ms 持续 10 分钟 |
api 扩到 3 台 |
| 出网带宽 |
持续 > 70% |
提升带宽或加 CDN |
| 存储增长 |
月增 > 20% |
中间产物生命周期收紧 / 冷数据转低频 |
| 单日 AI 成本 |
> 预算 80% |
告警;> 100% 自动降级到备用低价模型(QT-08) |
| 数据库 CPU |
> 70% 持续 15 分钟 |
慢查询治理 → 升配 |
成本控制三条硬规则:
- 任何批量任务提交前显示预估成本(PRD R-06),接口层强制;
- 账本按"客户 / 批次 / 模型"三维聚合,成本看板每日更新;
- 中间产物(关键帧、单镜、TTS)30 天清理,避免"跑一年存十年"。
4.9 安全加固清单(上线前)
5 数据模型
5.1 建模原则
- 客户是隔离单元:所有业务表带
customer_id(NOT NULL + 外键);行业由客户派生,冗余 industry_id 便于按行业统计与索引。
- 不可变优先:反映"当时发生了什么"的表(任务、账本、审计、快照)只追加不修改;反映"当前状态"的表才允许 UPDATE。
- 版本显式化:资产、schema、脚本、提示词都有独立版本表;业务记录存
xxx_version_id,不存"最新版本"的隐式引用。
- 动态值不污染主表:要素卡的值存
JSONB,可检索字段物化到 brief_card_index,报表与筛选走物化表。
- 枚举来自字典:失败原因、淘汰理由、平台规格等用字典表 + 快照值(
*_snapshot),不把中文写进代码。
- 软删除 + 归档:业务数据一律软删除(
deleted_at),物理删除只走"数据保留策略"任务并留审计。
- 审计与业务解耦:审计写独立表(append-only),不放在业务表的
updated_by 字段里凑合。
- ID 策略:业务主键用 ULID(有序、可读、无中心),对外暴露的 ID 可直接使用;不使用自增 ID 暴露给前端(避免枚举)。
5.2 核心实体关系
5.3 表清单
| 域 |
表 |
说明 |
关键约束 |
| 组织权限 |
departments |
部门 / 小组两级 |
parent_id 最多两级 |
| 组织权限 |
users |
内部账号 |
手机号唯一;离职停用不删除 |
| 组织权限 |
roles |
可配置角色 |
超管 is_system=true 不可改 |
| 组织权限 |
permissions |
权限点目录(代码注册 + 表同步) |
code 唯一,如 script:review |
| 组织权限 |
role_permissions |
角色 → 权限点 |
复合主键 |
| 组织权限 |
user_roles |
用户 → 角色(可多) |
复合主键 |
| 组织权限 |
data_scopes |
角色数据范围 |
scope_type + 目标 ID |
| 客户与行业 |
industries |
行业目录(两级树:一级 → 二级) |
一级名称全局唯一;二级同一父级下唯一;最多两级 |
| 客户与行业 |
customers |
客户 |
(industry_id, name) 唯一;short_name 必填 |
| 客户与行业 |
customer_groups |
客户组 |
授权与范围最小单位 |
| 客户与行业 |
customer_group_members |
客户组 ↔ 客户 |
复合主键 |
| 客户与行业 |
customer_fields |
客户级字段扩展与审批 |
每客户 ≤ 5 个进入生成字段 |
| 行业包 |
industry_packs |
行业包(逻辑实体) |
与行业 1:1 或 1:N |
| 行业包 |
industry_pack_versions |
版本与状态机 |
发布后不可变 |
| 行业包 |
form_schemas |
要素卡 schema(JSON Schema + UI 元数据) |
版本内 key 唯一、字段 ≤ 60 |
| 行业包 |
compliance_rules |
校核与合规规则集 |
按行业包版本 |
| 行业包 |
prompt_templates |
提示词模板(版本、生效范围) |
引用版本号 |
| 脚本 |
brief_cards |
要素卡实例(动态值) |
存 form_schema_id 快照引用 |
| 脚本 |
brief_card_index |
可检索字段物化 |
每卡每字段一行,带类型值 |
| 脚本 |
selling_points |
卖点库(行业级 / 客户级) |
scope + customer_id 可空 |
| 脚本 |
script_templates |
脚本模板(结构化) |
版本化 |
| 脚本 |
scripts |
脚本实例(当前状态) |
客户归属、状态机 |
| 脚本 |
script_versions |
脚本内容版本 |
已入库修改产生新版本 |
| 脚本 |
script_serials |
编号取号序列(客户 + 日期) |
原子自增 + 行锁,编号唯一 |
| 脚本 |
structure_templates |
结构模板库(人工 / AI 挖掘) |
状态机 + usage_count |
| 脚本 |
script_references |
脚本 → 引用脚本 / 事实来源 |
唯一约束,用于溯源与权重 |
| 脚本 |
review_batches |
客户审核批次 |
access_token 唯一 + 过期时间 |
| 脚本 |
review_changes |
客户改稿前后快照 |
保留原稿与操作人 |
| 脚本 |
handoffs |
交付包(Word / 飞书)记录 |
关联脚本与文件 key |
| 素材 |
media_tasks |
任务头 |
状态机见 3.6;成本预估字段 |
| 素材 |
media_items |
任务项(1 图 / 1 视频) |
幂等键唯一 |
| 素材 |
shots |
镜头(视频线最小单元) |
归属 media_item_id |
| 素材 |
render_profiles |
编码/规格配置 |
数据化,不写死 |
| 素材 |
preview_marks |
勾选/淘汰记录 |
含淘汰理由(字典) |
| 素材 |
templates / template_versions |
PSD 模板与其版本(layer_config 快照、预览图、规格绑定) |
解析失败不入库;历史批次按版本快照复现 |
| 素材 |
production_batches / production_tasks |
图片批次与任务项(参数快照、命名快照、进度与失败明细) |
批次快照保证历史可复现;任务项支持单项重试 |
| 素材 |
materials |
素材主对象(图片 / 视频;规格是属性不是对象) |
客户归属 + 状态机;与脚本多对多 |
| 素材 |
material_versions |
素材版本(v1..vn) |
每素材至多一个采用版本(部分唯一索引) |
| 素材 |
material_specs |
规格明细(平台 / 比例 / 分辨率 / 时长上限 / 命名后缀) |
与版本一对多 |
| 素材 |
material_files |
对象 key、hash、大小、mime、时长 / 分辨率 / 首帧 |
hash 唯一索引(秒传) |
| 素材 |
material_links |
素材 ↔ 脚本(多对多) |
复合唯一;禁止 JSON 列存脚本 ID |
| 素材 |
upload_sessions |
分片上传会话(断点续传) |
过期清理;与 media_items 无关 |
| 交付 |
exports |
导出任务 |
状态 + 包大小 + 签名 key |
| 交付 |
export_items |
导出项 |
关联 media_item_id |
| 交付 |
delivery_manifests |
交付清单快照 |
不可变 |
| 交付 |
review_records |
送审批次与结论(内部 / 外部共用) |
审核基准 = 提交时冻结版本 |
| 交付 |
external_submissions |
外部平台提审登记(平台 / 时间 / 状态 / 驳回原因 / 凭证) |
非投放 API 直连 |
| 交付 |
share_links |
分享链接(token 哈希、有效期、撤销时间、限流桶) |
只存 token 哈希;撤销即失效 |
| 效果 |
placements |
投放登记(平台 / 账户 / 素材 / 起止) |
素材外键;手工登记为主 |
| 效果 |
material_metrics_daily |
素材日指标(消耗 / 曝光 / 点击 / CTR / CVR / 转化 / ROI) |
按日分区;派生指标不落库 |
| 资产 |
assets |
15 类资产(asset_type 区分) |
客户归属 + 软删除 |
| 资产 |
asset_versions |
资产版本 |
文件 key、元数据 |
| 资产 |
asset_refs |
引用关系(谁用了谁) |
唯一约束 + 计数缓存 |
| 资产 |
asset_reviews |
复审任务 |
到期降权 |
| 平台 |
dict_types / dict_items |
数据字典 |
变更只影响新数据 |
| 平台 |
ai_providers |
供应商(Base URL / 密钥密文 / 状态) |
密钥只存密文,出参 hasApiKey |
| 平台 |
ai_models |
模型(上下文 / 最大输出 / 默认参数 JSON) |
归属供应商;参数 JSONB |
| 平台 |
ai_task_mappings |
任务类型 → 主 / 备模型 + 任务参数 |
task_type 唯一,主模型必填 |
| 平台 |
ai_call_logs |
调用账本(append-only) |
按月分区,禁 UPDATE / DELETE |
| 平台 |
ai_configs / ai_config_versions |
提示词 / 行业指南等键值配置与版本 |
每键版本自增,可回滚 |
| 平台 |
ai_routes |
客户级覆盖路由(P1) |
与 ai_task_mappings 合并求值 |
| 平台 |
ai_usage |
调用账本(append-only) |
按月分区 |
| 平台 |
cost_budgets |
预算与告警阈值 |
按角色/客户/月 |
| 平台 |
audit_logs |
审计(append-only) |
按月分区,禁 UPDATE/DELETE |
| 平台 |
system_configs |
系统配置(命名、导出、通知) |
带版本与变更审计 |
| 平台 |
system_config_versions |
系统配置变更历史(append-only:前后值 / 原因 / 操作人) |
A4 新增;配置版本与审计可查 |
| 平台 |
outbox_events |
事务性事件投递 |
已投递标记 |
| 平台 |
idempotency_keys |
请求幂等 |
TTL 24 小时 |
说明:脚本批量生成复用 media_tasks / media_items(type=script),与素材共用任务协议、SSE 进度与成本账本,因此不单独建「脚本任务表」(对应 6.3.4 的 /script-tasks 接口)。
5.4 关键表字段设计
5.4.1 customers(客户,隔离单元)
| 字段 |
类型 |
说明 |
id |
ulid PK |
— |
industry_id |
ulid FK → industries |
不可为空;必须指向末级行业(有子行业的一级行业不可选);跨行业移动禁止(应用层 + 触发器可选) |
name |
text |
同行业内唯一 |
short_name |
text |
必填,用于自动命名,需满足通用字符规则 |
status |
enum |
在服务 / 暂停 / 结束 |
industry_pack_version_id |
ulid FK |
绑定的行业包版本(可被客户覆盖层覆盖) |
owner_user_id |
ulid FK → users |
客户负责人 |
preferences |
jsonb |
命名习惯、目录结构、常用规格、素材禁忌 |
custom_fields |
jsonb |
基础扩展字段(≤ 10,仅检索/报表) |
created_at / updated_at / deleted_at |
timestamptz |
软删除 |
索引:(industry_id, name) 唯一;(owner_user_id);(status)。
5.4.2 brief_cards + brief_card_index(动态表单)
| 表 |
字段 |
说明 |
brief_cards |
id / customer_id / form_schema_id / schema_version / values jsonb / status |
values 只存字段 key → 值;不存字段中文名(中文来自 schema 快照) |
brief_card_index |
card_id / field_key / value_text / value_num / value_date |
只有 schema 声明"可检索"的字段才写入;用于筛选与报表 |
为什么不用「每行业一张表」:行业包要能在 5 个工作日内上线(PRD 5.4.7),每接行业就改表结构意味着发版 + 迁移;JSONB + 物化索引让"加字段"退化为数据操作。
如何保证查询性能:筛选高频字段(平台、状态、时间、客户)走固定列;要素卡的可检索字段(如价格区间、品类)走 brief_card_index 的复合索引 (customer_id, field_key, value_num)。
5.4.3 media_tasks / media_items / shots(异步任务三层)
| 表 |
关键字段 |
说明 |
media_tasks |
id / customer_id / type(image / video / script)/ status / params jsonb / estimate_cost / actual_cost / created_by |
任务头;存提交时的参数快照 |
media_items |
id / task_id / seq / idempotency_key / status / asset_refs jsonb / result_asset_id / checks jsonb / error_code |
幂等键唯一;checks 存校验结果 |
shots |
id / media_item_id / seq / script_version_id / prompt_version_id / keyframe_key / clip_key / audio_key / status |
单镜可替换、可重试;对象 key 直接落库,回收站可定位孤立对象 |
任务项状态与产物分离:media_items 只记状态与结果引用,产物文件在对象存储;这样"重跑失败项"只改状态,不复制数据。
5.4.4 assets + asset_versions + asset_refs
| 字段 |
说明 |
assets.asset_type |
15 类枚举:industry_pack / script_template / video_direction / script / image_template / logo / product_position / warning_text / persona_pool / scene_pool / lighting_preset / bgm / prompt_template / brief_card_template / render |
assets.scope |
行业级 / 客户级 / 批次级(PRD 5.3.2 三级) |
assets.version_id |
指向当前启用的版本 |
asset_versions.file_key |
对象存储 key |
asset_versions.meta |
尺寸、时长、图层信息、授权到期日等 |
asset_refs |
(source_asset_version_id, target_type, target_id);删除前检查 + 计数缓存 |
删除保护实现:asset_refs 有引用时,删除接口返回 409 + 引用清单(PRD R-02);同时数据库外键 RESTRICT 兜底。
5.4.5 audit_logs(append-only)
| 字段 |
类型 |
说明 |
id |
ulid |
— |
occurred_at |
timestamptz |
分区键(按月) |
actor_user_id |
ulid |
操作人(系统操作为空) |
action |
text |
如 export.create、role.update、industry_pack.publish |
target_type / target_id |
text / ulid |
对象 |
customer_id |
ulid |
涉及客户(可空) |
before / after |
jsonb |
前后值(敏感字段脱敏) |
ip / user_agent / trace_id |
text |
追溯 |
result |
enum |
success / denied |
约束:应用账号只有 INSERT;REVOKE UPDATE, DELETE;查询走只读副本(P2);月度分区 + archive 桶归档。
5.5 客户数据隔离的三层实现
| 层 |
机制 |
兜底能力 |
| 库层 |
所有业务表 customer_id NOT NULL + 外键;跨表查询必须经客户路径 |
防止"忘记过滤"导致的写入污染 |
| 应用层 |
ScopeContext(用户可见客户集合)由守卫解析,注入 Repository;Repository 方法签名强制携带 |
编译期强制,评审可检查 |
| 数据层 |
P1 开启 PG Row Level Security:current_setting('app.customer_ids') 与行 customer_id 匹配 |
防止绕过应用的直连查询(BI / 运维) |
ScopeContext 解析顺序(取交集):
- 用户是否有效、角色是否停用(
permission.changed 事件使会话失效);
- 角色数据范围 → 客户集合(全部 / 本部门及下级 / 本部门 / 指定客户组 / 负责客户 / 本人创建);
- 请求显式指定的客户(如
/customers/{id}/...)必须落在集合内,否则 403;
- 多角色取并集,再与"显式指定"取交集。
5.6 版本与快照策略
| 对象 |
版本化对象 |
业务记录保存什么 |
历史如何解释 |
| 行业包 |
industry_pack_versions |
版本 ID |
按当时版本渲染与校核(PRD R-11) |
| 要素卡 schema |
form_schemas |
form_schema_id |
字段弃用仍可解释历史值 |
| 脚本 |
script_versions |
script_version_id |
审核记录绑定版本 |
| 资产 |
asset_versions |
asset_version_id |
被引用时记录版本号 |
| 提示词 |
ai_prompts |
prompt_version_id |
可复现"这条素材用了哪版提示词" |
| 字典 |
dict_items |
*_snapshot 文本 |
历史统计不失真(PRD R-10) |
| 交付清单 |
delivery_manifests |
快照 JSON |
导出当时的完整信息 |
5.7 分区、索引与查询约定
| 表 |
分区键 |
保留 |
说明 |
audit_logs |
月 |
≥ 12 个月(热 3 个月) |
超期归档到对象存储,可检索索引保留 |
ai_usage |
月 |
≥ 24 个月 |
成本趋势与对账 |
media_items |
月(按 created_at) |
热 6 个月 |
历史明细归档 |
outbox_events |
无 |
7 天 |
已投递即清理 |
查询约定:
- 列表一律游标分页(
(created_at, id)),不用 OFFSET 深翻页;
- 计数类走缓存或近似值,不做实时
COUNT(*) 大表;
- 报表查询走只读副本(P2)或预聚合表(
cost_daily / output_daily);
- 禁止
SELECT * 返回大 JSONB 字段到列表接口(列表只返回摘要);
- 脚本检索用全文索引(
pg_trgm GIN 或分词插件)+ 结构化条件(行业 / 客户 / 方向 / 卖点);M1–M2 不引入向量检索,M3 起在同一个 PG 18 内启用 pgvector(HNSW、维度 1024、cosine),语义块存 script_chunks,向量访问全部收敛到 VectorStore 接口(upsert / search / delete / hybridSearch);迁移 Qdrant 的触发条件:向量 > 1000 万、P95 > 200ms 且调优无效、需要多向量字段 + 复杂过滤 + > 200 QPS。参考脚本检索结果缓存 5 分钟(键 = 条件哈希)。
- 生效阈值只从
system_configs 读取(爆量消耗阈值、审核超时等),前端上限、DTO 校验、任务判定共用同一 getter,禁止在切片内写常量;派生指标(汇总 / 均值 / 占比)查询时计算,不落库。
5.8 数据保留与删除
| 场景 |
策略 |
| 客户暂停 |
全部只读;不能发起新生成(PRD 5.4.2) |
| 客户结束 |
只读归档;资产转"只读资产";数据保留至合同约定到期 |
| 用户离职 |
停用账号,名下客户转交(PRD 5.4.1) |
| 资产删除 |
未引用可删(软删 + 30 天后物理清理);被引用只能停用 |
| 任务与产物 |
成片永久(随资产);中间产物 30 天;失败任务记录保留 90 天 |
| 审计 |
≥ 12 个月,归档后仍可检索 |
| 数据主体删除请求 |
按 QT-11 流程执行,删除前导出审计留痕 |
6 接口设计
6.1 风格与约定
| 约定 |
规则 |
| 风格 |
REST,路径 /api/v1/...;资源名复数;动作用子资源或 :action |
| 版本 |
URL 版本号;破坏性变更升 v2,不做 header 协商 |
| 认证 |
Authorization: Bearer <access_token>;刷新走 /auth/refresh |
| 幂等 |
写接口支持 Idempotency-Key 头(24 小时去重);任务提交必带 |
| 分页 |
?cursor=xxx&limit=50;返回 next_cursor |
| 排序 |
?sort=-created_at(前缀 - 降序) |
| 过滤 |
显式白名单参数,禁止把 JSON 直接拼进查询 |
| 错误 |
统一结构 { code, message, details, trace_id };HTTP 状态语义化 |
| 时间 |
全部 ISO 8601 + 时区;存储 UTC |
| 大字段 |
列表不返回大文本 / 大 JSONB;详情接口按需 ?fields= |
错误码分段:
| 段 |
含义 |
示例 |
| 400xx |
参数与校验 |
40001 字段校验失败、40002 schema 版本不匹配 |
| 401xx |
认证 |
40101 token 过期、40102 账号停用 |
| 403xx |
授权 |
40301 无权限点、40302 数据范围外、40303 特殊权限缺失、40304 职责互斥冲突 |
| 404xx |
不存在 |
40401 资源不存在或不可见(统一措辞,避免探测) |
| 409xx |
冲突 |
40901 资产被引用、40902 名称重复、40903 状态不允许 |
| 422xx |
业务规则 |
42201 必填字段缺失、42202 超出批量上限、42203 预算不足 |
| 429xx |
限流 |
42901 供应商限流、42902 平台并发上限 |
| 500xx |
系统 |
50001 内部错误、50002 外部模型不可用 |
6.2 鉴权与权限校验链
| 守卫 |
职责 |
失败返回 |
JwtGuard |
验签、过期、jti 吊销 |
40101 / 40102 |
PermissionGuard |
权限点(按角色聚合) |
40301 |
SpecialPermissionGuard |
导出 / AI 配置 / 审计 / 发布等 |
40303 |
ScopeGuard |
解析客户集合,注入请求上下文 |
40302 |
SoDGuard(配置类接口) |
保存角色 / 发布行业包时互斥校验 |
40304 |
RateLimitGuard |
每用户 / 每客户并发与频次 |
42902 |
6.3 核心端点清单
只列主链路端点;完整清单在 OpenAPI 中生成。权限点列表示"最低要求"。
6.3.1 认证与账号(identity)
| 方法 |
路径 |
权限点 |
说明 |
| POST |
/auth/login |
— |
账号密码 / SSO 回调 |
| POST |
/auth/refresh |
— |
刷新 access(旋转 refresh) |
| POST |
/auth/logout |
— |
吊销会话 |
| GET |
/me |
— |
当前用户、角色、权限点、可见客户范围 |
| GET/POST/PATCH |
/users、/users/{id} |
user:view / user:manage |
用户管理;离职停用 |
| GET/POST/PATCH |
/roles、/roles/{id} |
role:view / role:manage |
角色配置;保存时 SoD 校验 |
| GET |
/permissions |
role:view |
权限点目录(前端渲染勾选树) |
| POST |
/users/{id}/transfer-customers |
customer:transfer |
客户转交 |
6.3.2 客户与行业(client)
| 方法 |
路径 |
权限点 |
说明 |
| GET/POST/PATCH |
/industries、/industries/{id} |
customer:read / customer:manage |
两级行业树;删除前校验无子行业 / 客户 / 资产;有启用子行业禁止停用 |
| GET/POST/PATCH |
/customers、/customers/{id} |
customer:view / customer:manage |
客户;short_name 必填 |
| GET/POST |
/customer-groups、/customer-groups/{id}/members |
customer_group:manage |
客户组与成员 |
| POST |
/customers/{id}/apply-industry-pack |
customer:manage |
套用行业资产包(冷启动) |
| POST/POST |
/customers/{id}/fields、/customer-fields/{id}/approve |
customer_field:apply / customer_field:approve |
客户级字段扩展与审批 |
6.3.3 行业包与表单(industry)
| 方法 |
路径 |
权限点 |
说明 |
| GET/POST |
/industry-packs、/industry-packs/{id}/versions |
industry_pack:view / industry_pack:edit |
行业包与版本 |
| GET/PUT |
/industry-packs/{id}/versions/{ver}/schema |
industry_pack:edit |
保存草稿 schema |
| POST |
/industry-packs/{id}/versions/{ver}/validate |
industry_pack:edit |
自动校验(变量/联动/上限) |
| POST |
/industry-packs/{id}/versions/{ver}/review |
industry_pack:review |
复核 |
| POST |
/industry-packs/{id}/versions/{ver}/publish |
industry_pack:publish |
发布(特殊权限) |
| GET |
/industry-packs/{id}/versions/{ver}/export |
industry_pack:view |
导出 JSON |
| GET |
/form-schemas/{id} |
customer:view |
前端渲染要素卡表单(按客户解析到实际版本) |
6.3.4 脚本(script)
| 方法 |
路径 |
权限点 |
说明 |
| GET/POST/PATCH |
/brief-cards、/brief-cards/{id} |
brief_card:view / brief_card:edit |
要素卡(动态字段) |
| GET/POST |
/selling-points |
selling_point:view / selling_point:edit |
卖点库 |
| GET/POST/PATCH |
/script-templates、/script-templates/{id} |
script_template:view / script_template:edit |
脚本模板与版本 |
| POST |
/script-tasks |
script:generate |
提交批量生成(返回 taskId + 预估成本) |
| GET |
/script-tasks/{id} |
script:view |
任务状态与逐条结果 |
| POST |
/scripts/{id}/approve、/scripts/{id}/reject |
script:review |
审核;批量走 /scripts:batch-review |
| POST |
/scripts/{id}/regenerate |
script:generate |
局部重写 / 整条重生成 |
| GET |
/scripts |
script:view |
检索(含标签、引用) |
| POST |
/scripts/{id}/assign-code |
script:review |
过审时分配脚本编号(幂等,重复调用返回同号) |
| GET |
/scripts/{id}/versions/{v} |
script:view |
版本内容与 diff |
| POST |
/structure-templates、/structure-templates/{id}/publish |
script_template:edit |
结构模板库;AI 挖掘产物发布 |
| POST |
/scripts/{id}/handoff |
script:export |
生成 Word 交付包(异步,返回文件 key) |
| POST |
/review-batches |
script:review |
生成客户审核链接(返回 token 与二维码内容) |
| GET |
/public/review/{token} |
无需登录 |
客户审核页数据(校验签名与有效期) |
| PATCH |
/public/review/{token}/scripts/{id} |
无需登录 |
客户改稿,写 review_changes 快照 |
/public/review/* 是唯一的无 JWT 路由组:不经过用户守卫,但必须校验 HMAC 签名、有效期与范围,并单独限流(IP + token 双维度);链接可随时作废,作废后立即 410。
6.3.5 素材(media)
| 方法 |
路径 |
权限点 |
说明 |
| POST |
/media-tasks |
media:generate |
提交图片 / 视频任务(返回 taskId、预估成本与条数) |
| GET |
/media-tasks、/media-tasks/{id} |
media:view |
任务列表 / 详情(含逐项状态) |
| GET |
/media-tasks/{id}/events |
media:view |
SSE 进度流(也支持轮询) |
| POST |
/media-tasks/{id}/cancel |
media:generate |
取消 |
| POST |
/media-tasks/{id}/retry |
media:generate |
仅重试失败项 |
| GET |
/media-items/{id}/preview |
media:view |
签名 URL + 缩略图 + Range |
| POST |
/media-items/{id}/mark |
media:review |
勾选 / 淘汰(含理由、快捷键批量) |
| POST |
/media-items/{id}/shots/{seq}/regenerate |
media:generate |
单镜替换 |
| POST |
/exports |
export:create |
提交导出(打包任务) |
| POST |
/uploads/presign |
material:upload |
预签名直传;入参 hash,命中则返回秒传结果 |
| POST |
/uploads/{uploadId}/complete |
material:upload |
校验分片(headObject)→ 写 material_files → 入 media:post 队列 |
| GET/POST |
/materials |
material:view / material:edit |
素材列表(游标分页)/ 登记外部成片 |
| POST |
/materials/{id}/versions |
material:edit |
重传新版本 / 标记采用版本 |
| POST |
/materials/{id}/links |
material:edit |
关联 / 解除关联脚本(多对多) |
| POST |
/materials/{id}/reviews |
material:review |
提交送审批次(内部审核) |
| POST |
/external-submissions |
material:review |
登记外部平台提审结果 |
| POST |
/share-links |
material:share |
签发分享链接(返回一次性 URL) |
| DELETE |
/share-links/{id} |
material:share |
撤销(立即失效) |
| GET |
/public/share/{token} |
无(签名校验) |
免登录预览;独立前缀 + IP/token 限流 + 访问审计 |
| POST |
/placements |
metric:import |
投放登记 |
| POST |
/metrics/import |
metric:import |
日指标导入(手工 / 平台导出文件) |
6.3.6 资产与交付(catalog / delivery)
| 方法 |
路径 |
权限点 |
说明 |
| GET/POST/PATCH |
/assets、/assets/{id} |
asset:view / asset:edit |
15 类资产统一入口(?type=) |
| POST |
/assets/{id}/versions |
asset:edit |
新版本 |
| DELETE |
/assets/{id} |
asset:delete |
未引用才可删;返回引用清单 |
| GET |
/assets/{id}/refs |
asset:view |
引用关系(双向) |
| GET |
/exports/{id} |
export:create |
导出进度与清单 |
| GET |
/exports/{id}/download |
export:create |
302 到签名 URL(zip 流) |
| GET |
/delivery-manifests/{id} |
export:create |
交付清单 |
6.3.7 平台管理(platform)
| 方法 |
路径 |
权限点 |
说明 |
| GET/POST/PATCH |
/ai/providers、/ai/routes、/ai/prompts |
ai:view / ai:configure |
模型、路由、提示词 |
| GET/PUT |
/ai/budgets |
ai:configure |
额度与预算 |
| GET |
/ai/usage |
ai:view |
账本查询(按客户 / 模型 / 批次) |
| GET/POST/PATCH |
/dicts、/dicts/{type}/items |
dict:manage |
字典(A2 定稿的 30 个权限点中字典为单个 dict:manage,见 ADR-001) |
| GET |
/audit-logs |
audit:view |
审计检索(只读) |
| POST |
/audit-logs/export |
audit:export |
审计导出(自身也审计) |
| GET/PUT |
/system/configs |
system:configure |
命名规则、导出设置、通知 |
| GET |
/dashboards/output、/cost、/quality、/asset |
对应看板权限点 |
看板数据(预聚合) |
6.4 异步任务协议(所有耗时操作统一)
| 环节 |
约定 |
| 提交 |
POST /xxx-tasks,带 Idempotency-Key;响应 { taskId, estimatedItems, estimatedCost, estimatedSeconds } |
| 进度 |
优先 SSE:GET /xxx-tasks/{id}/events(text/event-stream,事件类型 item / progress / done / error);降级为轮询 GET /xxx-tasks/{id} |
| 结果 |
逐项状态在任务详情返回;产物通过签名 URL 获取,不把二进制塞进 JSON |
| 取消 |
POST .../cancel:任务头置取消,已投递消息在 worker 侧快速返回 |
| 重试 |
POST .../retry:只重跑失败项;幂等键保证不重复计费 |
| 断线恢复 |
前端记录 lastEventId,重连后从断点续推(SSE Last-Event-ID) |
为什么用 SSE 而不是 WebSocket:进度是单向推送,SSE 基于 HTTP/2 更简单、可穿代理、断线重连语义原生;WebSocket 只在需要双向(如实时协同)时才引入。
6.5 文件上传与下载
上传(预签名直传):
- 前端
POST /assets/upload-intents,带文件名、大小、MIME → 后端校验类型/大小/配额,返回预签名 PUT URL 与对象 key;
- 前端直传对象存储(不经过 API,避免大文件占内存);
- 前端
POST /assets 提交对象 key 与元数据 → 后端 HEAD 校验(大小、类型、是否存在)→ 入队 media:verify(图片重编码 / 视频探测 / 病毒扫描可选)→ 资产进入"草稿"。
下载:
GET /exports/{id}/download 校验权限与状态 → 302 到 15 分钟签名 URL;
- 大包走导出任务生成,不在请求内同步打包;
- 支持 Range(断点续传),
Content-Disposition 带文件名(按命名规则生成后的名称)。
6.6 AI 回调与幂等
| 项 |
约定 |
| 回调地址 |
/api/v1/callbacks/ai/{provider}(无用户态,验签而非 JWT) |
| 验签 |
供应商签名头 / 共享密钥 HMAC;失败记 401 并审计 |
| 幂等 |
以 provider_job_id 为唯一键更新任务项;重复回调直接 200 |
| 竞态 |
回调与轮询可能同时到达:状态迁移用条件更新(WHERE status IN (...)),只允许前进 |
| 超时 |
超过配置时长未回调 → 轮询兜底;再超时 → 标记失败并允许重试 |
6.7 契约与测试
| 项 |
做法 |
| 单一真源 |
DTO 用 Zod 定义,Nest 生成 OpenAPI;前端由 OpenAPI 生成类型与请求客户端 |
| 契约测试 |
CI 中对 OpenAPI 做 breaking change 检测(oasdiff),破坏性变更必须升版本 |
| 权限测试 |
每个端点至少 3 条用例:有权限点 / 无权限点 / 数据范围外 |
| 任务链路测试 |
用 Mock AI Provider(固定延迟与固定产物)跑完整链路,保证 CI 不依赖外网 |
| 性能基线 |
列表接口 P95 < 800ms(生产数据量 10% 的样本库) |
7 关键链路设计
每个链路给"时序 + 关键实现 + 失败处理"。评审时重点看:事务边界在哪、幂等靠什么、失败后数据是否可解释。
7.1 登录与权限上下文
| 要点 |
实现 |
| 权限点聚合 |
用户 → 角色(可多)→ 权限点并集;角色变更发 permission.changed,缓存按事件失效 |
| 数据范围解析 |
data_scopes(scope_type, target_id) 六类:全部客户 / 本部门 / 本部门及下级 / 指定客户组 / 负责客户 / 本人创建;解析结果写入请求上下文与 ScopeContext |
| 可见客户集合上限 |
单用户可见客户 > 500 时,范围片段改为"客户组 + 例外清单"表达,避免 IN (...) 过长 |
| 会话失效 |
access 短、refresh 旋转;停用账号即时在 Redis 置吊销标记,请求侧一次判定 |
| SSO |
预留 OIDC 适配器(企业微信 / 飞书 / 钉钉),登录成功后仍落内部账号,权限体系不变 |
失败处理:数据范围解析失败一律 403(不降级为"全部客户")——这是隔离要求,宁可不可用也不越权。
7.2 脚本批量生成与入库
| 要点 |
实现 |
| 任务表复用 |
脚本任务复用 media_tasks(type=script),与素材共用任务协议、SSE 与成本账,避免两套异步机制 |
| 结果解析 |
要求模型输出 JSON(结构化字段),解析失败先重试 1 次,再失败则落"原文 + 解析失败"标记,交人工处理,不静默丢弃 |
| 校核 7 项 |
分为"阻断项"(必填缺失、时长超限、敏感词)与"提示项"(卖点覆盖不足、重复度高);阻断项自动重生成一次 |
| 变量完整性 |
生成前校验 R-14:要素卡可解析字段集合必须覆盖模板变量,缺一个即拒绝提交(避免生成一半才发现) |
| 入库 |
审核通过 → 写"已入库"版本;后续修改产生新版本,旧版本仍被成片引用 |
| 重复度 |
生成后两两相似度(向量或 n-gram)超阈值即标提示,避免"20 条几乎一样" |
| 生成上下文 |
三段拼装:系统提示词(JSON 契约 + 行业指南)→ 生成条件逐行 → 参考脚本(RAG);行业指南按「精确行业 → 二级回退一级 → default」匹配 |
| 参考检索 |
三级降级:全文索引 → 多维筛选打分 → 同客户近期过审兜底;每条截断 1500 字、最多 10 条、缓存 300s;结果编号供模型回填引用 |
| 候选决策 |
submit:选中项转「待审核」、其余丢弃;stash:全部转草稿;两种都要写操作留痕 |
| 编号分配 |
审核通过事务内按(客户 + 日期)原子取号,编号回填脚本与交付清单;重复调用幂等 |
| 结构化解析 |
JSON schema 校验 8 个字段;失败重试 1 次 → 落原文 + 标记「待人工处理」 |
7.3 图片批量合成(PSD 套版 + 抠图,确定性链路)
图片线不是 AI 生成:模板由设计师用 PS 做(分层 + 命名约定),平台只做「解析图层 → 抠图 → 落位合成 → 编码 → 打包」。这一判断见 8.11 D-11。
图层命名约定(模板能否入库的硬条件):
| 图层名前缀 |
语义 |
渲染行为 |
校验 |
image_replacement_ |
商品图位 |
抠图结果按位等比缩放 / 裁切落位 |
必须能找到边界与锚点 |
clipping_mask_ |
剪贴蒙版容器 |
上层图片被裁进形状(圆 / 圆角矩形 / 文字轮廓) |
必须与上层成对 |
text_replacement_ |
文字位 |
字段值按 PSD 样式绘制;字体从字体库解析 |
字体必须已注册,否则拒绝(不允许回退到 DejaVu) |
| 要点 |
实现 |
| 解析与入库一体 |
ag-psd 读 PSD → 登记可替换层 → 存版本快照;解析失败 / 无命名图层 → 模板不入库(对应 PRD R-02 的图层语义风险,这里直接在校验层堵住) |
| 抠图 |
@imgly/background-removal-node 本地 ONNX(CPU),图片不出内网;按内容 hash 缓存,同图多点复用;失败退原图并写 low_quality=true |
| 合成引擎 |
@napi-rs/canvas(ag-psd 的 canvas 注入)+ sharp 编码;PNG 体积用 colours 控制(quality 对 PNG 无效),JPG 用 quality |
| 字体 |
镜像内置中文字体并注册通用别名(sans-serif / Arial);启动自检缺字体即失败退出,避免「豆腐块静默交付」 |
| 规格矩阵 |
由模板画布尺寸 + 目标规格决定缩放策略;主体区禁止非等比拉伸,越界进 checks |
| 并发 |
队列并发默认 2,按「实测单任务内存 + 可用核数」取小值;超并发只会 OOM 与抢占,没有吞吐收益 |
| 幂等 |
任务项按(模板版本 + 商品 hash + 规格 + 参数快照)幂等;对象按内容 hash 判重 |
| 临时文件 |
临时 PNG 路径必须带随机后缀——毫秒时间戳会在并发下撞名,导致「删除别人的临时文件 → 静默降级」 |
| 失败语义 |
重试 3 次仍失败才判失败;中间轮次不写失败事件(避免用户看到抖动);最终失败保留错误码与原因 |
7.4 视频逐镜生成与拼接
| 要点 |
实现 |
| 镜头级幂等 |
幂等键 = taskItemId:shotSeq:modelVersion;单镜替换只生成新键,不影响其他镜头 |
| 轮询退避 |
1s → 2s → 4s → 8s(上限 15s),最长等待按供应商配置;超时先查一次状态再判失败 |
| 拼接参数 |
统一 H.264 / yuv420p / 30fps / 1080×1920 / faststart;音量归一化到 -16 LUFS,避免不同镜响度跳变 |
| 字幕 |
按脚本字幕与 TTS 时间戳对齐;无时间戳时按字数均分并允许人工微调(P1) |
| AI 标识 |
画面角标(可配置位置与透明度)+ MP4 元数据 comment 字段;media:verify 检查存在性 |
| 失败分类 |
内容违规(不重试,人工改提示词)、限流/超时(重试)、供应商 5xx(换路由)、素材问题(回退到编辑) |
7.5 预览、勾选、命名与导出
| 要点 |
实现 |
| 勾选即状态 |
preview_marks 记录勾选 / 淘汰与理由;前端本地缓存勾选状态,刷新后以服务端为准(PRD 9.1 恢复要求) |
| 命名 |
统一命名服务:占位符解析 → 字符白名单校验 → 冲突追加序号;命名规则进 system_configs,可按客户覆盖 |
| 导出即归档 |
导出成功自动把成片登记为 assets(type=render),后续复用不必再找下载包 |
| 大包 |
超过阈值(如 2 GB 或 500 个文件)改为分卷或在后台生成,前端轮询进度 |
| 审计 |
导出记录含:操作人、客户、勾选项 ID 清单、命名规则版本、包 hash、下载次数 |
7.6 行业包从配置到发布
| 校验项 |
规则 |
失败后果 |
| 字段数与 key |
≤ 60 个字段;key 全局唯一且不可与系统字段冲突 |
禁止保存 |
| 类型白名单 |
仅允许 14 类受支持类型 |
禁止保存 |
| 必填与变量 |
进入生成的必填字段必须映射到变量槽 |
禁止发布 |
| 联动 |
最多 2 级;禁止循环 |
禁止保存 |
| 选项来源 |
字典项必须存在且未停用 |
禁止发布 |
| 合规规则 |
行业包必须至少声明 1 条阻断类合规规则(金融强制) |
禁止发布 |
| 影响面 |
发布前提示受影响客户数与要素卡数 |
仅提示 |
发布事务:版本状态迁移 + schema 快照落库 + 事件 industry.pack.published + 审计写入,同一事务;缓存失效通过事件异步执行,失败可重放(缓存过期兜底)。
7.7 AI 路由、限流、降级与记账
| 错误类型 |
判定 |
动作 |
| 限流(429) |
供应商返回或本地令牌不足 |
指数退避 + 抖动,重试 3 次;持续则切备用模型 |
| 超时 |
超过该用途配置的超时 |
重试 1 次;再失败换路由 |
| 5xx / 网络 |
供应商不可用 |
熔断 60 秒,期间新任务直接走备用 |
| 内容违规 |
供应商安全拦截 |
不重试(重试只会再花钱),落原因并提示修改提示词或素材 |
| 参数错误 |
4xx 语义(非限流) |
不重试,落原因,标记为"配置问题" |
记账规则:调用前预占(estimate),调用后按实际用量结算(settle);预占与结算都写 ai_usage,cost_budgets 按客户 / 角色 / 月聚合,80% 触发告警、100% 阻断新任务(超管可临时放宽,操作留痕)。
配置合并顺序:任务级参数 > 模型级默认参数 > 供应商默认;提示词同理,ai_config 整段覆盖代码兜底常量。任务创建时把「模型 ID + 参数快照 + 提示词版本号」写进任务头,事后可复现。
方言与健壮性:thinking / enable_thinking 由适配器映射,不支持的参数按供应商白名单过滤而不是静默透传;通义要求提示词含 JSON 字样否则 400,DeepSeek 可能返回空 content,统一做「空内容重试 + 解析兜底」;联网搜索作为独立前置步骤(Responses 支持、Chat 静默忽略),结果落库、失败降级不阻断主生成。
7.8 故障场景与恢复矩阵
| 场景 |
用户可见表现 |
系统动作 |
数据一致性 |
| worker 进程崩溃 |
任务卡在"生成中" |
队列消息重回;media_items 有幂等键,已完成项跳过 |
无重复计费;最多漏掉正在执行的那一项,重跑补上 |
| 队列积压 |
进度不动 |
告警;自动扩 worker(P2 自动,前期手动) |
无影响 |
| 供应商限流 |
生成变慢 / 部分失败 |
退避重试、切备用、降低并发令牌 |
失败项可单独重试 |
| 供应商整体不可用 |
大面积失败 |
熔断 + 切换备用路由;前端提示"当前使用备用模型" |
已成功项不重算 |
| 对象存储不可用 |
预览报错、上传失败 |
重试;暂停新任务入队(保护性限流) |
任务项保持"生成中",恢复后续跑 |
| Redis 不可用 |
队列停摆、缓存全部穿透 |
只读降级:列表与详情可看,提交任务返回 503 |
队列丢失的消息靠 outbox 重投 |
| 数据库主从切换 |
短时 5xx |
连接池重连 + 重试 |
事务已提交的不受影响 |
| 浏览器刷新 / 断网 |
进度丢 |
SSE 支持 Last-Event-ID 续推;轮询兜底 |
勾选以服务端为准 |
| 导出中途失败 |
下载 404 / 包不完整 |
任务可重试;清单只在成功时落库 |
不产生"半成品清单" |
| 要素卡 schema 已发布后修改 |
老卡显示异常 |
按 schema_version_id 解释,不回改历史;只允许弃用字段 |
历史卡永远可解释 |
7.9 素材入库、转码与发布链路(外部成片 / 历史素材)
生成产物走 3.4 / 3.5;本节只覆盖从外部进来的成片(实拍、外包制作、历史素材)。参考项目(baoliangmofang)在这一段有可用设计,但其自身没有出图实现,本节是「入库与管理」而非「生成」。
| 步骤 |
关键约束 |
| 预签名 |
校验客户范围与配额;Idempotency-Key 防重复登记;秒传命中不覆盖已有引用 |
| 分片直传 |
单分片 5–16MB;并发 ≤ 4;断点续传按 upload_sessions 恢复;分片 7 天过期 |
| 完成校验 |
必须 headObject 核对大小与 etag,不信任前端声明 |
| 转码 |
统一 H.264 / AAC / faststart;竖版 1080×1920;预览生成 HLS + 水印版,原片不公开 |
| 状态推进 |
只有 media:post 成功才允许「待送审」;转码失败保留原文件并标记「待完善」 |
| 清理 |
分片过期清理;失败中间产物保留 24 小时;exports 产物 3 天 |
为什么不复用生成链路:生成链路的 media_items 是「一次请求的产物」,有成对的重试与成本语义;外部成片没有成本、没有镜头,混用会让两套语义互相污染。因此素材入库走 materials + upload_sessions,与生成任务只在「送审」与「导出」两处汇合。
8 关键设计决策(ADR)
每条决策都写"背景 → 候选 → 选择 → 代价 → 重评条件"。评审时若不同意某条,只需要针对"代价"和"重评条件"提异议,不必重开整场讨论。
8.1 D-01 模块化单体而非微服务
| 项 |
内容 |
| 背景 |
8 个业务域、2–4 人研发、日 < 500 条成片、强事务(任务 + 账本 + 资产) |
| 候选 |
A 单体分层;B 模块化单体;C 8 个微服务 |
| 决策 |
B:单进程内按 Nest 模块划分边界,跨模块只走 Service / 事件;扩缩按进程类型(api / image / video / export) |
| 代价 |
模块边界靠纪律(ESLint + review)而非网络隔离;单库单 schema,无法按域独立选库 |
| 重评 |
某域 QPS 独立增长 3 倍以上、或出现独立团队独立发布 → 先拆"媒体处理"与"AI 接入"两个最脏的边 |
8.2 D-02 BullMQ + Redis 队列,而非 Kafka / 云 MQ
| 项 |
内容 |
| 背景 |
任务量大、可重建、无跨团队消费方;需要延迟、重试、并发控制、优先级 |
| 候选 |
A BullMQ + Redis;B Kafka;C 阿里云 MNS/RocketMQ;D 数据库轮询 |
| 决策 |
A:延迟/重试/并发/优先级开箱即用;Redis 同时承担缓存与限流,组件数最少 |
| 代价 |
消息持久化弱于 MQ(Redis 持久化 + AOF everysec 可接受);队列堆积依赖监控 |
| 重评 |
出现跨系统订阅、需要严格一次投递、或消息量 > 100 万/日 |
8.3 D-03 动态要素卡用 JSON Schema + 物化索引,而非每行业建表 / EAV
| 项 |
内容 |
| 背景 |
行业包要求"5 个工作日上新行业",要素卡字段随行业变化,字段有"可检索"标记 |
| 候选 |
A 每行业一张宽表;B EAV(一行一字段);C JSONB + 按需物化索引;D 外部表单引擎 + 独立库 |
| 决策 |
C:值存 JSONB,brief_card_index 只落"可检索"字段,schema 版本不可变 |
| 代价 |
任意字段的复杂查询受限(只能查已物化字段);JSONB 写放大 |
| 重评 |
出现"对未物化字段做高频组合筛选"的强需求,或单表 > 5000 万行 |
8.4 D-04 客户隔离三层:库层强制 + 应用层注入 +(P1)行级安全
| 项 |
内容 |
| 背景 |
R-04 要求客户级隔离;越权一次就是事故 |
| 候选 |
A 仅应用层过滤;B 应用层 + 库层强制列/外键;C B + PostgreSQL RLS;D 物理分库 |
| 决策 |
B 先落地(customer_id NOT NULL + Repository 必传 ScopeContext + 外键),C 作为 P1 加固;D 明确不做 |
| 代价 |
Repository 接口变重;RLS 会带来连接与会话变量管理成本(放 P1 评估) |
| 重评 |
出现监管级隔离要求、或第二套后端服务直连同库 |
8.5 D-05 媒体处理用 Node + ffmpeg CLI,不自建 Python 服务
| 项 |
内容 |
| 背景 |
日产 < 500 条、拼接与转码为主、无自建 GPU;团队 TS 全栈 |
| 候选 |
A Node + ffmpeg/sharp;B Python + moviepy/ffmpeg;C 云媒体处理(MPS/智能媒体);D 前端 Remotion 渲染 |
| 决策 |
A:进程隔离即可控,ffmpeg 覆盖拼接/转码/抽帧/水印;C 作为峰值兜底备选 |
| 代价 |
复杂多层动画与精细时间线弱于专业库;CPU 密集,需独立机器 |
| 重评 |
出现"多轨道动画 / 动效模板"强需求,或拼接耗时成为瓶颈 → 引入 Remotion 或 Python 服务 |
8.6 D-06 对象存储走 S3 协议,M0 用 MinIO
| 项 |
内容 |
| 背景 |
用户选择"对象存储(自建 MinIO)+ 浏览器打包下载";同时不希望绑死厂商 |
| 候选 |
A 直接 OSS/COS SDK;B S3 兼容抽象 + MinIO;C 本地文件系统 |
| 决策 |
B:接口层只用 S3 API;M0 自建 MinIO(2 节点纠删码),可无痛切云 OSS(同接口) |
| 代价 |
自运维磁盘与副本;跨地域复制需自建脚本 |
| 重评 |
对象量 > 50 TB、或需要 CDN 加速下载 → 切云 OSS + CDN |
8.7 D-07 权限 = 代码注册的权限点 + 数据化角色 + 特殊权限 + SoD
| 项 |
内容 |
| 背景 |
仅内置超管,其余角色可配置(PRD 3、5.4.3、R-16~R-18) |
| 候选 |
A 硬编码角色判断;B 纯 RBAC 表驱动;C RBAC + 数据范围 + 特殊权限 + SoD 校验 |
| 决策 |
C:权限点用常量表 + 装饰器注册(防"配了一个不存在的点"),角色/范围/特殊权限为数据 |
| 代价 |
权限点变更需发版(但这是期望的:权限点应受控);角色配置界面需要解释成本 |
| 重评 |
出现"客户侧自助分配权限"需求 → 增加租户内角色模板 |
8.8 D-08 异步任务统一协议:SSE 优先 + 轮询兜底
| 项 |
内容 |
| 背景 |
长任务进度、勾选状态恢复、多人同时看同一任务 |
| 候选 |
A 纯轮询;B WebSocket;C SSE + 轮询兜底 |
| 决策 |
C:SSE 简单、可穿代理、支持 Last-Event-ID 续推;轮询作为降级与兜底 |
| 代价 |
SSE 单向(够用);连接数受 Nginx 配置限制(100 人规模无压力) |
| 重评 |
出现"任务间协同 / 实时评论"需求 → 引入 WebSocket |
8.9 D-09 导出走服务端打包 + 短签名 URL
| 项 |
内容 |
| 背景 |
PRD 5.5:仅导出勾选项、命名规则、同名加序号、留痕;每日 < 500 条 |
| 候选 |
A 前端逐张下载;B 服务端打 zip;C 共享网盘挂载 |
| 决策 |
B:打包任务 + 清单快照 + 15 分钟签名 URL;大包分卷 |
| 代价 |
打包占 CPU 与临时盘;包内命名错误需重打整包 |
| 重评 |
单包 > 10 GB 或需要直投达人/媒体平台 → 改对象存储直链 + 客户端脚本 |
8.10 D-10 成本治理前置:预占 + 结算 + 账本 + 预算闸门
| 项 |
内容 |
| 背景 |
生成能力全部外采,单条视频 5 次图生视频,成本不可控会直接吃利润 |
| 候选 |
A 事后统计;B 调用后记账;C 调用前预占 + 后结算 + 预算闸门 |
| 决策 |
C:提交接口强返预估、超预算二次确认、调用前预占、调用后结算 |
| 代价 |
预占需要回收机制(超时释放),否则额度会被"僵尸任务"占住 |
| 重评 |
模型改为包年/包量计费后,改为"配额池 + 用量告警" |
8.11 D-11 图片线走确定性合成,不做 AI 生成
| 项 |
内容 |
| 背景 |
PRD 5.2.1 要求图片批量产出;素材工坊(sucaigongfang)已验证「PSD 模板 + 剪贴蒙版 + 抠图 + 合成」可规模化生产;AI 生图需求文档存在但未上线 |
| 候选 |
A AI 文生图 / 图生图为主;B PSD 模板套版为主(确定性);C 混合(模板为主 + AI 兜底) |
| 决策 |
B:模板套版 + 抠图 + 多规格合成;AI 生图仅作 M5 预研,不进入本期 |
| 代价 |
创意上限受模板供给限制,设计产能成为新瓶颈;非电商行业(金融 / 本地生活)模板需要重新设计一批 |
| 重评 |
模板复用率连续两季度下降,或非电商行业素材占比 > 30% 且模板供给跟不上 → 评估 C(AI 生成作补充位) |
9 非功能落地(逐条对应 PRD 第 9 章)
9.1 性能预算分解
| PRD 指标 |
预算分解 |
实现机制 |
验收方式 |
| 列表首屏 ≤ 2s |
接口 P95 ≤ 500ms + 前端渲染 ≤ 800ms + 静态资源 ≤ 300ms |
游标分页、覆盖索引、虚拟滚动、Nginx 强缓存静态资源(CDN 暂不做,见 1.4) |
生产样本库压测 + 真实用户监测 |
| 脚本单条 ≤ 30s |
模型 P95 ≤ 20s + 排队 ≤ 5s + 校核 ≤ 5s |
异步 + 队列优先、供应商路由选低延迟档 |
按模型统计 P95,超时告警 |
| 图片单张 ≤ 10s |
拉物料 ≤ 2s + 合成 ≤ 5s + 上传 ≤ 3s |
物料缓存、sharp 并行、对象存储内网直传 |
任务项耗时分布看板 |
| ≥ 50 张预览不卡 |
首屏只加载 12 张缩略图 + 其余懒加载 |
虚拟网格、缩略图 240px、浏览器缓存 |
前端性能测试(低配机) |
| 视频预览首帧 ≤ 2s |
faststart MP4 + 首帧图预生成 + Range |
转码统一 faststart;首帧单独存 jpg |
抽样测首帧时间 |
| 断点续播 |
支持 Range 请求 |
对象存储原生 Range + 签名 URL |
手工 + e2e |
9.2 缓存策略
| 对象 |
位置 |
TTL |
失效方式 |
| 权限点与数据范围 |
Redis + 请求内 |
5 分钟 |
permission.changed 事件主动删 |
| 行业包 schema(编译后 Ajv) |
进程内 LRU |
无(跟随版本) |
industry.pack.published 事件 |
| 字典项 |
Redis |
30 分钟 |
字典写操作删 key |
| 客户资产包 |
Redis |
5 分钟 |
客户/资产变更事件 |
| 模板与物料缩略图 |
浏览器 + 对象存储 |
1 天 |
版本号变更即换 URL |
| 看板聚合 |
Redis |
5 分钟 |
定时任务刷新 |
缓存原则:权限与隔离相关的不做长缓存;宁可多查一次,也不要拿旧权限放行。
9.3 并发、队列与限流
| 层级 |
限制 |
目的 |
| 用户级 |
同时 ≤ 3 个批量任务;单任务条数上限(可配) |
防误操作与刷量 |
| 客户级 |
单客户并发任务 ≤ 5;单客户队列占比 ≤ 30% |
防一个大客户挤占全平台 |
| 供应商级 |
按 QPS / 并发令牌池,独立于业务队列 |
避免触发供应商封禁 |
| 接口级 |
登录 5 次/分钟;导出 10 次/分钟 |
防暴力与误点 |
| 公平性 |
队列按客户分片轮询(round-robin),而非 FIFO |
大客户不饿死小客户 |
9.4 可靠性
| 机制 |
落点 |
| 幂等 |
请求 Idempotency-Key;任务项唯一键;AI 回调 provider_job_id 唯一 |
| 超时 |
接口 15s、图片单张 60s、视频单镜 10 分钟、拼接 15 分钟;分层超时,外层必须大于内层 |
| 重试 |
仅对"可重试错误"(限流/超时/5xx)重试;内容违规不重试;重试次数与退避写配置 |
| 断点 |
任务项级状态落库,重投跳过已完成 |
| 降级 |
供应商熔断 → 备用路由;Redis 故障 → 只读 + 拒绝新任务;对象存储故障 → 保护性暂停 |
| 数据修复 |
提供"任务一致性巡检"脚本:孤儿对象、状态与产物不一致、账本缺记录,每日跑并告警 |
9.5 安全
| 面 |
措施 |
| 认证 |
密码 Argon2id;登录失败锁定;refresh 旋转 + 复用检测(复用即全端下线) |
| 授权 |
权限点 + 数据范围 + 特殊权限三层;越权返回 403 且审计 |
| 越权测试 |
每个端点的"数据范围外"用例进 CI(见 6.7) |
| 上传 |
预签名直传 + 类型/大小/尺寸校验 + 图片重编码(剥离 EXIF 与潜在脚本)+ 病毒扫描(可选) |
| SSRF |
出网仅允许白名单域名(供应商 API),禁止用户可控 URL 的服务端抓取 |
| 密钥 |
KMS / 加密环境变量;日志与异常上报统一脱敏;密钥轮换流程 |
| 注入 |
Prisma 参数化;原生 SQL 必须走参数绑定(lint 规则) |
| 审计 |
变更类操作全量;审计表只插不改;导出审计本身也审计 |
| 传输 |
全站 TLS 1.2+;内部服务间走内网;对象存储签名 URL 短时有效 |
9.6 合规
| 要求 |
实现 |
| AI 生成内容标识(显式 + 隐式) |
画面角标 + MP4 元数据;校验任务检查;导出清单标注 |
| 肖像与素材授权 |
人物池资产带授权材料与到期日;到期前 30 天提醒;过期禁止用于生成 |
| 分行业合规 |
行业包 compliance_rules 数据化;金融必须有风险提示与禁止承诺收益 |
| 数据保留 |
审计 ≥ 12 个月;成片默认永久(客户结束只读);中间产物 30 天 |
| 日志脱敏 |
手机号/身份证/账号 token 掩码;模型请求不落完整提示词(落 hash + 版本) |
| 数据出境 |
默认全部在境内云与境内供应商;若用境外模型,需单独评估并开关控制 |
9.7 可观测性
| 类别 |
清单 |
| 指标 |
接口 P50/P95/P99、错误率;队列长度/等待时长/失败率;任务成功率与耗时分布;单条成本与日成本;模型成功率/P95;缓存命中率;DB 连接与慢查询 |
| 日志 |
结构化 JSON,字段含 traceId、taskId、itemId、customerId、action;禁止打印 token 与完整提示词 |
| 链路 |
API → 队列(消息头带 trace)→ worker → 外部 API,全链路可追 |
| 看板 |
产能(日成片/人效)、质量(保留率/淘汰理由分布)、成本(按客户/模型/批次)、稳定性(失败率/积压) |
| 告警 |
队列积压 > 10 分钟;任务失败率 > 5%;模型可用性 < 95%;日成本 > 预算 80%;审计写入失败;备份失败 |
| 巡检 |
孤儿对象、僵尸任务、账本缺口、分区剩余、磁盘水位 |
9.8 容量与成本联动(三条硬规则)
- 提交即预估:任何批量提交必须返回预估条数、预估成本、预估时长;缺一项不允许提交。
- 成本超阈值即拦截:客户/角色/月预算 100% 时阻断新任务(超管可临时放宽,留痕)。
- 中间产物有生命周期:
temp 桶 30 天自动清理,避免"为了排查方便"把存储成本翻 4 倍。
10 研发交付路径
10.1 仓库结构(Monorepo)
工程约定:pnpm workspaces + Turborepo;TypeScript strict;禁 any(unknown + 收窄);提交前 lint + typecheck + 单测;PR 必须带"影响面 + 回滚方式"。
包管理:只用 pnpm(硬约定)
| 规则 |
落地方式 |
| 唯一包管理器 |
根 package.json 声明 "packageManager": "pnpm@10.x";人、CI、Dockerfile 一律用 pnpm |
| 禁用 npm / yarn |
preinstall 脚本用 only-allow pnpm 直接失败;仓库根只提交 pnpm-lock.yaml,出现 package-lock.json / yarn.lock 视为错误(CI lint 检查) |
| 可重复安装 |
CI 与镜像构建一律 pnpm install --frozen-lockfile(不一致即失败,不隐式升级) |
| 工作区依赖 |
内部包用 workspace:* 引用;禁止 npm link / 相对路径跨包子路径引用 |
| 例外 |
仅当某个全局 CLI 只能经 npm 安装(如个别脚手架)时,限定在构建镜像内并登记到运维手册,仓库依赖一律不得使用 |
| Node 版本 |
.nvmrc / engines 与镜像统一 24.15.0,由 pnpm 启动前校验 |
10.2 里程碑(与 PRD M0–M3 对齐)
| 里程碑 |
PRD 范围 |
技术交付物 |
验收标准 |
估算人日 |
| T0 骨架 |
M0 前置 |
Monorepo、CI、鉴权、权限点体系、客户/行业、行业包 v1(电商)、字典、审计、对象存储、队列骨架、观测基线 |
能登录、能建客户、能配置一个行业包、能跑通一个空任务 |
25–30 |
| T1 可用 |
M0 |
要素卡与表单渲染、脚本生成 + 校核 + 审核入库、图片线(模板合成 + 规格矩阵 + 预览勾选 + 导出)、任务协议与 SSE |
单客户完成"要素卡 → 脚本 → 图片 → 导出"闭环 |
45–55 |
| T2 提效 |
M1 |
视频线(逐镜 + 拼接 + 校验 + 预览下载)、AI 路由与预算、快筛、成片复用、金融包 v1 |
单条视频从脚本到成片跑通,成本可查 |
50–60 |
| T3 规模 |
M2–M3 |
第三个行业包、批量变体矩阵、效果回流、看板完善、扩容与压测、RLS 评估 |
双行业并行生产,峰值 500 条/日无积压 |
40–50 |
人日为「净开发」,不含评审、联调与等待外部 API 的时间。按 2 后端 + 1 前端 + 1 全栈/基础设施 + 0.5 测试(约 4.5 人)测算,T0–T2 净开发 120–145 人日 ≈ 27–32 个并行工作日,叠加评审、联调与外部依赖等待(按 40%–60%)后,日历约 2.5–3.5 个月到 T2;T3 另需约 1.5–2 个月。
10.3 人力配置
| 角色 |
人数 |
主要职责 |
| 后端 |
2 |
业务模块、权限与审计、任务编排、AI 适配 |
| 前端 |
1–2 |
后台页面、表单渲染器、预览与快筛、看板 |
| 全栈 / 基础设施 |
1 |
部署、CI/CD、观测、备份、成本与容量 |
| 测试 |
0.5–1 |
用例设计、接口与 e2e、性能与越权测试 |
| AI 工程(兼) |
0.5 |
提示词体系、模型评测与路由策略 |
10.4 测试策略
| 层 |
工具 / 做法 |
门槛 |
| 单元测试 |
Vitest;核心规则(命名、范围解析、状态机、schema 校验)必须覆盖 |
关键包行覆盖 ≥ 70% |
| 集成测试 |
Testcontainers(PG + Redis + MinIO)跑真实依赖 |
主链路必须有 |
| 契约测试 |
OpenAPI + oasdiff;Zod 单一真源 |
破坏性变更必须升版本 |
| 端到端 |
Playwright:登录 → 生成 → 预览 → 勾选 → 导出 |
每条主链路 1 条 e2e |
| 权限测试 |
每个端点 3 类用例(有权限 / 无权限 / 范围外) |
全端点覆盖 |
| 性能测试 |
k6:列表、提交、导出;Mock AI 固定延迟 |
达标 PRD 9.1 |
| 视觉回归 |
图片合成结果对模板做像素级基线 |
模板/渲染变更时必跑 |
10.5 上线与灰度
- 先内测(1 个客户、1 个行业包、图片线),用真实批次校验命名、导出、审计;
- 再灰度到 2–3 个客户,观察成本与失败率;
- 视频线单独开关(客户级 feature flag),出问题只关视频不影响图片;
- 每次上线带"回滚方式":镜像回滚 + 迁移向前兼容(expand/contract)。
11 风险与未决
11.1 技术风险
| 编号 |
风险 |
影响 |
概率 |
应对 |
监控 |
| R-T-01 |
外部模型限流或涨价 |
产能波动、成本上升 |
高 |
多供应商 + 路由切换 + 预占闸门 |
模型可用性、单条成本曲线 |
| R-T-02 |
视频质量不可控(人物抖动、商品变形) |
返工率高、保留率低 |
高 |
逐镜重生成、参考物料锁定、质量校核、快筛 |
保留率、淘汰理由分布 |
| R-T-03 |
单个视频任务耗时过长 |
用户等待、队列积压 |
中 |
镜头并行、超时控制、进度可见、部分交付 |
任务 P95 时长、积压 |
| R-T-04 |
动态表单复杂度失控 |
渲染慢、校验漏洞、配置难懂 |
中 |
字段上限、类型白名单、联动 2 级、发布前 7 项校验 |
schema 复杂度统计 |
| R-T-05 |
ffmpeg 资源争抢 |
拼接排队、机器打满 |
中 |
独立队列与机器、并发上限、进程级限流 |
CPU/内存、队列等待 |
| R-T-06 |
越权或数据串客户 |
事故级,信任受损 |
低 |
三层隔离 + CI 权限用例 + 审计告警 |
403 分布、异常访问 |
| R-T-07 |
对象存储单点或磁盘故障 |
素材不可用 |
中 |
2 节点纠删码 + 云冷备 + 版本化 |
磁盘水位、复制延迟 |
| R-T-08 |
成本失控(尤其视频) |
毛利被吃 |
中 |
预占结算 + 预算闸门 + 看板 |
日/周成本、单条成本 |
| R-T-09 |
审计日志量增长 |
库膨胀、查询变慢 |
中 |
按月分区 + 归档 + 只保留必要字段 |
分区大小、查询耗时 |
| R-T-10 |
AI 内容合规(虚假宣传、肖像) |
下架、投诉、监管 |
中 |
合规规则集 + 人工审核 + 标识 + 授权管理 |
合规拦截率、投诉记录 |
| R-T-11 |
单地域故障 |
短时不可用 |
低 |
每日备份 + RTO 4 小时 + 演练 |
备份成功率 |
| R-T-12 |
团队规模小、知识集中 |
交付延期 |
中 |
文档化、模块 owner、避免过度设计 |
里程碑偏差 |
| R-T-13 |
参考项目无图片 / 视频渲染实现,图片线无先例 |
工期估算偏乐观、链路返工 |
高 |
渲染服务按独立进程设计(sharp / ffmpeg);先做单模板端到端打通再谈规格矩阵;保留 2 周缓冲 |
单模板端到端耗时、返工次数 |
| R-T-14 |
客户审核链接泄露或被转发 |
内容外泄、投诉 |
低 |
签名 + 有效期 + 可作废 + IP / UA 限流 + 只读范围;改稿留痕 |
链接访问来源分布、异常高频访问 |
| R-T-15 |
外部成片入库后转码堆积或失败 |
素材长时间不可用、队列积压 |
中 |
media:post 独立队列 + 失败保留原文件 + 状态「待完善」;转码失败不影响预览原片 |
队列等待、失败率、待完善存量 |
| R-T-16 |
对象存储增长失控(外部成片 + 多规格) |
存储成本超预算 |
中 |
秒传去重 + temp/exports 生命周期 + 冷数据转低频 + 月度容量看板 |
桶增长曲线、单素材平均体积 |
| R-T-17 |
PSD 图层命名不规范(设计师漏标 / 命名漂移) |
模板解析不出替换位、批次整体失败 |
高 |
上传即校验 + 图纸规范文档 + 模板预览叠加替换位提示 + 拒绝入库而不静默跳过 |
模板入库失败率、缺图层告警数 |
| R-T-18 |
镜像缺中文字体导致文字渲染成豆腐块 |
成片文字全错但任务「成功」,属最危险的静默交付 |
高 |
镜像内置字体 + 启动自检 + 渲染前字体可用性断言 + 抽样 OCR 校验(可选) |
启动自检结果、字体回退告警 |
| R-T-19 |
抠图质量不达标(发丝 / 透明材质 / 复杂背景) |
主体带白边或残缺,返工与客诉 |
中 |
抠图降级原图并标「低质量」+ 低质量默认不勾选 + 质量看板 + 复杂品类转人工 |
抠图一次通过率、低质量产出率 |
11.2 待确认(技术侧)
| 编号 |
问题 |
影响 |
建议 |
| QT-01 |
云厂商与地域(阿里云 / 腾讯云 / 其他) |
资源选型、备案、出网策略 |
建议先用阿里云华东,境内模型供应商同地域 |
| QT-02 |
生产数据库 / Redis 的主机形态(独立数据节点 vs 与应用同机) |
可用性与备份窗口 |
已定自建 PG 18 + Redis;建议数据节点独立成机,不与 worker 混部 |
| QT-03 |
域名与备案主体 |
上线时间 |
需提前 2–3 周启动 |
| QT-04 |
SSO 协议(企业微信 / 飞书 / 钉钉 / OIDC) |
登录实现 |
先账号密码,SSO 留适配器 |
| QT-05 |
AI 供应商清单与账号主体、结算方式 |
成本与合规 |
需在 T1 前确认 2 家文本 + 2 家视频可商用 |
| QT-06 |
各供应商并发上限与单账号配额 |
队列并发设置 |
需要商务确认后写入路由配置 |
| QT-07 |
月度 AI 预算与超预算策略 |
闸门阈值 |
PRD Q-09 未定,建议先给"软上限 + 告警" |
| QT-08 |
是否需要 CDN 与下载加速 |
成本与体验 |
首年不需要,出网超阈值再评估 |
| QT-09 |
备份 RPO / RTO 的正式要求 |
架构与费用 |
建议 RPO 5 分钟(DB)/ 24 小时(对象),RTO 4 小时 |
| QT-10 |
审计与成片的保留年限 |
存储与合规 |
建议审计 24 个月、成片永久(客户结束只读) |
| QT-11 |
是否允许使用境外模型 |
合规 |
默认不允许,需单独评估并开关控制 |
| QT-12 |
图片/视频单文件大小与格式上限 |
上传与转码 |
PRD 待定,建议图片 ≤ 20 MB、视频 ≤ 200 MB |
| QT-13 |
是否需要私有化 / 内网部署版本 |
架构分叉 |
当前不做,S3 + Compose 已具备迁移条件 |
| QT-14 |
团队是否接受 Monorepo + TS 全栈 |
工程效率 |
已按用户选择落地;若团队以 Java 为主需重评 |
12 附录
附录 A 术语与缩写
| 术语 |
含义 |
| ADR |
架构决策记录(Architecture Decision Record) |
| SSE |
Server-Sent Events,单向服务端推送 |
| Outbox |
事务性消息表,保证"业务写入 + 消息投递"一致 |
| RLS |
PostgreSQL 行级安全(Row Level Security) |
| PITR |
时间点恢复(Point-In-Time Recovery) |
| LRU |
最近最少使用缓存淘汰 |
| Mock AI Provider |
固定延迟与固定产物的假供应商,用于 CI 与压测 |
| Feature Flag |
按客户/用户开关功能的配置 |
| faststart |
MP4 moov 前置,支持边下边播 |
| LUFS |
音频响度单位,用于多镜音量归一 |
附录 B PRD 追溯矩阵
| PRD 需求 |
架构落点 |
| S-01~S-05(要素卡 / 模板 / 生成 / 校核) |
3.2 script、5.4.2、6.3.4、7.2 |
| S-06~S-12(审核、入库、版本、检索、复用) |
5.6 版本与快照、6.3.4、7.2 |
| M-01~M-05(图片线、规格矩阵、预览勾选) |
3.4、5.4.3、7.3 |
| M-06~M-14(视频线、逐镜、拼接、快筛、一致性) |
3.5、3.6、7.4 |
| M-15~M-17(视频方向、预览、命名下载) |
2.4 队列、5.4.3、7.5 |
| M-18~M-20(成片入库、版本链、素材库批量操作) |
2.5 桶与直传、7.9、5.3 素材域 |
| M-21~M-24(关联血缘、分享链接、送审、效果回流) |
5.3 交付/效果域、6.3.5、5.7 阈值同源 |
| M-25~M-27(爆量反哺、看板、元素拆解) |
5.3 material_metrics_daily、8.10 成本治理、12 附录 F |
| A-01~A-11(15 类资产、三级继承、复用、复审) |
3.2 catalog、5.3、5.4.4、5.6 |
| P-01~P-06(组织、账号、角色、权限) |
3.2 identity、5.3、6.2、7.1 |
| P-07~P-08(行业包、表单设计器) |
3.2 industry、5.4.2、7.6 |
| P-09~P-13(AI 配置、字典、审计、系统配置、看板) |
3.2 platform、5.3、6.3.7、9.7 |
| P-14~P-18(行业包、设计器、发布、字段审批、合规规则) |
5.3、7.6、9.6 |
| E-01~E-06(导出规则、命名、清单、留痕) |
3.2 delivery、7.5、6.5 |
| R-01 命名规则 |
7.5 命名服务、system_configs |
| R-02 引用资产禁删 |
5.4.4 asset_refs + 外键 RESTRICT |
| R-04 客户隔离 |
5.5 三层、9.5 |
| R-05 导出审计 |
5.4.5、7.5 |
| R-25 素材粒度(素材为主对象) |
5.3 materials / material_specs、7.9 |
| R-26 阈值同源 |
5.7 查询约定第 6 条、system_configs |
| R-27 送审基准(提交时冻结版本) |
3.6 状态机、5.3 review_records |
| R-28 分享与外部访问 |
6.3.5 /share-links、9.5 安全 |
| R-06 成本预估 |
7.7 预占结算、9.8 |
| R-09 AI 标识与肖像 |
7.4、9.6 |
| R-11 schema 版本 / R-14 变量完整 |
5.4.2、7.2、7.6 |
| R-16~R-18 角色可配置与 SoD |
6.2、7.1、8.7 |
| R-19 视频方向边界 |
5.4.3、3.5 |
| R-20 行业/客户目录 |
5.3、5.4.1、9.5 |
| 9.1 性能 / 9.2 安全 / 9.3 合规 |
全部第 9 章 |
| AC-01~AC-04(DoD) |
10.2 验收标准 + 10.4 测试策略 |
附录 C 关键环境变量
| 变量 |
示例 |
说明 |
NODE_ENV |
production |
运行环境 |
DATABASE_URL |
postgres://... |
主库连接(含连接池参数) |
REDIS_URL |
redis://... |
缓存与队列(按 db 分库) |
S3_ENDPOINT |
http://minio:9000 |
S3 兼容地址 |
S3_BUCKET_ASSETS / S3_BUCKET_RENDERS / S3_BUCKET_TEMP |
assets / renders / temp |
桶名 |
S3_ACCESS_KEY / S3_SECRET_KEY |
— |
走密钥管理,不入库 |
JWT_SECRET / JWT_REFRESH_SECRET |
— |
签名密钥,支持轮换 |
AI_PROVIDER_*_API_KEY |
— |
各供应商密钥(KMS 注入) |
QUEUE |
video |
worker 启动时消费的队列组 |
WORKER_CONCURRENCY |
12 |
并发上限 |
FFMPEG_PATH / FFPROBE_PATH |
/usr/bin/ffmpeg |
二进制路径 |
MAX_UPLOAD_MB |
200 |
上传上限 |
OTEL_EXPORTER_OTLP_ENDPOINT |
http://otel:4318 |
链路导出 |
FEATURE_VIDEO |
on |
视频线开关 |
COST_SOFT_LIMIT / COST_HARD_LIMIT |
0.8 / 1.0 |
预算闸门比例 |
附录 D 容量与成本测算口径
| 项 |
公式 |
示例(按 500 条/日估算) |
| 视频模型时长 |
条数 × 镜数 × 单镜耗时 |
500 × 5 × 90s ≈ 62.5 模型小时/日 |
| 峰值并发 |
日均时长 ÷ 有效工作小时 × 峰值系数 |
62.5 ÷ 8 × 1.5 ≈ 12 |
| 成片存储 |
条数 × 单条大小 × 天数 |
500 × 10 MB × 365 ≈ 1.8 TB/年 |
| 含中间产物 |
成片 × 3~5 |
≈ 8 TB/年(30 天清理后可降至 ~2.5 TB) |
| 数据库增长 |
任务项 + 审计 + 账本 |
约 150–200 GB/年 |
| 月成本 |
模型费用 + 云资源 + 人力 |
模型费用占大头,云资源约占 15–25% |
具体单价随云厂商与供应商报价变化,本文不写死金额;预算评审时按此口径填数即可。
附录 E 与同期文档的关系
| 文档 |
关系 |
docs/PRD/灵犀-信息流广告素材一站式生产平台PRD.md V1.3 |
需求基线;本文所有"PRD 5.x / R-xx / Q-xx"均指该文档 |
docs/PRD/灵犀_信息流素材生产平台_PRD_V0.3.md |
同事基线;本文已合并其行业属性/信息模块/视频方向口径 |
docs/立项建议/...立项建议书_V0.1.md |
论证与自建 vs 采购结论;本文第 8 章决策以其为前提 |
docs/skills/电商-达人口播skill、电商-达人展示skill |
提示词与分镜 know-how 来源;本文 7.2/7.4 的提示词体系需以其为初始资产 |
项目参考/爆量魔方(已 gitignore,不随仓库分发) |
参考实现:脚本生成 / 结构化解析 / 审核 / AI 配置可复用;图片与视频渲染无实现;复用矩阵见附录 F |
附录 F 与参考项目(爆量魔方 / baoliangmofang)的复用矩阵
| 能力 |
参考实现 |
本文处理 |
理由 |
| 数据字典 / 受控词表 |
Enumeration 单表(type + code + parent + industry) |
直接复用,对应 5.3 的 dict_types / dict_items |
一张表同时表达二级树与词表,已被验证 |
| 模型四层配置 |
Provider / Model / TaskMapping / CallLog + 主备 + 计量 |
直接复用,见 2.6 与 5.3 |
多供应商是既定约束,四层是必需结构 |
| 提示词与行业指南 |
ai_config 键值 + 版本历史,system 末尾追加行业指南 |
直接复用,见 2.6 / 7.2 |
行业差异用配置表达,与 PRD 行业包一致 |
| 结构化解析 |
JSON schema 8 字段 + 失败重试 + 原文保留 |
直接复用,见 7.2 |
解析失败不静默丢稿 |
| 脚本编号 |
ScriptSerial 原子取号 + 过审时分配 |
直接复用,见 7.2 |
并发不重号、草稿不占号 |
| 客户审核 |
批次 + HMAC 签名 token + 改稿快照 |
改造后复用(P1),见 6.3.4 |
原实现缺 IP 限流与作废机制,需补齐 |
| 批量异步交互 |
提交 → 轮询 → 三态 + 失败明细行号 |
改造后复用(前端),明细改走 SSE |
轮询在 500 条量级可用,但 SSE 更省资源 |
| 图片 / 视频渲染 |
无实现(无 sharp / canvas / ffmpeg / 对象存储) |
自建,见 3.4 / 7.3 / 7.4 |
参考项目的「转素材制作」只是导出 Word 交付包 |
| 权限模型 |
@Roles + @RequirePermissions 双轨、空数组默认放行 |
自建单轨 + 默认拒绝,见 6.2 |
默认放行不适合多客户平台 |
| 客户隔离 |
每个 service 自写 getAuthorizedClientIds |
自建 ScopeContext + 强制 customer_id,见 6.2 |
原实现重复 6+ 处、易漏;本文以守卫统一收口 |
| 任务状态存储 |
进程内 Map / Redis |
自建落库(media_tasks + outbox) |
进程重启即丢,无法审计与重试 |
| 权限隐藏 |
直接 removeChild DOM |
自建路由守卫 + 条件渲染 |
不可恢复、不可测试 |
| 素材 / 规格模型(baoliangmofang) |
素材为主对象、规格为属性(Q4 结论「同源多规格」) |
直接采纳,见 5.3 materials / material_specs |
多规格重复计费与重复审核是必然坑 |
| 分片直传与秒传(baoliangmofang) |
hash → presign → 直传 → complete → 入队 |
直接采纳,见 7.9 |
应用服务器不中转大文件是硬约束 |
| 送审抽象(baoliangmofang) |
内部审核 + 外部提审登记共用批次;审核基准=提交时刻 |
直接采纳,见 3.6 / 6.3.5 |
两套批次并行会导致对账困难 |
| 效果回流与阈值同源(baoliangmofang) |
日指标落表、派生指标不落库、阈值单点 |
直接采纳,见 5.7 / material_metrics_daily |
历史项目在这类口径上反复对账 |
| 向量检索(baoliangmofang) |
PG18 + pgvector(HNSW / 1024 / cosine),M3 引入 |
改为「M3 起可有」,见 5.7 |
与本项目「M1–M2 不上向量」的节奏一致 |
图片出图 / 套版渲染(爆量魔方 / baoliangmofang) |
无实现(两仓库均无 sharp / canvas / 出图 API 接入) |
— |
这两个仓库只能参考管理与脚本侧 |
图片套版与抠图(素材工坊 sucaigongfang) |
PSD 剪贴蒙版 + ag-psd 解析 + ONNX 抠图 + canvas/sharp 合成 + BullMQ 批次 + 并发建议器 |
按实现对齐,见 3.4 / 7.3 / 8.11 |
唯一有生产验证的图片链路(位于 /Users/norman/Documents/项目/sucaigongfang,不在 项目参考/ 内,不入库) |
结论:三个参考来源分工明确——爆量魔方 提供「脚本生产」骨架(数据字典 → 提示词 → 解析 → 审核 → 编号 → 交付),baoliangmofang(其重写版)提供「素材管理」骨架(素材/规格模型、分片直传与秒传、送审登记、效果回流、阈值同源、pgvector 预研),素材工坊(sucaigongfang,本地目录、不入库)提供图片批量生产的唯一实现(PSD 图层约定、ONNX 抠图、canvas 合成、批次与并发控制)。图片线的工期与风险不再按"净新增"评估,而是按"对齐素材工坊实现 + 适配本项目资产与规格模型"评估(见 R-T-17~R-T-19)。
修订记录
| 版本 |
日期 |
修订内容 |
| V0.1 |
2026-11-02 |
初稿:技术选型、总体架构、部署、数据模型、接口、关键链路、10 条关键决策、非功能落地、交付路径、风险与待确认 |
| V0.1.1 |
2026-11-02 |
基础设施版本校准:数据库统一 PostgreSQL 18;Node.js 锁定 24.15.0;依赖统一 pnpm(禁用 npm / yarn,packageManager 锁定 + CI 校验) |
| V0.1.2 |
2026-11-02 |
前端组件库统一为 Semi Design 2.x(移除 Ant Design / ProComponents,补 React 19 adapter 与二次封装说明);图标统一 Remix Icon,禁止其他图标库与内联 SVG,补 ESLint 白名单与统一出口 |
| V0.1.3 |
2026-11-02 |
数据层改为自建:移除阿里云 RDS / 云 Redis 托管假设,统一 PostgreSQL 18 与 Redis 7.4 容器镜像(开发机 Docker Compose、生产云主机 + 主备),备份改为 pgBackRest / WAL-G 归档到 MinIO;新增 4.1.1 本地开发环境(先 up 依赖再开发);更正 api 镜像为 Node 24.15.0 |
| V0.2 |
2026-11-02 |
吸收参考项目「爆量魔方」:AI 接入层补四层配置与方言抽象、表清单补 ai_models / ai_task_mappings / ai_call_logs / ai_configs 与脚本编号、模板库、审核、交付表;脚本端点补编号 / 版本 diff / 审核链接 / 交付包;7.2 补生成上下文与 RAG 三级降级;7.7 补配置合并与健壮性;新增 R-T-13 / R-T-14、附录 F 复用矩阵;明确图片与视频渲染无先例、按自研评估 |
| V0.4 |
2026-11-02 |
图片线改为确定性流水线(依据素材工坊 sucaigongfang 实测实现):image:matting / image:render / delivery:zip 三队列、PSD 图层命名约定与解析入库校验、ONNX 抠图与低质量降级、canvas 合成与字体自检、批次参数与命名快照、模板与批次表(templates / template_versions / production_batches / production_tasks);3.4 / 7.3 重写、新增 8.11 D-11、R-T-17~R-T-19、附录 F 补素材工坊行;需求基线对齐 PRD V1.6 |
| V0.3 |
2026-11-02 |
吸收 baoliangmofang:素材为主对象 + 规格为属性(materials / material_versions / material_specs / material_files / material_links / upload_sessions);分片直传 + hash 秒传 + 短时效签名 URL + StorageProvider 接口(2.5 / 7.9);送审与外部提审登记、分享链接安全口径(share_links);投放登记与日指标(placements / material_metrics_daily)、阈值同源与派生指标不落库(5.7);新增 media:post / embed:index 队列、素材端点 13 个、3.6 素材与送审状态机、7.9 入库转码链路、R-T-15 / R-T-16、附录 F 补 6 行;向量检索改为「M3 起 pgvector、访问收敛 VectorStore」;需求基线对齐 PRD V1.5 |
| V0.1.4 |
2026-11-02 |
React 19 的 Semi 包名改为 @douyinfe/semi-ui-19(禁与 @douyinfe/semi-ui 混装,ESLint 强制);明确入口仍需 import '@douyinfe/semi-ui-19/react19-adapter'(React 19 已移除 react-dom 的 createRoot 导出) |
| V0.4(A4 回写) |
2026-11-04 |
A4 数据字典与系统配置:新增 system_config_versions(配置变更前后值 / 版本 / 审计);字典权限按 A2 定稿统一为 dict:manage(ADR-001);明确字典通配表 dict_types / dict_items 与三层合并、R-10 快照口径 |
待办