阶段3 任务3.06:TTS 合成 API #166

Merged
xiaoxia merged 1 commits from feature/task-306-tts-api into develop 2026-07-02 11:16:40 +08:00
Owner

内容

阶段3 任务3.06 — TTS 合成 API(/api/v1/tts)

变更

  • POST /api/v1/tts/synthesize — 创建TTS合成任务
  • GET /api/v1/tts/jobs — 列出用户任务(page/page_size分页)
  • GET /api/v1/tts/jobs/{job_id} — 获取任务详情
  • GET /api/v1/tts/jobs/{job_id}/status — 查询合成状态
  • DELETE /api/v1/tts/jobs/{job_id} — 删除任务
  • Schema: TTSSynthesizeRequest, TTSJobResponse, ListTTSJobResponse, TTSStatusResponse
  • Use Cases: Create/List/Get/GetStatus/DeleteTTSJob
  • SQLAlchemy 适配器 + ORM 模型
  • Alembic 迁移 020(tts_jobs 表)
  • 14 个单元测试全部通过

测试

14/14 通过
## 内容 阶段3 任务3.06 — TTS 合成 API(/api/v1/tts) ## 变更 - POST /api/v1/tts/synthesize — 创建TTS合成任务 - GET /api/v1/tts/jobs — 列出用户任务(page/page_size分页) - GET /api/v1/tts/jobs/{job_id} — 获取任务详情 - GET /api/v1/tts/jobs/{job_id}/status — 查询合成状态 - DELETE /api/v1/tts/jobs/{job_id} — 删除任务 - Schema: TTSSynthesizeRequest, TTSJobResponse, ListTTSJobResponse, TTSStatusResponse - Use Cases: Create/List/Get/GetStatus/DeleteTTSJob - SQLAlchemy 适配器 + ORM 模型 - Alembic 迁移 020(tts_jobs 表) - 14 个单元测试全部通过 ## 测试 ``` 14/14 通过 ```
xiaoxia added 1 commit 2026-07-02 11:10:58 +08:00
feat: implement TTS synthesis API (task 3.06)
Deploy / Build Production Runtime Images (push) Has been skipped
Deploy / Deploy Production (push) Has been skipped
Deploy / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 169h43m28s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 169h43m33s
Deploy / Deploy Staging (push) Failing after 169h47m22s
CI/CD Pipeline / Frontend Lint (push) Failing after 169h47m58s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 169h48m6s
e29a698325
- POST /api/v1/tts/synthesize: create TTS job (status=pending)
- GET /api/v1/tts/jobs: list user's jobs (page/page_size pagination)
- GET /api/v1/tts/jobs/{job_id}: get job details
- GET /api/v1/tts/jobs/{job_id}/status: query status for polling
- DELETE /api/v1/tts/jobs/{job_id}: delete job (soft delete)

Includes:
- Schema: TTSSynthesizeRequest, TTSJobResponse, ListTTSJobResponse, TTSStatusResponse
- Use Cases: CreateTTSJob, ListTTSJobs, GetTTSJob, GetTTSJobStatus, DeleteTTSJob
- SQLAlchemy adapter: SQLAlchemyTTSJobRepository
- ORM model: TTSJobModel
- Alembic migration: 020_add_tts_jobs_table
- Unit tests: 14 tests covering all use cases

CosyVoice API calls deferred to task 3.07.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
Owner

PR #166 审查报告 — 任务 3.06 TTS 合成 API

PR: #166
分支: feature/task-306-tts-apidevelop
Commit: e29a698
变更: 9 文件,+868 / -0
结论: 有条件通过(0 P0 / 0 P1 / 1 P2 / 4 P3)
mergeable: True


一、审查要点逐项验证

1. 分页参数使用 page/page_size 风格

  • routes/tts.py 第 82-83 行:page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100)
  • 路由层正确转换:skip = (page - 1) * page_size 后传给 Use Case
  • ListTTSJobResponse 包含 pagepage_size 字段
  • 已修复 PR #165 的 P2 教训

