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.1 KiB

AI Service 集成 FileStorageService

日期: 2026-01-30
类型: Feature Enhancement
影响范围: AI Service, AI Tasks, File Storage
关联文档: docs/requirements/backend/04-services/ai/ai-service.md

变更概述

为 AI 生成任务集成 FileStorageService,实现 AI 生成文件的自动下载和上传到自有对象存储(MinIO/OSS),解决 AI 提供商临时 URL 时效性问题,确保数据主权和文件持久化。

变更动机

问题

  1. 临时 URL 时效性:AI 提供商(如 OpenAI)返回的文件 URL 通常只有 1 小时有效期
  2. 数据主权缺失:生成的内容存储在第三方服务器,无法完全控制
  3. 无法去重:重复生成相同内容时无法利用已有文件
  4. 引用计数缺失:无法追踪文件被哪些资源引用

解决方案

  • 在每个 AI 生成任务完成后,自动下载文件并上传到 FileStorageService
  • 利用 SHA256 校验和实现文件去重
  • 使用引用计数管理文件生命周期
  • 支持 MinIO(开发环境)和云 OSS(生产环境)无缝切换

技术实现

1. 新增依赖

ai_tasks.py 中添加:

import httpx  # 用于下载 AI 生成的文件
from app.services.file_storage_service import FileStorageService

2. 新增辅助函数

_download_and_upload_file()

async def _download_and_upload_file(
    file_url: str,
    filename: str,
    content_type: str,
    category: str,
    user_id: str
) -> Dict[str, Any]

功能

  1. 使用 httpx.AsyncClient 下载 AI 提供商返回的文件
  2. 调用 FileStorageService.upload_file() 上传到自有 OSS
  3. 返回文件元数据(包含自有 URL、校验和、文件大小等)

超时设置:300 秒(5 分钟),适配大文件下载

3. 更新所有生成任务

为以下 5 个任务添加文件上传逻辑:

图片生成任务 (generate_image_task)

  • 进度: 30% → 70% → 100%
  • 文件类型: image/png
  • 存储路径: ai-generated/images/
  • 文件名: image_{job_id}.png

视频生成任务 (generate_video_task)

  • 进度: 30% → 70% → 100%
  • 文件类型: video/mp4
  • 存储路径: ai-generated/videos/
  • 文件名: video_{job_id}.mp4

音效生成任务 (generate_sound_task)

  • 进度: 30% → 70% → 100%
  • 文件类型: audio/mpeg
  • 存储路径: ai-generated/sounds/
  • 文件名: sound_{job_id}.mp3

配音生成任务 (generate_voice_task)

  • 进度: 30% → 70% → 100%
  • 文件类型: audio/mpeg
  • 存储路径: ai-generated/voices/
  • 文件名: voice_{job_id}.mp3

字幕生成任务 (generate_subtitle_task)

  • 进度: 30% → 70% → 100%
  • 文件类型: text/plain
  • 存储路径: ai-generated/subtitles/
  • 文件名: subtitle_{job_id}.srt

文本处理任务 (process_text_task)

  • 进度: 30% → 70% → 100%
  • 文件类型: text/plain
  • 存储路径: ai-generated/texts/
  • 文件名: text_{job_id}.txt
  • 说明: 文本处理任务通常不返回文件,但为了完整性也添加了处理逻辑

4. 输出数据格式

更新后的 output_data 包含以下字段:

{
  "file_url": "https://minio.example.com/ai-generated/images/xxx.png",
  "file_size": 1024000,
  "checksum": "abc123...",
  "mime_type": "image/png",
  "storage_provider": "minio",
  "storage_path": "ai-generated/images/user_id/checksum.png",
  "original_url": "https://openai.com/temp/xxx.png"
}

字段说明

  • file_url: 自有 OSS URL(永久有效)
  • file_size: 文件大小(字节)
  • checksum: SHA256 校验和(用于去重)
  • mime_type: MIME 类型
  • storage_provider: 存储提供商(minio/oss)
  • storage_path: 对象存储路径
  • original_url: AI 提供商原始 URL(保留用于调试)

5. 日志格式修复

将所有日志从 f-string 格式改为 %-formatting:

