feat: 配音列表 API 增强 — 预置音色 + 克隆音色统一接口 #164

Merged
xiaoxia merged 2 commits from feature/task-304-voice-list-api into develop 2026-07-02 10:40:40 +08:00
Owner

任务 3.04: 配音列表 API 增强

变更内容

  • 新增预置音色配置(8 个 CosyVoice 真实音色)
    • 龙小淳、龙小夏、龙小晨、龙悦、龙书、龙静、龙博、龙甜
    • 男女各 3/5 分布,覆盖温柔/知性/磁性/甜美/沉稳/优雅/浑厚/活泼等风格
  • 增强 GET /api/v1/voices 接口
    • 新增 type 查询参数:preset(仅预置)/ clone(仅克隆)/ 不传(全部)
    • 统一响应格式,包含 preset_countclone_count
    • 预置音色排在前面
  • 新增 GET /api/v1/voices/presets 端点
    • 返回所有预置音色列表
  • 保留原有 CRUD 端点向后兼容(/legacy 端点)

架构

  • 预置音色作为配置(packages/domain/preset_voices.py),不存数据库
  • 统一响应 Schema(apps/api/app/schemas/voice.py
  • 遵循六边形架构:Domain → Port → Adapter

测试

  • 18 个预置音色单元测试(全部通过)
  • 累计 83 个 Phase 3 单元测试通过

文件变更

  • packages/domain/preset_voices.py — 预置音色配置
  • apps/api/app/schemas/voice.py — 统一响应 Schema
  • apps/api/app/api/routes/voices.py — 路由增强
  • tests/unit/test_preset_voices.py — 单元测试

🤖 Generated with Claude Code

## 任务 3.04: 配音列表 API 增强 ### 变更内容 - **新增预置音色配置**(8 个 CosyVoice 真实音色) - 龙小淳、龙小夏、龙小晨、龙悦、龙书、龙静、龙博、龙甜 - 男女各 3/5 分布,覆盖温柔/知性/磁性/甜美/沉稳/优雅/浑厚/活泼等风格 - **增强 GET /api/v1/voices 接口** - 新增 `type` 查询参数:`preset`(仅预置)/ `clone`(仅克隆)/ 不传(全部) - 统一响应格式,包含 `preset_count` 和 `clone_count` - 预置音色排在前面 - **新增 GET /api/v1/voices/presets 端点** - 返回所有预置音色列表 - **保留原有 CRUD 端点向后兼容**(/legacy 端点) ### 架构 - 预置音色作为配置(`packages/domain/preset_voices.py`),不存数据库 - 统一响应 Schema(`apps/api/app/schemas/voice.py`) - 遵循六边形架构:Domain → Port → Adapter ### 测试 - 18 个预置音色单元测试(全部通过) - 累计 83 个 Phase 3 单元测试通过 ### 文件变更 - `packages/domain/preset_voices.py` — 预置音色配置 - `apps/api/app/schemas/voice.py` — 统一响应 Schema - `apps/api/app/api/routes/voices.py` — 路由增强 - `tests/unit/test_preset_voices.py` — 单元测试 🤖 Generated with [Claude Code](https://claude.com/claude-code)
xiaoxia added 1 commit 2026-07-02 10:27:00 +08:00
feat: 配音列表 API 增强 — 预置音色 + 克隆音色统一接口
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 170h24m47s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 170h24m52s
Deploy / Deploy Staging (push) Failing after 170h27m34s
CI/CD Pipeline / Frontend Lint (push) Failing after 170h28m8s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 170h28m14s
44dc89360a
- 新增预置音色配置(8 个 CosyVoice 真实音色)
- 增强 GET /api/v1/voices 接口,支持 type 查询参数(preset/clone/all)
- 新增 GET /api/v1/voices/presets 端点
- 统一响应格式,包含 preset_count 和 clone_count
- 保留原有 CRUD 端点向后兼容
- 添加 18 个预置音色单元测试(全部通过)

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

🔍 PR #164 审查报告

PR: feature/task-304-voice-list-api → develop
Commit: 44dc893
变更: 4 files, +607 / -4
审查时间: 2026-07-02 10:35


审查结论:通过(有条件)

核心功能设计合理,预置音色配置、type 过滤、排序逻辑均正确。发现 1 P1(count_by_user 传了不存在的 status 参数,运行时 TypeError)+ 5 P2,需修复 P1 后可合并。


📄 文件审查

1. packages/domain/preset_voices.py 设计合理

  • PresetVoice dataclass(frozen=True, slots=True)不可变,内存高效
  • 8 个预置音色,覆盖多种场景(情感/播报/有声书/教育/纪录片/科技/短视频)
  • 辅助函数:get_preset_voices()get_preset_voice_by_id()is_preset_voice()
  • 位于 domain 层,不依赖数据库,符合 Hexagonal 架构

2. apps/api/app/schemas/voice.py Schema 完整

  • UnifiedVoiceItemResponsetype: Literal["preset", "clone"] 区分类型
  • UnifiedVoiceListResponse:含 preset_count / clone_count
  • PresetVoiceItemResponse / PresetVoiceListResponse:独立端点响应
  • 克隆音色特有字段均为 Optional,不影响预置音色

3. apps/api/app/api/routes/voices.py⚠️ 核心逻辑有 P1

正确部分:

  • type 过滤逻辑正确:None → 全部,"preset" → 仅预置,"clone" → 仅克隆
  • 排序正确:items.extend(preset_items) 先于 items.extend(clone_items),预置在前
  • /presets 端点路由顺序正确(在 /{voice_id} 之前)
  • /legacy 端点保留原有格式(但路径不同,见 P2-3)
  • /presets 不需要认证(合理设计)

P1:clone_repository_count 调用不存在的方法签名

# voices.py 第 167-172 行
def clone_repository_count(...):
    if status_filter:
        return voice_repository.count_by_user(user_id, status=status_filter)  # ❌
    return voice_repository.count_by_user(user_id)

SQLAlchemyVoiceLibraryRepository.count_by_user 签名为 count_by_user(self, user_id: str) -> int不接受 status 参数。当用户传入 ?status=xxx 查询参数时,会触发 TypeError: count_by_user() got an unexpected keyword argument 'status'

4. tests/unit/test_preset_voices.py 覆盖充分

  • 18 个测试全部通过
  • 覆盖:字段验证、默认值、不可变性、序列化、数量检查、唯一性、CosyVoice 音色名验证、性别分布、辅助函数

🔴 P1 — 必须修复

# 文件 问题
P1-1 voices.py:170 count_by_user(user_id, status=status_filter) 调用不存在的方法签名。需给 count_by_user 增加 status 参数,或在 clone_repository_count 中过滤 status 后再计数

🟡 P2 — 建议修复

# 文件 问题
P2-1 preset_voices.py 音色 ID 缺少版本号后缀。阿里云 CosyVoice v3 音色参数为 longxiaochun_v3longyue_v3 等,代码中使用无后缀版本(longxiaochun)。需确认使用的模型版本是否支持无后缀参数名
P2-2 preset_voices.py 3 个音色在 CosyVoice v3 列表中未找到:longxiaochen(龙小晨)、longshu(龙书)、longjing(龙静)。可能在旧版模型中存在,但建议替换为 v3 可用音色
P2-3 voices.py 向后兼容性破坏:GET /api/v1/voices 响应模型从 ListVoiceLibraryResponse{items, total})变更为 UnifiedVoiceListResponse{items, total, preset_count, clone_count}),且 items 字段名变化(textdescription,新增 type/gender 等)。/legacy 端点保留旧格式但路径不同。建议前端同步适配,或保持原路径不变
P2-4 voices.py:119 全量模式(type=None)克隆音色获取使用 limit=1000 全量拉取后内存分页。若用户克隆音色较多,建议改为数据库层分页
P2-5 preset_voices.py 预置音色描述与阿里云官方特质描述不完全一致(如龙小淳官方为"知性积极女",代码为"温柔女声")。建议对齐官方描述,便于用户预期一致