2. TTS 状态机符合领域模型

  • 状态枚举:pending → processing → completed / failedfailed → pending(重试),cancelled 为终态
  • _VALID_TRANSITIONS 严格定义合法转换
  • 辅助方法完整:mark_processing()(记录 started_at)/ mark_completed()(记录 output_audio_url 等)/ mark_failed() / mark_cancelled() / prepare_retry()
  • create() 工厂方法有参数校验:user_id 非空、input_text 非空且 ≤10000 字符、format 限定 mp3/wav/pcm

3. API 认证/鉴权正确

  • 所有 5 个端点均依赖 get_current_user 获取认证用户
  • Use Case 层额外验证 job.user_id != user_id → 抛出 TTSJobNotFoundError
  • 防止跨用户数据访问

4. 错误处理完善

  • 404:TTSJobNotFoundError → GET/{id}、GET/{id}/status、DELETE
  • 204:DELETE 成功返回 No Content
  • 领域层 ValueError 用于参数校验和非法状态转换

5. 软删除实现正确

  • Repository delete() 设置 status = "deleted"
  • get()list_by_user() 均过滤 status != "deleted"
  • 与 PR #165 的 VoiceCloneProfile 保持一致的软删除模式

6. Hexagonal 架构分层合理

domain (TTSJob + TTSJobStatus)
  ↓ 依赖
ports (TTSJobRepository Protocol)
  ↓ 实现
adapters (SQLAlchemyTTSJobRepository)
  ↑ 调用
application (Use Cases: Create/List/Get/GetStatus/Delete)
  ↑ 编排
api (Routes + Schemas)

