feat: 预置音色 preview_url 动态生成,解决签名 URL 过期问题 #1222
Reference in New Issue
Block a user
Delete Branch "feat/preset-voice-dynamic-preview"
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?
问题
预置音色的 8 个
preview_url是 DashScope 签名 URL,有效期约 30 天,过期后前端无法播放试听。方案
采用方案 A(推荐):修改
/voices/presets和/voices接口,返回时动态生成 preview_url,不再依赖硬编码的过期 URL。改动
_resolve_preset_preview_url():缓存优先(7天TTL),缓存失效时调 CosyVoice TTS 重新合成,合成失败降级到硬编码 URL_resolve_all_preset_preview_urls():ThreadPoolExecutor(max_workers=4)并行生成 8 个音色的 preview_url,避免串行超时list_preset_voices()和list_voices_unified():注入CosyVoiceService依赖,动态解析 preview_url_preset_to_unified_response():支持preview_url_map参数验证
关联
🚀 预览环境已部署
612ce94720to4e36aebabb代码审查结果 - PR #1222
⚠️ 问题(3个需要修改)
apps/api/app/api/routes/voices.py 第93行:异常处理逻辑错误,导致降级失效。
_resolve_all_preset_preview_urls的except Exception块中,将结果设为空字符串""。_resolve_preset_preview_url内部发生未预料到的异常抛出(尽管它有 try-except,但仍存在抛出异常的可能,如参数传递错误),result_map会存入空字符串。在list_preset_voices中,preview_url_map.get(p.voice_id, p.preview_url)会因为 key 存在且值为""而返回"",导致前端拿到无效 URL,无法播放音频,且无法降级到硬编码的fallback_url。future_to_voice的结构,使其能关联到preset对象或fallback_url,以便在异常处理时能回退到fallback_url,而不是空字符串。apps/api/app/api/routes/voices.py 第272行:在请求主线程中执行耗时任务,存在严重的性能隐患。
list_voices_unified接口负责返回音色列表,属于高频且对延迟敏感的路径。该接口内部调用了_resolve_all_preset_preview_urls,虽然使用了线程池,但as_completed会阻塞等待所有 TTS 任务完成。list_voices_unified接口响应时间显著增加,甚至导致请求超时,严重影响前端加载音色列表的用户体验。此外,该操作触发了“惊群效应”,多个并发请求会同时触发 TTS 合成。apps/api/app/api/routes/voices.py 第58行:全局变量
_preset_preview_cache存在线程安全隐患。_preset_preview_cache是模块级别的全局字典,被ThreadPoolExecutor中的多个线程并发读写(检查存在性、读取、写入)。threading.Lock对缓存访问进行加锁保护,或者使用线程安全的数据结构。💡 建议(1个可选)
future.result()的异常块中,除了回退处理外,建议添加logger.error记录具体的异常堆栈,以便排查为何_resolve_preset_preview_url抛出了未被内部捕获的异常。✅ 格式检查通过 | ❌ 逻辑审查需修改 | ⚠️ 建议关注性能
🤖 由 AI 代码审查机器人自动生成 | 2026-08-02 14:12:26 | 模型:
🗑️ 预览环境已清理
PR #1222 已关闭或合并,对应的预览环境已被清理。