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
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_trgmidx_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
)
待完成工作
高优先级
-
FileStorageService 集成
- 实现文件去重功能
- 集成 MinIO/S3 对象存储
- 生成预签名下载 URL
-
Storyboard 模型实现
- 实现 Storyboard 模型
- 取消 Repository 中的验证跳过逻辑
- 实现分镜权限检查
-
Screenplay 模型实现
- 实现 ScreenplayCharacter 模型
- 实现 ScreenplayScene 模型
- 取消 Repository 中的验证跳过逻辑
中优先级
-
单元测试
- Model 层测试
- Repository 层测试
- Service 层测试
- API 层测试
-
集成测试
- 上传附件流程测试
- 权限检查测试
- 多态查询测试
低优先级
-
性能优化
- 批量查询优化
- 缓存策略
- 索引优化
-
监控告警
- 上传失败告警
- 存储空间监控
- 访问统计
部署步骤
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
实施人员:开发团队
状态:代码实现完成,待部署测试