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.
4.6 KiB
4.6 KiB
文件 URL 存储策略迁移指南
日期: 2026-02-06
版本: v2.0
类型: 数据迁移
📋 背景
为了支持多环境部署和 CDN 切换,我们将文件 URL 的存储策略从完整 URL改为相对路径。
架构变更
改进前:
数据库存储: https://oss.example.com/screenplays/xxx.md
API 返回: https://oss.example.com/screenplays/xxx.md
改进后:
数据库存储: screenplays/xxx.md (相对路径)
API 返回: https://oss.example.com/screenplays/xxx.md (动态拼接)
✅ 已完成的代码修改
1. 智能 URL 构建函数
# app/core/storage.py
def build_file_url(path_or_url: str) -> str:
"""
智能判断输入类型:
- 完整 URL → 直接返回(向后兼容)
- 相对路径 → 拼接域名
"""
if path_or_url.startswith('http://') or path_or_url.startswith('https://'):
return path_or_url # 旧数据,直接返回
return f"{settings.S3_PUBLIC_URL}/{path_or_url}" # 新数据,拼接域名
2. 存储服务返回相对路径
# app/core/storage.py
class StorageService:
async def upload_bytes(self, ...) -> str:
"""返回相对路径,不含域名"""
self.client.put_object(...)
return object_name # 例如 "screenplays/xxx.md"
3. 文件去重统一返回相对路径
# app/services/file_storage_service.py
class FileStorageService:
async def upload_file(self, ...) -> FileMetadata:
existing = await self.checksum_repo.get_by_checksum(checksum)
if existing:
# ✅ 使用 storage_path(相对路径),而不是 file_url(可能是旧的完整 URL)
return FileMetadata(
file_url=existing.storage_path, # 统一返回相对路径
...
)
4. Schema 计算字段
# app/schemas/*.py
class ScreenplayResponse(BaseModel):
file_url: Optional[str] # 数据库存储相对路径
@computed_field(alias="parsedFileUrl")
@property
def parsed_file_url(self) -> Optional[str]:
"""API 返回完整 URL"""
return build_file_url(self.file_url) if self.file_url else None
🔄 当前状态
✅ 新数据(2026-02-06 之后)
所有新上传的文件会自动存储为相对路径:
attachments.file_url→attachment_image/019c.../abc123.jpgscreenplays.file_url→screenplays/019c.../xxx.mdfile_checksums.file_url→attachment_document/019c.../doc.pdf
⚠️ 旧数据(2026-02-06 之前)
仍然存储完整 URL:
https://static.timelab.cn/screenplays/xxx.mdhttps://storage.example.com/attachments/xxx.pdf
影响:✅ 无影响!build_file_url() 智能判断,旧数据仍然可用。
📊 数据迁移(可选)
虽然旧数据不影响功能,但如果您希望数据库统一存储相对路径,可以运行迁移脚本。
迁移前检查
# 1. 进入容器
docker compose exec app bash
# 2. 检查需要迁移的数据量
cd /app
python test_relative_path.py
执行迁移
⚠️ 重要:先备份数据库!
# 备份数据库
docker compose exec postgres pg_dump -U jointo jointo > backup_$(date +%Y%m%d).sql
# 执行迁移
docker compose exec app python scripts/migrate_urls_to_relative.py
迁移脚本功能
脚本会自动转换以下表的 file_url 字段:
attachments- 附件表screenplays- 剧本表file_checksums- 文件去重表
转换规则:
https://domain.com/path/to/file.pdf → path/to/file.pdf
https://bucket.s3.region.amazonaws.com/path/file.pdf → path/file.pdf
验证迁移结果
# 再次检查
python test_relative_path.py
# 预期输出
# ✅ 相对路径: screenplays/019c.../xxx.md
# ✅ 相对路径: attachment_image/019c.../avatar.jpg
🎯 总结
优势
- ✅ 域名无关 - 便于多环境部署(dev/staging/prod)
- ✅ CDN 切换 - 只需修改配置文件
S3_PUBLIC_URL - ✅ 向后兼容 - 旧数据不受影响,自动兼容
- ✅ 零停机 - 无需立即迁移,可选择性清理
注意事项
- 新数据自动使用相对路径 - 无需手动干预
- 旧数据可选迁移 - 不影响功能,可延后处理
- API 响应不变 - 前端无感知,仍然收到完整 URL