feat(phase8): 任务 2.10 — JobService 异步任务管理 #160
Reference in New Issue
Block a user
Delete Branch "feature/phase8-task210-job-service"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
变更说明
领域层
Job领域模型(状态机模式:pending → running → success/failed,支持重试和取消)JobStatus/JobType枚举,支持 6 种任务类型数据层
JobRepository端口接口 + SQLAlchemy 实现JobModelORM 模型get_job_repository依赖注入应用层
服务层
JobService服务层,集成 VideoComposeServicesubmit_compose_if_not_exists防重复提交方法API 层
Worker 层
compose_videoCelery 任务,集成进度追踪(10%/20%/30%/50%/80% 各阶段汇报)测试
代码审查报告 — PR #160(任务 2.10 JobService 异步任务管理 + Phase 8 后端收尾审计)
审查范围: 18 个文件,+2327/-0
packages/domain/job.py(289 行)— Job 实体 + 状态机packages/ports/job_repository.py(64 行)— Repository Protocolpackages/adapters/sqlalchemy_impl/job_repository.py(169 行)+models.py(+24 行)packages/application/jobs.py(257 行)— 10 个 Use Caseapps/api/app/services/job_service.py(268 行)apps/api/app/api/routes/jobs.py(323 行)+schemas/job.py(109 行)apps/worker/worker_app/tasks/compose_video.py(136 行)alembic/versions/018_add_jobs_table.py(54 行)tests/unit/test_job_service.py(578 行)— 44 个测试✅ 审查结论:有条件通过(1 P1 + 5 P2 + 4 P3)
Phase 8 整体架构完整:领域模型 → 端口 → 适配器 → 应用层 Use Case → 服务层 → API 路由 → Celery Worker,Hexagonal 架构执行到位。Job 状态机设计严谨(
_VALID_TRANSITIONS显式定义合法转换),测试覆盖全面。以下是需关注的问题。P1 — 建议修复
P1-1:
submit_job路由中权限检查在状态变更之后执行问题:非授权用户调用
/submit会导致任务状态从pending变为running,然后才返回 403。状态已经持久化到数据库了。建议: 将权限检查移到
use_case.execute()之前:P2 — 可选优化
P2-1:
submit_compose_if_not_exists存在竞态条件两个并发请求可能同时通过
find_active_by_source检查,然后各自创建新 Job。建议在数据库层加唯一约束(source_id + job_type + status IN ('pending','running')的部分索引),或在服务层加锁。P2-2:
cancel_job不撤销 Celery 任务用户点击"取消"后,Celery Worker 中的 FFmpeg 进程会继续执行直到完成。建议在 cancel 路由中���加 Celery 任务撤销逻辑。
P2-3:
compose_video硬编码输出路径输出路径硬编码为
/tmp,没有从 payload 或配置中读取。如果/tmp磁盘空间不足或不存在该目录,会直接报错。建议:output_path,有默认值兜底Path(output_path).parent.mkdir(parents=True, exist_ok=True))P2-4:
GetJobStatisticsUseCase执行 5 次独立查询5 次
COUNT(*)查询,虽然对当前规模可接受,但可以用一次GROUP BY status查询替代。建议在JobRepository中新增count_by_project_grouped方法。P2-5:
updated_at在 ORMupdate()中未自动更新SQLAlchemyJobRepository.update()逐字段复制 domain 对象属性到 model,但updated_at依赖 domain 层手动设置。如果某个 Use Case 修改了 Job 但忘记调用updated_at = datetime.now(),数据库中的updated_at不会变化。建议在 ORM 层加onupdate=sa.func.now()。P2-6:
Job.create()未校验max_retries >= 0如果传入
max_retries=-1,is_retryable永远为 False(retry_count < max_retries即0 < -1为 False),任务无法重试且不会报错。建议增加if max_retries < 0: raise ValueError("max_retries 不能为负数")。P3 — 可选优化
P3-1:迁移脚本
down_revision = "017"未验证链完整性建议确认
017迁移确实存在且是 develop 分支上的最新迁移。P3-2:
compose_video中错误信息截断到 500 字符长错误信息可能被截断丢失关键上下文。建议将完整 stderr 记录到日志,error_message 中只放摘要。
P3-3:API 路由缺少统一前缀
jobs_router没有 prefix,导致列表接口在/projects/{project_id}/jobs而操作接口在/jobs/{job_id}/*。建议给 router 加prefix="/jobs"或保持现状但统一文档说明。P3-4:测试覆盖了领域模型和 Use Case,但缺少 Celery 任务的集成测试
compose_video.py中的 Celery 任务逻辑(FFmpeg 调用、OSS 上传、进度汇报)没有单元测试覆盖。建议后续补充。👍 亮点
_VALID_TRANSITIONS显式定义合法转换,is_terminal/is_retryable属性语义清晰bind=True+self.retry+max_retries=3+ 进度汇报,异常处理完整progress: ge=0.0, le=100.0、max_retries: ge=0, le=10、error_message: min_length=1submit_compose_if_not_exists+find_active_by_source防止同一 source 重复创建任务Phase 8 整体架构评价
Phase 8 后端架构整体质量良好,为后续 ClipPlanService、RenderOrchestrator 打下了坚实基础。
总结: 核心问题集中在权限检查顺序(P1-1)和并发防重(P2-1),建议修复后合并。其余 P2/P3 可后续迭代处理。mergeable=True,可直接合并。