feat(exception): GlobalExceptionHandler 新增 WechatApiException handler

解决问题:
- 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 保持一致
This commit is contained in:
2026-06-03 23:19:21 +08:00
parent 76d4d7465c
commit 2413b2bf58
2 changed files with 111 additions and 0 deletions
@@ -72,6 +72,45 @@ public class GlobalExceptionHandler {
}
}
/**
* 处理微信 API 异常(业务错误 400 / 内部错误 500 / 上游错误 502)
*
* <p>根据异常内部 code 映射到不同友好提示(不直接暴露原始异常消息给前端):
* <ul>
* <li>400 - 微信业务错误(如 errcode 40029 invalid code):"微信授权失败,请重试"</li>
* <li>500 - 内部 JSON 解析失败:"微信登录失败,请重试"</li>
* <li>502 - 网络异常 / 非 2xx / Content-Type 不是 JSON"微信服务暂不可用,请稍后重试"</li>
* </ul>
*
* <p>详细异常消息仅记录到日志(含 [WeChatAPI] 前缀),前端仅看到友好提示。
*/
@ExceptionHandler(WechatApiException.class)
public Result<Void> 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);
}
}
/**
* 处理参数校验异常
*/
@@ -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"));
}
}