|
@@ -4,16 +4,20 @@ import cn.hutool.core.exceptions.ExceptionUtil;
|
|
|
import cn.hutool.core.map.MapUtil;
|
|
|
import cn.hutool.core.util.StrUtil;
|
|
|
import com.citu.framework.apilog.core.service.ApiErrorLogFrameworkService;
|
|
|
+import com.citu.framework.common.exception.ErrorCode;
|
|
|
import com.citu.framework.common.exception.ServiceException;
|
|
|
import com.citu.framework.common.pojo.CommonResult;
|
|
|
import com.citu.framework.common.util.collection.SetUtils;
|
|
|
import com.citu.framework.common.util.json.JsonUtils;
|
|
|
import com.citu.framework.common.util.monitor.TracerUtils;
|
|
|
import com.citu.framework.common.util.servlet.ServletUtils;
|
|
|
+import com.citu.framework.common.util.string.StrUtils;
|
|
|
import com.citu.framework.web.core.util.WebFrameworkUtils;
|
|
|
import com.citu.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;
|
|
|
import lombok.AllArgsConstructor;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.springframework.context.MessageSource;
|
|
|
+import org.springframework.context.i18n.LocaleContextHolder;
|
|
|
import org.springframework.security.access.AccessDeniedException;
|
|
|
import org.springframework.util.Assert;
|
|
|
import org.springframework.validation.BindException;
|
|
@@ -56,12 +60,17 @@ public class GlobalExceptionHandler {
|
|
|
|
|
|
private final ApiErrorLogFrameworkService apiErrorLogFrameworkService;
|
|
|
|
|
|
+ @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
|
|
|
+ private final boolean enableI18n;
|
|
|
+
|
|
|
+ private final MessageSource messageSource;
|
|
|
+
|
|
|
/**
|
|
|
* 处理所有异常,主要是提供给 Filter 使用
|
|
|
* 因为 Filter 不走 SpringMVC 的流程,但是我们又需要兜底处理异常,所以这里提供一个全量的异常处理过程,保持逻辑统一。
|
|
|
*
|
|
|
* @param request 请求
|
|
|
- * @param ex 异常
|
|
|
+ * @param ex 异常
|
|
|
* @return 通用返回
|
|
|
*/
|
|
|
public CommonResult<?> allExceptionHandler(HttpServletRequest request, Throwable ex) {
|
|
@@ -98,26 +107,56 @@ public class GlobalExceptionHandler {
|
|
|
return defaultExceptionHandler(request, ex);
|
|
|
}
|
|
|
|
|
|
+
|
|
|
/**
|
|
|
- * 处理 SpringMVC 请求参数缺失
|
|
|
+ * 公共返回结果
|
|
|
*
|
|
|
+ * @param code
|
|
|
+ * @param msg
|
|
|
+ * @return 公共结构
|
|
|
+ **/
|
|
|
+ private CommonResult<?> commonResult(Integer code, String msg) {
|
|
|
+ if (!enableI18n) {
|
|
|
+ // 国际化关闭
|
|
|
+ return CommonResult.error(code, msg);
|
|
|
+ }
|
|
|
+ // 国际化开启
|
|
|
+ String i18nMsg = messageSource.getMessage(StrUtils.formatNumberWithUnderscores(code, 10),
|
|
|
+ null, LocaleContextHolder.getLocale());
|
|
|
+ if (String.valueOf(code).equals(i18nMsg)) {
|
|
|
+ // 找不到国际化信息,则使用默认的错误提示
|
|
|
+ return CommonResult.error(code, msg);
|
|
|
+ } else {
|
|
|
+ return CommonResult.error(code, i18nMsg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private CommonResult<?> commonResult(ErrorCode errorCode) {
|
|
|
+ return commonResult(errorCode.getCode(), errorCode.getMsg());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理 SpringMVC 请求参数缺失
|
|
|
+ * <p>
|
|
|
* 例如说,接口上设置了 @RequestParam("xx") 参数,结果并未传递 xx 参数
|
|
|
*/
|
|
|
@ExceptionHandler(value = MissingServletRequestParameterException.class)
|
|
|
public CommonResult<?> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
|
|
|
log.warn("[missingServletRequestParameterExceptionHandler]", ex);
|
|
|
- return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数缺失:%s", ex.getParameterName()));
|
|
|
+ // return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数缺失:%s", ex.getParameterName()));
|
|
|
+ return commonResult(BAD_REQUEST.getCode(), ex.getParameterName());
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 处理 SpringMVC 请求参数类型错误
|
|
|
- *
|
|
|
+ * <p>
|
|
|
* 例如说,接口上设置了 @RequestParam("xx") 参数为 Integer,结果传递 xx 参数类型为 String
|
|
|
*/
|
|
|
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
|
|
public CommonResult<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
|
|
|
log.warn("[missingServletRequestParameterExceptionHandler]", ex);
|
|
|
- return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", ex.getMessage()));
|
|
|
+ // return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", ex.getMessage()));
|
|
|
+ return commonResult(BAD_REQUEST.getCode(), ex.getMessage());
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -128,7 +167,8 @@ public class GlobalExceptionHandler {
|
|
|
log.warn("[methodArgumentNotValidExceptionExceptionHandler]", ex);
|
|
|
FieldError fieldError = ex.getBindingResult().getFieldError();
|
|
|
assert fieldError != null; // 断言,避免告警
|
|
|
- return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
|
|
|
+ // return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
|
|
|
+ return commonResult(BAD_REQUEST.getCode(), fieldError.getDefaultMessage());
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -139,7 +179,8 @@ public class GlobalExceptionHandler {
|
|
|
log.warn("[handleBindException]", ex);
|
|
|
FieldError fieldError = ex.getFieldError();
|
|
|
assert fieldError != null; // 断言,避免告警
|
|
|
- return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
|
|
|
+ // return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
|
|
|
+ return commonResult(BAD_REQUEST.getCode(), fieldError.getDefaultMessage());
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -149,7 +190,8 @@ public class GlobalExceptionHandler {
|
|
|
public CommonResult<?> constraintViolationExceptionHandler(ConstraintViolationException ex) {
|
|
|
log.warn("[constraintViolationExceptionHandler]", ex);
|
|
|
ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
|
|
|
- return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", constraintViolation.getMessage()));
|
|
|
+ // return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", constraintViolation.getMessage()));
|
|
|
+ return commonResult(BAD_REQUEST.getCode(), constraintViolation.getMessage());
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -159,12 +201,12 @@ public class GlobalExceptionHandler {
|
|
|
public CommonResult<?> validationException(ValidationException ex) {
|
|
|
log.warn("[constraintViolationExceptionHandler]", ex);
|
|
|
// 无法拼接明细的错误信息,因为 Dubbo Consumer 抛出 ValidationException 异常时,是直接的字符串信息,且人类不可读
|
|
|
- return CommonResult.error(BAD_REQUEST);
|
|
|
+ return commonResult(BAD_REQUEST);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 处理 SpringMVC 请求地址不存在
|
|
|
- *
|
|
|
+ * <p>
|
|
|
* 注意,它需要设置如下两个配置项:
|
|
|
* 1. spring.mvc.throw-exception-if-no-handler-found 为 true
|
|
|
* 2. spring.mvc.static-path-pattern 为 /statics/**
|
|
@@ -172,35 +214,37 @@ public class GlobalExceptionHandler {
|
|
|
@ExceptionHandler(NoHandlerFoundException.class)
|
|
|
public CommonResult<?> noHandlerFoundExceptionHandler(NoHandlerFoundException ex) {
|
|
|
log.warn("[noHandlerFoundExceptionHandler]", ex);
|
|
|
- return CommonResult.error(NOT_FOUND.getCode(), String.format("请求地址不存在:%s", ex.getRequestURL()));
|
|
|
+ // return CommonResult.error(NOT_FOUND.getCode(), String.format("请求地址不存在:%s", ex.getRequestURL()));
|
|
|
+ return commonResult(NOT_FOUND.getCode(), ex.getRequestURL());
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 处理 SpringMVC 请求方法不正确
|
|
|
- *
|
|
|
+ * <p>
|
|
|
* 例如说,A 接口的方法为 GET 方式,结果请求方法为 POST 方式,导致不匹配
|
|
|
*/
|
|
|
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
|
|
public CommonResult<?> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {
|
|
|
log.warn("[httpRequestMethodNotSupportedExceptionHandler]", ex);
|
|
|
- return CommonResult.error(METHOD_NOT_ALLOWED.getCode(), String.format("请求方法不正确:%s", ex.getMessage()));
|
|
|
+ // return CommonResult.error(METHOD_NOT_ALLOWED.getCode(), String.format("请求方法不正确:%s", ex.getMessage()));
|
|
|
+ return commonResult(METHOD_NOT_ALLOWED.getCode(), ex.getMessage());
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 处理 Spring Security 权限不足的异常
|
|
|
- *
|
|
|
+ * <p>
|
|
|
* 来源是,使用 @PreAuthorize 注解,AOP 进行权限拦截
|
|
|
*/
|
|
|
@ExceptionHandler(value = AccessDeniedException.class)
|
|
|
public CommonResult<?> accessDeniedExceptionHandler(HttpServletRequest req, AccessDeniedException ex) {
|
|
|
log.warn("[accessDeniedExceptionHandler][userId({}) 无法访问 url({})]", WebFrameworkUtils.getLoginUserId(req),
|
|
|
req.getRequestURL(), ex);
|
|
|
- return CommonResult.error(FORBIDDEN);
|
|
|
+ return commonResult(FORBIDDEN);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 处理业务异常 ServiceException
|
|
|
- *
|
|
|
+ * <p>
|
|
|
* 例如说,商品库存不足,用户手机号已存在。
|
|
|
*/
|
|
|
@ExceptionHandler(value = ServiceException.class)
|
|
@@ -209,7 +253,7 @@ public class GlobalExceptionHandler {
|
|
|
// 不包含的时候,才进行打印,避免 ex 堆栈过多
|
|
|
log.info("[serviceExceptionHandler]", ex);
|
|
|
}
|
|
|
- return CommonResult.error(ex.getCode(), ex.getMessage());
|
|
|
+ return commonResult(ex.getCode(), ex.getMessage());
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -228,7 +272,7 @@ public class GlobalExceptionHandler {
|
|
|
// 插入异常日志
|
|
|
createExceptionLog(req, ex);
|
|
|
// 返回 ERROR CommonResult
|
|
|
- return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
|
|
|
+ return commonResult(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
|
|
|
}
|
|
|
|
|
|
private void createExceptionLog(HttpServletRequest req, Throwable e) {
|
|
@@ -240,7 +284,7 @@ public class GlobalExceptionHandler {
|
|
|
// 执行插入 errorLog
|
|
|
apiErrorLogFrameworkService.createApiErrorLog(errorLog);
|
|
|
} catch (Throwable th) {
|
|
|
- log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(), JsonUtils.toJsonString(errorLog), th);
|
|
|
+ log.error("[createExceptionLog][url({}) log({}) 发生异常]", req.getRequestURI(), JsonUtils.toJsonString(errorLog), th);
|
|
|
}
|
|
|
}
|
|
|
|