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.
 

8.8 KiB

附件服务多态关联代码实现

变更类型:功能实现
影响范围:附件服务
日期:2026-01-28

实施概述

完成附件服务多态关联设计的代码实现,包括 Model、Schema、Repository、Service、API 层和数据库迁移脚本。

实施内容

1. Model 层

文件server/app/models/attachment.py

实现内容

  • AttachmentCategory 枚举(DOCUMENT=1, IMAGE=2)
  • RelatedType 枚举(USER=1, PROJECT=2, STORYBOARD=3, CHARACTER=4, SCENE=5, PROP=6, LOCATION=7)
  • AttachmentPurpose 枚举(AVATAR=1, COVER=2, THUMBNAIL=3, DOCUMENT=4, REFERENCE=5, ATTACHMENT=6)
  • Attachment 模型(多态关联字段:related_id, related_type, attachment_purpose)
  • 枚举转换方法(from_string, to_string, get_display_name)
  • to_dict() 方法(camelCase 字段名)

核心字段

related_id: UUID                    # 关联实体 ID
related_type: int (SMALLINT)        # 关联实体类型
attachment_purpose: int (SMALLINT)  # 附件用途

2. Schema 层

文件server/app/schemas/attachment.py

实现内容

  • AttachmentUploadRequest - 上传请求 Schema(支持多态关联参数)
  • AttachmentQueryRequest - 查询请求 Schema
  • AttachmentResponse - 附件响应 Schema
  • AttachmentListResponse - 附件列表响应 Schema
  • DownloadUrlResponse - 下载链接响应 Schema
  • 字段验证器(category, related_type, attachment_purpose)

3. Repository 层

文件server/app/repositories/attachment_repository.py

实现内容

  • exists_user() - 验证用户存在
  • exists_related_entity() - 验证关联实体存在(多态)
  • check_related_permission() - 检查关联实体权限(多态)
  • get_by_id() - 根据 ID 获取附件
  • get_by_related() - 获取关联实体的附件列表(多态查询)
  • get_by_purpose() - 获取指定用途的附件
  • create() - 创建附件
  • soft_delete() - 软删除附件
  • increment_access_count() - 增加访问计数
  • increment_download_count() - 增加下载计数

注意事项

  • Storyboard、ScreenplayCharacter、ScreenplayScene 模型尚未实现,暂时跳过验证
  • 待相关模型实现后,需要取消注释相应的验证代码

4. Service 层

文件server/app/services/attachment_service.py

实现内容

  • upload_attachment() - 上传附件(多态关联)
  • get_attachment() - 获取附件详情
  • get_download_url() - 获取下载链接
  • delete_attachment() - 删除附件
  • get_attachments_by_related() - 获取关联实体的附件列表
  • get_attachment_by_purpose() - 获取指定用途的附件
  • 文件类型验证(ALLOWED_DOCUMENT_TYPES, ALLOWED_IMAGE_TYPES)
  • 文件大小验证(MAX_FILE_SIZE)
  • 权限检查(应用层引用完整性)

临时实现

  • 文件存储暂时使用简单逻辑(计算 checksum,生成临时 URL)
  • 待 FileStorageService 实现后,需要集成文件去重功能

5. API 层

文件server/app/api/v1/attachments.py

实现内容

  • POST /attachments - 上传附件
  • GET /attachments/{attachment_id} - 获取附件详情
  • GET /attachments/{attachment_id}/download - 获取下载链接
  • DELETE /attachments/{attachment_id} - 删除附件
  • GET /attachments - 获取关联实体的附件列表
  • GET /attachments/purpose/{related_type}/{related_id}/{purpose} - 获取指定用途的附件
  • 依赖注入(get_attachment_service)
  • 统一 ApiResponse 响应格式

路由注册

  • server/app/api/v1/router.py 中注册 attachments 路由

6. 数据库迁移

文件server/alembic/versions/20260128_2100_create_attachments_table.py

实现内容

  • 创建 attachments 表
  • 添加表和列注释
  • 创建核心索引(多态关联必须)
    • idx_attachments_related (related_id, related_type, attachment_purpose)
    • idx_attachments_related_type (related_type, related_id)
    • idx_attachments_uploaded_by (uploaded_by)
    • idx_attachments_category (category)
    • idx_attachments_created_at (created_at DESC)
    • idx_attachments_checksum (checksum)
  • 创建组合索引
    • idx_attachments_uploader_created (uploaded_by, created_at DESC)
  • 创建全文搜索索引(pg_trgm)
    • idx_attachments_name_trgm
    • idx_attachments_original_name_trgm
  • 创建触发器(自动更新 updated_at)
  • downgrade() 回滚方法

