全局异常工具类GlobalExceptionHandler

全局异常处理

以前我们在开发接口时,如果出现异常,为了给用户一个更友好的提示,例如:

@RestController
public class ExtController {
    @GetMapping("/add")
    public String add() {
        int a = 10 / 0;
        return "成功";
    }
}

如果不做任何处理请求add接口结果直接报错:

在这里插入图片描述

这种交互方式给用户的体验非常差,为了解决这个问题,我们通常会在接口中捕获异常:

@GetMapping("/add")
public String add() {
    String result = "成功";
    try {
        int a = 10 / 0;
    } catch (Exception e) {
        result = "数据异常";
    }
    return result;
}

接口改造后,出现异常时会提示:“数据异常”,对用户来说更友好。

如果只是一个接口还好,但是如果项目中有成百上千个接口,都要加上异常捕获代码吗?答案是否定的,这时全局异常处理就派上用场了:RestControllerAdvice

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(Exception.class)
    public String handleException(Exception e) {
        if (e instanceof ArithmeticException) {
            return "数据异常";
        }
        if (e instanceof Exception) {
            return "服务器内部异常";
        }
        retur null;
    }
}

只需在handleException方法中处理异常情况,业务接口中可以放心使用,不再需要捕获异常(有人统一处理了)

系统错误码设计思考

在进行系统开发时,后端与前端的交互中会有因为某些原因,造成后端无法处理前端请求,此时需要快速定位后端问题还是前端的问题。

解决办法

查看后端的日志

如果没有日志系统,需要ssh 到远端服务器,如果有多台服务器,还需要更进一步去定位报错的服务器。 比较的费时费力

根据错误码来进行判定

1、错误码的设定,分为正负之分。

  • 正的用于表示外围的错误原因(字段必填),在发生错误时,可以直接了当的告知外围原因,并根据原因来进行改正。
  • 负的用于表示系统本身的原因,如数据异常,但是这些原因不会告之外围,对外显示是一些模糊的原因如”网络开小差“,“系统异常,请稍后再试"之类。之后再技术去分析这些错误的真正原因。 相对直接查看日志, 至少是节省一半的时间

2、数字部分参考阿里巴巴 Java 开发手册提供的错误码设计
在这里插入图片描述

在这里插入图片描述

原来自己总是使用有全正的来定义错误,虽能也能解决上面的问题, 但是使用正负能更好的解决, 并除去一些记忆的困扰。

根据状态码设计异常规范

在这里插入图片描述
在这里插入图片描述

封装状态码 BaseExceptionCode

1、BaseExceptionCode

/**
 * <p>
 * 公共异常编码类
 * 错误码为字符串类型,共 5 位,分成两个部分:错误产生来源+四位数字编号。
 * </p>
 *
 * 错误产生来源分为 A/B/C,A 表示错误来源于用户,比如参数错误,用户安装版本过低,用户支付
 * 超时等问题;B 表示错误来源于当前系统,往往是业务逻辑出错,或程序健壮性差等问题;C 表示错误来源
 * 于第三方服务,比如 CDN 服务出错,消息投递超时等问题;四位数字编号从 0001 到 9999,大类之间的
 * 步长间距预留 100
 *
 */
public interface BaseExceptionCode {
    /**
     * 异常编码
     *
     * @return
     */
    String getCode();

    /**
     * 异常消息
     * @return
     */
    String getMsg();
}

2、AExceptionCode

/**
 * <p>
 *  A 表示错误来源于用户,比如参数错误,用户安装版本过低,用户支付,超时等问题;
 * </p>
 */
public enum AExceptionCode implements BaseExceptionCode {
    // ==================系统业务往外抛异常 start=======================//
    A0001("A0001","系统执行出错"),

    A0151("A0121","手机格式校验失败"),
    A0152("A0152","地址格式校验失败"),
    A0153("A0153","邮箱格式校验失败"),


    A0320("A0320","用户访问被拦截"),
    A0425("A0425","数量超出限制"),
    A0424("A0424","金额超出限制"),
    A0427("A0427","请求 JSON 解析失败"),
    A0430("A0430","用户输入内容非法"),
    ;

    private String code;
    private String msg;

    AExceptionCode(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    @Override
    public String getCode() {
        return code;
    }

    @Override
    public String getMsg() {
        return msg;
    }
}

3、BExceptionCode

/**
 * <p>
 * B 表示错误来源于当前系统,往往是业务逻辑出错,或程序健壮性差等问题;
 * </p>
 *
 */
public enum BExceptionCode implements BaseExceptionCode {
    // ==================系统业务内部异常 start=======================//
    SYSTEM_BUSY_SUCCESS("1000", "操作成功"),
    OPERATION_EX("1020", "操作异常"),
    SYSTEM_BUSY_ERROR("1020", "操作失败"),
    SYSTEM_TIMEOUT("1002", "系统维护中~请稍后再试~"),
    PARAM_EX("1003", "参数类型解析异常"),
    SQL_EX("1004", "运行SQL出现异常"),
    NULL_POINT_EX("1005", "空指针异常"),
    ILLEGALA_ARGUMENT_EX("1006", "无效参数异常"),
    MEDIA_TYPE_EX("1007", "请求类型异常"),
    LOAD_RESOURCES_ERROR("1008", "加载资源出错"),
    BASE_VALID_PARAM("1009", "统一验证参数异常"),
    // ==================系统业务内部异常 end=======================//

    ;

