SpringBoot全局异常处理(优缺点)以及统一返回对象、错误码按业务模块设计示例,序列化后与数据库数据时间差8小时

项目工程中的全局异常处理原理

为什么要统一处理异常?

在开发过程中,不管是Dao、Servie、Controller,层都有可能发生异常,对于异常处理,通常是try-catch或者直接throw,这会让try-catch的代码在代码中任意出现,系统的代码耦合度高,代码不美观,统一异常处理可以美化代码。

错误处理

Spring Boot/error默认提供了一个映射,以合理的方式处理所有错误,并在 servlet 容器中注册为“全局”错误页面。对于机器客户端(如postman等),它将生成一个 JSON 响应,其中包含错误的详细信息、HTTP 状态和异常消息。对于浏览器客户端,有一个“whitelabel”错误视图,它以 HTML 格式呈现相同的数据(要自定义它,只需添加一个View解析为“错误”的数据)。要完全替换默认行为,您可以实现 ErrorController并注册该类型的 bean 定义,或者简单地添加一个类型的 beanErrorAttributes以使用现有机制替换内容。

BasicErrorController可以用作自定义基类ErrorController。如果您想为新的内容类型添加处理程序(默认是text/html专门处理并为其他所有内容提供后备),这将特别有用。要做到这一点,只需扩展BasicErrorController并添加一个@RequestMapping具有produces属性的公共方法 ,然后创建一个新类型的 bean。

DispatchServlet中doService中会进行异常处理

@ExceptionHandler注解实现异常处理 参考下面的链接SpringBoot官方文档

定义@ControllerAdvice来自定义 JSON 文档以针对特定控制器和/或异常类型返回。

或者定义@RestControllerAdvice 

与controller层上的注解一一对应。

全局统一异常处理好处:

1、用户友好返回

2、统一处理,简化业务逻辑

3、异常标准化

SpringBoot官方文档

在当前控制器中进行异常处理

在controller类中加上如下方法,进行controller层所有异常拦截,String改为你的全局返回对象,并返回必要的提示信息。

    /**
     * 基础异常
     */
    @ExceptionHandler(Exception.class)
    public Sting baseException(Exception e) {
        return e.getMessage();
    }

统一异常处理缺点

与没有统一异常处理的代码发生异常比较起来,统一异常处理会吃掉异常信息,使得问题排查变得复杂。

全局异常统一处理

全局异常处理步骚
1:写一个类,需要加上RestControllerAdvice注解
2:写一个异常处理方法,方法上面需要加上@ExceptionHandler(value=Exception.class)这个注解,在该方法里面处理异常,value的异常类可以更明细一些,具体化

 

