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.
 

12 KiB

附件与资源管理架构方案

文档版本:v1.0
创建时间:2025-01-27
作者:系统架构师


目录

  1. 背景与问题
  2. 架构设计
  3. 表结构对比
  4. 资源流转路径
  5. 数据量预估
  6. 性能优化策略
  7. 实施计划

背景与问题

1.1 原始设计问题

在初始设计中,所有文件(文档、图片、视频、音频)都计划存储在单一的 attachments 表中,这会导致以下问题:

  1. 表过大风险:随着项目增多,单表可能达到百万级甚至千万级记录
  2. 业务混乱:角色/场景/道具素材与普通文档附件的使用场景完全不同
  3. 查询性能差:不同类型文件的查询模式不同,单表难以针对性优化
  4. 扩展困难:后期需要支持素材市场功能,单表设计难以扩展

1.2 核心需求

  1. 业务分层:区分"文档附件"、"项目素材"、"系统资源库"
  2. 性能保证:每个表的数据量控制在合理范围内
  3. 去重机制:相同文件只存储一次,节省存储空间
  4. 扩展性:支持后期素材市场功能扩展

架构设计

2.1 核心思想

业务分离 + 服务层统一

  • 不同业务场景的文件使用不同的表管理
  • 通过公共服务层(FileStorageService)实现代码复用和去重
  • 使用 file_checksums 表实现全局去重

2.2 架构图

┌─────────────────────────────────────────────────────────────┐
│                    业务层(Services)                         │
├─────────────────┬─────────────────┬─────────────────────────┤
│ AttachmentService│ProjectResource │  VideoService          │
│ (文档附件)       │Service(项目素材)│  (视频管理)            │
└────────┬────────┴────────┬────────┴────────┬───────────────┘
         │                 │                 │
         └─────────────────┼─────────────────┘
                           │
                ┌──────────▼──────────┐
                │ FileStorageService  │ (公共服务层)
                │  - 文件上传         │
                │  - 文件去重         │
                │  - 引用计数管理     │
                └──────────┬──────────┘
                           │
         ┌─────────────────┼─────────────────┐
         │                 │                 │
    ┌────▼────┐      ┌────▼────┐      ┌────▼────┐
    │file_    │      │Object   │      │Database │
    │checksums│      │Storage  │      │Tables   │
    │(去重表) │      │(MinIO)  │      │         │
    └─────────┘      └─────────┘      └─────────┘

2.3 表结构分层

attachments 表
├─ 用途:文档类附件(剧本、合同、参考资料、头像、封面)
├─ 分类:document, image
├─ 关联:project_id, script_id
└─ 数据量:1-5万条(1000个项目)

project_resources 表
├─ 用途:项目专属素材(角色、场景、道具)
├─ 分类:character, scene, prop
├─ 关联:project_id
└─ 数据量:5-30万条(1000个项目)

resources 表(后期扩展)
├─ 用途:系统资源库 + 素材市场
├─ 特点:is_public, is_official, price
└─ 数据量:1-10万条(公共资源)

file_checksums 表
├─ 用途:全局文件去重
└─ 数据量:与文件总数相当

videos/sound_effects/voiceovers 表
├─ 用途:视频、音效、配音管理
└─ 继续独立管理

表结构对比

3.1 调整前 vs 调整后

attachments 表

调整前

CREATE TABLE attachments (
    attachment_id BIGINT PRIMARY KEY,
    category ENUM('script', 'document', 'contract', 'reference', 'other'),
    project_id BIGINT,
    user_id BIGINT,  -- 多态关联,语义不清
    checksum TEXT,   -- 可选
    -- ...
);

调整后

CREATE TABLE attachments (
    attachment_id BIGINT PRIMARY KEY,
    category ENUM('document', 'image'),  -- 简化分类
    project_id BIGINT,
    script_id BIGINT,  -- 新增剧本关联
    checksum TEXT NOT NULL,  -- 必填,支持去重
    -- 移除 user_id(改为业务表存储)
    -- ...
);

-- 业务表新增字段(一对一关联)
ALTER TABLE users ADD COLUMN avatar_id BIGINT REFERENCES attachments;
ALTER TABLE projects ADD COLUMN cover_image_id BIGINT REFERENCES attachments;
ALTER TABLE storyboards ADD COLUMN thumbnail_id BIGINT REFERENCES attachments;

resources 表

调整前

CREATE TABLE resources (
    resource_id BIGINT PRIMARY KEY,
    type ENUM('character', 'scene', 'prop'),
    file_url TEXT,
    is_public BOOLEAN,  -- 素材市场
    price NUMERIC,
    -- 项目和公共资源混在一起
);

调整后

-- 拆分为两个表

-- 1. 项目专属素材(MVP 阶段实现)
CREATE TABLE project_resources (
    project_resource_id BIGINT PRIMARY KEY,
    project_id BIGINT NOT NULL,  -- 归属项目
    type ENUM('character', 'scene', 'prop'),
    file_url TEXT NOT NULL,
    checksum TEXT NOT NULL,
    -- ...
);

-- 2. 系统资源库(后期扩展)
CREATE TABLE resources (
    resource_id BIGINT PRIMARY KEY,
    type ENUM('character', 'scene', 'prop'),
    file_url TEXT NOT NULL,
    is_public BOOLEAN,  -- 素材市场
    is_official BOOLEAN,  -- 官方资源
    price NUMERIC,
    -- ...
);

3.2 新增表

file_checksums 表(全局去重)