    private String code;
    private String msg;

    BExceptionCode(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    @Override
    public String getCode() {
        return code;
    }

    @Override
    public String getMsg() {
        return msg;
    }
}

4、CExceptionCode

/**
 * <p>
 * C 表示错误来源于第三方服务或中间件,比如 CDN 服务出错,消息投递超时, 数据库,缓存, 消息队列等问题
 * </p>
 */
public enum CExceptionCode implements BaseExceptionCode {
    C0001("C0001","调用第三方服务出错"),
    C0100("C0100","中间件服务出错"),
    C0113("C0113","接口不存在"),
    C0120("C0120","消息服务出错"),
    C0130("C0130","缓存服务出错"),
    C0140("C0140","配置服务出错"),
    C0150("C0150","网络资源服务出错"),

    C0200("C0200","第三方系统执行超时"),
    C0300("C0300","数据库服务出错"),
    C0311("C0311","表不存在"),
    C0312("C0312","列不存在"),
    C0321("C0321","多表关联中存在多个相同名称的列"),
    C0331("C0331","数据库死锁"),
    C0341("C0341","主键冲突"),
    C0400("C0400","第三方容灾系统被触发"),
    C0500("C0500","通知服务出错"),
    C0501("C0501","短信提醒服务失败"),
    C0502("C0502","语音提醒服务失败"),
    C0503("C0503","邮件提醒服务失败"),
    ;

    private String code;
    private String msg;

    CExceptionCode(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    @Override
    public String getCode() {
        return code;
    }

    @Override
    public String getMsg() {
        return msg;
    }
}

封装自定义异常类 BaseException

1、BaseException

/**
 * <p>
 * 异常接口类
 * </p>
 *
 */
public interface BaseException {

    /**
     * 统一参数验证异常码
     */
    int BASE_VALID_PARAM = -9;

    /**
     * 返回异常信息
     *
     * @return
     */
    String getMessage();

    /**
     * 返回异常编码
     *
     * @return
     */
    String getCode();

}

2、BaseUncheckedException

/**
 * <p>
 * 非运行期异常基类,所有自定义非运行时异常继承该类
 * </p>
 */
public class BaseUncheckedException extends RuntimeException implements BaseException {

    private static final long serialVersionUID = -778887391066124051L;

    /**
     * 异常信息
     */
    protected String message;

    /**
     * 具体异常码
     */
    protected String code;

    public BaseUncheckedException(String code, String message) {
        super(message);
        this.code = code;
        this.message = message;
    }

    @Override
    public String getMessage() {
        return message;
    }
    @Override
    public String getCode() {
        return code;
    }
}


3、AException

/**
 * <p>
 * 业务异常 用于在处理业务逻辑时,进行抛出A状态码异常
 * </p>
 */
public class AException extends BaseUncheckedException {

    private static final long serialVersionUID = -3843907364558373811L;

    public AException(BaseExceptionCode ex) {
        this(ex.getCode(), ex.getMsg());
    }

    private AException(String code, String message) {
        super(code, message);
    }
    /**
     * 实例化异常
     * @param ex
     * @return
     */
    public static AException fail(BaseExceptionCode ex) {
        return new AException(ex.getCode(), ex.getMsg());
    }

    @Override
    public String toString() {
        return "AException [message=" + message + ", code=" + code + "]";
    }

}

4、BException

/**
 * <p>
 * 业务异常 用于在处理业务逻辑时,进行抛出B状态码异常
 * </p>
 */
public class BException extends BaseUncheckedException {

    private static final long serialVersionUID = -3843907364558373812L;

    public BException(BaseExceptionCode ex) {
        this(ex.getCode(), ex.getMsg());
    }

    private BException(String code, String message) {
        super(code, message);
    }
    /**
     * 实例化异常
     * @param ex
     * @return
     */
    public static BException fail(BaseExceptionCode ex) {
        return new BException(ex.getCode(), ex.getMsg());
    }

    @Override
    public String toString() {
        return "BException [message=" + message + ", code=" + code + "]";
    }

}

5、CException

/**
 * <p>
 * 业务异常 用于在处理业务逻辑时,进行抛出C状态码异常
 * </p>
 */
public class CException extends BaseUncheckedException {

    private static final long serialVersionUID = -3843907364558373813L;

    public CException(BaseExceptionCode ex) {
        this(ex.getCode(), ex.getMsg());
    }

    private CException(String code, String message) {
        super(code, message);
    }
    /**
     * 实例化异常
     * @param ex
     * @return
     */
    public static CException fail(BaseExceptionCode ex) {
        return new CException(ex.getCode(), ex.getMsg());
    }

    @Override
    public String toString() {
        return "CException [message=" + message + ", code=" + code + "]";
    }

}

封装全局异常处理类 GlobalExceptionHandler

1、ReturnData

/**
 * <p>
 * 封装通用返回值
 * </p>
 */
public class ReturnData<T> implements Serializable {
    private static final long serialVersionUID = 1L;

    private String code;

    private String reason;

    private T data;

    public ReturnData() {
    }

    public ReturnData(String code, String reason) {
        this.code = code;
        this.reason = reason;
    }

