# 音频异步与词典RPC专项（当前源码静态深审）

结论：新增异步链路有5项需处置，其中草稿被补发布为P1。旧19应按最新格式门槛重新裁决；旧20已修复。没有实际业务调用、数据库写入或Maven执行。以下为源码机制结论，不代表部署结果。新增原方法隔离javac探针，验证分支机制；外部服务及DAO均为替身。

## 版本与边界
当前0baa9ebe8b7f9ecc46f675acbf1f56ac65378957；旧e9f2fb1b58a2a0292e5a95a9cbeb6fae7f956682；最新master由主报告统一核对。音频回填Job相对旧版新增；最新Spec2.1.9包含17章异步化、5.1人工必填和12.2 phraseIds聚合契约。源AGENTS及Spec全文已读；Plan05已补读166–265、266–365、366–399，前1–165已先前完整读取，当前全文闭合。Plan02/06已读。父评审已另行完整读PRD正文/14条评论及Plan01，以父评审材料记录为准。未使用CodeGraph、未建索引。

## 正常机制：改造前、改造后与最终读取
旧版导入/补录调用TTS合成并等待，应用层下载/OSS上传/登记local_resource，拿资源ID后建词；失败音频为空主体仍可落库。当前导入/补录只建主体且音频为空，行source保持2/3。回填job以Redis watchdog锁保证同一轮互斥；先扫描每表音频空、任务空且创建10分钟以前的候选，word/phrase按source过滤、variant按父phrase来源二次过滤，再提交TTS并记taskId/秒级taskTime。人工来源1不自动提交。

每表在途行按taskTime/id升序取batch；查状态串行。running未超时保留、超时取消并清标记；failed/cancelled清标记。completed先保留已有人工资源；无资源才下载字节、OSS上传、登记bizType2 local_resource，随后仅在DB音频仍空的条件下回填，避免并发人工上传被覆盖。taskTime独立于updateTime，普通编辑和发布刷新updateTime不会改变超时起点。

回填后从未发布行不补发布；曾发布行调用完整单行发布。发布锁内重读当前JY，PROD一个事务显式映射主表/子表/资源ID，资源独立占行；翻译/变体差集软删；taskId清空、taskTime=0，学生库不跑合成。资源缺失/已删时loadAliveResources提前失败，不留本次半发布。无音频null允许导入/补录发布，资源集合为空时无需资源映射。RPC读取缺资源仅audioUrl=null，不因单项资源不存在拖垮整个批次。发布清PROD详情缓存，但异步回填本身没有失效TEACHING缓存。

## 当前RPC契约与QA边界
EnglishDictListByIdsReq已删除phraseRefs，改phraseIds。单词/短语列表可分别为空，原始列表长度合计1~100（重复也计数）；env只0/1，ID必须正且按1亿边界归属同环境。split绑定单库，不自动拆分；cache查询显式source，单词/短语各按ID去重升序。短语返回原形及全部variants，由C端按下发variantId定位，故旧版“variantId归属参数错误”不再适用。缓存键有default/prod数据源段，无跨库兜底。旧调用方仅phraseRefs将等效空短语：混合请求可能仅回words，phrase-only总数0报参数错，须跨项目联调升级，不能视为兼容成功。

旧19：当前assertAudioResources仍要求mp3/wav。最新Spec5.1明确这道格式门槛，故不是已移除白名单，而是目标变更后旧缺陷不再成立；Spec5.2、Plan02“无格式校验”残留需文档统一。旧20：saveWord当前mergeForCompleteness合并existing，且缺audioUrl会被必填拦截，原始误标路径已闭合。

## 已核实问题

### AUDIO-N01 P1 音频补发布把曾发布对象的人工待发布草稿一并发布

条件：导入/补录无音频词已发布且有在途TTS；运营上传人工音频、改翻译并保存，未点发布；TTS随后completed。

机制：编辑保留hasPublished=1并置status=0；completed发现人工音频仍返回true；queryInFlight只检查hasPublished而非当前草稿状态，调用完整publishWord/Phrase，锁内重读当前主表、翻译、全部变体发布。

最终表现：未审核翻译、改名或变体配置进入PROD，待发布状态被系统改为已发布，违反Spec6只改不发布正式库完全不变。

反证与边界：人工音频不会被TTS覆盖；从未发布hasPublished=0确实不自动发布。Spec17自身写hasPublished=1补发布，与草稿隔离冲突，需设计裁决而不能说仅实现漏判。

源码：`application/src/main/java/cn/jojo/cc/biz/service/english/dict/impl/EnglishAudioBackfillBizServiceImpl.java:168-176`；均是新增异步链路。

### AUDIO-N02 P2 completed下载永久失败与查询异常均绕过超时收口

条件：状态持续completed，但下载404/过期/OSS保存失败；或状态查询持续报错；提交时间超过1800秒。

机制：elapsed超时只位于null/running分支；query异常直接return，completed保存null也return并保留任务。每轮ORDER BY taskTime,id LIMIT batch重复拿最老行。

最终表现：该词永久无音频且不再补提交；当坏任务达到batch还会饿死后续在途任务。

反证与边界：null/running超时确实cancel并清理，普通一过性下载失败下轮能恢复；问题限定持续失败。

源码：`application/src/main/java/cn/jojo/cc/biz/service/english/dict/impl/EnglishAudioBackfillBizServiceImpl.java:248-292`；均是新增异步链路。

### AUDIO-N03 P2 失败上限只统计提交抛错，平台终态失败与超时会无限重新提交

条件：平台submit一直成功，但每次合成都failed/cancelled或running超时。

机制：submit成功立即clearFailCount；终态失败和超时只清任务号、不增加失败计数；下轮pending重新提交；永远达不到maxRetry=3。

