소스 검색

fix(backend): 文章创建更新防检测超时-受限超时并fail-open

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
E2E Test Bot 3 주 전
부모
커밋
643f433a8d

+ 31 - 6
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminArticleController.java

@@ -9,12 +9,17 @@ import com.etotem.cfc.service.ArticleService;
 import com.etotem.cfc.service.FileStorageService;
 import com.etotem.cfc.service.api.WechatServiceInterface;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.MultipartFile;
 import javax.annotation.Resource;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 import com.etotem.cfc.util.SortUtil;
 import com.etotem.cfc.util.ParamUtils;
 
@@ -22,6 +27,8 @@ import com.etotem.cfc.util.ParamUtils;
 @RequestMapping("/api/admin/articles")
 public class AdminArticleController {
 
+    private static final Logger log = LoggerFactory.getLogger(AdminArticleController.class);
+
     @Resource
     private ArticleService articleService;
 
@@ -37,6 +44,24 @@ public class AdminArticleController {
     @Resource
     private WechatServiceInterface wechatService;
 
+    @Resource
+    private ThreadPoolTaskExecutor taskExecutor;
+
+    /**
+     * 文本内容安全检测:带 5 秒硬超时,超时/异常时 fail-open 放行并记录日志,
+     * 避免微信接口卡顿导致文章创建/更新接口耗时数十秒(BUG-J)。
+     */
+    private boolean checkTextSafe(String text) {
+        if (text == null || text.trim().isEmpty()) return true;
+        try {
+            Future<Boolean> future = taskExecutor.submit(() -> wechatService.checkText(text));
+            return future.get(5, TimeUnit.SECONDS);
+        } catch (Exception e) {
+            log.warn("文本内容安全检测超时或执行失败,fail-open: {}", e.getMessage());
+            return true;
+        }
+    }
+
     @PostMapping("/list")
     public Result<Page<Article>> list(@RequestBody Map<String, Object> body) {
         String status = (String) body.get("status");
@@ -62,11 +87,11 @@ public class AdminArticleController {
         String title = (String) body.get("title");
         String content = (String) body.get("content");
 
-        // 文本内容安全检测
-        if (title != null && !title.isEmpty() && !wechatService.checkText(title)) {
+        // 文本内容安全检测(带超时,不阻塞接口)
+        if (!checkTextSafe(title)) {
             return Result.error("文章标题包含敏感信息,发布失败");
         }
-        if (content != null && !content.isEmpty() && !wechatService.checkText(content)) {
+        if (!checkTextSafe(content)) {
             return Result.error("文章内容包含敏感信息,发布失败");
         }
 
@@ -134,11 +159,11 @@ public class AdminArticleController {
         String title = (String) body.get("title");
         String content = (String) body.get("content");
 
-        // 文本内容安全检测
-        if (title != null && !title.isEmpty() && !wechatService.checkText(title)) {
+        // 文本内容安全检测(带超时,不阻塞接口)
+        if (!checkTextSafe(title)) {
             return Result.error("文章标题包含敏感信息,更新失败");
         }
-        if (content != null && !content.isEmpty() && !wechatService.checkText(content)) {
+        if (!checkTextSafe(content)) {
             return Result.error("文章内容包含敏感信息,更新失败");
         }
 

+ 14 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/WechatService.java

@@ -63,7 +63,20 @@ public class WechatService implements WechatServiceInterface {
     @Value("${wechat.env-version:release}")
     private String envVersion;
 
-    private final RestTemplate restTemplate = new RestTemplate();
+    private final RestTemplate restTemplate = createRestTemplate();
+
+    /**
+     * 为微信 API 调用配置超时(connect 3s / read 5s)。
+     * 默认 RestTemplate 无超时,微信接口慢或网络异常时会无限期阻塞 HTTP 线程,
+     * 导致文章创建/更新接口耗时数十秒(BUG-J)。
+     */
+    private static RestTemplate createRestTemplate() {
+        org.springframework.http.client.SimpleClientHttpRequestFactory factory =
+                new org.springframework.http.client.SimpleClientHttpRequestFactory();
+        factory.setConnectTimeout(3000);
+        factory.setReadTimeout(5000);
+        return new RestTemplate(factory);
+    }
 
     // ===== stable access_token 缓存(独立缓存,用于避免"not latest"冲突)=====
     private volatile String cachedStableAccessToken;