    public ReturnData(String code, String reason, T data) {
        this.code = code;
        this.reason = reason;
        this.data = data;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getReason() {
        return reason;
    }

    public void setReason(String reason) {
        this.reason = reason;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}


2、ResultUtil

/**
 * <p>
 * 通用返回值工具类
 * </p>
 *
 */
public class ResultUtil {

    public static <T> ReturnData<T> ok() {

        return result(BExceptionCode.SYSTEM_BUSY_SUCCESS,null);
    }

    public static <T> ReturnData<T> ok(T data) {
        return result(BExceptionCode.SYSTEM_BUSY_SUCCESS,data);
    }

    public static <T> ReturnData<T> ok(BaseExceptionCode resultCode) {
        return result(resultCode,null);
    }

    public static <T> ReturnData<T> ok(BaseExceptionCode resultCode, T data) {
        return result(resultCode,data);
    }

    public static <T> ReturnData<T> fail() {
        return result(BExceptionCode.SYSTEM_BUSY_ERROR,null);
    }

    public static <T> ReturnData<T> fail(BaseExceptionCode resultCode) {
        return result(BExceptionCode.SYSTEM_BUSY_ERROR,null);
    }


    public static <T> ReturnData<T> fail(T data) {
        return result(BExceptionCode.SYSTEM_BUSY_ERROR,data);
    }

    public static <T> ReturnData<T> fail(BaseExceptionCode resultCode, T data) {
        return result(resultCode,data);
    }

    private static <T>  ReturnData<T> result(BaseExceptionCode resultCode, T data) {
        return new ReturnData<>(resultCode.getCode(), resultCode.getMsg(), data);
    }

    public static <T> ReturnData<T> returnError(String code, String message) {
        return  new ReturnData<T>(code, message);
    }
}

3、GlobalExceptionHandler

/**
 * <p>
 * 全局异常处理类
 * </p>
 */
@RestControllerAdvice(annotations = {RestController.class, Controller.class})
public class GlobalExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);


    @ExceptionHandler(AException.class)
    public ReturnData<String> aException(AException ex) {
        log.warn("AException:", ex);
        return ResultUtil.returnError(ex.getCode(), ex.getMessage());
    }

    @ExceptionHandler(BException.class)
    public ReturnData<String> bException(BException ex) {
        log.warn("BException:", ex);
        return ResultUtil.returnError(ex.getCode(), ex.getMessage());
    }

    @ExceptionHandler(CException.class)
    public ReturnData<String> cException(CException ex) {
        log.warn("CException:", ex);
        return ResultUtil.returnError(ex.getCode(), ex.getMessage());
    }

    /**
     * 其他异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(Exception.class)
    public ReturnData<String> otherExceptionHandler(Exception ex) {
        log.warn("Exception:", ex);
        if (ex.getCause() instanceof AException) {
            return this.aException((AException) ex.getCause());
        }
        if (ex.getCause() instanceof BException) {
            return this.bException((BException) ex.getCause());
        }
        if (ex.getCause() instanceof CException) {
            return this.cException((CException) ex.getCause());
        }
        return ResultUtil.returnError(BExceptionCode.SYSTEM_BUSY_ERROR.getCode(), ex.getMessage());
    }
}

实现二、GlobalExceptionHandler

自定义异常类 APIException

// 自定义异常
@Getter
public class APIException extends  RuntimeException{
    private int code;
    private String message;

    public  APIException(String message){
        this(1001,message);
    }

    public APIException(int code,String message){
        super(message);
        this.code = code;
        this.message = message;
    }
}

全局异常处理类 GlobalExceptionHandler

/**
 * 全局异常处理类
 *
 */
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

     @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResultVO<Map<String, Object>> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
        // 从异常对象中拿到ObjectError对象
        //ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
        // 然后提取错误提示信息进行返回

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

        // 获取所有异常
        List<String> errors = e.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(DefaultMessageSourceResolvable::getDefaultMessage)
                .collect(Collectors.toList());
        body.put("errors", errors);

        return ResultVO.fail(body);
    }

    @ExceptionHandler(APIException.class)
    public ResultVO<String> APIExceptionHandler(APIException e){
        // 注意哦,这里返回类型是自定义响应体

        return ResultVO.fail(e.getMessage());
    }
}

统一响应枚举ResultCode

@Getter
@AllArgsConstructor
public enum ResultCode {
    USERNAME_PASSWORD_ERROR(5001,"用户名密码错误"),
    SUCCESS(1000, "操作成功"),
    FAILED(1001, "响应失败"),
    VALIDATE_FAILED(1002, "参数校验失败"),
    ERROR(5000, "未知错误");

    private Integer code;
    private String message;
    
}

统一响应结果ResultVO

@Data
public class ResultVO<T> {
    private static final ResultCode SUCCESS = ResultCode.SUCCESS;
    private static final ResultCode FAIL = ResultCode.FAILED;
    
    private Integer code;

    private String message;

    private T  data;
    
    public static <T> ResultVO<T> ok() {

        return result(SUCCESS,null);
    }

    public static <T> ResultVO<T> ok(T data) {
        return result(SUCCESS,data);
    }

    public static <T> ResultVO<T> ok(ResultCode resultCode) {
        return result(resultCode,null);
    }
    
    public static <T> ResultVO<T> ok(ResultCode resultCode, T data) {
        return result(resultCode,data);
    }

    public static <T> ResultVO<T> fail() {
        return result(FAIL,null);
    }

    public static <T> ResultVO<T> fail(ResultCode resultCode) {
        return result(FAIL,null);
    }


    public static <T> ResultVO<T> fail(T data) {
        return result(FAIL,data);
    }

    public static <T> ResultVO<T> fail(ResultCode resultCode, T data) {
        return result(resultCode,data);
    }
    
