处理 SpringMVC 全局处理

This commit is contained in:
YunaiV
2020-07-16 19:39:49 +08:00
parent 02dda60e60
commit 3b5199b60b
25 changed files with 360 additions and 91 deletions

View File

@@ -0,0 +1,51 @@
package cn.iocoder.common.framework.enums;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
/**
* 全局错误码枚举
* 1-999 系统异常编码保留
*
* 一般情况下,{@link GlobalErrorCodeEnum#getCode()} ()} 使用 HTTP 响应状态码 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
* 虽然说HTTP 响应状态码作为业务使用表达能力偏弱,但是使用在系统层面还是非常不错的
* 比较特殊的是,因为之前一直使用 0 作为成功,就不使用 200 啦。
*/
public enum GlobalErrorCodeEnum implements ServiceExceptionUtil.Enumerable<GlobalErrorCodeEnum> {
SUCCESS(0, "成功"),
// ========== 客户端错误段 ==========
BAD_REQUEST(400, "请求参数不正确"),
UNAUTHORIZED(401, "账号未登录"),
FORBIDDEN(403, "没有该操作权限"),
NOT_FOUND(404, "请求未找到"),
METHOD_NOT_ALLOWED(405, "请求方法不正确"),
// ========== 服务端错误段 ==========
INTERNAL_SERVER_ERROR(500, "系统异常"),
;
private final int code;
private final String message;
GlobalErrorCodeEnum(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
@Override
public int getGroup() {
return 0;
}
}

View File

@@ -1,32 +0,0 @@
package cn.iocoder.common.framework.enums;
/**
* 错误码枚举类
*
* 系统级异常,使用 2-001-000-000 段
*/
public enum SysErrorCodeEnum {
SYS_ERROR(2001001000, "服务端发生异常"),
MISSING_REQUEST_PARAM_ERROR(2001001001, "参数缺失"),
VALIDATION_REQUEST_PARAM_ERROR(2001001002, "参数校验不正确")
;
private final int code;
private final String message;
SysErrorCodeEnum(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
;
}

View File

@@ -1,5 +1,6 @@
package cn.iocoder.common.framework.vo;
import cn.iocoder.common.framework.enums.GlobalErrorCodeEnum;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.util.Assert;
@@ -13,7 +14,7 @@ import java.io.Serializable;
*/
public final class CommonResult<T> implements Serializable {
private static final Integer CODE_SUCCESS = 0;
private static final Integer CODE_SUCCESS = GlobalErrorCodeEnum.SUCCESS.getCode();
/**
* 错误码

View File

@@ -1,5 +1,6 @@
package cn.iocoder.mall.security.admin.core.interceptor;
import cn.iocoder.common.framework.enums.GlobalErrorCodeEnum;
import cn.iocoder.common.framework.enums.UserTypeEnum;
import cn.iocoder.common.framework.util.CollectionUtils;
import cn.iocoder.common.framework.util.HttpUtil;
@@ -7,7 +8,6 @@ import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.security.admin.core.context.AdminSecurityContext;
import cn.iocoder.mall.security.admin.core.context.AdminSecurityContextHolder;
import cn.iocoder.mall.systemservice.enums.SystemErrorCodeEnum;
import cn.iocoder.mall.systemservice.rpc.oauth.OAuth2Rpc;
import cn.iocoder.mall.systemservice.rpc.oauth.vo.OAuth2AccessTokenVO;
import cn.iocoder.mall.systemservice.rpc.permission.PermissionRpc;
@@ -21,7 +21,6 @@ import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import static cn.iocoder.mall.systemservice.enums.SystemErrorCodeEnum.OAUTH_USER_TYPE_ERROR;
@@ -69,7 +68,7 @@ public class AdminSecurityInterceptor extends HandlerInterceptorAdapter {
private void checkAuthentication(HandlerMethod handlerMethod, Integer adminId) {
boolean requiresAuthenticate = !handlerMethod.hasMethodAnnotation(RequiresNone.class); // 对于 ADMIN 来说,默认需登录
if (requiresAuthenticate && adminId == null) {
throw ServiceExceptionUtil.exception(SystemErrorCodeEnum.OAUTH2_NOT_AUTHENTICATION);
throw ServiceExceptionUtil.exception(GlobalErrorCodeEnum.UNAUTHORIZED);
}
}

View File

@@ -1,12 +1,12 @@
package cn.iocoder.mall.security.user.core.interceptor;
import cn.iocoder.common.framework.enums.GlobalErrorCodeEnum;
import cn.iocoder.common.framework.enums.UserTypeEnum;
import cn.iocoder.common.framework.util.HttpUtil;
import cn.iocoder.common.framework.util.ServiceExceptionUtil;
import cn.iocoder.common.framework.vo.CommonResult;
import cn.iocoder.mall.security.user.core.context.UserSecurityContext;
import cn.iocoder.mall.security.user.core.context.UserSecurityContextHolder;
import cn.iocoder.mall.systemservice.enums.SystemErrorCodeEnum;
import cn.iocoder.mall.systemservice.rpc.oauth.OAuth2Rpc;
import cn.iocoder.mall.systemservice.rpc.oauth.vo.OAuth2AccessTokenVO;
import cn.iocoder.mall.web.core.util.CommonWebUtil;
@@ -64,7 +64,7 @@ public class UserSecurityInterceptor extends HandlerInterceptorAdapter {
requiresAuthenticate = true;
}
if (requiresAuthenticate && userId == null) {
throw ServiceExceptionUtil.exception(SystemErrorCodeEnum.OAUTH2_NOT_AUTHENTICATION);
throw ServiceExceptionUtil.exception(GlobalErrorCodeEnum.UNAUTHORIZED);
}
}

View File

@@ -1,6 +1,6 @@
package cn.iocoder.mall.web.core.handler;
import cn.iocoder.common.framework.enums.SysErrorCodeEnum;
import cn.iocoder.common.framework.enums.GlobalErrorCodeEnum;
import cn.iocoder.common.framework.exception.ServiceException;
import cn.iocoder.common.framework.util.ExceptionUtil;
import cn.iocoder.common.framework.util.HttpUtil;
@@ -17,11 +17,18 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.util.Assert;
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 java.util.Date;
@@ -47,33 +54,107 @@ public class GlobalExceptionHandler {
@Reference(validation = "true", version = "${dubbo.consumer.SystemExceptionLogRpc.version}")
private SystemExceptionLogRpc systemExceptionLogRpc;
// 逻辑异常
/**
* 处理 SpringMVC 请求参数缺失
*
* 例如说,接口上设置了 @RequestParam("xx") 参数,结果并未传递 xx 参数
*/
@ExceptionHandler(value = MissingServletRequestParameterException.class)
public CommonResult missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
logger.warn("[missingServletRequestParameterExceptionHandler]", ex);
return CommonResult.error(GlobalErrorCodeEnum.BAD_REQUEST.getCode(),
String.format("请求参数缺失:%s", ex.getParameterName()));
}
/**
* 处理 SpringMVC 请求参数类型错误
*
* 例如说,接口上设置了 @RequestParam("xx") 参数为 Integer结果传递 xx 参数类型为 String
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public CommonResult methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {
logger.warn("[missingServletRequestParameterExceptionHandler]", ex);
return CommonResult.error(GlobalErrorCodeEnum.BAD_REQUEST.getCode(),
String.format("请求参数类型错误:%s", ex.getMessage()));
}
/**
* 处理 SpringMVC 参数校验不正确
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public CommonResult methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex) {
logger.warn("[methodArgumentNotValidExceptionExceptionHandler]", ex);
FieldError fieldError = ex.getBindingResult().getFieldError();
assert fieldError != null; // 断言,避免告警
return CommonResult.error(GlobalErrorCodeEnum.BAD_REQUEST.getCode(),
String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
}
/**
* 处理 SpringMVC 参数绑定不正确,本质上也是通过 Validator 校验
*/
@ExceptionHandler(BindException.class)
public CommonResult bindExceptionHandler(BindException ex) {
logger.warn("[handleBindException]", ex);
FieldError fieldError = ex.getFieldError();
assert fieldError != null; // 断言,避免告警
return CommonResult.error(GlobalErrorCodeEnum.BAD_REQUEST.getCode(),
String.format("请求参数不正确:%s", fieldError.getDefaultMessage()));
}
/**
* 处理 Validator 校验不通过产生的异常
*/
@ExceptionHandler(value = ConstraintViolationException.class)
public CommonResult constraintViolationExceptionHandler(ConstraintViolationException ex) {
logger.warn("[constraintViolationExceptionHandler]", ex);
ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();
return CommonResult.error(GlobalErrorCodeEnum.BAD_REQUEST.getCode(),
String.format("请求参数不正确:%s", constraintViolation.getMessage()));
}
/**
* 处理 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) {
logger.warn("[noHandlerFoundExceptionHandler]", ex);
return CommonResult.error(GlobalErrorCodeEnum.NOT_FOUND.getCode(),
String.format("请求地址不存在:%s", ex.getRequestURL()));
}
/**
* 处理 SpringMVC 请求方法不正确
*
* 例如说A 接口的方法为 GET 方式,结果请求方法为 POST 方式,导致不匹配
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public CommonResult httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {
logger.warn("[httpRequestMethodNotSupportedExceptionHandler]", ex);
return CommonResult.error(GlobalErrorCodeEnum.METHOD_NOT_ALLOWED.getCode(),
String.format("请求方法不正确:%s", ex.getMessage()));
}
/**
* 处理业务异常 ServiceException
*
* 例如说,商品库存不足,用户手机号已存在。
*/
@ExceptionHandler(value = ServiceException.class)
public CommonResult serviceExceptionHandler(ServiceException ex) {
logger.debug("[serviceExceptionHandler]", ex);
return CommonResult.error(ex.getCode(), ex.getMessage());
}
// Spring MVC 参数不正确
@ExceptionHandler(value = MissingServletRequestParameterException.class)
public CommonResult missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {
logger.warn("[missingServletRequestParameterExceptionHandler]", ex);
return CommonResult.error(SysErrorCodeEnum.MISSING_REQUEST_PARAM_ERROR.getCode(), SysErrorCodeEnum.MISSING_REQUEST_PARAM_ERROR.getMessage() + ":" + ex.getMessage());
}
@ExceptionHandler(value = ConstraintViolationException.class)
public CommonResult constraintViolationExceptionHandler(ConstraintViolationException ex) {
logger.info("[constraintViolationExceptionHandler]", ex);
// TODO 芋艿,后续要想一个更好的方式。
// 拼接详细报错
StringBuilder detailMessage = new StringBuilder("\n\n详细错误如下");
ex.getConstraintViolations().forEach(constraintViolation -> detailMessage.append("\n").append(constraintViolation.getMessage()));
return CommonResult.error(SysErrorCodeEnum.VALIDATION_REQUEST_PARAM_ERROR.getCode(),
SysErrorCodeEnum.VALIDATION_REQUEST_PARAM_ERROR.getMessage() + detailMessage.toString());
}
/**
* 处理系统异常,兜底处理所有的一切
*/
@ExceptionHandler(value = Exception.class)
public CommonResult exceptionHandler(HttpServletRequest req, Exception e) {
public CommonResult exceptionHandler(HttpServletRequest req, Throwable e) {
logger.error("[exceptionHandler]", e);
// 插入异常日志
SystemExceptionLogCreateDTO exceptionLog = new SystemExceptionLogCreateDTO();
@@ -88,10 +169,10 @@ public class GlobalExceptionHandler {
logger.error("[exceptionHandler][插入访问日志({}) 发生异常({})", JSON.toJSONString(exceptionLog), ExceptionUtils.getRootCauseMessage(th));
}
// 返回 ERROR CommonResult
return CommonResult.error(SysErrorCodeEnum.SYS_ERROR.getCode(), SysErrorCodeEnum.SYS_ERROR.getMessage());
return CommonResult.error(GlobalErrorCodeEnum.INTERNAL_SERVER_ERROR.getCode(), GlobalErrorCodeEnum.INTERNAL_SERVER_ERROR.getMessage());
}
private void initExceptionLog(SystemExceptionLogCreateDTO exceptionLog, HttpServletRequest request, Exception e) {
private void initExceptionLog(SystemExceptionLogCreateDTO exceptionLog, HttpServletRequest request, Throwable e) {
// 设置账号编号
exceptionLog.setUserId(CommonWebUtil.getUserId(request));
exceptionLog.setUserType(CommonWebUtil.getUserType(request));