审查要点逐项回应

# 审查要点 结论
1 CosyVoice 真实可用音色名 ⚠️ 音色名基本正确,但缺版本号后缀,3个在v3中未找到(P2-1, P2-2)
2 type 过滤逻辑 正确
3 预置音色排在前面 extend(preset_items) 先于 extend(clone_items)
4 向后兼容性 ⚠️ 响应格式变更,/legacy 路径不同(P2-3)
5 Hexagonal 架构分层 domain 层配置、schemas 层响应、routes 层编排
6 单元测试覆盖 18 个测试覆盖全面

总结

维度 评估
核心功能 type 过滤 + 排序 + 统一响应
架构设计 Hexagonal 分层合理
测试覆盖 18 个测试充分
运行时安全 ⚠️ 1 P1(count_by_user 参数错误)
CosyVoice 音色准确性 ⚠️ 2 P2(版本号 + 缺失音色)
向后兼容 ⚠️ 1 P2(响应格式变更)

结论: 通过(有条件) — 修复 P1-1(count_by_user status 参数)后可合并。P2 建议后续迭代处理。

## 🔍 PR #164 审查报告 **PR**: feature/task-304-voice-list-api → develop **Commit**: 44dc893 **变更**: 4 files, +607 / -4 **审查时间**: 2026-07-02 10:35 --- ### ✅ 审查结论:通过(有条件) 核心功能设计合理,预置音色配置、type 过滤、排序逻辑均正确。发现 1 P1(`count_by_user` 传了不存在的 `status` 参数,运行时 TypeError)+ 5 P2,需修复 P1 后可合并。 --- ### 📄 文件审查 #### 1. `packages/domain/preset_voices.py` — ✅ 设计合理 - `PresetVoice` dataclass(frozen=True, slots=True)不可变,内存高效 - 8 个预置音色,覆盖多种场景(情感/播报/有声书/教育/纪录片/科技/短视频) - 辅助函数:`get_preset_voices()`、`get_preset_voice_by_id()`、`is_preset_voice()` - 位于 domain 层,不依赖数据库,符合 Hexagonal 架构 #### 2. `apps/api/app/schemas/voice.py` — ✅ Schema 完整 - `UnifiedVoiceItemResponse`:`type: Literal["preset", "clone"]` 区分类型 - `UnifiedVoiceListResponse`:含 `preset_count` / `clone_count` - `PresetVoiceItemResponse` / `PresetVoiceListResponse`:独立端点响应 - 克隆音色特有字段均为 Optional,不影响预置音色 #### 3. `apps/api/app/api/routes/voices.py` — ⚠️ 核心逻辑有 P1 **✅ 正确部分:** - `type` 过滤逻辑正确:`None` → 全部,`"preset"` → 仅预置,`"clone"` → 仅克隆 - 排序正确:`items.extend(preset_items)` 先于 `items.extend(clone_items)`,预置在前 - `/presets` 端点路由顺序正确(在 `/{voice_id}` 之前) - `/legacy` 端点保留原有格式(但路径不同,见 P2-3) - `/presets` 不需要认证(合理设计) **❌ P1:`clone_repository_count` 调用不存在的方法签名** ```python # voices.py 第 167-172 行 def clone_repository_count(...): if status_filter: return voice_repository.count_by_user(user_id, status=status_filter) # ❌ return voice_repository.count_by_user(user_id) ``` `SQLAlchemyVoiceLibraryRepository.count_by_user` 签名为 `count_by_user(self, user_id: str) -> int`,**不接受 `status` 参数**。当用户传入 `?status=xxx` 查询参数时,会触发 `TypeError: count_by_user() got an unexpected keyword argument 'status'`。 #### 4. `tests/unit/test_preset_voices.py` — ✅ 覆盖充分 - 18 个测试全部通过 - 覆盖:字段验证、默认值、不可变性、序列化、数量检查、唯一性、CosyVoice 音色名验证、性别分布、辅助函数 --- ### 🔴 P1 — 必须修复 | # | 文件 | 问题 | |---|------|------| | P1-1 | voices.py:170 | `count_by_user(user_id, status=status_filter)` 调用不存在的方法签名。需给 `count_by_user` 增加 `status` 参数,或在 `clone_repository_count` 中过滤 status 后再计数 | --- ### 🟡 P2 — 建议修复 | # | 文件 | 问题 | |---|------|------| | P2-1 | preset_voices.py | 音色 ID 缺少版本号后缀。阿里云 CosyVoice v3 音色参数为 `longxiaochun_v3`、`longyue_v3` 等,代码中使用无后缀版本(`longxiaochun`)。需确认使用的模型版本是否支持无后缀参数名 | | P2-2 | preset_voices.py | 3 个音色在 CosyVoice v3 列表中未找到:`longxiaochen`(龙小晨)、`longshu`(龙书)、`longjing`(龙静)。可能在旧版模型中存在,但建议替换为 v3 可用音色 | | P2-3 | voices.py | 向后兼容性破坏:`GET /api/v1/voices` 响应模型从 `ListVoiceLibraryResponse`(`{items, total}`)变更为 `UnifiedVoiceListResponse`(`{items, total, preset_count, clone_count}`),且 items 字段名变化(`text`→`description`,新增 `type`/`gender` 等)。`/legacy` 端点保留旧格式但路径不同。建议前端同步适配,或保持原路径不变 | | P2-4 | voices.py:119 | 全量模式(type=None)克隆音色获取使用 `limit=1000` 全量拉取后内存分页。若用户克隆音色较多,建议改为数据库层分页 | | P2-5 | preset_voices.py | 预置音色描述与阿里云官方特质描述不完全一致(如龙小淳官方为"知性积极女",代码为"温柔女声")。建议对齐官方描述,便于用户预期一致 | --- ### 审查要点逐项回应 | # | 审查要点 | 结论 | |---|----------|------| | 1 | CosyVoice 真实可用音色名 | ⚠️ 音色名基本正确,但缺版本号后缀,3个在v3中未找到(P2-1, P2-2) | | 2 | type 过滤逻辑 | ✅ 正确 | | 3 | 预置音色排在前面 | ✅ `extend(preset_items)` 先于 `extend(clone_items)` | | 4 | 向后兼容性 | ⚠️ 响应格式变更,/legacy 路径不同(P2-3) | | 5 | Hexagonal 架构分层 | ✅ domain 层配置、schemas 层响应、routes 层编排 | | 6 | 单元测试覆盖 | ✅ 18 个测试覆盖全面 | --- ### 总结 | 维度 | 评估 | |------|------| | 核心功能 | ✅ type 过滤 + 排序 + 统一响应 | | 架构设计 | ✅ Hexagonal 分层合理 | | 测试覆盖 | ✅ 18 个测试充分 | | 运行时安全 | ⚠️ 1 P1(count_by_user 参数错误) | | CosyVoice 音色准确性 | ⚠️ 2 P2(版本号 + 缺失音色) | | 向后兼容 | ⚠️ 1 P2(响应格式变更) | **结论:✅ 通过(有条件)** — 修复 P1-1(`count_by_user` status 参数)后可合并。P2 建议后续迭代处理。
xiaoxia added 1 commit 2026-07-02 10:39:19 +08:00
fix: PR#164审查修复 - P1 count_by_user status参数 + P2 内存分页优化
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 170h11m59s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 170h12m4s
Deploy / Deploy Staging (push) Failing after 170h14m34s
CI/CD Pipeline / Frontend Lint (push) Failing after 170h15m9s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 170h15m14s
5584df3d55
P1修复:
- voice_library_repository.count_by_user() 增加可选 status 参数
- 修复 list_voices_unified 中 TypeError: count_by_user got unexpected keyword argument 'status'

P2修复:
- 移除 limit=1000 全量拉取,改为直接传递 skip/limit 到仓储层
- 删除冗余的 clone_repository_count() 辅助函数
- 优化统一列表分页逻辑,代码更清晰

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
xiaoxia merged commit 1971120f48 into develop 2026-07-02 10:40:40 +08:00
Sign in to join this conversation.