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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user