# 附件与资源管理架构方案 > **文档版本**: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 表 **调整前**: ```sql CREATE TABLE attachments ( attachment_id BIGINT PRIMARY KEY, category ENUM('script', 'document', 'contract', 'reference', 'other'), project_id BIGINT, user_id BIGINT, -- 多态关联,语义不清 checksum TEXT, -- 可选 -- ... ); ``` **调整后**: ```sql 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 表 **调整前**: ```sql CREATE TABLE resources ( resource_id BIGINT PRIMARY KEY, type ENUM('character', 'scene', 'prop'), file_url TEXT, is_public BOOLEAN, -- 素材市场 price NUMERIC, -- 项目和公共资源混在一起 ); ``` **调整后**: ```sql -- 拆分为两个表 -- 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 表(全局去重) ```sql 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 索引策略 ```sql -- 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 缓存策略 ```python # 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 定时清理 ```python # 每天凌晨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: 数据库迁移(如果已有数据) ```sql -- 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