    private static <T>  ResultVO<T> result(ResultCode resultCode, T data) {
        ResultVO<T> resultVO = new ResultVO<>();
        resultVO.setCode(resultCode.getCode());
        resultVO.setMessage(resultCode.getMessage());
        resultVO.setData(data);
        return resultVO;
    }
}

实现三、 DefaultGlobalExceptionHandler

封装复用

包结构
在这里插入图片描述

pom

<dependency>
    <groupId>com.spring4all</groupId>
    <artifactId>swagger-spring-boot-starter</artifactId>
    <version>1.9.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.17</version>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>
 <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
   <groupId>com.alibaba.fastjson2</groupId>
   <artifactId>fastjson2</artifactId>
   <version>2.0.25</version>
</dependency>
<dependency>
   <groupId>com.google.guava</groupId>
   <artifactId>guava</artifactId>
   <version>32.0.0-jre</version>
</dependency>

封装状态码

BaseExceptionCode

/**
 * @ClassName: BaseExceptionCode
 * @Description: 公共异常编码类
 */
public interface BaseExceptionCode {
    /**
     * 异常编码
     *
     * @return
     */
    int getCode();

    /**
     * 异常消息
     * @return
     */
    String getMsg();
}

ExceptionCode

/**
 * 全局错误码 10000-15000
 * <p>
 * 预警异常编码    范围: 30000~34999
 * 标准服务异常编码 范围:35000~39999
 * 邮件服务异常编码 范围:40000~44999
 * 短信服务异常编码 范围:45000~49999
 * 权限服务异常编码 范围:50000-59999
 * 文件服务异常编码 范围:60000~64999
 * 日志服务异常编码 范围:65000~69999
 * 消息服务异常编码 范围:70000~74999
 * 开发者平台异常编码 范围:75000~79999
 * 搜索服务异常编码 范围:80000-84999
 * 共享交换异常编码 范围:85000-89999
 * 移动终端平台 异常码 范围:90000-94999
 * <p>
 * 安全保障平台    范围:        95000-99999
 * 软硬件平台 异常编码 范围:    100000-104999
 * 运维服务平台 异常编码 范围:  105000-109999
 * 统一监管平台异常 编码 范围:  110000-114999
 * 认证方面的异常编码  范围:115000-115999
 *
 */
public enum ExceptionCode implements BaseExceptionCode {

    //系统相关 start
    SUCCESS(0, "成功"),
    SYSTEM_BUSY(-1, "系统繁忙~请稍后再试~"),
    SYSTEM_TIMEOUT(-2, "系统维护中~请稍后再试~"),
    PARAM_EX(-3, "参数类型解析异常"),
    SQL_EX(-4, "运行SQL出现异常"),
    NULL_POINT_EX(-5, "空指针异常"),
    ILLEGALA_ARGUMENT_EX(-6, "无效参数异常"),
    MEDIA_TYPE_EX(-7, "请求类型异常"),
    LOAD_RESOURCES_ERROR(-8, "加载资源出错"),
    BASE_VALID_PARAM(-9, "统一验证参数异常"),
    OPERATION_EX(-10, "操作异常"),


    OK(200, "OK"),
    BAD_REQUEST(400, "错误的请求"),
    /**
     * {@code 401 Unauthorized}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7235#section-3.1">HTTP/1.1: Authentication, section 3.1</a>
     */
    UNAUTHORIZED(401, "未经授权"),
    /**
     * {@code 404 Not Found}.
     *
     * @see <a href="http://tools.ietf.org/html/rfc7231#section-6.5.4">HTTP/1.1: Semantics and Content, section 6.5.4</a>
     */
    NOT_FOUND(404, "没有找到资源"),
    METHOD_NOT_ALLOWED(405, "不支持当前请求类型"),

    TOO_MANY_REQUESTS(429, "请求超过次数限制"),
    INTERNAL_SERVER_ERROR(500, "内部服务错误"),
    BAD_GATEWAY(502, "网关错误"),
    GATEWAY_TIMEOUT(504, "网关超时"),
    //系统相关 end

    REQUIRED_FILE_PARAM_EX(1001, "请求中必须至少包含一个有效文件"),
    //jwt token 相关 start

    JWT_TOKEN_EXPIRED(40001, "会话超时,请重新登录"),
    JWT_SIGNATURE(40002, "不合法的token,请认真比对 token 的签名"),
    JWT_ILLEGAL_ARGUMENT(40003, "缺少token参数"),
    JWT_GEN_TOKEN_FAIL(40004, "生成token失败"),
    JWT_PARSER_TOKEN_FAIL(40005, "解析token失败"),
    JWT_USER_INVALID(40006, "用户名或密码错误"),
    JWT_USER_ENABLED(40007, "用户已经被禁用!"),
    //jwt token 相关 end

    ;

    private int code;
    private String msg;

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

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

    @Override
    public String getMsg() {
        return msg;
    }


    public ExceptionCode build(String msg, Object... param) {
        this.msg = String.format(msg, param);
        return this;
    }

    public ExceptionCode param(Object... param) {
        msg = String.format(msg, param);
        return this;
    }
}

封装自定义异常类

BaseException

/**
 * @ClassName: BaseException
 * @Description: 异常接口类
 */
public interface BaseException {

    /**
     * 统一参数验证异常码
     */
    int BASE_VALID_PARAM = -9;

