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.
 

18 KiB

项目素材服务 - 流程图与关系图

文档版本:v1.0
最后更新:2026-02-02
关联文档项目素材服务


目录

  1. 素材上传流程
  2. AI 生成素材流程
  3. 素材删除流程
  4. 素材与分镜关联流程
  5. 数据库表关系图
  6. 素材类型枚举
  7. 服务依赖关系图
  8. 文件去重机制

素材上传流程

sequenceDiagram
    participant U as 用户
    participant F as 前端
    participant A as API 服务
    participant PS as ProjectService
    participant PRS as ProjectResourceService
    participant FS as FileStorageService
    participant DB as 数据库
    participant S3 as 对象存储
    
    U->>F: 上传素材文件
    F->>F: 选择素材类型(character/scene/prop/footage)
    F->>F: 可选:关联标签(element_tag_id)
    F->>A: POST /api/v1/projects/{id}/resources
    
    A->>PRS: upload_resource()
    PRS->>PS: 检查项目存在性和权限
    PS->>DB: 查询项目信息
    DB-->>PS: 返回项目数据
    PS-->>PRS: 验证通过
    
    alt 素材类型为 footage
        PRS->>PRS: 检查文件类型(图片或视频)
        alt 文件为视频
            PRS->>PRS: 验证视频大小(<= 500MB)
            PRS->>PRS: 使用 ffmpeg 获取视频元数据
        else 文件为图片
            PRS->>PRS: 验证图片大小(<= 20MB)
            PRS->>PRS: 获取图片尺寸
        end
    else 其他素材类型
        PRS->>PRS: 验证图片类型和大小(<= 20MB)
        PRS->>PRS: 获取图片尺寸
    end
    
    PRS->>FS: upload_file()
    FS->>FS: 计算文件 SHA256 校验和
    FS->>DB: 检查文件是否已存在(去重)
    alt 文件已存在
        FS->>DB: 增加引用计数
    else 文件不存在
        FS->>S3: 上传文件到对象存储
        S3-->>FS: 返回文件 URL
        FS->>DB: 创建 file_checksums 记录
    end
    FS-->>PRS: 返回文件元数据
    
    alt 文件为视频
        PRS->>PRS: 使用 ffmpeg 提取第一帧
        PRS->>FS: 上传视频缩略图
        FS->>S3: 上传缩略图
        S3-->>FS: 返回缩略图 URL
        FS-->>PRS: 返回缩略图元数据
    else 文件为图片
        PRS->>PRS: 生成 300x300 缩略图
        PRS->>FS: 上传图片缩略图
        FS->>S3: 上传缩略图
        S3-->>FS: 返回缩略图 URL
        FS-->>PRS: 返回缩略图元数据
    end
    
    alt 提供了 element_tag_id
        PRS->>DB: 查询标签信息
        DB-->>PRS: 返回 element_name 和 tag_label
        PRS->>PRS: 填充冗余字段
    end
    
    PRS->>PRS: 生成 UUID v7
    PRS->>DB: 创建 project_resources 记录
    DB-->>PRS: 返回素材信息
    PRS-->>A: 返回素材数据
    A-->>F: 返回成功响应
    F-->>U: 显示上传成功

流程说明

  1. 项目权限检查:确保用户有权限在项目中上传素材
  2. 文件类型验证
    • character/scene/prop:仅支持图片(JPEG、PNG、GIF、WebP、BMP),最大 20MB
    • footage:支持图片和视频(MP4、MOV、AVI、WebM),图片最大 20MB,视频最大 500MB
  3. 文件去重:通过 SHA256 校验和检查文件是否已存在
  4. 缩略图生成
    • 图片:生成 300x300 缩略图
    • 视频:使用 ffmpeg 提取第一帧作为缩略图
  5. 标签关联:如果提供 element_tag_id,自动填充 element_nametag_label 冗余字段
  6. UUID v7 生成:应用层生成主键

AI 生成素材流程