# ❌ 错误
logger.info(f"任务完成: job_id={job_id}")

# ✅ 正确
logger.info("任务完成: job_id=%s", job_id)

文件修改清单

修改文件

  • server/app/tasks/ai_tasks.py
    • 添加 httpx 导入
    • 添加 FileStorageService 导入
    • 新增 _download_and_upload_file() 函数
    • 更新 6 个生成任务的文件上传逻辑
    • 修复所有日志格式为 %-formatting

新增文件

  • docs/server/changelogs/2026-01-30-ai-service-file-storage-integration.md

测试建议

单元测试

# 测试文件下载和上传
async def test_download_and_upload_file():
    # Mock httpx.AsyncClient
    # Mock FileStorageService
    # 验证文件元数据
    pass

集成测试

# 测试完整的图片生成流程
async def test_generate_image_with_file_storage(async_client, test_auth):
    # 1. 调用图片生成 API
    # 2. 等待任务完成
    # 3. 验证 output_data 包含 file_url
    # 4. 验证文件可以访问
    # 5. 验证 file_checksum 表有记录
    pass

手动测试

# 1. 启动服务
docker-compose up -d

# 2. 调用图片生成 API
curl -X POST http://localhost:8000/api/v1/ai/generate/image \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "A beautiful sunset",
    "width": 1024,
    "height": 1024
  }'

# 3. 查询任务状态
curl http://localhost:8000/api/v1/ai/jobs/{job_id} \
  -H "Authorization: Bearer $TOKEN"

# 4. 验证 MinIO 中是否有文件
# 访问 http://localhost:9001 (MinIO Console)
# 用户名: minioadmin, 密码: minioadmin

环境配置

开发环境(MinIO)

STORAGE_PROVIDER=minio
MINIO_ENDPOINT=minio:9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_BUCKET=jointo
MINIO_SECURE=false

生产环境(阿里云 OSS)

STORAGE_PROVIDER=oss
OSS_ENDPOINT=oss-cn-hangzhou.aliyuncs.com
OSS_ACCESS_KEY_ID=your_access_key
OSS_ACCESS_KEY_SECRET=your_secret_key
OSS_BUCKET=jointo-prod

性能考虑

下载超时

  • 设置 300 秒超时,适配大文件(如 4K 视频)
  • 如果下载失败,任务会自动重试(最多 3 次)

文件去重

  • 使用 SHA256 校验和去重,避免重复存储
  • 相同内容的文件只存储一次,节省存储空间

引用计数

  • 每次使用文件时增加引用计数
  • 删除资源时减少引用计数
  • 引用计数为 0 时自动删除文件

后续优化

短期(1-2 周)

  1. 添加文件压缩(图片、视频)
  2. 添加缩略图生成(图片、视频)
  3. 添加文件格式转换(如 PNG → WebP)

中期(1-2 月)

  1. 实现 CDN 加速
  2. 添加文件访问统计
  3. 实现文件分级存储(热数据/冷数据)

长期(3-6 月)

  1. 实现多云存储(阿里云 + 腾讯云)
  2. 添加文件加密存储
  3. 实现文件版本管理

风险与注意事项

风险

  1. 下载失败:AI 提供商 URL 可能失效或网络不稳定

    • 缓解措施:设置重试机制(最多 3 次)
  2. 存储空间不足:大量生成文件可能占用大量存储

    • 缓解措施:定期清理无引用文件(30 天)
  3. 并发冲突:多个任务同时上传相同文件

    • 缓解措施:使用校验和去重,数据库唯一约束

注意事项

  1. 确保 MinIO/OSS 服务正常运行
  2. 确保网络可以访问 AI 提供商的文件 URL
  3. 监控存储空间使用情况
  4. 定期备份重要文件

相关文档

总结

此次集成完成了 AI Service 与 FileStorageService 的深度整合,实现了:

  • AI 生成文件自动下载和上传
  • 文件去重和引用计数
  • 支持 MinIO 和云 OSS
  • 完整的错误处理和重试机制
  • 符合 jointo-tech-stack 规范

所有 AI 生成的文件现在都会永久存储在自有系统中,解决了临时 URL 时效性问题,确保了数据主权和文件持久化。