SpringBoot全局异常处理
慕歌 人气:0前言
在前一节的学习中,慕歌带大家使用了全局结果集返回,通过使用全局结果集配置,优雅的返回后端数据,为前端的数据拿取提供了非常好的参考。同时通过不同的状态码返回,我们能够清晰的了解报错的位置,排除错误。如果大家有需要,可以使用我提供的的同一结果集以及状态码,并且可以使用全局异常拦截,实现异常的标准返回。接下来,我们一起来了解如何使用全局异常处理吧!
异常工具
先定义一个合适 的异常处理类,在之后的异常都会以这种格式返回前端,前端根据我们的异常进行自己的返回,以一种优雅的方式呈现错误,优化用户体验。
异常结果集:
/** * 返回结果封装 */ @Data public class ResultVo { // 状态码 private int code; // 状态信息 private String msg; // 返回对象 private Object data; // 手动设置返回vo public ResultVo(int code, String msg) { this.code = code; this.msg = msg; } // 手动设置返回vo public ResultVo(int code, String msg, Object data) { this.code = code; this.msg = msg; this.data = data; } // 只返回状态码 public ResultVo(StatusCode statusCode) { this.code = statusCode.getCode(); this.msg = statusCode.getMsg(); } // 默认返回成功状态码,数据对象 public ResultVo(Object data) { this.code = ResultCode.SUCCESS.getCode(); this.msg = ResultCode.SUCCESS.getMsg(); this.data = data; } // 返回指定状态码,数据对象 public ResultVo(StatusCode statusCode, Object data) { this.code = statusCode.getCode(); this.msg = statusCode.getMsg(); this.data = data; } public ResultVo(StatusCode statusCode,String msg, Object data) { this.code = statusCode.getCode(); this.msg = msg; this.data = data; } }
异常状态码,通过返回的状态码,以及状态信息,能够高效反映错误,并且可以全局统一管理,方便快捷:
@Getter public enum ExceptionCode implements StatusCode { // 系统级别错误码 ERROR(-1, "操作异常"), NOT_LOGIN(102, "请先登录!"), NO_Role(102,"无权限"), NO_PERMISSION(102,"无权限"), OUT_TIME(102,"登录信息过期"), DISABLE_ACCOUNT(102,"帐号已被禁用!"), EMAIL_DISABLE_LOGIN(102,"该邮箱账号已被管理员禁止登录!"), IP_REPEAT_SUBMIT(102,"访问次数过多,请稍后重试"), ERROR_DEFAULT(105,"系统繁忙,请稍后重试"); //异常码 private int code; //异常信息 private String msg; //自定义方法 ExceptionCode(int code, String msg) { this.code = code; this.msg = msg; } }
当我们对异常通过以上工具类进行封装之后,所有异常将以一种固定的格式返回,不会导致错乱:
{3 items "code":105 "msg":"系统繁忙,请稍后重试" "data":NULL }
异常处理
在spring boot中需要使用异常拦截器,拦截全局的异常,不直接将异常返回,而是在我们进行处理之后,以一种清晰可读的方式返回。并且前端能够清晰解读我们的异常,呈现给用户。
//捕获校验器异常 @RestControllerAdvice public class ControllerExceptionAdvice { @ExceptionHandler({BindException.class}) public ResultVo ValidExceptionHandler(BindException e) { // 从异常对象中拿到ObjectError对象 ObjectError objectError = e.getBindingResult().getAllErrors().get(0); return new ResultVo(ResultCode.VALIDATE_ERROR.getCode(),objectError.getDefaultMessage()); } }
对特定异常进行拦截,并包装异常:
/** * 对返回结果进行包装 */ @RestControllerAdvice(basePackages = {"channel.cert"}) public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { // response是ResultVo类型,或者注释了NotControllerResponseAdvice都不进行包装 return !methodParameter.getParameterType().isAssignableFrom(ResultVo.class); } @Override public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) { // String类型不能直接包装 if (returnType.getGenericParameterType().equals(String.class)) { ObjectMapper objectMapper = new ObjectMapper(); try { // 将数据包装在ResultVo里后转换为json串进行返回 return objectMapper.writeValueAsString(new ResultVo(data)); } catch (JsonProcessingException e) { throw new APIException(ResultCode.RESPONSE_PACK_ERROR, e.getMessage()); } } // 否则直接包装成ResultVo返回 return new ResultVo(data); } }
异常捕捉
自定义异常:
@Getter public class APIException extends RuntimeException { private int code; private String msg; //自定义异枚举 public APIException(StatusCode statusCode){ super(statusCode.getMsg()); this.code = statusCode.getCode(); this.msg = statusCode.getMsg(); } // 手动设置异常 public APIException(StatusCode statusCode, String message) { // message用于用户设置抛出错误详情,例如:当前价格-5,小于0 super(message); // 状态码 this.code = statusCode.getCode(); // 状态码配套的msg this.msg = statusCode.getMsg(); } // 默认异常使用APP_ERROR状态码 public APIException(String errorMsg) { super(errorMsg); this.code = ExceptionCode.ERROR_DEFAULT.getCode(); this.msg = ExceptionCode.ERROR_DEFAULT.getMsg(); } //自定义参数 错误码 错误信息 public APIException(int errorCode, String errorMsg) { super(errorMsg); this.code = errorCode; this.msg = ExceptionCode.ERROR_DEFAULT.getMsg(); } //自定义参数 错误码 错误信息 异常 public APIException(int errorCode, String errorMsg, Throwable cause) { super(errorMsg); this.code = errorCode; this.msg = errorMsg; } }
对自定义异常进行捕获,通过定义好的异常的结果集返回。
/** * 全局异常处理 */ @Slf4j @RestControllerAdvice public class GlobalExceptionAdvice { // Assert业务异常 @ExceptionHandler(IllegalArgumentException.class) public ResultVo AssertExceptionHandler(IllegalArgumentException ex) { log.error( " msg : " + ex.getMessage(), ex); if(StringUtils.isBlank(ex.getLocalizedMessage())){ return new ResultVo(ExceptionCode.ERROR_DEFAULT); } return new ResultVo(ex.getMessage()); } // 登录失效异常 @ExceptionHandler(SaTokenException.class) public ResultVo LoginOutExceptionHandler(SaTokenException ex) { log.error( " msg : " + ex.getMessage(), ex); return new ResultVo(ExceptionCode.OUT_TIME); } // 登录异常 @ExceptionHandler(NotLoginException.class) public ResultVo NotLoginExceptionHandler(NotLoginException ex) { log.error( " msg : " + ex.getMessage(), ex); return new ResultVo(ExceptionCode.NOT_LOGIN); } // 权限异常 @ExceptionHandler(NotPermissionException.class) public ResultVo NotPermissionExceptionHandler(NotPermissionException ex) { log.error( " msg : " + ex.getMessage(), ex); return new ResultVo(ExceptionCode.NO_PERMISSION); } //角色异常 @ExceptionHandler(NotRoleException.class) public ResultVo NotRoleExceptionHandler(NotRoleException ex) { log.error( " msg : " + ex.getMessage(), ex); return new ResultVo(ExceptionCode.NO_Role); } //处理自定义异常 @ExceptionHandler(APIException.class) public ResultVo APIExceptionHandler(APIException e) { log.error(e.getMessage(), e); return new ResultVo(e.getCode(), e.getMsg()); } //处理运行异常 @ExceptionHandler(RuntimeException.class) public ResultVo RuntimeExceptionHandler(RuntimeException e) { log.error(e.getMessage(), e); return new ResultVo(ExceptionCode.ERROR_DEFAULT); } }
通过以上自定义,我们就能在项目中,使用自定义异常,在我们认为可能的报错处插入,当发生错误时,我们更快定位是之前记录的错误点导致。
//查询数字证书 @Override public GroupUser searchCert(String certCode) { try { //查询证书编号 GroupUser user = queryCert(certCode); if(ObjectUtil.isNotNull(user)){ return user; } }catch (Exception e){ throw new APIException("区块链调用失败"+e); } return null; }
加载全部内容