SpringBoot项目rest接口的全局异常处理
当使用SpringBoot开发api接口时,常常需要和前端约定项目的返回码,返回体等数据,同时还要定义后台的异常处理时的返回逻辑。spring web模块提供了全局异常处理的方法,即ControllerAdvice 和 RestControllerAdvice ,由于本文讲述的是rest接口的异常处理,这里使用的是 RestControllerAdvice来做 rest接口的全局异常处理。
物料准备:
1.一个全局异常处理类
2.一个自定义的通用业务异常类
3.一个公共的返回体泛型类
4.系统返回码枚举类
全局异常处理类RestGlobalExceptionHandler.java
package cn.ath.knowwikibackend.sys;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONPath;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolationException;
/**
* 根据需要,此处可以全局处理
* 各种异常应该返回的 code/msg信息
*/
@Slf4j
@RestControllerAdvice(annotations = RestController.class)
public class RestGlobalExceptionHandler {
/**
* 运行时业务异常
*
* @param e APIException
* @return String
*/
@ExceptionHandler({APIException.class})
public ResultVO<String> APIExceptionHandler(APIException e) {
// 注意这里返回类型是自定义响应体
return new ResultVO<>(e.getCode(), e.getMsg());
}
/**
* @param e HttpMessageNotReadableException
* @return ResultVO
* @RequestBody @Valid put/post请求体缺少异常
*/
@ExceptionHandler({HttpMessageNotReadableException.class})
public ResultVO reqBodyIsNullException(HttpMessageNotReadableException e) {
log.error("requestBody请求体异常",e);
return new ResultVO(ResultCodeEnum.REQ_BODY_IS_NULL, e.getMessage());
}
/**
* 对 jsr303 参数校验失败抛出的MethodArgumentNotValidException全局处理
*
* @param e MethodArgumentNotValidException
* @return String
*/
@ExceptionHandler({MethodArgumentNotValidException.class})
public ResultVO<String> MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
log.error("自动抛的MethodArgumentNotValidException",e);
// 从异常对象中拿到ObjectError对象
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
log.error("objectError:{}", JSONObject.toJSONString(objectError, true));
String field = String.valueOf(JSONPath.eval(objectError, "$.field"));
log.error("field:{}", field);
String objName = objectError.getObjectName();
System.err.println(objName);
// 然后提取错误提示信息进行返回
return new ResultVO<>(ResultCodeEnum.VALIDATE_FAILED,
objName + "." + field + objectError.getDefaultMessage());
}
/**
* 处理 ArithmeticException 类的异常 (如除0异常)
*/
@ExceptionHandler({ArithmeticException.class})
public ResultVO arithmeticExceptionHandler(ArithmeticException e) {
return new ResultVO(ResultCodeEnum.ARITHMETIC_FAILED, e.getMessage());
}
/**
* 手动抛出的或 Assert断言的 非法参数异常
*
* @param e IllegalArgumentException
* @return ResultVO
*/
@ExceptionHandler({IllegalArgumentException.class})
public ResultVO illegalArgumentExceptionHandler(IllegalArgumentException e) {
log.error("人工抛出异常的IllegalArgumentException",e);
return new ResultVO<>(ResultCodeEnum.IllEGAL_ARG_EXCEPTION,
e.getMessage());
}
/**
* 处理 数组越界 异常
*/
@ExceptionHandler({ArrayIndexOutOfBoundsException.class})
public ResultVO arrayIndexOutOfBoundsExceptionHandler(ArrayIndexOutOfBoundsException e) {
return new ResultVO(ResultCodeEnum.ARRAY_INDEX_OUT_OF_BOUNDS_FAILED, e.getMessage());
}
/**
* Validated全局自动校验,要@Validated加在Controller类名上,同时手动使用BindingResult
*/
@ExceptionHandler({ConstraintViolationException.class})
public ResultVO constraintViolationExceptionHandler(ConstraintViolationException e) {
log.error("Validated全局自动校验出的异常",e);
//校验框架会自动返回多条约束提示信息的拼接
//格式如 test1234.req.company.name: 长度需要在4-30之间,test1234.req.account: 长度需要在2-30之间
//(逗号拼接的字符串 接口方法名.参数名.子参数对象.子参数属性:描述)
return new ResultVO<>(ResultCodeEnum.VALIDATE_FAILED,
e.getMessage());
}
/**
* 未知异常
*
* @param e
* @return
*/
@ExceptionHandler({Exception.class})
public ResultVO otherExceptionHandle(Exception e) {
log.error("otherExceptionHandle", e);
return new ResultVO(ResultCodeEnum.ERROR, e.getMessage());
}
}
通用业务异常类APIException.java
package cn.ath.knowwikibackend.sys;
import lombok.Getter;
@Getter
public class APIException extends RuntimeException {
int code;
String msg;
public APIException(int code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}
public APIException(String msg) {
this(1001, msg);
}
public APIException() {
this(ResultCodeEnum.FAILED.getCode(), ResultCodeEnum.FAILED.getMsg());
}
public APIException(ResultCodeEnum resultCodeEnum){
this(resultCodeEnum.getCode(),resultCodeEnum.getMsg());
}
}
公共返回体泛型类ResultVO.java
package cn.ath.knowwikibackend.sys;
import cn.hutool.core.date.DateTime;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class ResultVO<T> {
int code;
String msg;
T content;
Long timestamp;
String traceDateTime;
public ResultVO(int code,String msg,T content){
this.code = code;
this.msg = msg;
this.content = content;
this.timestamp = System.currentTimeMillis();
this.traceDateTime = DateTime.now().toString();
}
public ResultVO(int code,String msg){
this.code = code;
this.msg = msg;
this.content = null;
this.timestamp = System.currentTimeMillis();
this.traceDateTime = DateTime.now().toString();
}
public ResultVO(ResultCodeEnum resultCode, T content) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
this.content = content;
this.timestamp = System.currentTimeMillis();
this.traceDateTime = DateTime.now().toString();
}
public ResultVO(T content){
this(ResultCodeEnum.SUCCESS, content);
}
}
系统返回码枚举类ResultCodeEnum.java
package cn.ath.knowwikibackend.sys;
import lombok.Getter;
@Getter
public enum ResultCodeEnum {
SUCCESS(0, "操作成功"),
VERITY_GEN_ERROR(9801,"验证码生成失败"),
VERITY_IDENTITY_ERROR(9803,"验证码验证失败"),
TOKEN_NULL(9777, "请求头里没有token"),
TOKEN_TIME_OUT(9776, "token已过期,请重新登录"),
TOKEN_USER_ALREADY_SIGN(9775, "token用户已登录,请退出重登后再请求本接口"),
FAILED(9500, "响应失败"),
BUSY(9600, "系统繁忙"),
ACCOUNT_PASS_FAILED(9301, "密码错误"),
ACCOUNT_NOT_EXIST(9302, "用户名不存在"),
ACCOUNT_DISABLED(9303, "用户没有权限/已禁用"),
NOT_LOGIN(9403,"未登录"),
REQ_BODY_IS_NULL(9995,"requestBody请求体异常"),
VALIDATE_FAILED(9996, "参数校验异常"),
ARITHMETIC_FAILED(9997, "算术异常"),
ARRAY_INDEX_OUT_OF_BOUNDS_FAILED(9998, "数组越界异常"),
USER_IS_USING(7701,"目标用户正在使用中"),
IllEGAL_ARG_EXCEPTION(9988,"非法参数异常"),
ERROR(9999, "未知错误");
private int code;
private String msg;
ResultCodeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
}