/**
 * 全局异常处理器
 *
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 基础异常
     */
    @ExceptionHandler(BaseException.class)
    public AjaxResult baseException(BaseException e) {
        return AjaxResult.error(e.getMessage());
    }

    /**
     * 业务异常
     */
    @ExceptionHandler(CustomException.class)
    public AjaxResult businessException(CustomException e) {
        if (StringUtils.isNull(e.getCode())) {
            return AjaxResult.error(e.getMessage());
        }
        return AjaxResult.error(e.getCode(), e.getMessage());
    }

    @ExceptionHandler(NoHandlerFoundException.class)
    public AjaxResult handlerNoFoundException(Exception e) {
        log.error(e.getMessage(), e);
        return AjaxResult.error(HttpStatus.NOT_FOUND, "路径不存在,请检查路径是否正确");
    }

    @ExceptionHandler(AccessDeniedException.class)
    public AjaxResult handleAuthorizationException(AccessDeniedException e) {
        log.error(e.getMessage());
        return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权");
    }

    @ExceptionHandler(AccountExpiredException.class)
    public AjaxResult handleAccountExpiredException(AccountExpiredException e) {
        log.error(e.getMessage(), e);
        return AjaxResult.error(e.getMessage());
    }

    @ExceptionHandler(UsernameNotFoundException.class)
    public AjaxResult handleUsernameNotFoundException(UsernameNotFoundException e) {
        log.error(e.getMessage(), e);
        return AjaxResult.error(e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public AjaxResult handleException(Exception e) {
        log.error(e.getMessage(), e);
        return AjaxResult.error(e.getMessage());
    }

    /**
     * 自定义验证异常
     */
    @ExceptionHandler(BindException.class)
    public AjaxResult validatedBindException(BindException e) {
        log.error(e.getMessage(), e);
        String message = e.getAllErrors().get(0).getDefaultMessage();
        return AjaxResult.error(message);
    }

    /**
     * 自定义验证异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public AjaxResult validExceptionHandler(MethodArgumentNotValidException e) {
        log.error(e.getMessage(), e);
        String message = e.getBindingResult().getFieldError().getDefaultMessage();
        return AjaxResult.error(message);
    }


}

如果统一异常处理中又出现了异常怎么办?BasicErrorController

  • 是一个Controller
  • 类上注解:@RequestMapping("${server.error.path:${error.path:/error}}")
  • 处理默认 /error 路径的请求;
  • 页面响应 new ModelAndView("error", model);
  • 容器中有组件 View->id是error;(响应默认错误页)
  • 容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。
@Controller
@RequestMapping({"${server.error.path:${error.path:/error}}"})
public class BasicErrorController extends AbstractErrorController {
    private final ErrorProperties errorProperties;

    public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
        this(errorAttributes, errorProperties, Collections.emptyList());
    }

    public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes, errorViewResolvers);
        Assert.notNull(errorProperties, "ErrorProperties must not be null");
        this.errorProperties = errorProperties;
    }

    public String getErrorPath() {
        return this.errorProperties.getPath();
    }

    /**
    * html形式的请求错误返回
    **/
    @RequestMapping(
        produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = this.getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        // 如果自定义了视图,使用自定义的,否则使用默认的
        ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
        return modelAndView != null ? modelAndView : new ModelAndView("error", model);
    }


    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        HttpStatus status = this.getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return new ResponseEntity(status);
        } else {
            Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));
            return new ResponseEntity(body, status);
        }
    }

    /**
    * 机器请求错误返回
    **/
    @ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
    public ResponseEntity<String> mediaTypeNotAcceptable(HttpServletRequest request) {
        HttpStatus status = this.getStatus(request);
        return ResponseEntity.status(status).build();
    }

    protected boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
        IncludeStacktrace include = this.getErrorProperties().getIncludeStacktrace();
        if (include == IncludeStacktrace.ALWAYS) {
            return true;
        } else {
            return include == IncludeStacktrace.ON_TRACE_PARAM ? this.getTraceParameter(request) : false;
        }
    }

    protected ErrorProperties getErrorProperties() {
        return this.errorProperties;
    }
}

BasicErrorController在何处注入到容器中的?

在ErrorMvcAutoConfiguration类中注入到容器中。

自定义错误处理(getErrorAttributes)

/**
 * 自定义错误处理controller
 */
public class MyErrorController extends BasicErrorController{
    public MyErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List<ErrorViewResolver> errorViewResolvers) {
        super(errorAttributes, errorProperties, errorViewResolvers);
    }
    /**
     * {
     x"timestamp": "2021-08-22 13:41:17",
     x"status": 500,
     x"error": "Internal Server Error",
     x"exception": "java.lang.IllegalArgumentException",
     "message": "编号不可为空",
     x"path": "/manager/products"
     + code
     + canRetry
     }
     */
    @Override
    protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
        Map<String, Object> attrs = super.getErrorAttributes(request, includeStackTrace);
        attrs.remove("timestamp");
        attrs.remove("status");
        attrs.remove("error");
        attrs.remove("exception");
        attrs.remove("path");
        String errorCode = (String) attrs.get("message");
        ErrorEnum errorEnum = ErrorEnum.getByCode(errorCode);
        attrs.put("message",errorEnum.getMessage());
        attrs.put("code",errorEnum.getCode());
        attrs.put("canRetry",errorEnum.isCanRetry());
        return attrs;
    }
}

相关配置 


/**
 * 错误处理相关配置
 */