    /**
     * 返回异常信息
     *
     * @return
     */
    String getMessage();

    /**
     * 返回异常编码
     *
     * @return
     */
    int getCode();

}

BaseUncheckedException

/**
 * @ClassName: BaseUncheckedException
 * @Description: 非运行期异常基类,所有自定义非运行时异常继承该类
 */
public class BaseUncheckedException extends RuntimeException implements BaseException {

    private static final long serialVersionUID = -778887391066124051L;

    /**
     * 异常信息
     */
    protected String message;

    /**
     * 具体异常码
     */
    protected int code;

    public BaseUncheckedException(int code, String message) {
        super(message);
        this.code = code;
        this.message = message;
    }

    public BaseUncheckedException(int code, String format, Object... args) {
        super(String.format(format, args));
        this.code = code;
        this.message = String.format(format, args);
    }
	

    @Override
    public String getMessage() {
        return message;
    }
    @Override
    public int getCode() {
        return code;
    }
}

BizException

/**
 * @ClassName: BizException
 * @Description: 业务异常 用于在处理业务逻辑时,进行抛出的异常。
 */
public class BizException extends BaseUncheckedException {

    private static final long serialVersionUID = -3843907364558373817L;

    public BizException(String message) {
        super(-1, message);
    }

    public BizException(int code, String message) {
        super(code, message);
    }

    public BizException(int code, String message, Object... args) {
        super(code, message, args);
    }

    /**
     * 实例化异常
     *
     * @param code    自定义异常编码
     * @param message 自定义异常消息
     * @param args    已定义异常参数
     * @return
     */
    public static BizException wrap(int code, String message, Object... args) {
        return new BizException(code, message, args);
    }

    public static BizException wrap(String message, Object... args) {
        return new BizException(-1, message, args);
    }

    public static BizException validFail(String message, Object... args) {
        return new BizException(-9, message, args);
    }

    public static BizException wrap(BaseExceptionCode ex) {
        return new BizException(ex.getCode(), ex.getMsg());
    }

    @Override
    public String toString() {
        return "BizException [message=" + message + ", code=" + code + "]";
    }

}

封装通用返回值 R

7、R

/**
 * @ClassName: R
 * @Description: 统一返回实体
 */
@Getter
@Setter
@SuppressWarnings({"AlibabaClassNamingShouldBeCamel"})
@Accessors(chain = true)
public class R<T> {
    public static final String DEF_ERROR_MESSAGE = "系统繁忙,请稍候再试";
    public static final String HYSTRIX_ERROR_MESSAGE = "请求超时,请稍候再试";
    public static final int SUCCESS_CODE = 0;
    public static final int FAIL_CODE = -1;
    public static final int TIMEOUT_CODE = -2;
    /**
     * 统一参数验证异常
     */
    public static final int VALID_EX_CODE = -9;
    public static final int OPERATION_EX_CODE = -10;
    /**
     * 调用是否成功标识,0:成功,-1:系统繁忙,此时请开发者稍候再试 详情见[ExceptionCode]
     */
    @ApiModelProperty(value = "响应编码:0/200-请求处理成功")
    private int code;

    /**
     * 调用结果
     */
    @ApiModelProperty(value = "响应数据")
    private T data;

    /**
     * 结果消息,如果调用成功,消息通常为空T
     */
    @ApiModelProperty(value = "提示消息")
    private String msg = "ok";

    @ApiModelProperty(value = "请求路径")
    private String path;
    /**
     * 附加数据
     */
    @ApiModelProperty(value = "附加数据")
    private Map<String, Object> extra;

    /**
     * 响应时间
     */
    @ApiModelProperty(value = "响应时间戳")
    private long timestamp = System.currentTimeMillis();

    private R() {
        super();
    }

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

    public static <E> R<E> result(int code, E data, String msg) {
        return new R<>(code, data, msg);
    }

    /**
     * 请求成功消息
     *
     * @param data 结果
     * @return RPC调用结果
     */
    public static <E> R<E> success(E data) {
        return new R<>(SUCCESS_CODE, data, "ok");
    }

    public static R<Boolean> success() {
        return new R<>(SUCCESS_CODE, true, "ok");
    }

    /**
     * 请求成功方法 ,data返回值,msg提示信息
     *
     * @param data 结果
     * @param msg  消息
     * @return RPC调用结果
     */
    public static <E> R<E> success(E data, String msg) {
        return new R<>(SUCCESS_CODE, data, msg);
    }

    /**
     * 请求失败消息
     *
     * @param msg
     * @return
     */
    public static <E> R<E> fail(int code, String msg) {
        return new R<>(code, null, (msg == null || msg.isEmpty()) ? DEF_ERROR_MESSAGE : msg);
    }

    public static <E> R<E> fail(String msg) {
        return fail(OPERATION_EX_CODE, msg);
    }

    public static <E> R<E> fail(String msg, Object... args) {
        String message = (msg == null || msg.isEmpty()) ? DEF_ERROR_MESSAGE : msg;
        return new R<>(OPERATION_EX_CODE, null, String.format(message, args));
    }

    public static <E> R<E> fail(BaseExceptionCode exceptionCode) {
        return validFail(exceptionCode);
    }

    public static <E> R<E> fail(BizException exception) {
        if (exception == null) {
            return fail(DEF_ERROR_MESSAGE);
        }
        return new R<>(exception.getCode(), null, exception.getMessage());
    }

