|
|
|
|
@ -0,0 +1,257 @@
|
|
|
|
|
package cn.iocoder.dashboard.framework.web.core.handler;
|
|
|
|
|
|
|
|
|
|
import cn.iocoder.dashboard.common.exception.GlobalException;
|
|
|
|
|
import cn.iocoder.dashboard.common.exception.ServiceException;
|
|
|
|
|
import cn.iocoder.dashboard.common.pojo.CommonResult;
|
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
|
|
|
import org.springframework.validation.BindException;
|
|
|
|
|
import org.springframework.validation.FieldError;
|
|
|
|
|
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
|
|
|
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
|
|
|
|
import org.springframework.web.bind.MissingServletRequestParameterException;
|
|
|
|
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
|
|
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|
|
|
|
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
|
|
|
|
import org.springframework.web.servlet.NoHandlerFoundException;
|
|
|
|
|
|
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
|
|
|
import javax.validation.ConstraintViolation;
|
|
|
|
|
import javax.validation.ConstraintViolationException;
|
|
|
|
|
import javax.validation.ValidationException;
|
|
|
|
|
|
|
|
|
|
import static cn.iocoder.dashboard.common.exception.enums.GlobalErrorCodeConstants.*;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 全局异常处理器,将 Exception 翻译成 CommonResult + 对应的异常编号
|
|
|
|
|
*/
|
|
|
|
|
@RestControllerAdvice
|
|
|
|
|
@Slf4j
|
|
|
|
|
public class GlobalExceptionHandler {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理所有异常,主要是提供给 Filter 使用
|
|
|
|
|
* 因为 Filter 不走 SpringMVC 的流程,但是我们又需要兜底处理异常,所以这里提供一个全量的异常处理过程,保持逻辑统一。
|
|
|
|
|
*
|
|
|
|
|
* @param request 请求
|
|
|
|
|
* @param ex 异常
|
|
|
|
|
* @return 通用返回
|
|
|
|
|
*/
|
|
|
|
|
public CommonResult<?> allExceptionHandler(HttpServletRequest request, Throwable ex) {
|
|
|
|
|
if (ex instanceof MissingServletRequestParameterException) {
|
|
|
|
|
return missingServletRequestParameterExceptionHandler((MissingServletRequestParameterException) ex);
|
|
|
|
|
}
|
|
|
|
|
if (ex instanceof MethodArgumentTypeMismatchException) {
|
|
|
|
|
return methodArgumentTypeMismatchExceptionHandler((MethodArgumentTypeMismatchException) ex);
|
|
|
|
|
}
|
|
|
|
|
if (ex instanceof MethodArgumentNotValidException) {
|
|
|
|
|
return methodArgumentNotValidExceptionExceptionHandler((MethodArgumentNotValidException) ex);
|
|
|
|
|
}
|
|
|
|
|
if (ex instanceof BindException) {
|
|
|
|
|
return bindExceptionHandler((BindException) ex);
|
|
|
|
|
}
|
|
|
|
|
if (ex instanceof ConstraintViolationException) {
|
|
|
|
|
return constraintViolationExceptionHandler((ConstraintViolationException) ex);
|
|
|
|
|
}
|
|
|
|
|
if (ex instanceof ValidationException) {
|
|
|
|
|
return validationException((ValidationException) ex);
|
|
|
|
|
}
|
|
|
|
|
if (ex instanceof NoHandlerFoundException) {
|
|
|
|
|
return noHandlerFoundExceptionHandler((NoHandlerFoundException) ex);
|
|
|
|
|
}
|
|
|
|
|
if (ex instanceof HttpRequestMethodNotSupportedException) {
|
|
|
|
|
return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
|
|
|
|
|
}
|
|
|
|
|
if (ex instanceof ServiceException) {
|
|
|
|
|
return serviceExceptionHandler((ServiceException) ex);
|
|
|
|
|
}
|
|
|
|
|
if (ex instanceof GlobalException) {
|
|
|
|
|
return globalExceptionHandler(request, (GlobalException) ex);
|
|
|
|
|
}
|
|
|
|
|
return defaultExceptionHandler(request, ex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理 SpringMVC 请求参数缺失
|
|
|
|
|
*
|
|
|
|
|
* 例如说,接口上设置了 @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()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理 SpringMVC 请求参数类型错误
|
|
|
|
|
*
|
|
|
|
|
* 例如说,接口上设置了 @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()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理 SpringMVC 参数校验不正确
|
|
|
|
|
*/
|
|
|
|
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
|
|
|
|
public CommonResult<?> methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex) {
|
|
|
|
|
log.warn("[methodArgumentNotValidExceptionExceptionHandler]", ex);
|
|
|
|
|
FieldError fieldError = ex.getBindingResult().getFieldError();
|
|
|
|
|
assert fieldError != null; // 断言,避免告警
|
|
|
|
|
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理 SpringMVC 参数绑定不正确,本质上也是通过 Validator 校验
|
|
|
|
|
*/
|
|
|
|
|
@ExceptionHandler(BindException.class)
|
|
|
|
|
public CommonResult<?> bindExceptionHandler(BindException ex) {
|
|
|
|
|
log.warn("[handleBindException]", ex);
|
|
|
|
|
FieldError fieldError = ex.getFieldError();
|
|
|
|
|
assert fieldError != null; // 断言,避免告警
|
|
|
|
|
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理 Validator 校验不通过产生的异常
|
|
|
|
|
*/
|
|
|
|
|
@ExceptionHandler(value = ConstraintViolationException.class)
|
|
|
|
|
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()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理 Dubbo Consumer 本地参数校验时,抛出的 ValidationException 异常
|
|
|
|
|
*/
|
|
|
|
|
@ExceptionHandler(value = ValidationException.class)
|
|
|
|
|
public CommonResult<?> validationException(ValidationException ex) {
|
|
|
|
|
log.warn("[constraintViolationExceptionHandler]", ex);
|
|
|
|
|
// 无法拼接明细的错误信息,因为 Dubbo Consumer 抛出 ValidationException 异常时,是直接的字符串信息,且人类不可读
|
|
|
|
|
return CommonResult.error(BAD_REQUEST.getCode(), "请求参数不正确");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理 SpringMVC 请求地址不存在
|
|
|
|
|
*
|
|
|
|
|
* 注意,它需要设置如下两个配置项:
|
|
|
|
|
* 1. spring.mvc.throw-exception-if-no-handler-found 为 true
|
|
|
|
|
* 2. spring.mvc.static-path-pattern 为 /statics/**
|
|
|
|
|
*/
|
|
|
|
|
@ExceptionHandler(NoHandlerFoundException.class)
|
|
|
|
|
public CommonResult<?> noHandlerFoundExceptionHandler(NoHandlerFoundException ex) {
|
|
|
|
|
log.warn("[noHandlerFoundExceptionHandler]", ex);
|
|
|
|
|
return CommonResult.error(NOT_FOUND.getCode(), String.format("请求地址不存在:%s", ex.getRequestURL()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理 SpringMVC 请求方法不正确
|
|
|
|
|
*
|
|
|
|
|
* 例如说,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()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理业务异常 ServiceException
|
|
|
|
|
*
|
|
|
|
|
* 例如说,商品库存不足,用户手机号已存在。
|
|
|
|
|
*/
|
|
|
|
|
@ExceptionHandler(value = ServiceException.class)
|
|
|
|
|
public CommonResult<?> serviceExceptionHandler(ServiceException ex) {
|
|
|
|
|
log.info("[serviceExceptionHandler]", ex);
|
|
|
|
|
return CommonResult.error(ex.getCode(), ex.getMessage());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理全局异常 ServiceException
|
|
|
|
|
*
|
|
|
|
|
* 例如说,Dubbo 请求超时,调用的 Dubbo 服务系统异常
|
|
|
|
|
*/
|
|
|
|
|
@ExceptionHandler(value = GlobalException.class)
|
|
|
|
|
public CommonResult<?> globalExceptionHandler(HttpServletRequest req, GlobalException ex) {
|
|
|
|
|
// 系统异常时,才打印异常日志
|
|
|
|
|
if (INTERNAL_SERVER_ERROR.getCode().equals(ex.getCode())) {
|
|
|
|
|
// 插入异常日志
|
|
|
|
|
this.createExceptionLog(req, ex);
|
|
|
|
|
// 普通全局异常,打印 info 日志即可
|
|
|
|
|
} else {
|
|
|
|
|
log.info("[globalExceptionHandler]", ex);
|
|
|
|
|
}
|
|
|
|
|
// 返回 ERROR CommonResult
|
|
|
|
|
return CommonResult.error(ex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 处理系统异常,兜底处理所有的一切
|
|
|
|
|
*/
|
|
|
|
|
@ExceptionHandler(value = Exception.class)
|
|
|
|
|
public CommonResult<?> defaultExceptionHandler(HttpServletRequest req, Throwable ex) {
|
|
|
|
|
log.error("[defaultExceptionHandler]", ex);
|
|
|
|
|
// 插入异常日志
|
|
|
|
|
this.createExceptionLog(req, ex);
|
|
|
|
|
// 返回 ERROR CommonResult
|
|
|
|
|
return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMessage());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO 芋艿:增加异常日志
|
|
|
|
|
public void createExceptionLog(HttpServletRequest req, Throwable e) {
|
|
|
|
|
// // 插入异常日志
|
|
|
|
|
// SystemExceptionLogCreateDTO exceptionLog = new SystemExceptionLogCreateDTO();
|
|
|
|
|
// try {
|
|
|
|
|
// // 增加异常计数 metrics TODO 暂时去掉
|
|
|
|
|
//// EXCEPTION_COUNTER.increment();
|
|
|
|
|
// // 初始化 exceptionLog
|
|
|
|
|
// initExceptionLog(exceptionLog, req, e);
|
|
|
|
|
// // 执行插入 exceptionLog
|
|
|
|
|
// createExceptionLog(exceptionLog);
|
|
|
|
|
// } catch (Throwable th) {
|
|
|
|
|
// log.error("[createExceptionLog][插入访问日志({}) 发生异常({})", JSON.toJSONString(exceptionLog), ExceptionUtils.getRootCauseMessage(th));
|
|
|
|
|
// }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// // TODO 优化点:后续可以增加事件
|
|
|
|
|
// @Async
|
|
|
|
|
// public void createExceptionLog(SystemExceptionLogCreateDTO exceptionLog) {
|
|
|
|
|
// try {
|
|
|
|
|
// systemExceptionLogRpc.createSystemExceptionLog(exceptionLog);
|
|
|
|
|
// } catch (Throwable th) {
|
|
|
|
|
// log.error("[addAccessLog][插入异常日志({}) 发生异常({})", JSON.toJSONString(exceptionLog), ExceptionUtils.getRootCauseMessage(th));
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//
|
|
|
|
|
// private void initExceptionLog(SystemExceptionLogCreateDTO exceptionLog, HttpServletRequest request, Throwable e) {
|
|
|
|
|
// // 设置账号编号
|
|
|
|
|
// exceptionLog.setUserId(CommonWebUtil.getUserId(request));
|
|
|
|
|
// exceptionLog.setUserType(CommonWebUtil.getUserType(request));
|
|
|
|
|
// // 设置异常字段
|
|
|
|
|
// exceptionLog.setExceptionName(e.getClass().getName());
|
|
|
|
|
// exceptionLog.setExceptionMessage(ExceptionUtil.getMessage(e));
|
|
|
|
|
// exceptionLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e));
|
|
|
|
|
// exceptionLog.setExceptionStackTrace(ExceptionUtil.getStackTrace(e));
|
|
|
|
|
// StackTraceElement[] stackTraceElements = e.getStackTrace();
|
|
|
|
|
// Assert.notEmpty(stackTraceElements, "异常 stackTraceElements 不能为空");
|
|
|
|
|
// StackTraceElement stackTraceElement = stackTraceElements[0];
|
|
|
|
|
// exceptionLog.setExceptionClassName(stackTraceElement.getClassName());
|
|
|
|
|
// exceptionLog.setExceptionFileName(stackTraceElement.getFileName());
|
|
|
|
|
// exceptionLog.setExceptionMethodName(stackTraceElement.getMethodName());
|
|
|
|
|
// exceptionLog.setExceptionLineNumber(stackTraceElement.getLineNumber());
|
|
|
|
|
// // 设置其它字段
|
|
|
|
|
// exceptionLog.setTraceId(MallUtils.getTraceId())
|
|
|
|
|
// .setApplicationName(applicationName)
|
|
|
|
|
// .setUri(request.getRequestURI()) // TODO 提升:如果想要优化,可以使用 Swagger 的 @ApiOperation 注解。
|
|
|
|
|
// .setQueryString(HttpUtil.buildQueryString(request))
|
|
|
|
|
// .setMethod(request.getMethod())
|
|
|
|
|
// .setUserAgent(HttpUtil.getUserAgent(request))
|
|
|
|
|
// .setIp(HttpUtil.getIp(request))
|
|
|
|
|
// .setExceptionTime(new Date());
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
}
|