You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6.9 KiB
6.9 KiB
ADR 008: 分镜统一关联表设计
状态:已接受
日期:2026-02-01
决策者:架构团队
标签:数据库设计, 架构重构, 分镜管理
背景
在原始设计中,分镜与剧本元素(角色、场景、道具)的关联采用了两种方式:
-
UUID[] 数组字段:在
storyboards表中使用数组字段存储关联 IDscreenplay_character_ids UUID[]screenplay_scene_ids UUID[]screenplay_prop_ids UUID[]- 以及对应的标签 ID 数组
-
独立关联表:
storyboard_resources表用于关联项目素材
这种设计在原型阶段看起来简单,但随着业务需求的深入,暴露出严重的架构问题。
核心问题
1. UUID[] 数组的致命缺陷
无法存储关联元数据(最大痛点):
- 数组只能存储
[id1, id2] - 无法描述"角色在这个镜头里的动作、位置、状态"
- 例如:无法表达"孙悟空在这个镜头里是'站着'还是'坐着'?是'正面'还是'背影'?是否在画面内?"
- 这些元数据对 AI 视频生成至关重要,是生成精准 Prompt 的关键来源
引用完整性难以维护:
- 删除角色时需要遍历所有分镜更新数组
- 应用层验证复杂,效率低下
查询困难:
- 需要使用
@>操作符,不如标准 JOIN 直观 - "查找所有包含'金箍棒'的分镜"需要特殊语法
顺序管理困难:
- 数组中间插入/删除元素非常麻烦
- 无法灵活调整元素顺序
2. 架构不一致
- UUID[] 数组用于剧本元素
- 独立表用于项目素材
- 两种方式并存,增加理解和维护成本
业务需求
作为 AI 视频生成工作台,我们需要:
-
精准的 Prompt 生成:
- "孙悟空(角色)拿着金箍棒(道具),动作是'挥舞',位置在画面左侧"
- 这些细节信息是 AI 生成高质量视频的关键
-
复杂的分镜制作需求:
- 元素的可见性控制(画外音、旁白)
- 元素的空间位置(左/中/右/背景/前景)
- 元素的视觉层级(用于合成时的图层顺序)
- 元素的动作描述(用于动画生成)
-
统一的前端接口:
- 前端需要一次性获取分镜的所有关联元素
- 不希望分别调用多个 API
决策
采用统一的 storyboard_items 关联表,废弃 UUID[] 数组和独立的 storyboard_resources 表。
新表设计
CREATE TABLE storyboard_items (
item_id UUID PRIMARY KEY,
storyboard_id UUID NOT NULL,
-- 多态关联核心
item_type SMALLINT NOT NULL, -- 1=Character, 2=Location, 3=Prop, 4=Resource
target_id UUID NOT NULL,
-- 冗余字段(优化读性能,避免 JOIN)
target_name TEXT,
target_cover_url TEXT,
-- 核心优势:关联属性(Metadata on Relation)
is_visible BOOLEAN DEFAULT true,
spatial_position TEXT,
action_description TEXT,
-- 标签关联
tag_id UUID,
-- 排序与层级
display_order INTEGER DEFAULT 0,
z_index INTEGER DEFAULT 0,
-- 扩展字段
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT now(),
CONSTRAINT storyboard_items_unique UNIQUE (storyboard_id, target_id, tag_id) NULLS NOT DISTINCT
);
设计原则
- 多态关联:
item_type区分不同类型的元素 - 冗余字段:
target_name,target_cover_url避免 JOIN,极大提升读性能 - 关联属性:在关联行上存储元数据(动作、位置、状态等)
- 统一管理:所有类型的元素使用同一张表
后果
优势
1. 极强的元数据能力(核心价值)
可以在关联行上添加任意属性:
action_description:动作描述(如"大笑"、"奔跑"、"挥舞")spatial_position:画面位置(left/center/right/background/foreground)is_visible:是否在画面内(画外音设为 false)z_index:视觉层级(用于合成时的图层顺序)
业务价值:为 AI 视频生成提供精准的 Prompt 来源。
2. 统一的 API 接口
前端只需调用一个 API:
GET /api/v1/storyboards/{id}/items
返回该分镜的所有关联元素(角色、场景、道具、素材),前端统一渲染。
3. 简化的排序管理
display_order字段支持拖拽调整顺序- 数组中间插入/删除非常困难,关联表轻松实现
4. 高效的删除逻辑
- 删除分镜:
DELETE FROM storyboard_items WHERE storyboard_id = ? - 删除角色:
DELETE FROM storyboard_items WHERE target_id = ? - 索引命中率极高
5. 符合关系型数据库设计范式
- 符合第三范式(3NF)
- 易于理解和维护
- 支持复杂查询
劣势及解决方案
1. 表数据量大
问题:千万级关联行
解决方案:
- PostgreSQL 擅长处理亿级简单关联表
- 配合分区(按
project_id哈希分区)轻松解决 - 正确的索引策略保证查询性能
2. 多态关联的 JOIN 复杂性
问题:target_id 可能指向不同表,SQL 没法写死 JOIN
解决方案:
- 冗余字段(
target_name,target_cover_url) - 读取时直接查
storyboard_items表,完全不需要 JOIN 原表 - "空间换时间",对于读多写少的业务非常划算
3. 数据一致性维护
问题:修改角色名称时需同步更新
解决方案:
- 异步触发更新(PostgreSQL LISTEN/NOTIFY 或 Celery)
- 虽然增加了写复杂度,但换来了极致的读性能
实施计划
阶段 1:数据库变更
- 创建
storyboard_items表 - 创建必要的索引
- 更新数据库迁移脚本
阶段 2:Service 层改造
- 更新
StoryboardService - 新增元素管理方法:
get_storyboard_items()add_element_to_storyboard()remove_element_from_storyboard()update_element_metadata()
阶段 3:API 接口更新
- 新增 API 端点:
GET /api/v1/storyboards/{id}/itemsPOST /api/v1/storyboards/{id}/itemsPATCH /api/v1/storyboard-items/{id}DELETE /api/v1/storyboard-items/{id}
阶段 4:前端适配
- 更新前端 API 调用
- 统一元素渲染逻辑
- 支持元素元数据编辑
阶段 5:文档更新
- 更新 Storyboard Service 文档
- 创建 ADR 文档(本文档)
- 创建 Changelog
相关决策
参考资料
决策日期:2026-02-01
实施状态:进行中
预计完成:2026-02-15