CREATE TABLE file_checksums (
    checksum TEXT PRIMARY KEY,
    file_url TEXT NOT NULL,
    file_size BIGINT NOT NULL,
    mime_type TEXT NOT NULL,
    storage_path TEXT NOT NULL,
    reference_count INTEGER NOT NULL DEFAULT 1,
    created_at TIMESTAMPTZ NOT NULL,
    last_accessed_at TIMESTAMPTZ NOT NULL
);

作用

  • 跨所有表实现文件去重
  • 记录引用计数,便于清理
  • 记录最后访问时间,便于清理过期文件

资源流转路径

4.1 MVP 阶段(前期开发)

用户上传/AI生成
    ↓
项目素材库 (project_resources)
    ↓
分镜/时间轴 (storyboard_resources, timeline_items)

4.2 完整版(后期扩展)

系统资源库 (resources, is_official=true)
    ↓ 复制
素材市场 (resources, is_public=true)
    ↓ 下载/复制
项目素材库 (project_resources)
    ↓ 使用
分镜/时间轴 (storyboard_resources, timeline_items)

4.3 文件去重流程

用户上传文件
    ↓
计算 SHA256 校验和
    ↓
查询 file_checksums 表
    ├─ 存在 → 增加引用计数,返回已有 URL
    └─ 不存在 → 上传到对象存储,记录到 file_checksums

数据量预估

5.1 单个项目数据量

类型 数量 说明
文档附件 10-50 剧本、合同、参考资料
项目素材 50-300 角色、场景、道具图片
视频片段 50-500 分镜视频、合成素材
音效 20-100 背景音乐、音效
配音 10-50 角色配音
总计 140-1000 单个项目文件总数

5.2 系统总数据量(1000个项目)

表名 数据量 说明
attachments 1-5万 文档附件
project_resources 5-30万 项目素材
videos 5-50万 视频
sound_effects 2-10万 音效
voiceovers 1-5万 配音
file_checksums 14-100万 去重表
总计 28-200万 分散在6个表中

5.3 对比分析

方案 单表最大数据量 查询性能 扩展性
单一附件表 28-200万
业务分离方案 5-50万

性能优化策略

6.1 索引策略

-- attachments 表
CREATE INDEX idx_attachments_project_id ON attachments (project_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_attachments_script_id ON attachments (script_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_attachments_checksum ON attachments (checksum);

-- project_resources 表
CREATE INDEX idx_project_resources_project_id ON project_resources (project_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_project_resources_type ON project_resources (type) WHERE deleted_at IS NULL;
CREATE INDEX idx_project_resources_checksum ON project_resources (checksum);

-- file_checksums 表
CREATE INDEX idx_file_checksums_reference_count ON file_checksums (reference_count);
CREATE INDEX idx_file_checksums_last_accessed ON file_checksums (last_accessed_at);

6.2 缓存策略

# Redis 缓存热点文件
# Key: file:checksum:{checksum}
# Value: {file_url, file_size, mime_type}
# TTL: 7天

# 缓存项目素材列表
# Key: project:resources:{project_id}:{type}
# Value: [resource_ids]
# TTL: 1小时

6.3 CDN 加速

用户请求文件
    ↓
CDN 缓存
    ├─ 命中 → 直接返回
    └─ 未命中 → 回源到对象存储

6.4 定时清理

# 每天凌晨3点执行
# 清理30天未访问且无引用的文件
@celery_app.task
async def cleanup_unused_files():
    file_storage = FileStorageService(db)
    deleted_count = await file_storage.cleanup_unused_files(days=30)

实施计划

7.1 Phase 1: 数据库迁移(如果已有数据)

-- 1. 创建新表
CREATE TABLE project_resources (...);
CREATE TABLE file_checksums (...);

-- 2. 数据迁移
INSERT INTO project_resources (...)
SELECT ... FROM resources WHERE ...;

-- 3. 更新业务表
ALTER TABLE users ADD COLUMN avatar_id BIGINT;
ALTER TABLE projects ADD COLUMN cover_image_id BIGINT;
ALTER TABLE storyboards ADD COLUMN thumbnail_id BIGINT;

-- 4. 数据迁移
UPDATE users SET avatar_id = (SELECT attachment_id FROM attachments WHERE ...);

-- 5. 清理旧数据
-- 备份后删除

7.2 Phase 2: 代码更新

  1. 创建 FileStorageService
  2. 更新 AttachmentService
  3. 创建 ProjectResourceService
  4. 更新 API 接口
  5. 更新前端调用

7.3 Phase 3: 测试验证

  1. 单元测试:各服务功能测试
  2. 集成测试:文件上传、下载、删除流程测试
  3. 性能测试:大数据量查询性能测试
  4. 压力测试:并发上传测试

7.4 Phase 4: 上线部署

  1. 灰度发布:先在测试环境验证
  2. 数据备份:上线前完整备份
  3. 监控告警:监控文件上传、下载成功率
  4. 回滚方案:准备回滚脚本

总结

优势

业务边界清晰:附件、素材、视频、音频各司其职
表大小可控:每个表都在合理范围内(5-50万条)
查询性能好:无需复杂 JOIN,可针对性优化
代码复用:FileStorageService 统一处理上传、去重
去重机制:file_checksums 表全局去重,节省存储
扩展性好:后期可无缝扩展素材市场功能

风险与应对

风险 应对措施
数据迁移失败 提前备份,分步迁移,验证数据完整性
API 兼容性问题 保持接口向后兼容,提供迁移文档
性能下降 压力测试,优化索引,增加缓存
文件引用计数错误 定期校验,自动修复脚本

文档版本:v1.0
创建时间:2025-01-27