docs: 修复 spec 评审 Round 2 发现的 1 P0 编译错误 + 3 P2 一致性问题

P0 编译错误:
- MediaType.includesCompatibleWith() 不存在于 Spring Framework
  改为 MediaType.APPLICATION_JSON.isCompatibleWith(contentType)
  (避免实现阶段 mvn 编译直接阻塞)

P2 一致性:
- WechatResponseErrorHandler 类注释第 1 条错误('显式重写返回 true')
  改为正确的 '委托父类 4xx/5xx 判断'
- hasError() 方法内联注释自相矛盾
  改为正确的 '委托父类 DefaultResponseErrorHandler.hasError()'
- GlobalExceptionHandler.handleWechatApiException rawBody 日志未脱敏
  (Interceptor 已脱敏响应 body,但 rawBody 字段在异常对象中再次传递会绕过脱敏)
  新增 desensitizeForLog() 方法复用正则,覆盖 code 字段(authorization code 也需脱敏)
- 移除 @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  (与现有 BusinessException handler 不一致;HTTP 层统一返回 200,业务错误由 Result.code 表达)
This commit is contained in:
2026-06-03 07:42:59 +08:00
parent 13f773f3f0
commit 82c308b40a
@@ -247,7 +247,7 @@ import java.io.IOException;
* 微信 API 错误处理器
*
* 设计要点:
* 1. 显式重写 hasError() 返回 true,确保 handleError() 总是被调用
* 1. 显式重写 hasError() 委托父类判断(4xx/5xx → true),让 handleError() 在错误时被调用
* 2. handleError() 不抛 RestClientException,由业务层(WechatMiniProgramServiceImpl
* 根据 status code 判断如何处理
* 3. 不在此处读 body(避免流消费,由 LoggingInterceptor 统一处理日志)
@@ -257,7 +257,7 @@ public class WechatResponseErrorHandler extends DefaultResponseErrorHandler {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
// 显式重写:让 handleError 总是被调用(默认实现根据 status 4xx/5xx 判断)
// 委托父类 DefaultResponseErrorHandler.hasError()4xx/5xx 返回 true,其他返回 false
return super.hasError(response);
}
@@ -330,7 +330,8 @@ public WechatCodeSessionResponse code2Session(String code) {
}
// 第三步:content-type 防御
if (contentType == null || !contentType.includesCompatibleWith(MediaType.APPLICATION_JSON)) {
// 注意:Spring MediaType 没有 includesCompatibleWith 方法(编译错误),用 isCompatibleWith 替代
if (contentType == null || !MediaType.APPLICATION_JSON.isCompatibleWith(contentType)) {
log.error("[WeChatAPI] code2Session 非 JSON 响应: contentType={}, body={}", contentType, rawBody);
throw new WechatApiException(502,
"WeChat returned " + contentType + " (likely WAF interception)", rawBody);
@@ -363,16 +364,30 @@ public WechatCodeSessionResponse code2Session(String code) {
`GlobalExceptionHandler.java` 中新增:
```java
// 复用 Interceptor 的脱敏正则(避免重复定义)
private static final java.util.regex.Pattern SENSITIVE_FIELDS_FOR_LOG = java.util.regex.Pattern.compile(
"\"(session_key|openid|unionid|access_token|refresh_token|secret|appsecret|code)\"\\s*:\\s*\"[^\"]*\"");
private String desensitizeForLog(String body) {
if (body == null || body.isEmpty()) {
return body;
}
return SENSITIVE_FIELDS_FOR_LOG.matcher(body).replaceAll("\"$1\":\"***\"");
}
/**
* 处理微信 API 异常(脱敏后返回友好提示)
*
* 注意:不使用 @ResponseStatus,保持与现有 BusinessException handler 一致
* (HTTP 层返回 200,业务错误通过 Result.code 表达)
*/
@ExceptionHandler(WechatApiException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public Result<Void> handleWechatApiException(WechatApiException e, HttpServletRequest request) {
// 服务端日志:记录完整上下文(含 rawBody
// 服务端日志:rawBody 需脱敏(Interceptor 已对响应 body 脱敏,
// 但 rawBody 字段是再次传递到异常对象,必须再次脱敏以防日志明文泄露)
log.error("[WeChatAPI] 异常: {} {} - code={}, msg={}, rawBody={}",
request.getMethod(), request.getRequestURI(),
e.getCode(), e.getMessage(), e.getRawBody());
e.getCode(), e.getMessage(), desensitizeForLog(e.getRawBody()));
// 前端响应:脱敏友好提示(不暴露 rawBody / errcode / 内部技术细节)
String userMessage;