全局异常工具类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);
}