sequenceDiagram
    participant U as 用户
    participant F as 前端
    participant A as API 服务
    participant AI as AI Service
    participant Q as Celery 队列
    participant W as Worker
    participant PRS as ProjectResourceService
    participant FS as FileStorageService
    participant DB as 数据库
    participant S3 as 对象存储
    
    U->>F: 输入提示词生成素材
    F->>A: POST /api/v1/ai/generate-image
    A->>AI: 创建 AI 任务
    AI->>DB: 检查积分余额
    AI->>DB: 创建 AI 任务记录
    AI->>DB: 扣除积分
    AI->>Q: 提交异步任务
    AI-->>A: 返回任务 ID
    A-->>F: 返回任务 ID
    F-->>U: 显示生成中
    
    Q->>W: 分配任务
    W->>W: 调用 AI 大模型 API
    W->>W: 获取生成的图片数据
    
    W->>PRS: create_ai_generated_resource()
    PRS->>PRS: 解析图片格式和尺寸
    PRS->>FS: upload_file()
    FS->>FS: 计算 SHA256 校验和
    FS->>S3: 上传图片
    S3-->>FS: 返回文件 URL
    FS->>DB: 创建 file_checksums 记录
    FS-->>PRS: 返回文件元数据
    
    PRS->>PRS: 生成 300x300 缩略图
    PRS->>FS: 上传缩略图
    FS->>S3: 上传缩略图
    S3-->>FS: 返回缩略图 URL
    FS-->>PRS: 返回缩略图元数据
    
    alt 提供了 element_tag_id
        PRS->>DB: 查询标签信息
        DB-->>PRS: 返回 element_name 和 tag_label
        PRS->>PRS: 填充冗余字段
    end
    
    PRS->>PRS: 生成 UUID v7
    PRS->>DB: 创建 project_resources 记录
    PRS->>DB: 关联 ai_job_id
    DB-->>PRS: 返回素材信息
    
    W->>DB: 更新 AI 任务状态(completed)
    
    loop 轮询任务状态
        F->>A: GET /api/v1/ai/jobs/{taskId}
        A->>DB: 查询任务状态
        alt 任务完成
            DB-->>A: 返回素材信息
            A-->>F: 返回素材数据
            F->>F: 显示生成结果
            F-->>U: 显示素材图片
        end
    end

流程说明

  1. 积分检查:AI Service 检查用户积分余额
  2. 异步任务:创建 Celery 任务,立即返回 task_id
  3. AI 生成:Worker 调用 AI 大模型 API 生成图片
  4. 文件处理:与上传流程类似,计算校验和、生成缩略图
  5. 任务关联:记录 ai_job_id,便于追溯生成历史
  6. 轮询状态:前端轮询任务状态,完成后显示结果

素材删除流程

sequenceDiagram
    participant U as 用户
    participant F as 前端
    participant A as API 服务
    participant PRS as ProjectResourceService
    participant FS as FileStorageService
    participant DB as 数据库
    
    U->>F: 点击删除素材
    F->>A: GET /api/v1/resources/{id}/usage
    A->>PRS: check_resource_usage()
    PRS->>DB: 查询 usage_count
    DB-->>PRS: 返回使用情况
    PRS-->>A: 返回使用统计
    A-->>F: 返回 usage_count
    
    alt usage_count > 0
        F->>F: 显示警告对话框
        F-->>U: 提示"素材正在被 N 个分镜使用"
        U->>F: 选择是否强制删除
        alt 用户取消
            F-->>U: 取消删除
        else 用户确认强制删除
            F->>A: DELETE /api/v1/resources/{id}?force=true
        end
    else usage_count = 0
        F->>F: 显示确认对话框
        U->>F: 确认删除
        F->>A: DELETE /api/v1/resources/{id}?force=false
    end
    
    A->>PRS: delete_resource()
    PRS->>DB: 检查项目权限
    
    alt force=false 且 usage_count > 0
        PRS-->>A: 抛出 ValidationError
        A-->>F: 返回 400 错误
        F-->>U: 显示错误提示
    else 允许删除
        PRS->>DB: 软删除(设置 deleted_at)
        PRS->>FS: decrease_reference_count()
        FS->>DB: 减少文件引用计数
        alt 引用计数为 0
            FS->>DB: 标记文件可清理
            Note over FS: 定时任务会清理无引用文件
        end
        FS-->>PRS: 返回成功
        PRS-->>A: 返回成功
        A-->>F: 返回成功响应
        F->>F: 从列表中移除素材
        F-->>U: 显示删除成功
    end

