28 KiB
ADR 04: 移除废弃的 storyboard_resources 表
状态
提议中(Proposed)
背景
项目中存在一个命名冲突和设计废弃的问题:
问题 1:命名冲突
存在两个不同用途的 storyboard_resources:
-
废弃的关联表(数据库表)
- 表名:
storyboard_resources - 模型:
StoryboardResource(在project_resource.py中) - 用途:分镜与项目素材的多对多关联(已被
storyboard_items替代)
- 表名:
-
正在使用的 API 路由
- 路由前缀:
/api/v1/storyboard-resources/* - 模块:
storyboard_resources.py - 用途:管理分镜生成的资源(图片、视频、对白、配音)
- 实际表:
storyboard_images,storyboard_videos,storyboard_dialogues,storyboard_voiceovers
- 路由前缀:
问题 2:设计演进
原设计(已废弃):
storyboard_resources 表
├── storyboard_id (分镜ID)
├── project_resource_id (项目素材ID)
├── resource_type (素材类型:角色/场景/道具/实拍)
└── display_order (显示顺序)
新设计(正在使用):
storyboard_items 表
├── storyboard_id (分镜ID)
├── item_type (元素类型:1=ElementTag, 2=Resource)
├── element_tag_id (剧本元素标签ID,item_type=1时使用)
├── resource_id (项目素材ID,item_type=2时使用)
├── element_name (元素名称,冗余字段)
├── tag_label (标签名称,冗余字段)
├── cover_url (封面URL,冗余字段)
├── is_visible (是否在画面内)
├── spatial_position (画面位置)
├── action_description (动作描述)
├── display_order (显示顺序)
└── z_index (视觉层级)
核心差异:
storyboard_items支持多态关联(ElementTag + Resource)storyboard_items包含视觉属性(位置、层级、动作)storyboard_items通过ItemType枚举区分元素类型
依赖分析
✅ 已迁移到 storyboard_items 的功能
-
Storyboard Service(
storyboard_service.py)- ✅
add_element_to_storyboard()- 使用StoryboardItem - ✅
update_element_metadata()- 使用StoryboardItem - ✅
get_storyboard_items()- 使用StoryboardItem - ✅
reorder_items()- 使用StoryboardItem
- ✅
-
Screenplay Service(
screenplay_service.py)- ✅
_create_storyboards_from_scenes()- 创建分镜时使用StoryboardItem
- ✅
-
API 路由(
storyboards.py)- ✅
POST /storyboards/{storyboard_id}/items- 添加元素 - ✅
PATCH /items/{item_id}- 更新元素 - ✅
GET /storyboards/{storyboard_id}/items- 获取元素列表 - ✅
POST /storyboards/{storyboard_id}/items/reorder- 重新排序
- ✅
⚠️ 仍依赖 storyboard_resources 的代码
1. StoryboardResourceService(storyboard_project_resource_service.py)
状态:⚠️ 完全废弃,但代码未删除
方法列表:
add_resource_to_storyboard()- 添加素材到分镜remove_resource_from_storyboard()- 从分镜移除素材get_storyboard_resources()- 获取分镜的所有素材get_resource_storyboards()- 获取素材关联的分镜batch_add_resources()- 批量添加素材batch_remove_resources()- 批量移除素材
影响:所有方法都操作 storyboard_resources 表
2. API 端点(project_resources.py)
状态:⚠️ 路由存在,但功能已失效
端点列表:
POST /api/v1/storyboards/{storyboard_id}/resources/batch # 批量添加
DELETE /api/v1/storyboards/{storyboard_id}/resources/batch # 批量移除
POST /api/v1/storyboards/{storyboard_id}/resources/{resource_id} # 单个添加
DELETE /api/v1/storyboards/{storyboard_id}/resources/{resource_id} # 单个移除
GET /api/v1/storyboards/{storyboard_id}/resources # 获取分镜素材
GET /api/v1/resources/{resource_id}/storyboards # 获取素材关联的分镜
3. AI Conversation Service(ai_conversation_service.py)
状态:⚠️ 关键功能受影响
问题代码:
async def _get_storyboard_resources(
self,
storyboard_id: UUID,
resource_type: Optional[str] = None,
search: Optional[str] = None
) -> List[Dict[str, Any]]:
"""获取分镜的可提及资源"""
from app.repositories.storyboard_resource_repository import StoryboardResourceRepository
storyboard_resource_repo = StoryboardResourceRepository(self.db)
# ❌ 调用已废弃的方法
storyboard_resources = await storyboard_resource_repo.get_by_storyboard(storyboard_id)
# ...
当前实现:
# StoryboardResourceRepository.get_by_storyboard()
async def get_by_storyboard(self, storyboard_id: UUID) -> List:
"""获取分镜的所有资源(用于 AI Conversation 提及功能)
注意:这是一个简化的实现,返回空列表
实际应该查询 storyboard_items 表获取关联的资源
"""
# TODO: 实现完整的分镜资源查询逻辑
return []
影响:AI 对话中无法提及分镜关联的资源
验证逻辑:
# 验证资源是否关联到分镜
is_linked = await storyboard_resource_repo.is_linked(
conversation.target_id,
resource_id
)
if not is_linked:
raise ValidationError("资源未关联到该分镜")
影响:资源关联验证失效
4. Screenplay Service(screenplay_service.py)
状态:⚠️ 方法存在但未使用
问题代码:
async def _sync_storyboard_resources(
self,
screenplay_id: UUID,
project_id: UUID,
tag_id_maps: Dict[str, Dict[str, UUID]]
) -> int:
"""为标签创建占位符项目资源记录"""
# 该方法创建 project_resources 记录
# 但不涉及 storyboard_resources 表
# 命名误导,实际是创建项目资源
影响:命名误导,但不依赖废弃表
📊 测试代码
状态:⚠️ 测试 fixtures 和集成测试依赖废弃表
文件列表:
tests/conftest.py-test_storyboard_resourcesfixturetests/integration/test_storyboard_project_resource_api.py- 完整的 API 测试套件tests/unit/services/test_storyboard_project_resource_service.py- Service 单元测试
🎨 前端代码
状态:⚠️ 前端严重依赖废弃的 API 端点
1. API 服务层(client/src/services/api/storyboards.ts)
问题代码:
const syncStoryboardResources = async (
storyboardId: string,
resources: StoryboardResources
) => {
const desiredIds = collectResourceIds(resources);
// ❌ 调用废弃的 API 端点
const existing = await apiClient.get(
`/storyboards/${storyboardId}/resources`
);
// ❌ 批量添加(废弃端点)
if (toAdd.length > 0) {
await apiClient.post(
`/storyboards/${storyboardId}/resources/batch`,
{ resource_ids: toAdd }
);
}
// ❌ 批量移除(废弃端点)
if (toRemove.length > 0) {
await apiClient.delete(
`/storyboards/${storyboardId}/resources/batch`,
{ data: { resource_ids: toRemove } }
);
}
};
// 在更新分镜时调用
async update(id: string, data: UpdateStoryboardDto) {
// ...
if (data.resources) {
await syncStoryboardResources(id, data.resources);
}
// ...
}
影响:
- ❌ 更新分镜时无法同步资源关联
- ❌ 前端 UI 显示的资源列表可能不准确
2. 类型定义(client/src/types/storyboard.ts)
问题代码:
export interface StoryboardResources {
characters: StoryboardResourceItem[];
locations: StoryboardResourceItem[];
props: StoryboardResourceItem[];
footages?: StoryboardResourceItem[];
}
export interface Storyboard {
// ...
/** 关联的资源(详情接口返回) */
resources?: StoryboardResources; // ❌ 废弃字段
/** v3.0 统一关联表 - 分镜元素关联项 */
items?: StoryboardItem[]; // ✅ 新字段
// ...
}
影响:
- ⚠️ 前端同时支持两种数据结构(
resources和items) - ⚠️ 造成代码复杂度增加
3. UI 组件依赖
受影响的组件:
-
VideoGenerationPrecheck.tsxconst storyboardCharacters = storyboard.resources?.characters || []; const storyboardLocations = storyboard.resources?.locations || []; const storyboardProps = storyboard.resources?.props || [];- 用途:视频生成前的资源检查
- 影响:无法正确检查资源完整性
-
StoryboardResourcesPreview.tsxif (storyboard.resources) { characters = getResourceWithTag(storyboard.resources.characters, 'character'); locations = getResourceWithTag(storyboard.resources.locations, 'location'); props = getResourceWithTag(storyboard.resources.props, 'prop'); footages = getResourceWithTag(storyboard.resources.footages, 'footage'); }- 用途:分镜资源预览面板
- 影响:资源列表显示不完整
-
StoryboardList.tsxcharacters: getResourceNames(s.resources?.characters), locations: getResourceNames(s.resources?.locations), props: getResourceNames(s.resources?.props),- 用途:分镜列表展示
- 影响:列表中资源信息缺失
-
StoryboardEditForm.tsx- 用途:分镜编辑表单
- 影响:资源选择器功能失效
-
ParseFlowDialog.tsxconst characters = item.resources?.characters ?.map((c) => resourceMap.get(c.resourceId) || '') .filter(Boolean) || [];- 用途:剧本解析流程
- 影响:资源映射失败
-
StoryboardBoardItem.tsxprocessV2Items(storyboard.resources.characters); processV2Items(storyboard.resources.locations); processV2Items(storyboard.resources.props); processV2Items(storyboard.resources.footages);- 用途:分镜看板项
- 影响:看板显示异常
4. Hooks 依赖
受影响的 Hooks:
-
usePreviewActions.tsconst existingResources = currentStoryboard.resources || { characters: [], locations: [], props: [], footages: [] };- 用途:预览操作逻辑
- 影响:资源添加/移除功能失效
-
useStoryboardBoardLogic.tsconst updatedResources = { ...(storyboard.resources || { characters: [], locations: [], props: [], footages: [] }), };- 用途:分镜看板逻辑
- 影响:看板资源同步失败
5. 工具函数依赖
受影响的工具函数:
storyboard-board-status.tsif (storyboard.resources) { storyboard.resources.characters?.forEach((item) => relatedResourceIds.add(item.resourceId) ); storyboard.resources.locations?.forEach((item) => relatedResourceIds.add(item.resourceId) ); storyboard.resources.props?.forEach((item) => relatedResourceIds.add(item.resourceId) ); storyboard.resources.footages?.forEach((item) => relatedResourceIds.add(item.resourceId) ); }- 用途:分镜状态计算
- 影响:状态判断不准确
6. Mock 数据
受影响的文件:
client/src/services/mockApi.tsclient/src/services/mock/storyboardApi.tsclient/src/mocks/storyboard-board.ts
影响:Mock 数据结构与实际 API 不一致
决策
移除 storyboard_resources 表及相关代码,并修复依赖功能。
理由
- 设计已废弃:
storyboard_items提供更强大的多态关联能力 - 命名冲突:与正在使用的 API 路由同名,造成混淆
- 功能重复:
storyboard_items已完全覆盖原有功能 - 代码冗余:Service 和 API 代码存在但无实际作用
- 维护成本:保留废弃代码增加维护负担
- 前端依赖严重:前端大量组件依赖废弃的 API,导致功能失效
影响范围总结
| 层级 | 受影响模块 | 严重程度 | 状态 |
|---|---|---|---|
| 后端 - 数据库 | storyboard_resources 表 |
🔴 高 | 废弃但未删除 |
| 后端 - 模型 | StoryboardResource 类 |
🔴 高 | 废弃但未删除 |
| 后端 - Service | StoryboardResourceService |
🔴 高 | 完全废弃 |
| 后端 - API | 6 个废弃端点 | 🔴 高 | 路由存在但失效 |
| 后端 - Repository | StoryboardResourceRepository.get_by_storyboard() |
🟡 中 | 返回空列表 |
| 后端 - AI Service | _get_storyboard_resources() |
🟡 中 | 功能失效 |
| 后端 - 测试 | 3 个测试文件 | 🟡 中 | 依赖废弃表 |
| 前端 - API 服务 | syncStoryboardResources() |
🔴 高 | 调用废弃端点 |
| 前端 - 类型定义 | StoryboardResources 接口 |
🟡 中 | 废弃字段 |
| 前端 - UI 组件 | 6 个组件 | 🔴 高 | 资源显示/操作失效 |
| 前端 - Hooks | 2 个 Hooks | 🔴 高 | 资源同步失败 |
| 前端 - 工具函数 | 状态计算函数 | 🟡 中 | 计算不准确 |
总计:
- 🔴 高优先级:9 项
- 🟡 中优先级:5 项
- ✅ 已迁移:4 项
实施方案
Phase 1: 修复 AI Conversation 功能
目标:使用 storyboard_items 实现资源提及功能
步骤:
-
修改 StoryboardResourceRepository
async def get_by_storyboard(self, storyboard_id: UUID) -> List[Dict]: """获取分镜的所有关联资源(基于 storyboard_items)""" from app.models.storyboard import StoryboardItem, ItemType from app.models.project_resource import ProjectResource from app.models.project_element_tag import ProjectElementTag stmt = ( select(StoryboardItem) .where(StoryboardItem.storyboard_id == storyboard_id) .order_by(StoryboardItem.display_order) ) result = await self.session.execute(stmt) items = result.scalars().all() resources = [] for item in items: if item.item_type == ItemType.ELEMENT_TAG and item.element_tag_id: # 查询 ElementTag 关联的资源 tag = await self.session.get(ProjectElementTag, item.element_tag_id) if tag and tag.resource_id: resource = await self.session.get(ProjectResource, tag.resource_id) if resource: resources.append({ 'item_id': item.item_id, 'item_type': 'element_tag', 'element_tag_id': item.element_tag_id, 'element_name': item.element_name, 'tag_label': item.tag_label, 'resource_id': resource.project_resource_id, 'resource_url': resource.file_url, 'cover_url': item.cover_url or resource.thumbnail_url, 'is_visible': item.is_visible, 'spatial_position': item.spatial_position, 'action_description': item.action_description }) elif item.item_type == ItemType.RESOURCE and item.resource_id: # 直接关联的项目素材 resource = await self.session.get(ProjectResource, item.resource_id) if resource: resources.append({ 'item_id': item.item_id, 'item_type': 'resource', 'resource_id': resource.project_resource_id, 'resource_url': resource.file_url, 'cover_url': item.cover_url or resource.thumbnail_url, 'is_visible': item.is_visible, 'spatial_position': item.spatial_position, 'action_description': item.action_description }) return resources -
添加 is_linked 方法
async def is_linked( self, storyboard_id: UUID, resource_id: UUID ) -> bool: """验证资源是否关联到分镜(基于 storyboard_items)""" from app.models.storyboard import StoryboardItem, ItemType # 检查直接关联 stmt = select(StoryboardItem).where( StoryboardItem.storyboard_id == storyboard_id, StoryboardItem.item_type == ItemType.RESOURCE, StoryboardItem.resource_id == resource_id ) result = await self.session.execute(stmt) if result.scalar_one_or_none(): return True # 检查通过 ElementTag 间接关联 stmt = ( select(StoryboardItem) .join( ProjectElementTag, StoryboardItem.element_tag_id == ProjectElementTag.element_tag_id ) .where( StoryboardItem.storyboard_id == storyboard_id, StoryboardItem.item_type == ItemType.ELEMENT_TAG, ProjectElementTag.resource_id == resource_id ) ) result = await self.session.execute(stmt) return result.scalar_one_or_none() is not None -
更新 AI Conversation Service
- 保持现有调用方式不变
- 内部实现已切换到
storyboard_items
Phase 2: 修复前端依赖
目标:将前端从 resources 字段迁移到 items 字段
2.1 修改 API 服务层
文件:client/src/services/api/storyboards.ts
步骤:
-
删除
syncStoryboardResources函数- 该函数调用废弃的 API 端点
- 资源关联应通过
storyboard_itemsAPI 管理
-
修改
update方法// 删除这段代码 if (data.resources) { await syncStoryboardResources(id, data.resources); } -
添加新的资源管理方法(如果需要)
// 使用 storyboard_items API async addItem(storyboardId: string, item: StoryboardItemCreate) { return apiClient.post(`/storyboards/${storyboardId}/items`, item); } async updateItem(itemId: string, data: StoryboardItemUpdate) { return apiClient.patch(`/items/${itemId}`, data); } async deleteItem(itemId: string) { return apiClient.delete(`/items/${itemId}`); }
2.2 修改类型定义
文件:client/src/types/storyboard.ts
步骤:
-
标记
resources字段为废弃export interface Storyboard { // ... /** @deprecated 使用 items 字段替代 */ resources?: StoryboardResources; /** v3.0 统一关联表 - 分镜元素关联项 */ items?: StoryboardItem[]; // ... } -
保留
StoryboardResources接口(向后兼容)- 短期内保留,逐步迁移
- 最终删除
2.3 修改 UI 组件
策略:优先使用 items 字段,resources 作为降级方案
通用工具函数:
// client/src/utils/storyboard-items.ts
export function getResourcesFromItems(
items?: StoryboardItem[]
): StoryboardResources {
const resources: StoryboardResources = {
characters: [],
locations: [],
props: [],
footages: []
};
if (!items) return resources;
items.forEach(item => {
if (item.itemType === 1 && item.elementTagId) {
// ElementTag 类型,根据 elementName 判断类型
// 需要配合后端返回的 metadata 或其他字段
} else if (item.itemType === 2 && item.resourceId) {
// Resource 类型,直接使用
const resourceItem = {
resourceId: item.resourceId,
tagId: item.elementTagId,
tagLabel: item.tagLabel,
displayOrder: item.displayOrder
};
// 根据 metadata 或其他字段判断资源类型
// 暂时放入 footages(需要后端提供类型信息)
resources.footages?.push(resourceItem);
}
});
return resources;
}
修改组件:
-
VideoGenerationPrecheck.tsxconst resources = storyboard.items ? getResourcesFromItems(storyboard.items) : storyboard.resources || { characters: [], locations: [], props: [] }; const storyboardCharacters = resources.characters || []; const storyboardLocations = resources.locations || []; const storyboardProps = resources.props || []; -
StoryboardResourcesPreview.tsxconst resources = useMemo(() => { if (storyboard.items) { return getResourcesFromItems(storyboard.items); } return storyboard.resources || { characters: [], locations: [], props: [], footages: [] }; }, [storyboard]); -
其他组件:类似修改
2.4 修改 Hooks
文件:
client/src/hooks/usePreviewActions.tsclient/src/hooks/useStoryboardBoardLogic.ts
修改策略:同 UI 组件
2.5 修改工具函数
文件:client/src/utils/storyboard-board-status.ts
修改:
// 优先使用 items
if (storyboard.items) {
storyboard.items.forEach(item => {
if (item.resourceId) {
relatedResourceIds.add(item.resourceId);
}
});
} else if (storyboard.resources) {
// 降级方案
storyboard.resources.characters?.forEach(item =>
relatedResourceIds.add(item.resourceId)
);
// ...
}
2.6 更新 Mock 数据
文件:
client/src/services/mockApi.tsclient/src/services/mock/storyboardApi.tsclient/src/mocks/storyboard-board.ts
修改:
- 添加
items字段 - 保留
resources字段(向后兼容)
Phase 3: 删除后端废弃代码
步骤:
-
删除 Service
- 删除
server/app/services/storyboard_project_resource_service.py
- 删除
-
删除 API 端点
- 从
server/app/api/v1/project_resources.py中删除相关端点:# 删除以下端点 POST /storyboards/{storyboard_id}/resources/batch DELETE /storyboards/{storyboard_id}/resources/batch POST /storyboards/{storyboard_id}/resources/{resource_id} DELETE /storyboards/{storyboard_id}/resources/{resource_id} GET /storyboards/{storyboard_id}/resources GET /resources/{resource_id}/storyboards - 删除
get_storyboard_resource_service依赖注入函数
- 从
-
删除 Schema
- 删除
server/app/schemas/storyboard_project_resource.py
- 删除
-
删除测试
- 删除
tests/integration/test_storyboard_project_resource_api.py - 删除
tests/unit/services/test_storyboard_project_resource_service.py - 从
tests/conftest.py删除test_storyboard_resourcesfixture
- 删除
-
删除模型
- 从
server/app/models/project_resource.py删除StoryboardResource类
- 从
-
清理导入
- 从
server/app/api/v1/__init__.py中移除相关导入(如果有) - 检查其他文件是否有残留导入
- 从
Phase 4: 数据库迁移
步骤:
-
创建迁移文件
docker exec jointo-server-app alembic revision -m "drop_storyboard_resources_table" -
编写迁移脚本
"""drop storyboard_resources table Revision ID: 20260210_xxxx Revises: <previous_revision> Create Date: 2026-02-10 说明: - 删除废弃的 storyboard_resources 表 - 该表已被 storyboard_items 替代 """ from alembic import op import sqlalchemy as sa from sqlalchemy import inspect revision = '20260210_xxxx' down_revision = '<previous_revision>' branch_labels = None depends_on = None def table_exists(table_name: str) -> bool: """检查表是否存在""" conn = op.get_bind() inspector = inspect(conn) return table_name in inspector.get_table_names() def upgrade() -> None: """删除 storyboard_resources 表""" if table_exists('storyboard_resources'): op.drop_table('storyboard_resources') print("✅ 已删除 storyboard_resources 表") else: print("⚠️ storyboard_resources 表不存在,跳过删除") def downgrade() -> None: """回滚:重新创建 storyboard_resources 表""" if not table_exists('storyboard_resources'): op.create_table( 'storyboard_resources', sa.Column('storyboard_resource_id', sa.UUID(), nullable=False), sa.Column('storyboard_id', sa.UUID(), nullable=False), sa.Column('project_resource_id', sa.UUID(), nullable=False), sa.Column('resource_type', sa.SmallInteger(), nullable=False), sa.Column('display_order', sa.Integer(), nullable=False, server_default='0'), sa.Column('created_at', sa.TIMESTAMP(timezone=True), nullable=False, server_default=sa.text('now()')), sa.PrimaryKeyConstraint('storyboard_resource_id'), sa.UniqueConstraint('storyboard_id', 'project_resource_id', name='storyboard_resources_unique') ) op.create_index('idx_storyboard_resources_storyboard_id', 'storyboard_resources', ['storyboard_id']) op.create_index('idx_storyboard_resources_project_resource_id', 'storyboard_resources', ['project_resource_id']) op.create_index('idx_storyboard_resources_type', 'storyboard_resources', ['resource_type']) print("✅ 已重新创建 storyboard_resources 表") -
执行迁移
docker exec jointo-server-app alembic upgrade head
Phase 5: 验证
检查清单:
后端验证
- AI Conversation 资源提及功能正常
- AI Conversation 资源关联验证正常
- Storyboard Items API 正常工作
- 所有单元测试通过
- 所有集成测试通过
- 数据库迁移成功
- 无残留的
storyboard_resources引用
前端验证
- 分镜列表正常显示资源信息
- 分镜详情页资源预览正常
- 分镜编辑表单资源选择器正常
- 视频生成预检查功能正常
- 分镜看板资源显示正常
- 剧本解析流程资源映射正常
- 预览操作(添加/移除资源)正常
- 分镜状态计算准确
集成验证
- 创建分镜 → 添加资源 → 查看详情(完整流程)
- 更新分镜资源 → 刷新页面(数据持久化)
- 删除分镜 → 资源关联清理(级联删除)
- AI 对话提及资源 → 验证关联(权限检查)
风险评估
低风险
- ✅
storyboard_items已在生产环境稳定运行 - ✅ 后端废弃代码未被实际使用
- ✅ 有完整的测试覆盖
中风险
- ⚠️ AI Conversation 功能需要重新实现
- ⚠️ 需要验证所有资源关联场景
- ⚠️ 前端需要大量组件修改
高风险
- 🔴 前端严重依赖废弃的 API 端点
- 🔴 多个核心功能受影响(视频生成、资源预览、分镜看板)
- 🔴 需要前后端协同修改
缓解措施
技术措施
-
分阶段部署
- Phase 1: 修复后端 AI Conversation(不影响前端)
- Phase 2: 修复前端依赖(向后兼容)
- Phase 3: 删除后端废弃代码
- Phase 4: 数据库迁移
- Phase 5: 删除前端废弃代码
-
向后兼容策略
- 前端同时支持
resources和items字段 - 优先使用
items,resources作为降级方案 - 逐步迁移,避免一次性破坏
- 前端同时支持
-
测试策略
- 在测试环境充分验证
- 编写端到端测试覆盖关键流程
- 保留数据库迁移的 downgrade 脚本
-
监控措施
- 添加日志记录资源关联操作
- 监控 API 调用失败率
- 设置告警阈值
回滚方案
-
数据库回滚
docker exec jointo-server-app alembic downgrade -1 -
代码回滚
- Git revert 相关提交
- 重新部署前一版本
-
数据恢复
- 如果有数据迁移,从备份恢复
- 验证数据完整性
应急预案
-
前端降级
- 如果
items字段有问题,回退到resources字段 - 临时恢复废弃的 API 端点
- 如果
-
功能降级
- 暂时禁用受影响的功能
- 显示维护提示
-
快速修复
- 准备 hotfix 分支
- 快速发布补丁版本
后续工作
-
重命名 Screenplay Service 方法
_sync_storyboard_resources()→_create_project_resources_from_tags()- 更准确反映其功能
-
文档更新
- 更新 API 文档
- 更新架构图
- 更新开发指南
-
性能优化
- 为
storyboard_items添加复合索引 - 优化资源查询的 JOIN 性能
- 为