7. Alembic 迁移正确

  • revision = "020", down_revision = "019" — 链路正确(接续 PR #165 的 019)
  • 表结构 22 列,与 ORM Model 完全一致
  • 索引:user_idstatus 有索引
  • 合理的默认值(status="pending", sample_rate=22050, format="mp3")

8. 单元测试覆盖充分

  • 14 个测试,覆盖所有 5 个 Use Case
  • 覆盖:正常创建、带元数据创建、空文本报错、空列表、分页、状态过滤、正常获取、不存在、用户不匹配、状态查询、正常删除、删除不存在、删除用户不匹配

9. CosyVoice API 调用确实未实现

  • 创建后状态保持 pending,不自动转 processing
  • 无 CosyVoice API 调用代码
  • 符合任务要求:延期到任务 3.07

二、发现的问题

P2-1:Schema 中 speedoutput_name 参数被静默丢弃

  • 文件: schemas/tts.py + routes/tts.py
  • 现状: TTSSynthesizeRequest 定义了 speed(0.5-2.0)和 output_name 字段,但路由层 synthesize() 未将这两个参数传递给 CreateTTSJobUseCase.execute();Use Case、领域模型、ORM Model 均无对应字段
  • 影响: 用户传入的 speedoutput_name 被完全忽略,不会报错也不会生效。当任务 3.07 实现 CosyVoice API 时需要这些参数
  • 建议: 至少在 TTSJob 领域模型和数据库中预留 speedoutput_name 字段,或在 Schema 中标注为"预留字段,当前版本暂不支持"

P3-1:metadata_ alias 命名模式

  • Schema 和 ORM 中使用 metadata_ + alias="metadata" 映射
  • 功能正确,与 PR #165 保持一致的项目约定
  • 建议:后续统一为 extra_meta 风格更清晰,但当前不影响功能

P3-2:软删除不更新 updated_at

  • Repository delete() 仅设置 status = "deleted",未更新 updated_at
  • 不利于审计追踪(无法知道何时被删除)

P3-3:format 字段遮蔽 Python 内置函数名

  • TTSJob dataclass 和 TTSJobModel 中使用 format 作为字段名
  • 虽然 format 不是 Python 关键字,但遮蔽了内置 format() 函数
  • 建议重命名为 output_formataudio_format

P3-4:缺少 retry 端点

  • TTSJob 领域模型设计了完整的重试机制(is_retryable / prepare_retry),但 API 层未暴露 retry 端点
  • 当前任务不要求,可作为后续增强

三、亮点

  1. 分页风格统一:主动采用 page/page_size 风格,修正了 PR #165 的 P2 问题
  2. 领域模型设计严谨:状态机 + 合法转换表 + 工厂方法 + 参数校验(text 长度、format 枚举)
  3. 权限检查双层保障:路由层 get_current_user + Use Case 层 user_id 校验
  4. Schema 设计合理TTSStatusResponse 轻量版用于轮询,TTSJobResponse 完整版用于详情,TTSSynthesizeResponse 简洁创建响应
  5. 额外提供 list_by_profile:Repository 预留按克隆档案查询 TTS 任务的能力,为后续集成做准备
  6. 14 个测试全部通过:覆盖充分

四、总结

级别 数量 说明
P0 0
P1 0
P2 1 speed/output_name 参数被静默丢弃
P3 4 metadata_ 命名、软删除不更新 updated_at、format 遮蔽内置函数、缺 retry 端点

结论: 有条件通过 — 代码质量高,架构清晰,核心功能完整,且主动修正了 PR #165 的分页问题。P2 建议在任务 3.07 实现 CosyVoice API 时一并补充字段,不阻塞当前合并。

可合并: mergeable=True

# PR #166 审查报告 — 任务 3.06 TTS 合成 API **PR**: [#166](https://git.xiaoxiaianji.com/xiaoxia/xiaoxia-saas/pulls/166) **分支**: `feature/task-306-tts-api` → `develop` **Commit**: `e29a698` **变更**: 9 文件,+868 / -0 **结论**: ✅ **有条件通过**(0 P0 / 0 P1 / 1 P2 / 4 P3) **mergeable**: ✅ True --- ## 一、审查要点逐项验证 ### ✅ 1. 分页参数使用 page/page_size 风格 - `routes/tts.py` 第 82-83 行:`page: int = Query(default=1, ge=1)`, `page_size: int = Query(default=20, ge=1, le=100)` - 路由层正确转换:`skip = (page - 1) * page_size` 后传给 Use Case - `ListTTSJobResponse` 包含 `page` 和 `page_size` 字段 - **已修复 PR #165 的 P2 教训** ✅ ### ✅ 2. TTS 状态机符合领域模型 - 状态枚举:`pending → processing → completed / failed`,`failed → pending`(重试),`cancelled` 为终态 - `_VALID_TRANSITIONS` 严格定义合法转换 - 辅助方法完整:`mark_processing()`(记录 started_at)/ `mark_completed()`(记录 output_audio_url 等)/ `mark_failed()` / `mark_cancelled()` / `prepare_retry()` - `create()` 工厂方法有参数校验:user_id 非空、input_text 非空且 ≤10000 字符、format 限定 mp3/wav/pcm ### ✅ 3. API 认证/鉴权正确 - 所有 5 个端点均依赖 `get_current_user` 获取认证用户 - Use Case 层额外验证 `job.user_id != user_id` → 抛出 `TTSJobNotFoundError` - 防止跨用户数据访问 ### ✅ 4. 错误处理完善 - 404:`TTSJobNotFoundError` → GET/{id}、GET/{id}/status、DELETE - 204:DELETE 成功返回 No Content - 领域层 `ValueError` 用于参数校验和非法状态转换 ### ✅ 5. 软删除实现正确 - Repository `delete()` 设置 `status = "deleted"` - `get()` 和 `list_by_user()` 均过滤 `status != "deleted"` - 与 PR #165 的 VoiceCloneProfile 保持一致的软删除模式 ### ✅ 6. Hexagonal 架构分层合理 ``` domain (TTSJob + TTSJobStatus) ↓ 依赖 ports (TTSJobRepository Protocol) ↓ 实现 adapters (SQLAlchemyTTSJobRepository) ↑ 调用 application (Use Cases: Create/List/Get/GetStatus/Delete) ↑ 编排 api (Routes + Schemas) ``` ### ✅ 7. Alembic 迁移正确 - `revision = "020"`, `down_revision = "019"` — 链路正确(接续 PR #165 的 019) - 表结构 22 列,与 ORM Model 完全一致 - 索引:`user_id`、`status` 有索引 - 合理的默认值(status="pending", sample_rate=22050, format="mp3") ### ✅ 8. 单元测试覆盖充分 - 14 个测试,覆盖所有 5 个 Use Case - 覆盖:正常创建、带元数据创建、空文本报错、空列表、分页、状态过滤、正常获取、不存在、用户不匹配、状态查询、正常删除、删除不存在、删除用户不匹配 ### ✅ 9. CosyVoice API 调用确实未实现 - 创建后状态保持 `pending`,不自动转 `processing` - 无 CosyVoice API 调用代码 - 符合任务要求:延期到任务 3.07 --- ## 二、发现的问题 ### P2-1:Schema 中 `speed` 和 `output_name` 参数被静默丢弃 - **文件**: `schemas/tts.py` + `routes/tts.py` - **现状**: `TTSSynthesizeRequest` 定义了 `speed`(0.5-2.0)和 `output_name` 字段,但路由层 `synthesize()` 未将这两个参数传递给 `CreateTTSJobUseCase.execute()`;Use Case、领域模型、ORM Model 均无对应字段 - **影响**: 用户传入的 `speed` 和 `output_name` 被完全忽略,不会报错也不会生效。当任务 3.07 实现 CosyVoice API 时需要这些参数 - **建议**: 至少在 `TTSJob` 领域模型和数据库中预留 `speed` 和 `output_name` 字段,或在 Schema 中标注为"预留字段,当前版本暂不支持" ### P3-1:`metadata_` alias 命名模式 - Schema 和 ORM 中使用 `metadata_` + `alias="metadata"` 映射 - 功能正确,与 PR #165 保持一致的项目约定 - 建议:后续统一为 `extra_meta` 风格更清晰,但当前不影响功能 ### P3-2:软删除不更新 `updated_at` - Repository `delete()` 仅设置 `status = "deleted"`,未更新 `updated_at` - 不利于审计追踪(无法知道何时被删除) ### P3-3:`format` 字段遮蔽 Python 内置函数名 - `TTSJob` dataclass 和 `TTSJobModel` 中使用 `format` 作为字段名 - 虽然 `format` 不是 Python 关键字,但遮蔽了内置 `format()` 函数 - 建议重命名为 `output_format` 或 `audio_format` ### P3-4:缺少 retry 端点 - TTSJob 领域模型设计了完整的重试机制(`is_retryable` / `prepare_retry`),但 API 层未暴露 retry 端点 - 当前任务不要求,可作为后续增强 --- ## 三、亮点 1. **分页风格统一**:主动采用 `page/page_size` 风格,修正了 PR #165 的 P2 问题 2. **领域模型设计严谨**:状态机 + 合法转换表 + 工厂方法 + 参数校验(text 长度、format 枚举) 3. **权限检查双层保障**:路由层 `get_current_user` + Use Case 层 `user_id` 校验 4. **Schema 设计合理**:`TTSStatusResponse` 轻量版用于轮询,`TTSJobResponse` 完整版用于详情,`TTSSynthesizeResponse` 简洁创建响应 5. **额外提供 `list_by_profile`**:Repository 预留按克隆档案查询 TTS 任务的能力,为后续集成做准备 6. **14 个测试全部通过**:覆盖充分 --- ## 四、总结 | 级别 | 数量 | 说明 | |------|------|------| | P0 | 0 | — | | P1 | 0 | — | | P2 | 1 | `speed`/`output_name` 参数被静默丢弃 | | P3 | 4 | metadata_ 命名、软删除不更新 updated_at、format 遮蔽内置函数、缺 retry 端点 | **结论**: ✅ **有条件通过** — 代码质量高,架构清晰,核心功能完整,且主动修正了 PR #165 的分页问题。P2 建议在任务 3.07 实现 CosyVoice API 时一并补充字段,不阻塞当前合并。 **可合并**: ✅ mergeable=True
xiaoxia merged commit 23e3b9cc8e into develop 2026-07-02 11:16:40 +08:00
Sign in to join this conversation.