# 跨项目资源共享:选择资源后的入库与关联 本文说明在「创建项目时选择共享资源」场景下,前端选中项如何映射为 API 请求、后端如何写入数据库并建立关联。设计依据:[ADR 02: 跨项目资源共享](../adrs/02-cross-project-resource-sharing.md)。 --- ## 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 一致): ```ts 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. 后端处理流程(入库与关联) 1. **创建项目** 先执行现有 `ProjectService.create_project()`,得到 `target_project_id`(新项目 ID)。 2. **若请求体无 `shared_resources` 或为空** 跳过以下步骤,直接返回项目信息。 3. **逐条处理 `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`(素材) - 得到 `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)`。 - 若资源不存在或无权限共享,则校验失败,不插入。 4. **权限** 插入前需校验:当前用户对 `source_project_id` 有访问权限(所有者或成员),参见 ADR 5.1 `check_share_permission`。 5. **响应** 可按 ADR 6.1 在创建项目响应中返回 `shared_resources: { total, shares: [...] }`,便于前端展示或调试。 --- ## 4. 与 CreateProjectModal 的对接要点 - **提交时**:将 `ResourceSelectorPanel` 的 `selectedResources`(`SharedResource[]`)按上表转换为 `shared_resources` 数组,随 `POST /api/v1/projects` 的 body 一起提交。 - **当前代码**:CreateProjectModal 中已有 TODO(如 `// TODO: 将 selectedResources 传递给后端 API`),只需: 1. 实现 `selectedResources` → `shared_resources` 的转换(folder/project/character/scene/prop → source_type/share_type/resource_type); 2. 在 `createProject.mutateAsync` 的请求参数中增加 `sharedResources`(或与后端约定字段名 `shared_resources`)。 - **后续**:若支持「按资源类型共享」(勾选某项目下全部角色),再增加 `share_type=2` 与对应 `resource_type` 的构建逻辑。 --- ## 5. 相关文档与代码位置 - **ADR**:[02-cross-project-resource-sharing.md](../adrs/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` 的请求体解析与校验。