private boolean handleInFlight(String table, Long id, String taskId, Long submitTime, Long currentAudioResourceId, long now, long timeoutSeconds, EnglishAudioBackfillResultBo result) { if (id == null || StringUtils.isBlank(taskId)) { return false; } result.setScanned(result.getScanned() + 1); TtsTaskStatus status; try { status = internalTtsClient.queryTask(taskId); } catch (Exception e) { // 查状态失败按「还没好」处理:下轮再查,不在这里清任务号 log.warn("音频任务状态查询失败, row={}:{}, taskId={}", table, id, taskId, e); result.setRunning(result.getRunning() + 1); return false; } if (status == null || status.isRunning()) { long elapsed = submitTime == null ? Long.MAX_VALUE : now - submitTime; if (elapsed >= timeoutSeconds) { // 平台丢任务或长时间不出结果:先取消平台任务,再清本地在途标记,否则该行永远挂着「生成中」 internalTtsClient.cancelTask(taskId); markCleared(table, id); result.setFailed(result.getFailed() + 1); log.warn("音频任务超时清理, row={}:{}, taskId={}, elapsed={}s", table, id, taskId, elapsed); } else { result.setRunning(result.getRunning() + 1); } return false; } if (status.isCompleted()) { // 人工优先:在途期间运营可能已上传了音频,这时只清任务号,既不下载也不覆盖 if (currentAudioResourceId != null && currentAudioResourceId > 0) { markCleared(table, id); result.setCompleted(result.getCompleted() + 1); log.info("音频任务完成但该行已有音频,保留人工音频并清任务号, row={}:{}", table, id); return true; } Long resourceId = englishAudioBizService.saveAudio(taskId); if (resourceId != null) { if (!markCompleted(table, id, resourceId)) { // 条件更新未命中:写完前的一瞬间人工补了音频(并发窗口),同样只清任务号,不回写 markCleared(table, id); log.info("音频回填时发现该行已被人工补齐,保留人工音频, row={}:{}", table, id); } result.setCompleted(result.getCompleted() + 1); return true; } // 下载或落库失败(未就绪/源文件过期/校验失败):保留任务号,由超时清理最终收口, // 不在这里清掉——清掉就意味着这个词再也不会自动补音频 result.setFailed(result.getFailed() + 1); log.warn("音频下载或保存失败,保留任务号待下轮, row={}:{}, taskId={}", table, id, taskId); return false; } // failed / cancelled:终态失败,清掉在途标记,音频留空(运营仍可手工上传) markCleared(table, id); result.setFailed(result.getFailed() + 1); log.warn("音频任务终态失败, row={}:{}, taskId={}, status={}, error={}", table, id, taskId, status.getStatus(), status.getError()); return false; } private void queryInFlight(int batch, EnglishAudioBackfillResultBo result) { long now = nowSeconds(); long timeoutSeconds = Math.max(60L, englishDictConfig.getTtsAudioTaskTimeoutSeconds()); for (EnglishWordDo row : englishWordCoreService.listAudioInFlight(batch)) { boolean filled = handleInFlight(ROW_WORD, row.getId(), row.getAudioTaskId(), row.getAudioTaskTime(), row.getAudioResourceId(), now, timeoutSeconds, result); if (filled && isPublished(row.getHasPublished())) { publishReleased(ROW_WORD, row.getId()); } } for (EnglishPhraseDo row : englishPhraseCoreService.listAudioInFlight(batch)) { boolean filled = handleInFlight(ROW_PHRASE, row.getId(), row.getAudioTaskId(), row.getAudioTaskTime(), row.getAudioResourceId(), now, timeoutSeconds, result); if (filled && isPublished(row.getHasPublished())) { publishReleased(ROW_PHRASE, row.getId()); } } // 变形没有自己的发布入口(随短语发布):先收集本轮回填成功的短语,最后一起去重后再发 Set filledPhraseIds = new LinkedHashSet<>(); for (EnglishPhraseVariantDo row : englishPhraseVariantCoreService.listAudioInFlight(batch)) { boolean filled = handleInFlight(ROW_VARIANT, row.getId(), row.getAudioTaskId(), row.getAudioTaskTime(), row.getAudioResourceId(), now, timeoutSeconds, result); if (filled && row.getPhraseId() != null) { filledPhraseIds.add(row.getPhraseId()); } } publishReleasedPhrases(filledPhraseIds); } private void publishReleased(String table, Long id) { try { if (ROW_WORD.equals(table)) { englishPublishBizService.publishWord(id, SYSTEM_OPERATOR); } else if (ROW_PHRASE.equals(table)) { englishPublishBizService.publishPhrase(id, SYSTEM_OPERATOR); } } catch (Exception e) { // 补发布失败不回滚音频回填:音频已经在教研库,运营下次发布或再跑一轮仍会带上; // 这里只留痕,避免因为一次锁竞争把已成功的回填也标记成失败 log.warn("音频回填后补发布失败, row={}:{}", table, id, e); } } private boolean isPublished(Integer hasPublished) { return Integer.valueOf(1).equals(hasPublished); }