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
修复剧本文件解析时的下载问题
日期: 2026-02-06
类型: Bug 修复
影响: 剧本文件上传和解析功能
📋 问题描述
在实施 RFC 140(文件 URL 存储优化)后,剧本文件解析一直失败,出现以下错误:
FileNotFoundError: [Errno 2] No such file or directory:
'screenplays/019c2d43-59a9-7e62-b325-b2bd786624d5/xxx.pdf'
症状
- ✅ 文件上传成功
- ✅ 附件记录创建成功(存储相对路径)
- ❌ Celery 解析任务失败
- ⏳ API 一直显示 "parsing" 状态
🔍 根本原因
ScreenplayFileParserService._download_file() 方法只处理两种情况:
- HTTP/HTTPS URL → 下载
- 本地文件路径 → 直接使用
缺少第三种情况:OSS 相对路径(新数据格式)
问题代码
# app/services/screenplay_file_parser_service.py (旧版本)
async def _download_file(self, file_path: str) -> str:
if file_path.startswith('http://') or file_path.startswith('https://'):
# 下载 HTTP URL
...
else:
return file_path # ❌ 直接返回相对路径作为本地文件路径
数据流
1. 用户上传 PDF → attachment.file_url = "screenplays/xxx.pdf" (相对路径)
2. Celery 任务触发 → parse_file(file_path="screenplays/xxx.pdf")
3. _download_file() → 返回 "screenplays/xxx.pdf" (误以为是本地路径)
4. _parse_pdf() → 尝试打开本地文件 ❌ FileNotFoundError
✅ 修复方案
1. 扩展 _download_file() 支持 OSS 相对路径
# app/services/screenplay_file_parser_service.py
async def _download_file(self, file_path: str) -> str:
"""下载文件到临时目录(支持 HTTP URL 和 OSS 相对路径)"""
# 场景 1:HTTP/HTTPS URL(旧数据或外部 URL)
if file_path.startswith('http://') or file_path.startswith('https://'):
# 通过 HTTP 下载
...
# 场景 2:本地文件路径(绝对路径)
elif file_path.startswith('/') or Path(file_path).is_absolute():
return file_path
# 场景 3:OSS 相对路径(新数据)✅
else:
logger.debug("从 OSS 下载文件 | 对象路径: %s", file_path)
from app.core.storage import StorageService
storage = StorageService()
# 从 OSS 下载文件内容
file_content = await storage.download_file(file_path)
# 保存到临时文件
suffix = Path(file_path).suffix
with tempfile.NamedTemporaryFile(delete=False, suffix=suffix) as tmp_file:
tmp_file.write(file_content)
return tmp_file.name
2. 添加 StorageService.download_file() 方法
# app/core/storage.py
class StorageService:
async def download_file(self, object_name: str) -> bytes:
"""从对象存储下载文件内容
Args:
object_name: 对象名称(存储路径,相对路径)
Returns:
bytes: 文件内容
"""
try:
response = self.client.get_object(
Bucket=self.bucket_name,
Key=object_name
)
return response['Body'].read()
except ClientError as e:
raise StorageError(f"下载文件失败: {str(e)}")
🎯 验证步骤
1. 重启 Celery Worker
docker compose restart celery-worker-ai
2. 上传测试文件
curl -X POST http://localhost:8000/api/v1/screenplays/upload \
-F "file=@test.pdf" \
-F "projectId=xxx"
3. 检查日志
docker compose logs celery-worker-ai --tail 50
预期输出:
[INFO] 从 OSS 下载文件 | 对象路径: screenplays/xxx.pdf
[INFO] 文件已从 OSS 下载到临时目录 | 路径: /tmp/xxx.pdf
[INFO] 开始解析剧本文件 | 剧本ID: xxx | 类型: application/pdf
[INFO] 剧本文件解析成功 | 字数: 1234
📊 影响范围
修改的文件
-
server/app/services/screenplay_file_parser_service.py- 修改
_download_file()方法
- 修改
-
server/app/core/storage.py- 新增
download_file()方法
- 新增
影响的功能
- ✅ 剧本文件上传和解析(PDF/DOCX)
- ✅ 文件去重功能
- ✅ 后台 Celery 任务
向后兼容性
✅ 完全兼容:
- HTTP/HTTPS URL(旧数据)仍然可用
- 本地文件路径仍然可用
- OSS 相对路径(新数据)现在正常工作