    /**
     * 请求失败消息,根据异常类型,获取不同的提供消息
     *
     * @param throwable 异常
     * @return RPC调用结果
     */
    public static <E> R<E> fail(Throwable throwable) {
        return fail(FAIL_CODE, throwable != null ? throwable.getMessage() : DEF_ERROR_MESSAGE);
    }

    public static <E> R<E> validFail(String msg) {
        return new R<>(VALID_EX_CODE, null, (msg == null || msg.isEmpty()) ? DEF_ERROR_MESSAGE : msg);
    }

    public static <E> R<E> validFail(String msg, Object... args) {
        String message = (msg == null || msg.isEmpty()) ? DEF_ERROR_MESSAGE : msg;
        return new R<>(VALID_EX_CODE, null, String.format(message, args));
    }

    public static <E> R<E> validFail(BaseExceptionCode exceptionCode) {
        return new R<>(exceptionCode.getCode(), null,
                (exceptionCode.getMsg() == null || exceptionCode.getMsg().isEmpty()) ? DEF_ERROR_MESSAGE : exceptionCode.getMsg());
    }

    public static <E> R<E> timeout() {
        return fail(TIMEOUT_CODE, HYSTRIX_ERROR_MESSAGE);
    }


    public R<T> put(String key, Object value) {
        if (this.extra == null) {
            this.extra = Maps.newHashMap();
        }
        this.extra.put(key, value);
        return this;
    }

    /**
     * 逻辑处理是否成功
     *
     * @return 是否成功
     */
    public Boolean getIsSuccess() {
        return this.code == SUCCESS_CODE || this.code == 200;
    }

    /**
     * 逻辑处理是否失败
     *
     * @return
     */
    public Boolean getIsError() {
        return !getIsSuccess();
    }
	 @Override
    public String toString() {
        return JSONObject.toJSONString(this);
    }
}

封装全局异常处理类

DefaultGlobalExceptionHandler

@Slf4j
public abstract class DefaultGlobalExceptionHandler {
    @ExceptionHandler(BizException.class)
    public R<String> bizException(BizException ex, HttpServletRequest request) {
        log.warn("BizException:", ex);
        return R.result(ex.getCode(), StringUtils.EMPTY, ex.getMessage()).setPath(request.getRequestURI());
    }

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public R httpMessageNotReadableException(HttpMessageNotReadableException ex, HttpServletRequest request) {
        log.warn("HttpMessageNotReadableException:", ex);
        String message = ex.getMessage();
        if (StrUtil.containsAny(message, "Could not read document:")) {
            String msg = String.format("无法正确的解析json类型的参数:%s", StrUtil.subBetween(message, "Could not read document:", " at "));
            return R.result(ExceptionCode.PARAM_EX.getCode(), StringUtils.EMPTY, msg).setPath(request.getRequestURI());
        }
        return R.result(ExceptionCode.PARAM_EX.getCode(), StringUtils.EMPTY, ExceptionCode.PARAM_EX.getMsg()).setPath(request.getRequestURI());
    }

    @ExceptionHandler(BindException.class)
    public R bindException(BindException ex, HttpServletRequest request) {
        log.warn("BindException:", ex);
        try {
            String msgs = ex.getBindingResult().getFieldError().getDefaultMessage();
            if (StrUtil.isNotEmpty(msgs)) {
                return R.result(ExceptionCode.PARAM_EX.getCode(), StringUtils.EMPTY, msgs).setPath(request.getRequestURI());
            }
        } catch (Exception ee) {
        }
        StringBuilder msg = new StringBuilder();
        List<FieldError> fieldErrors = ex.getFieldErrors();
        fieldErrors.forEach((oe) ->
                msg.append("参数:[").append(oe.getObjectName())
                        .append(".").append(oe.getField())
                        .append("]的传入值:[").append(oe.getRejectedValue()).append("]与预期的字段类型不匹配.")
        );
        return R.result(ExceptionCode.PARAM_EX.getCode(), StringUtils.EMPTY, msg.toString()).setPath(request.getRequestURI());
    }


    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public R methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex, HttpServletRequest request) {
        log.warn("MethodArgumentTypeMismatchException:", ex);
        MethodArgumentTypeMismatchException eee = (MethodArgumentTypeMismatchException) ex;
        StringBuilder msg = new StringBuilder("参数:[").append(eee.getName())
                .append("]的传入值:[").append(eee.getValue())
                .append("]与预期的字段类型:[").append(eee.getRequiredType().getName()).append("]不匹配");
        return R.result(ExceptionCode.PARAM_EX.getCode(), StringUtils.EMPTY, msg.toString()).setPath(request.getRequestURI());
    }

    @ExceptionHandler(IllegalStateException.class)
    public R illegalStateException(IllegalStateException ex, HttpServletRequest request) {
        log.warn("IllegalStateException:", ex);
        return R.result(ExceptionCode.ILLEGALA_ARGUMENT_EX.getCode(), StringUtils.EMPTY, ExceptionCode.ILLEGALA_ARGUMENT_EX.getMsg()).setPath(request.getRequestURI());
    }

    @ExceptionHandler(MissingServletRequestParameterException.class)
    public R missingServletRequestParameterException(MissingServletRequestParameterException ex, HttpServletRequest request) {
        log.warn("MissingServletRequestParameterException:", ex);
        StringBuilder msg = new StringBuilder();
        msg.append("缺少必须的[").append(ex.getParameterType()).append("]类型的参数[").append(ex.getParameterName()).append("]");
        return R.result(ExceptionCode.ILLEGALA_ARGUMENT_EX.getCode(), StringUtils.EMPTY, msg.toString()).setPath(request.getRequestURI());
    }