@Configuration
public class ErrorConfiguration {
    @Bean
    public MyErrorController basicErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties,
                                                  ObjectProvider<List<ErrorViewResolver>> errorViewResolversProvider) {
        return new MyErrorController(errorAttributes, serverProperties.getError(),
                errorViewResolversProvider.getIfAvailable());
    }
}

基础业务异常模块

/**
 * 基础异常
 *
 */
public class BaseException extends RuntimeException {
    private static final long serialVersionUID = 1L;

    /**
     * 所属模块
     */
    private String module;

    /**
     * 错误码
     */
    private String code;

    /**
     * 错误码对应的参数
     */
    private Object[] args;

    /**
     * 错误消息
     */
    private String defaultMessage;

    public BaseException(String module, String code, Object[] args, String defaultMessage) {
        this.module = module;
        this.code = code;
        this.args = args;
        this.defaultMessage = defaultMessage;
    }

    public BaseException(String module, String code, Object[] args) {
        this(module, code, args, null);
    }

    public BaseException(String module, String defaultMessage) {
        this(module, null, null, defaultMessage);
    }

    public BaseException(String code, Object[] args) {
        this(null, code, args, null);
    }

    public BaseException(String defaultMessage) {
        this(null, null, null, defaultMessage);
    }

    @Override
    public String getMessage() {
        String message = null;
        if (!StringUtils.isEmpty(code)) {
            message = MessageUtils.message(code, args);
        }
        if (message == null) {
            message = defaultMessage;
        }
        return message;
    }

    public String getModule() {
        return module;
    }

    public String getCode() {
        return code;
    }

    public Object[] getArgs() {
        return args;
    }

    public String getDefaultMessage() {
        return defaultMessage;
    }
}

非Map的统一返回结果对象

@Data
@ToString
@Accessors(chain = true)
@ApiModel
public class BaseRet<T> implements Serializable {

    public static final int SUCCESS_CODE = 0;

    private static final long serialVersionUID = 4167538907607374141L;

    @ApiModelProperty(value = "响应码,0为成功,错误一般为-1,具体看接口约定")
    Integer code;

    @ApiModelProperty(value = "提示信息")
    String msg;
    @ApiModelProperty(value = "返回数据,具体看接口约定")
    T data;

    public BaseRet() {}

