From 2413b2bf58acc4cdf24a975082a036ec712cb195 Mon Sep 17 00:00:00 2001 From: Peanut Date: Wed, 3 Jun 2026 23:19:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(exception):=20GlobalExceptionHandler=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=20WechatApiException=20handler?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 解决问题: - WechatApiException 抛出后未统一处理,会冒泡到 Spring 默认错误页(500 HTML) - 前端拿不到结构化错误信息 改动: - 新增 @ExceptionHandler(WechatApiException.class) 方法 - code=400 -> Result.badRequest()(微信业务错误) - code=502 -> Result.error(502, message)(上游/网络错误) - 其他(code=500) -> Result.error(message)(内部 JSON 解析错误) - 日志记录 code 和 message(已确保不含敏感信息) - 风格与现有 BusinessException handler 保持一致 --- .../exception/GlobalExceptionHandler.java | 39 ++++++++++ .../GlobalExceptionHandlerWechatTest.java | 72 +++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 backend-single/src/test/java/com/emotion/exception/GlobalExceptionHandlerWechatTest.java diff --git a/backend-single/src/main/java/com/emotion/exception/GlobalExceptionHandler.java b/backend-single/src/main/java/com/emotion/exception/GlobalExceptionHandler.java index 13da8bd..a301086 100644 --- a/backend-single/src/main/java/com/emotion/exception/GlobalExceptionHandler.java +++ b/backend-single/src/main/java/com/emotion/exception/GlobalExceptionHandler.java @@ -72,6 +72,45 @@ public class GlobalExceptionHandler { } } + /** + * 处理微信 API 异常(业务错误 400 / 内部错误 500 / 上游错误 502) + * + *

根据异常内部 code 映射到不同友好提示(不直接暴露原始异常消息给前端): + *

+ * + *

详细异常消息仅记录到日志(含 [WeChatAPI] 前缀),前端仅看到友好提示。 + */ + @ExceptionHandler(WechatApiException.class) + public Result handleWechatApiException(WechatApiException e, HttpServletRequest request) { + String userMessage; + switch (e.getCode()) { + case 400: + userMessage = "微信授权失败,请重试"; + log.warn("[WeChatAPI] 微信授权失败,code={}, method={}, uri={}, detail={}", + e.getCode(), request.getMethod(), request.getRequestURI(), e.getMessage()); + return Result.badRequest(userMessage); + case 500: + userMessage = "微信登录失败,请重试"; + log.warn("[WeChatAPI] 微信登录内部错误,code={}, method={}, uri={}, detail={}", + e.getCode(), request.getMethod(), request.getRequestURI(), e.getMessage()); + return Result.error(userMessage); + case 502: + userMessage = "微信服务暂不可用,请稍后重试"; + log.warn("[WeChatAPI] 微信服务上游错误,code={}, method={}, uri={}, detail={}", + e.getCode(), request.getMethod(), request.getRequestURI(), e.getMessage()); + return Result.error(e.getCode(), userMessage); + default: + userMessage = "微信登录异常,请重试"; + log.warn("[WeChatAPI] 微信登录未知错误,code={}, method={}, uri={}, detail={}", + e.getCode(), request.getMethod(), request.getRequestURI(), e.getMessage()); + return Result.error(userMessage); + } + } + /** * 处理参数校验异常 */ diff --git a/backend-single/src/test/java/com/emotion/exception/GlobalExceptionHandlerWechatTest.java b/backend-single/src/test/java/com/emotion/exception/GlobalExceptionHandlerWechatTest.java new file mode 100644 index 0000000..815fc3a --- /dev/null +++ b/backend-single/src/test/java/com/emotion/exception/GlobalExceptionHandlerWechatTest.java @@ -0,0 +1,72 @@ +package com.emotion.exception; + +import com.emotion.common.Result; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletRequest; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * GlobalExceptionHandler 的 WechatApiException handler 单元测试 + * + * 验证: + * 1. 400 业务错误 → "微信授权失败,请重试" + * 2. 500 内部错误 → "微信登录失败,请重试" + * 3. 502 上游错误 → "微信服务暂不可用,请稍后重试" + */ +@ExtendWith(MockitoExtension.class) +class GlobalExceptionHandlerWechatTest { + + @InjectMocks + private GlobalExceptionHandler handler; + + private MockHttpServletRequest request; + + @BeforeEach + void setUp() { + request = new MockHttpServletRequest(); + request.setMethod("POST"); + request.setRequestURI("/api/wechat/login"); + } + + @Test + void handleWechatApiException_Code400_ReturnsBadRequestWithFriendlyMessage() { + WechatApiException ex = new WechatApiException(400, "errcode=40029, errmsg=invalid code (internal detail)", ""); + + Result result = handler.handleWechatApiException(ex, request); + + assertNotNull(result); + assertEquals("微信授权失败,请重试", result.getMessage()); + // Ensure internal detail is NOT exposed to frontend + assertFalse(result.getMessage().contains("errcode")); + assertFalse(result.getMessage().contains("40029")); + } + + @Test + void handleWechatApiException_Code500_ReturnsInternalErrorWithFriendlyMessage() { + WechatApiException ex = new WechatApiException(500, "JSON parse failure at line 1 (internal detail)", ""); + + Result result = handler.handleWechatApiException(ex, request); + + assertNotNull(result); + assertEquals("微信登录失败,请重试", result.getMessage()); + assertFalse(result.getMessage().contains("JSON")); + assertFalse(result.getMessage().contains("parse")); + } + + @Test + void handleWechatApiException_Code502_ReturnsBadGatewayWithFriendlyMessage() { + WechatApiException ex = new WechatApiException(502, "Connection refused: api.weixin.qq.com (internal detail)", ""); + + Result result = handler.handleWechatApiException(ex, request); + + assertNotNull(result); + assertEquals("微信服务暂不可用,请稍后重试", result.getMessage()); + assertFalse(result.getMessage().contains("Connection")); + assertFalse(result.getMessage().contains("refused")); + } +}