6.7 KiB
跨项目资源共享:选择资源后的入库与关联
本文说明在「创建项目时选择共享资源」场景下,前端选中项如何映射为 API 请求、后端如何写入数据库并建立关联。设计依据:ADR 02: 跨项目资源共享。
1. 数据库关联方式
关联关系保存在表 project_resource_shares 中,不复制资源数据,只记录「谁共享给谁、共享粒度」。
| 字段 | 含义 |
|---|---|
share_id |
主键(UUID v7,应用层生成) |
source_project_id |
资源来源项目(被共享的项目) |
target_project_id |
资源目标项目(新建的、使用资源的项目) |
share_type |
1=整个项目,2=特定资源类型,3=特定资源 |
resource_type |
1=角色,2=场景,3=道具,4=素材(share_type=2 或 3 时使用) |
resource_id |
具体资源 ID(share_type=3 时使用) |
status |
1=激活,2=已断开 |
created_by |
创建人(目标项目所有者) |
- 禁止物理外键,引用完整性在 Service/Repository 层校验。
- 唯一约束:
(source_project_id, target_project_id, share_type, resource_type, resource_id),避免重复共享。 - 断开共享时更新
status=2,不物理删除。
2. 前端选中项 → API 请求体映射
创建项目接口:POST /api/v1/projects
请求体中的 shared_resources 数组元素格式(与 ADR 6.1 一致):
interface SharedResourceItem {
source_type: 'folder' | 'project' | 'resource';
source_id: string; // 文件夹ID / 项目ID / 资源ID
share_type: 1 | 2 | 3; // 1=整个项目, 2=特定资源类型, 3=特定资源
resource_type?: number | null; // share_type=2 或 3 时必填:1=角色, 2=场景, 3=道具, 4=素材
}
从 CreateProjectModal / ResourceSelectorPanel 的 SharedResource 构建 shared_resources:
前端选中类型 (SharedResource.type) |
source_type |
source_id |
share_type |
resource_type |
|---|---|---|---|---|
folder |
folder |
文件夹 ID | 1 |
null |
project |
project |
项目 ID | 1 |
null |
character |
resource |
角色资源 ID | 3 |
1 |
scene |
resource |
场景资源 ID | 3 |
2 |
prop |
resource |
道具资源 ID | 3 |
3 |
注意:当前前端若未区分类别,可暂不传 source_type: 'folder' 的「按类型共享」(share_type=2);仅实现「整个项目」和「特定资源」即可。
去重与展开(建议在前端或后端二选一统一规则):
- 文件夹:后端根据
source_type: 'folder'+source_id查询该文件夹下所有父项目(parent_project_id IS NULL),为每个项目插入一条share_type=1的记录;无需前端先展开为多个 project。 - 项目:一条
source_type: 'project',share_type=1对应一条project_resource_shares记录。 - 具体资源:一条
source_type: 'resource',share_type=3,resource_type+source_id对应一条记录;后端需用resource_id+resource_type反查source_project_id(见下节)。
ADR 文档中的 「构建 shared_resources 数组」 示例(见 6.1 与前端交互流程)可直接复用,只需保证前端 SharedResource 的 id、type 与上表一致。
3. 后端处理流程(入库与关联)
-
创建项目
先执行现有ProjectService.create_project(),得到target_project_id(新项目 ID)。 -
若请求体无
shared_resources或为空
跳过以下步骤,直接返回项目信息。 -
逐条处理
shared_resources(建议在ProjectResourceShareService.create_shares(target_project_id, shared_resources, user_id)中):source_type === 'folder'- 用
source_id查该文件夹下所有父项目(folder_id = source_id AND parent_project_id IS NULL AND deleted_at IS NULL)。 - 对每个父项目插入一条:
(source_project_id=该父项目ID, target_project_id, share_type=1, resource_type=NULL, resource_id=NULL, status=1, created_by=user_id)。 - 唯一约束冲突时
ON CONFLICT DO NOTHING或忽略重复。
- 用
source_type === 'project'- 插入一条:
(source_project_id=source_id, target_project_id, share_type=1, resource_type=NULL, resource_id=NULL, status=1, created_by=user_id)。
- 插入一条:
source_type === 'resource'- 根据
resource_type在对应表查资源所属项目:- 1 →
project_characters(或当前项目资源表名) - 2 →
project_locations - 3 →
project_props - 4 →
project_resources(素材)
- 1 →
- 得到
source_project_id后插入一条:
(source_project_id, target_project_id, share_type=3, resource_type=resource_type, resource_id=source_id, status=1, created_by=user_id)。 - 若资源不存在或无权限共享,则校验失败,不插入。
- 根据
-
权限
插入前需校验:当前用户对source_project_id有访问权限(所有者或成员),参见 ADR 5.1check_share_permission。 -
响应
可按 ADR 6.1 在创建项目响应中返回shared_resources: { total, shares: [...] },便于前端展示或调试。
4. 与 CreateProjectModal 的对接要点
- 提交时:将
ResourceSelectorPanel的selectedResources(SharedResource[])按上表转换为shared_resources数组,随POST /api/v1/projects的 body 一起提交。 - 当前代码:CreateProjectModal 中已有 TODO(如
// TODO: 将 selectedResources 传递给后端 API),只需:- 实现
selectedResources→shared_resources的转换(folder/project/character/scene/prop → source_type/share_type/resource_type); - 在
createProject.mutateAsync的请求参数中增加sharedResources(或与后端约定字段名shared_resources)。
- 实现
- 后续:若支持「按资源类型共享」(勾选某项目下全部角色),再增加
share_type=2与对应resource_type的构建逻辑。
5. 相关文档与代码位置
- ADR:02-cross-project-resource-sharing.md(表结构、共享粒度、API、权限、查询)
- 前端:
client/src/components/features/project/CreateProjectModal.tsx、ResourceSelectorPanel.tsx,类型SharedResource、shared-resource.ts - 后端(待实现):
ProjectResourceShareRepository、ProjectResourceShareService.create_shares()、ProjectService.create_project(..., shared_resources=...),以及POST /api/v1/projects的请求体解析与校验。