流程说明

  1. 使用检查:删除前检查 usage_count(被多少个分镜使用)
  2. 用户确认
    • usage_count > 0:显示警告,询问是否强制删除
    • usage_count = 0:直接确认删除
  3. 删除保护:默认禁止删除正在使用的素材(force=false
  4. 强制删除force=true 时允许删除,但会导致分镜失去素材引用
  5. 软删除:设置 deleted_at,不物理删除记录
  6. 引用计数:减少文件引用计数,引用为 0 时标记可清理

素材与分镜关联流程

sequenceDiagram
    participant U as 用户
    participant F as 前端
    participant A as API 服务
    participant SRS as StoryboardResourceService
    participant PRS as ProjectResourceService
    participant DB as 数据库
    
    U->>F: 将素材拖拽到分镜
    F->>A: POST /api/v1/storyboards/{id}/resources
    A->>SRS: add_resource_to_storyboard()
    
    SRS->>DB: 检查分镜存在性
    SRS->>PRS: 检查素材存在性
    PRS->>DB: 查询素材信息
    DB-->>PRS: 返回素材数据
    PRS-->>SRS: 验证通过
    
    SRS->>DB: 检查是否已关联
    alt 已关联
        SRS-->>A: 返回错误
        A-->>F: 返回 400 错误
        F-->>U: 提示"素材已添加"
    else 未关联
        SRS->>DB: 创建 storyboard_resources 记录
        SRS->>DB: 增加素材 usage_count
        DB-->>SRS: 返回关联信息
        SRS-->>A: 返回成功
        A-->>F: 返回关联数据
        F->>F: 更新分镜资源列表
        F-->>U: 显示素材已添加
    end
    
    Note over U,DB: 从分镜移除素材
    U->>F: 点击移除素材
    F->>A: DELETE /api/v1/storyboards/{id}/resources/{resourceId}
    A->>SRS: remove_resource_from_storyboard()
    
    SRS->>DB: 删除 storyboard_resources 记录
    SRS->>DB: 减少素材 usage_count
    DB-->>SRS: 返回成功
    SRS-->>A: 返回成功
    A-->>F: 返回成功响应
    F->>F: 从列表中移除素材
    F-->>U: 显示移除成功

流程说明

  1. 添加素材到分镜
    • 检查分镜和素材存在性
    • 检查是否已关联(避免重复)
    • 创建 storyboard_resources 关联记录
    • 增加素材的 usage_count
  2. 从分镜移除素材
    • 删除 storyboard_resources 关联记录
    • 减少素材的 usage_count
  3. 引用计数维护usage_countStoryboardResourceService 自动维护

数据库表关系图

erDiagram
    projects ||--o{ project_resources : "归属"
    users ||--o{ project_resources : "创建"
    screenplay_element_tags ||--o{ project_resources : "关联标签"
    ai_jobs ||--o{ project_resources : "AI生成"
    resources ||--o{ project_resources : "来源-后期扩展"
    file_checksums ||--o{ project_resources : "文件去重"
    
    project_resources ||--o{ storyboard_resources : "关联"
    storyboards ||--o{ storyboard_resources : "使用"
    
    projects {
        uuid project_id PK "项目ID"
        text name "项目名称"
        uuid created_by FK "创建者"
    }
    
    project_resources {
        uuid project_resource_id PK "素材ID-UUID_v7"
        uuid project_id FK "项目ID-逻辑外键"
        text name "素材名称"
        smallint type "素材类型-1到4"
        text file_url "文件URL"
        text thumbnail_url "缩略图URL"
        bigint file_size "文件大小"
        text checksum "文件校验和"
        uuid element_tag_id FK "标签ID-逻辑外键"
        text element_name "元素名称-冗余"
        text tag_label "标签名称-冗余"
        uuid ai_job_id FK "AI任务ID-逻辑外键"
        jsonb meta_data "元数据"
        int usage_count "引用计数"
        uuid created_by FK "创建者-逻辑外键"
        timestamptz created_at "创建时间"
        timestamptz deleted_at "删除时间"
    }
    
    storyboard_resources {
        uuid storyboard_resource_id PK "关联ID-UUID_v7"
        uuid storyboard_id FK "分镜ID-逻辑外键"
        uuid project_resource_id FK "素材ID-逻辑外键"
        smallint resource_type "素材类型-1到4"
        int display_order "显示顺序"
        timestamptz created_at "创建时间"
    }
    
    screenplay_element_tags {
        uuid element_tag_id PK "标签ID"
        text element_name "元素名称"
        text tag_label "标签名称"
    }
    
    file_checksums {
        text checksum PK "文件校验和"
        text file_url "文件URL"
        int reference_count "引用计数"
    }
    
    ai_jobs {
        uuid ai_job_id PK "AI任务ID"
        text status "任务状态"
    }
    
    users {
        uuid user_id PK "用户ID"
        text nickname "昵称"
    }
    
    storyboards {
        uuid storyboard_id PK "分镜ID"
        uuid project_id FK "项目ID"
        text title "分镜标题"
    }
    
    resources {
        uuid resource_id PK "资源ID"
        text name "资源名称"
    }

说明

  • 所有外键关系均为逻辑外键(无物理约束)
  • 引用完整性由应用层(Service/Repository)保证
  • element_nametag_label 为冗余字段,优化查询性能
  • usage_count 记录素材被多少个分镜使用
  • file_checksums 表实现文件去重

素材类型枚举

graph LR
    ResourceType[素材类型 SMALLINT] --> Character[1 = character 角色]
    ResourceType --> Scene[2 = scene 场景]
    ResourceType --> Prop[3 = prop 道具]
    ResourceType --> Footage[4 = footage 实拍]
    
    Character --> CharDesc[主角、配角、群演形象]
    Scene --> SceneDesc[室内外场景背景]
    Prop --> PropDesc[物品、工具、装饰]
    Footage --> FootageDesc[航拍、瞰景、实拍视频]
    
    Character --> AISupport1[✅ 支持 AI 生成]
    Scene --> AISupport2[✅ 支持 AI 生成]
    Prop --> AISupport3[✅ 支持 AI 生成]
    Footage --> AISupport4[❌ 不支持 AI 生成]
    
    Character --> TagSupport1[✅ 可关联标签]
    Scene --> TagSupport2[✅ 可关联标签]
    Prop --> TagSupport3[✅ 可关联标签]
    Footage --> TagSupport4[❌ 不可关联标签]
    
    style ResourceType fill:#e1f5e1
    style Footage fill:#ffe1e1

素材类型说明

类型 数值 字符串 描述 AI 生成 标签关联 文件类型
角色 1 character 主角、配角、群演形象 图片
场景 2 scene 室内外场景背景 图片
道具 3 prop 物品、工具、装饰 图片
实拍 4 footage 航拍、瞰景、实拍视频 图片/视频

数据库约束

-- 实拍素材不能有标签关联
CONSTRAINT project_resources_footage_no_tag_check CHECK (
    (type = 4 AND element_tag_id IS NULL AND element_name IS NULL AND tag_label IS NULL) OR
    (type IN (1, 2, 3))
)

服务依赖关系图

graph TB
    API[API 路由层] --> PRS[ProjectResourceService]
    
    PRS --> PS[ProjectService]
    PRS --> FS[FileStorageService]
    PRS --> Repo[ProjectResourceRepository]
    
    PS --> DB[(PostgreSQL)]
    FS --> DB
    FS --> S3[对象存储 MinIO/S3]
    Repo --> DB
    
    PRS --> TagRepo[ScreenplayTagRepository]
    PRS --> ScreenplayRepo[ScreenplayRepository]
    TagRepo --> DB
    ScreenplayRepo --> DB
    
    SRS[StoryboardResourceService] --> PRS
    SRS --> DB
    
    AI[AI Service] --> PRS
    AI --> Queue[Celery 队列]
    Queue --> Worker[Worker]
    Worker --> PRS
    
    style PRS fill:#e1f5e1
    style FS fill:#fff4e1
    style S3 fill:#e1f0ff

依赖说明

  1. ProjectResourceService(核心服务):

    • 依赖 ProjectService:检查项目权限
    • 依赖 FileStorageService:文件上传和去重
    • 依赖 ScreenplayTagRepository:验证标签关联
    • 依赖 ScreenplayRepository:验证标签归属项目
  2. StoryboardResourceService

    • 调用 ProjectResourceService 检查素材存在性
    • 维护素材的 usage_count
  3. AI Service

    • 通过 Celery Worker 调用 ProjectResourceService.create_ai_generated_resource()
    • 异步生成素材
  4. FileStorageService

    • 上传文件到对象存储(MinIO/S3)
    • 管理文件去重(file_checksums 表)

文件去重机制

graph TB
    Upload[上传文件] --> CalcHash[计算 SHA256 校验和]
    CalcHash --> CheckDB{文件已存在?}
    
    CheckDB -->|是| IncRef[增加引用计数]
    CheckDB -->|否| UploadS3[上传到 S3]
    
    UploadS3 --> CreateRecord[创建 file_checksums 记录]
    CreateRecord --> SetRef[设置引用计数 = 1]
    
    IncRef --> CreateResource[创建 project_resources 记录]
    SetRef --> CreateResource
    
    CreateResource --> ReuseURL[复用已有文件 URL]
    
    Delete[删除素材] --> DecRef[减少引用计数]
    DecRef --> CheckRef{引用计数 = 0?}
    
    CheckRef -->|是| MarkClean[标记文件可清理]
    CheckRef -->|否| Keep[保留文件]
    
    MarkClean --> CronJob[定时任务清理]
    CronJob --> DeleteS3[从 S3 删除文件]
    
    style CheckDB fill:#fff4e1
    style CheckRef fill:#fff4e1
    style UploadS3 fill:#e1f0ff

去重机制说明

  1. 上传阶段

    • 计算文件的 SHA256 校验和
    • 查询 file_checksums 表检查文件是否已存在
    • 如果存在:增加引用计数,复用已有 URL
    • 如果不存在:上传到 S3,创建新记录
  2. 删除阶段

    • 软删除 project_resources 记录
    • 减少 file_checksums 的引用计数
    • 引用计数为 0 时,标记文件可清理
  3. 清理阶段

    • 定时任务扫描引用计数为 0 的文件
    • 从 S3 删除物理文件
    • 删除 file_checksums 记录

优点

  • 节省存储空间(相同文件只存储一份)
  • 加快上传速度(已存在的文件秒传)
  • 安全删除(引用计数为 0 才物理删除)

相关文档


文档版本:v1.0
最后更新:2026-02-02