最终表现：同一无效词反复消耗合成配额，违反Spec17失败3次停止和配置同一行最多提交次数。

反证与边界：提交API连续抛错确实计数并停手7天；这不是所有失败均无上限。

源码：`application/src/main/java/cn/jojo/cc/biz/service/english/dict/impl/EnglishAudioBackfillBizServiceImpl.java:151-157`；均是新增异步链路。

### AUDIO-N04 P2 达到提交失败上限的最早ID持续占满候选批次，后续词无法补音频

条件：最小ID的batch条IMPORT/AUTOFILL词提交连续失败达到上限，随后TTS恢复且还有更大ID待生成词。

机制：SQL先按ID升序LIMIT batch；maxRetry仅业务循环内判断后return，未推进游标/排除冷却行；7天计数TTL内每轮仍选择同一批。

最终表现：后续健康词最多7天也无合成任务；批次条数配置变小放大此影响。

反证与边界：不是数据库全量饥饿，限某表前batch被失败上限占满；7天过期或人工补齐能释放。

源码：`application/src/main/java/cn/jojo/cc/biz/service/english/dict/impl/EnglishAudioBackfillBizServiceImpl.java:143-146`；均是新增异步链路。

### AUDIO-N05 P2 异步回填只更新音频列，完整性及教研缓存保持旧值

条件：导入词已有音标和翻译，唯一缺音频completeness=0；TTS成功回填。

机制：markAudioCompleted只set音频和清任务两列，不重算completeness；回填服务不调用教学缓存失效；补发布复制JY完整性0到PROD而不重算。

最终表现：即使最终音频齐全，管理筛选仍有缺失、RPC complete=false；已缓存TEACHING详情仍可无音频直到常规过期。

反证与边界：人工保存走mergeForCompleteness会重算；缺音标或翻译词仍应不完整，不能用它们验证本问题。

源码：`infrastructure/repository/src/main/java/cn/jojo/cc/core/service/impl/english/EnglishWordCoreServiceImpl.java:301-316`；均是新增异步链路。

## QA执行清单（未执行）

1. 单词/短语原形/变体分别覆盖：首次提交→running→completed→JY资源回填→已发布才补PROD；PROD任务列始终空，资源ID/父外键映射一次，URI相同。
2. 从未发布导入行回填不进PROD；曾发布在途行人工上传+改释义保存待发布，等待完成，检查草稿是否被自动发布（N01）；短语变体完成也检查整个父短语草稿。
3. 已有人工音频和下载期间并发人工音频两种窗口均不覆盖。改名必须提交人工音频，因此“改名仍空音频直接填旧TTS”正常HTTP入口不可达，不列确定问题；直接改库/其它内部入口需另证。
4. 三类持续故障：query异常、completed下载失败、running超时，各超过1800秒。前两类不得永久占批次；running应cancel清理，随后重提交。平台failed/cancelled反复3次检查是否停手（N02/N03）。
5. batch=1，最小ID提交失败3次后恢复服务，第二ID应能提交（N04）；变体候选按父来源二次过滤也要防前批次被不可提交行占满。
6. 唯一缺音频导入词回填后，查询JY详情/筛选/TEACHING RPC及PROD RPC，audioUrl应新值、complete应true（N05）；对照确实缺音标词应false。
7. 回填成功后注入一次补发布锁忙/资源写失败，JY保留音频、PROD仍旧；下一轮不会再选已清任务行，恢复依赖人工发布，不能声称下轮自动补发布。
8. 未发布行在途时删除，或短语编辑移除在途变体，检查删除行是否仍被listAudioInFlight选中、是否产生孤儿资源/无意义父发布。当前在途扫描与写条件没有deleteTime/taskId守卫；普通改名音频为空已被人工必填阻断，删除并发仍需业务环境验证，不扩大为所有编辑都会错音。
9. RPC word-only/phrase-only/混合、0/100/101长度、重复100/101、null/0/负数/跨库ID、旧phraseRefs、新phraseIds、多变体sortOrder相同、无变体[]、缺资源null；缓存关/开、坏值回源和default/prod隔离分别验。旧Plan06 variantId参数校验项已失效，改为返回集合归属与C端定位。

## 尚需闭合
未核实际部署、真实TTS下载响应/超时、Redis开关和DB数据；未做Dubbo客户端兼容联调；PRD与Plan01全文由父评审统一补读，Plan05本人已分段补完。报告不声称上述QA场景已运行。

## 原方法隔离探针实跑

`audio-probe/AudioProbe.java`包含从当前HEAD逐字抽取的`handleInFlight`、`queryInFlight`、`publishReleased`及`isPublished`方法；`extracted-methods.txt`留存抽取内容。仅外围类型、平台状态、DAO列表、发布服务与音频保存服务是最小替身，没有重写目标分支算法。Java21 javac编译及main运行成功，完整输出在`audio-probe/results.txt`。

- completed、elapsed=9999（阈值1800）、saveAudio返回null：done=false、clears=0、failed=1。证明下载失败超时不收口。
- query异常、elapsed=9999：clears=0、running=1。与下载失败是同一“超时判定仅存在running正常响应分支”根因，归N02一项。
- 原queryInFlight加载hasPublished=1且人工音频88的词，平台completed：clears=1、publishCalls=1。证明保留人工音频后仍自动发布。该探针不运行真实publish内部、数据库事务或人工保存流程，完整草稿越权传播以当前save/publish源码闭合。

N03失败上限根因已再核：submit返回任务成功后立即clearFailCount；failed/cancelled/超时的markCleared没有increaseFailCount，故accepted→failed→重新submit不限次数，不与N02混计。