    @ExceptionHandler(NullPointerException.class)
    public R nullPointerException(NullPointerException ex, HttpServletRequest request) {
        log.warn("NullPointerException:", ex);
        return R.result(ExceptionCode.NULL_POINT_EX.getCode(), StringUtils.EMPTY, ExceptionCode.NULL_POINT_EX.getMsg()).setPath(request.getRequestURI());
    }

    @ExceptionHandler(IllegalArgumentException.class)
    public R illegalArgumentException(IllegalArgumentException ex, HttpServletRequest request) {
        log.warn("IllegalArgumentException:", ex);
        return R.result(ExceptionCode.ILLEGALA_ARGUMENT_EX.getCode(), StringUtils.EMPTY, ExceptionCode.ILLEGALA_ARGUMENT_EX.getMsg()).setPath(request.getRequestURI());
    }

    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public R httpMediaTypeNotSupportedException(HttpMediaTypeNotSupportedException ex, HttpServletRequest request) {
        log.warn("HttpMediaTypeNotSupportedException:", ex);
        MediaType contentType = ex.getContentType();
        if (contentType != null) {
            StringBuilder msg = new StringBuilder();
            msg.append("请求类型(Content-Type)[").append(contentType.toString()).append("] 与实际接口的请求类型不匹配");
            return R.result(ExceptionCode.MEDIA_TYPE_EX.getCode(), StringUtils.EMPTY, msg.toString()).setPath(request.getRequestURI());
        }
        return R.result(ExceptionCode.MEDIA_TYPE_EX.getCode(), StringUtils.EMPTY, "无效的Content-Type类型").setPath(request.getRequestURI());
    }

    @ExceptionHandler(MissingServletRequestPartException.class)
    public R missingServletRequestPartException(MissingServletRequestPartException ex, HttpServletRequest request) {
        log.warn("MissingServletRequestPartException:", ex);
        return R.result(ExceptionCode.REQUIRED_FILE_PARAM_EX.getCode(), StringUtils.EMPTY, ExceptionCode.REQUIRED_FILE_PARAM_EX.getMsg()).setPath(request.getRequestURI());
    }

    @ExceptionHandler(ServletException.class)
    public R servletException(ServletException ex, HttpServletRequest request) {
        log.warn("ServletException:", ex);
        String msg = "UT010016: Not a multi part request";
        if (msg.equalsIgnoreCase(ex.getMessage())) {
            return R.result(ExceptionCode.REQUIRED_FILE_PARAM_EX.getCode(), StringUtils.EMPTY, ExceptionCode.REQUIRED_FILE_PARAM_EX.getMsg());
        }
        return R.result(ExceptionCode.SYSTEM_BUSY.getCode(), StringUtils.EMPTY, ex.getMessage()).setPath(request.getRequestURI());
    }

    @ExceptionHandler(MultipartException.class)
    public R multipartException(MultipartException ex, HttpServletRequest request) {
        log.warn("MultipartException:", ex);
        return R.result(ExceptionCode.REQUIRED_FILE_PARAM_EX.getCode(), StringUtils.EMPTY, ExceptionCode.REQUIRED_FILE_PARAM_EX.getMsg()).setPath(request.getRequestURI());
    }

    /**
     * jsr 规范中的验证异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public R<String> constraintViolationException(ConstraintViolationException ex, HttpServletRequest request) {
        log.warn("ConstraintViolationException:", ex);
        Set<ConstraintViolation<?>> violations = ex.getConstraintViolations();
        String message = violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.joining(";"));
        return R.result(ExceptionCode.BASE_VALID_PARAM.getCode(), StringUtils.EMPTY, message).setPath(request.getRequestURI());
    }

    /**
     * spring 封装的参数验证异常, 在conttoller中没有写result参数时,会进入
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object methodArgumentNotValidException(MethodArgumentNotValidException ex, HttpServletRequest request) {
        log.warn("MethodArgumentNotValidException:", ex);
        return R.result(ExceptionCode.BASE_VALID_PARAM.getCode(), StringUtils.EMPTY, ex.getBindingResult().getFieldError().getDefaultMessage()).setPath(request.getRequestURI());
    }

    /**
     * 其他异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(Exception.class)
    public R<String> otherExceptionHandler(Exception ex, HttpServletRequest request) {
        log.warn("Exception:", ex);
        if (ex.getCause() instanceof BizException) {
            return this.bizException((BizException) ex.getCause(), request);
        }
        return R.result(ExceptionCode.SYSTEM_BUSY.getCode(), StringUtils.EMPTY, ExceptionCode.SYSTEM_BUSY.getMsg()).setPath(request.getRequestURI());
    }


    /**
     * 返回状态码:405
     */
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    public R<String> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException ex, HttpServletRequest request) {
        log.warn("HttpRequestMethodNotSupportedException:", ex);
        return R.result(ExceptionCode.METHOD_NOT_ALLOWED.getCode(), StringUtils.EMPTY, ExceptionCode.METHOD_NOT_ALLOWED.getMsg()).setPath(request.getRequestURI());
    }

    @ExceptionHandler(SQLException.class)
    public R sqlException(SQLException ex, HttpServletRequest request) {
        log.warn("SQLException:", ex);
        return R.result(ExceptionCode.SQL_EX.getCode(), StringUtils.EMPTY, ExceptionCode.SQL_EX.getMsg()).setPath(request.getRequestURI());
    }
}