    public BaseRet( int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public BaseRet(int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static <T> BaseRet<T> createSuccessRet() {
        BaseRet<T> ret = new BaseRet<>();
        ret.setMsg("请求成功!");
        ret.setCode(SUCCESS_CODE);
        return ret;
    }

    public static <T> BaseRet<T> createSuccessRet(int code) {
        BaseRet<T> ret = new BaseRet<>();
        ret.setCode(code);
        return ret;
    }

    public static <T> BaseRet<T> createSuccessRet(T data) {
        BaseRet<T> ret = new BaseRet<>();
        ret.setMsg("请求成功!");
        ret.setCode(0);
        ret.setData(data);
        return ret;
    }

    public static <T> BaseRet<T> createSuccessRet(int code, String msg) {
        BaseRet<T> ret = new BaseRet<>();
        ret.setMsg(msg);
        ret.setCode(code);
        return ret;
    }

    public static <T> BaseRet<T> createFailureRet() {
        BaseRet<T> ret = new BaseRet<>();
        ret.setMsg("请求失败!");
        ret.setCode(-1);
        return ret;
    }

    public static <T> BaseRet<T> createFailureRet(int code, String msg) {
        BaseRet<T> ret = new BaseRet<>();
        ret.setMsg(msg);
        ret.setCode(code);
        return ret;
    }

    public static <T> BaseRet<T> createFailureRetPhoneBondWithData(T data) {
        BaseRet<T> ret = new BaseRet<>();
        ret.setMsg("该手机号已绑定其他账号");
        ret.setCode(-3);
        ret.setData(data);
        return ret;
    }


    public static <T> BaseRet<T> createFailureRet(String msg) {
        BaseRet<T> ret = new BaseRet<>();
        ret.setMsg(msg);
        ret.setCode(-1);
        return ret;
    }

    public static <T> BaseRet<T> createFailureRet(T data) {
        BaseRet<T> ret = new BaseRet<>();
        ret.setMsg("请求失败!");
        ret.setCode(-1);
        ret.setData(data);
        return ret;
    }

    public static <T> BaseRet<T> createMissParamFailureRet() {

        BaseRet <T> ret = new BaseRet<>();
        ret.setMsg("缺少必填参数!");
        ret.setCode(-2);
        return ret;
    }

    public static <T> BaseRet<T> createMissDataFailureRet() {

        BaseRet <T> ret = new BaseRet<>();
        ret.setMsg("数据异常丢失!");
        ret.setCode(-3);
        return ret;
    }

    public static <T> BaseRet<T> error(Integer code, String message) {
        BaseRet<T> response = new BaseRet<>();
        response.setCode(code);
        response.setMsg(message);
        return response;
    }

    /**
     * 创建失败,但是要返回参数
     */
    public static <T> BaseRet<T> createFailWithData(int code, String msg,T data) {
        BaseRet<T> ret = new BaseRet<>();
        ret.setMsg(msg);
        ret.setCode(code);
        ret.setData(data);
        return ret;
    }

    public static <T> BaseRet<T> createRecordNotFundRet() {
        BaseRet<T> ret = new BaseRet<>();
        ret.setMsg("记录不存在!");
        ret.setCode(-1000);
        return ret;
    }

    @JSONField(serialize=false)
    @JsonIgnore
    public boolean isSuccess() {
        return SUCCESS_CODE  == this.getCode() ;
    }


    @JSONField(serialize=false)
    @JsonIgnore
    public boolean isFail() {
        return !isSuccess();
    }

    public static BaseRet<Boolean> createRetByBoolean(Boolean succeed) {
        if (succeed) {
            BaseRet<Boolean> ret = createSuccessRet();
            ret.setData(true);
            return ret;
        } else {
            BaseRet<Boolean> ret = createFailureRet();
            ret.setData(false);
            return ret;
        }
    }
}

BaseController

public class BaseController { 
  /** 基于@ExceptionHandler异常处理 */
  @ExceptionHandler
  public String exp(HttpServletRequest request, Exception ex) { 
      
    request.setAttribute("ex", ex); 
      
    // 根据不同错误转向不同页面 
    if(ex instanceof BusinessException) { 
      return "error-business"; 
    }else if(ex instanceof ParameterException) { 
      return "error-parameter"; 
    } else { 
      return "error"; 
    } 
  } 
} 

统一请求返回对象

/**
 * 统一请求返回对象
 *
 */
public class AjaxResult extends HashMap<String, Object> {
    private static final long serialVersionUID = 1L;

    /**
     * 状态码
     */
    public static final String CODE_TAG = "code";

    /**
     * 返回内容
     */
    public static final String MSG_TAG = "msg";

    /**
     * 数据对象
     */
    public static final String DATA_TAG = "data";

    /**
     * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。
     */
    public AjaxResult() {
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg  返回内容
     */
    public AjaxResult(int code, String msg) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
    }

    /**
     * 初始化一个新创建的 AjaxResult 对象
     *
     * @param code 状态码
     * @param msg  返回内容
     * @param data 数据对象
     */
    public AjaxResult(int code, String msg, Object data) {
        super.put(CODE_TAG, code);
        super.put(MSG_TAG, msg);
        if (StringUtils.isNotNull(data)) {
            super.put(DATA_TAG, data);
        }
    }

    /**
     * 返回成功消息
     *
     * @return 成功消息
     */
    public static AjaxResult success() {
        return AjaxResult.success("操作成功");
    }

    /**
     * 返回成功数据
     *
     * @return 成功消息
     */
    public static AjaxResult success(Object data) {
        return AjaxResult.success("操作成功", data);
    }

    /**
     * 返回成功消息
     *
     * @param msg 返回内容
     * @return 成功消息
     */
    public static AjaxResult success(String msg) {
        return AjaxResult.success(msg, null);
    }

    /**
     * 返回成功消息
     *
     * @param msg  返回内容
     * @param data 数据对象
     * @return 成功消息
     */
    public static AjaxResult success(String msg, Object data) {
        return new AjaxResult(HttpStatus.SUCCESS, msg, data);
    }

    /**
     * 返回错误消息
     *
     * @return
     */
    public static AjaxResult error() {
        return AjaxResult.error("操作失败");
    }

    /**
     * 返回错误消息
     *
     * @param msg 返回内容
     * @return 警告消息
     */
    public static AjaxResult error(String msg) {
        return AjaxResult.error(msg, null);
    }

    /**
     * 返回错误消息
     *
     * @param msg  返回内容
     * @param data 数据对象
     * @return 警告消息
     */
    public static AjaxResult error(String msg, Object data) {
        return new AjaxResult(HttpStatus.ERROR, msg, data);
    }

    /**
     * 返回错误消息
     *
     * @param code 状态码
     * @param msg  返回内容
     * @return 警告消息
     */
    public static AjaxResult error(int code, String msg) {
        return new AjaxResult(code, msg, null);
    }
}

错误码枚举(按模块划分)

/**
 * 按模块定义公共错误码
 * 
 * 
 */
public enum CommonError{

    /**
     *  获取错误码
     *  @return  code
     */
    int getCode();

    /**
     *  获取错误消息
     *  @return  message
     */
    String getMessage();

    /**
     *  转换为消息
     *  @param code
     *  @param message
     *  @return  string
     */
    String toString(int code, String message) {
        return code + ":" + message;
    }

    /**
     * 按模块定义错误码
     */
    SUCCESS(0, "成功"),
    UNKNOWN_ERROR(-1, "未定义异常"),
    TOKEN_ERROR(1001, "获取token失败"),
    IDENTIFY_INVALID(3001, "密码错误"),

    CABINET_ERROR(4000, "商家账号与绑定取餐柜不匹配"),
    GET_FOURS_INVALID(4001, "获取门店信息失败"),
    GET_FOURS_EMPTY(4002, "商家查询门店信息为空"),
    GET_FOURS_OFFLINE(4003, "门店已下线"),


    // 商业云模块
    ORDER_CANCEL(6001, "订单已取消"),
    ORDER_INVALID(6002, "订单号或是取餐码错误"),
    ORDER_UNPAY(6004, "订单未支付"),
    ORDER_HAS_FINISHED(6003, "该订单已完成"),
    ORDER_ERROR(6005, "查询商业云订单信息失败"),
    STORE_ERROR(6006, "商业云订单号与此商家不匹配"),
    ORDER_NOT_EXSIST(6007, "订单不存在"),
    ORDER_IS_EXSIST(6008, "该订单已存餐"),
    ORDER_EXTACTED(6010, "订单已取餐"),
    ORDER_STATE_ERROR(6011, "订单状态错误"),
    ORDER_IS_CANCEL(6012, "该订单已取消"),
    


    // 取餐业务模块
    SAVE_ORDER_FAILED (8000, "存餐失败"),
    MEALCODE_ERROR(8001, "取餐码不正确,请确认后重新输入"),
    MEALCODE_CACELED(8002, "该订单已作废"),
    EXTRACTCODE_CACELED(8003, "该验证码已作废或是订单已作废"),
    MEAL_ERROR(8004, "取餐错误"),

    // 设备业务模块10
    IOTSERVER_ERROR(10004, "查询设备属性失败"),
    IOTSERVER_INVALID(10005, "查询设备失败"),
    BIND_ERROR(10006, "该设备没有绑定商家"),

    // **相关错误码 业务模块11


    NON_DISH_ERROR(110001, "无绑定餐盘信息"),
    MERCHANT_DISH_ERROR(110002, "餐盘校验错误"),
    BIND_DISH_ERROR(110006, "餐盘已被其他用户绑定"),

    /**
     * 其他
     */

    FEIGN_ERROR(500, "内部服务调用错误");

    private Integer code;
    private String message;


    CommonError(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    @Override
    public int getCode() {
        return this.code;
    }

    @Override
    public String getMessage() {
        return this.message;
    }

    @Override
    public String toString() {
        return toString(getCode(), getMessage());
    }
}

配置响应时间格式

spring:
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GM+8
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值