7. 模型导出

文件server/app/models/__init__.py

实现内容

  • 导出 Attachment 模型
  • 导出 AttachmentCategory 枚举
  • 导出 RelatedType 枚举
  • 导出 AttachmentPurpose 枚举

代码统计

层级 文件 行数 说明
Model attachment.py ~250 3个枚举 + 1个模型
Schema attachment.py ~120 5个 Schema 类
Repository attachment_repository.py ~220 11个方法
Service attachment_service.py ~280 6个方法
API attachments.py ~150 6个端点
Migration 20260128_2100_*.py ~180 upgrade + downgrade
总计 6个文件 ~1200行 完整实现

技术亮点

1. 多态关联设计

# 支持任意实体的附件管理
related_id: UUID           # 通用关联 ID
related_type: SMALLINT     # 实体类型(1-7)
attachment_purpose: SMALLINT  # 附件用途(1-6)

2. 枚举转换

# 字符串 ↔ 枚举 ↔ 显示名称
RelatedType.from_string('project')  # → RelatedType.PROJECT (2)
RelatedType.PROJECT.to_string()     # → 'project'
RelatedType.get_display_name(2)     # → '项目'

3. 应用层引用完整性

# Repository 层验证
await self.repository.exists_related_entity(related_id, related_type)
await self.repository.check_related_permission(user_id, related_id, related_type, 'editor')

4. 统一查询接口

# 查询任意实体的附件
await service.get_attachments_by_related(
    user_id, related_id, related_type, attachment_purpose, category
)

# 获取指定用途的附件
await service.get_attachment_by_purpose(
    user_id, related_id, related_type, purpose
)

待完成工作

高优先级

  1. FileStorageService 集成

    • 实现文件去重功能
    • 集成 MinIO/S3 对象存储
    • 生成预签名下载 URL
  2. Storyboard 模型实现

    • 实现 Storyboard 模型
    • 取消 Repository 中的验证跳过逻辑
    • 实现分镜权限检查
  3. Screenplay 模型实现

    • 实现 ScreenplayCharacter 模型
    • 实现 ScreenplayScene 模型
    • 取消 Repository 中的验证跳过逻辑

中优先级

  1. 单元测试

    • Model 层测试
    • Repository 层测试
    • Service 层测试
    • API 层测试
  2. 集成测试

    • 上传附件流程测试
    • 权限检查测试
    • 多态查询测试

低优先级

  1. 性能优化

    • 批量查询优化
    • 缓存策略
    • 索引优化
  2. 监控告警

    • 上传失败告警
    • 存储空间监控
    • 访问统计

部署步骤

1. 执行数据库迁移

# 在 Docker 容器内执行
docker exec jointo-server-app alembic upgrade head

2. 验证迁移

# 检查表是否创建成功
docker exec jointo-server-postgres psql -U jointoAI -d jointo -c "\d attachments"

# 检查索引
docker exec jointo-server-postgres psql -U jointoAI -d jointo -c "\di attachments*"

3. 重启服务

# 重启 FastAPI 服务
docker restart jointo-server-app

4. 测试 API

# 测试上传附件
curl -X POST http://localhost:8000/api/v1/attachments \
  -H "Authorization: Bearer <token>" \
  -F "file=@test.jpg" \
  -F "category=image" \
  -F "relatedId=<project_id>" \
  -F "relatedType=project" \
  -F "attachmentPurpose=cover"

# 测试查询附件
curl -X GET "http://localhost:8000/api/v1/attachments?relatedId=<project_id>&relatedType=project" \
  -H "Authorization: Bearer <token>"

验证清单

  • Model 层代码实现
  • Schema 层代码实现
  • Repository 层代码实现
  • Service 层代码实现
  • API 层代码实现
  • 数据库迁移脚本
  • 路由注册
  • 模型导出
  • 数据库迁移执行(待部署)
  • API 测试(待部署)
  • 单元测试(待实现)
  • 集成测试(待实现)

相关文档


实施日期:2026-01-28
实施人员:开发团队
状态:代码实现完成,待部署测试