Просмотр исходного кода

fix(backend): 腾讯云TTS错误解析嵌套位置,PkgExhausted 不再被误报为空音频

腾讯云 API 3.0 错误位于 Response.Error 而非顶层,原代码 getJSONObject("Error")
永远返回 null,导致资源包耗尽(PkgExhausted)被掩盖为"TTS合成返回空音频",
无法向用户/运营暴露真实错误。新增 extractError 从 Response.Error 提取,
单测覆盖嵌套错误解析与无错误两种场景。
Xiaogang Liao 3 недель назад
Родитель
Сommit
824ec81642

+ 18 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/TencentTtsService.java

@@ -122,7 +122,7 @@ public class TencentTtsService {
                     throw new RuntimeException("TTS合成失败: HTTP " + response.code());
                 }
                 JSONObject resp = JSON.parseObject(respBody);
-                JSONObject error = resp.getJSONObject("Error");
+                JSONObject error = extractError(resp);
                 if (error != null) {
                     String msg = error.getString("Message");
                     log.warn("腾讯云TTS业务错误: {}", msg);
@@ -199,6 +199,23 @@ public class TencentTtsService {
         return sb.toString();
     }
 
+    /**
+     * 从腾讯云 API 3.0 响应中提取业务错误对象。
+     * 错误嵌套在 Response.Error(requestId 平级),顶层没有 Error 字段;
+     * 直接在顶层 getJSONObject("Error") 会永远返回 null,掩盖真实错误
+     * (如 PkgExhausted 被误报为"返回空音频")。
+     */
+    static JSONObject extractError(JSONObject resp) {
+        if (resp == null) {
+            return null;
+        }
+        JSONObject response = resp.getJSONObject("Response");
+        if (response == null) {
+            return null;
+        }
+        return response.getJSONObject("Error");
+    }
+
     /**
      * 将 Unix 秒时间戳转换为 UTC 日期(yyyy-MM-dd),用于 TC3 签名 Credential。
      * 旧实现直接截取 Unix 秒字符串前 8 位(如 1792678570 → "17926785"),不符合腾讯云要求。

+ 25 - 0
cfc-backend/src/test/java/com/etotem/cfc/service/TencentTtsServiceAuthorizationTest.java

@@ -1,10 +1,14 @@
 package com.etotem.cfc.service;
 
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.springframework.test.util.ReflectionTestUtils;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 /**
@@ -45,4 +49,25 @@ public class TencentTtsServiceAuthorizationTest {
         assertTrue(auth.contains("Credential=AKIDtest/2026-10-22/tts/tc3_request"),
                 "Credential 应含 UTC 日期 2026-10-22,实际: " + auth);
     }
+
+    /**
+     * 腾讯云 API 3.0 的错误嵌套在 Response.Error(实测 PkgExhausted 响应),
+     * 顶层 getJSONObject("Error") 永远返回 null,会掩盖真实错误。
+     */
+    @Test
+    public void extractError_readsNestedResponseError() {
+        JSONObject resp = JSON.parseObject("{\"Response\":{\"RequestId\":\"r1\","
+                + "\"Error\":{\"Code\":\"UnsupportedOperation.PkgExhausted\","
+                + "\"Message\":\"The resource pack allowance has been exhausted, please check your resource pack.\"}}}");
+        JSONObject error = service.extractError(resp);
+        assertNotNull(error, "应从 Response.Error 提取错误,不得返回 null");
+        assertEquals("UnsupportedOperation.PkgExhausted", error.getString("Code"));
+    }
+
+    /** 合成成功(无 Error 字段)→ extractError 返回 null,不误报 */
+    @Test
+    public void extractError_nullWhenNoError() {
+        JSONObject resp = JSON.parseObject("{\"Response\":{\"Audio\":\"aGVsbG8=\",\"RequestId\":\"r2\"}}");
+        assertNull(service.extractError(resp));
+    }
 }