ExceptionConfiguration

@RestControllerAdvice(annotations = {RestController.class, Controller.class})
public class ExceptionConfiguration extends DefaultGlobalExceptionHandler{
}

抽象基类BaseController

/**
 * @ClassName: BaseController
 * @Description: controller 抽象基类
 */
public abstract class BaseController {
    /**
     * 成功返回
     *
     * @param data
     * @return
     */
    public <T> R<T> success(T data) {
        return R.success(data);
    }

    public R<Boolean> success() {
        return R.success();
    }

    /**
     * 失败返回
     *
     * @param msg
     * @return
     */
    public <T> R<T> fail(String msg) {
        return R.fail(msg);
    }

    public <T> R<T> fail(String msg, Object... args) {
        return R.fail(msg, args);
    }

    /**
     * 失败返回
     *
     * @param code
     * @param msg
     * @return
     */
    public <T> R<T> fail(int code, String msg) {
        return R.fail(code, msg);
    }

    public <T> R<T> fail(BaseExceptionCode exceptionCode) {
        return R.fail(exceptionCode);
    }

    public <T> R<T> fail(BizException exception) {
        return R.fail(exception);
    }

    public <T> R<T> fail(Throwable throwable) {
        return R.fail(throwable);
    }

    public <T> R<T> validFail(String msg) {
        return R.validFail(msg);
    }

    public <T> R<T> validFail(String msg, Object... args) {
        return R.validFail(msg, args);
    }

    public <T> R<T> validFail(BaseExceptionCode exceptionCode) {
        return R.validFail(exceptionCode);
    }
}

解决全局异常处理类无法捕获过滤器中抛出的异常

SpringBoot自带的全局异常捕获机制都是在业务层发生的异常来进行捕获的,只能捕获Controller层的异常,无法捕获filter抛出的异常。因为过滤器的执行顺序是在全局异常机制启动之前执行的,所以一旦过滤器中发生异常,全局异常捕获机制无法使用。
在这里插入图片描述
解决方法:
1、在filter当中引入HandlerExceptionResolver类,通过该类的resolveException方法抛出自定义异常:

@Component
@ConfigurationProperties(prefix = "security")
@WebFilter(urlPatterns = "/*",asyncSupported = true)
@Order(1)
public class XssAndSqlFilter implements Filter {

    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;

    /**
     * 是否启用
     */
    private boolean enable;
    /**
     * 忽略的URL
     */
    private List<String> excludes;


    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
            throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        //跳过不需要的Xss校验的地址
        // 不启用或者已忽略的URL不拦截
        if (!enable || isExcludeUrl(req.getServletPath())) {
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        //注入xss过滤器实例
        XssAndSqlHttpServletRequestWrapper reqW = new XssAndSqlHttpServletRequestWrapper(req);
        // 解决全局异常无法捕获的问题
        try {
            //过滤
            filterChain.doFilter(reqW, response);
        } catch (Exception e) {
            resolver.resolveException(req, response, null, e);
        }
    }


    /**
     * 判断是否为忽略的URL
     *
     * @param url URL路径
     * @return true-忽略,false-过滤
     */
    private boolean isExcludeUrl(String url) {
        if (excludes == null || excludes.isEmpty()) {
            return false;
        }
        return excludes.stream().map(pattern -> Pattern.compile("^" + pattern)).map(p -> p.matcher(url)).anyMatch(Matcher::find);
    }


    public void setEnable(boolean enable) {
        this.enable = enable;
    }
    public void setExcludes(List<String> excludes) {
        this.excludes = excludes;
    }
}

关键代码

// 解决全局异常无法捕获的问题
try {
    //过滤
    filterChain.doFilter(reqW, response);
} catch (Exception e) {
    resolver.resolveException(req, response, null, e);
}
  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在Spring Boot中,可以通过编写一个全局异常处理类来捕获并处理应用程序中的异常。以下是一个示例: ```java @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleException(Exception ex) { ErrorResponse errorResponse = new ErrorResponse(); errorResponse.setMessage("Internal Server Error"); errorResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR); } @ExceptionHandler(NotFoundException.class) public ResponseEntity<ErrorResponse> handleNotFoundException(NotFoundException ex) { ErrorResponse errorResponse = new ErrorResponse(); errorResponse.setMessage(ex.getMessage()); errorResponse.setStatus(HttpStatus.NOT_FOUND.value()); return new ResponseEntity<>(errorResponse, HttpStatus.NOT_FOUND); } // 添加其他自定义异常的处理方法 } ``` 在上面的示例中,`@ControllerAdvice`注解用于声明这是一个全局异常处理类。`@ExceptionHandler`注解用于指定处理特定异常类型的方法。 在`handleException`方法中,我们处理了 `Exception` 类型的异常,并返回一个包含错误信息和状态码的 `ErrorResponse` 对象。 在`handleNotFoundException`方法中,我们处理了自定义的 `NotFoundException` 类型的异常,并返回一个包含错误信息和状态码的 `ErrorResponse` 对象。 你可以根据自己的需求添加其他的异常处理方法。确保在每个处理方法中返回合适的 `ErrorResponse` 对象。 注意:为了让Spring Boot自动扫描到该全局异常处理类,需要将它放在主应用程序类所在的包或子包中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

李熠漾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值