前后端分离开发中,后台接口返回给前端的数据一般都是json格式,所以这种情况下,异常捕获之后也是返回前端json格式
1. 自定义异常类
注:
异常类里定义了一个参数为ResultCode 枚举类的构造方法,这种方法可以直接在枚举类里面配置各种状态的code和message,非常直观地就能知道返回的code和message信息,同时枚举类里面的信息也可以复用。
public class MyException extends RuntimeException {
protected Integer code;
protected String message;
public MyException(ResultCode resultCode) {
this.code = resultCode.code();
this.message = resultCode.message();
}
}
2. 异常提示枚举类
简单的配置了几种情况做测试,可以随意添加。
public enum ResultCode {
/* 成功状态码 */
SUCCESS(0, "成功"),
/* 失败状态码 */
FAIL(200, "失败"),
/* 系统500错误*/
SYSTEM_ERROR(10000, "系统异常,请稍后重试"),
UNAUTHORIZED(10401, "签名验证失败"),
TEST(500, "测试异常"),
/* 参数错误:10001-19999 */
PARAM_IS_INVALID(10001, "参数无效"),
/* 用户错误:20001-29999*/
USER_HAS_EXISTED(20001, "用户名已存在"),
USER_NOT_FIND(20002, "用户名不存在");
private Integer code;
private String message;
ResultCode(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer code() {
return this.code;
}
public String message() {
return this.message;
}
}
3. 异常信息封装VO类
这里单独定义了一个专门返回异常的VO类,里面有异常的信息,这样如果后台报异常了,返回前端的信息里面能够直观的看到报错的信息。
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class ErrorResult {
/**
* 异常状态码
*/
private Integer status;
/**
* 用户看得见的异常,例如 用户名重复!!,
*/
private String message;
/**
* 异常的名字
*/
private String exception;
/**
* 对异常提示语进行封装
*/
public static ErrorResult fail(ResultCode resultCode, Throwable e, String message) {
ErrorResult result = ErrorResult.fail(resultCode, e);
result.setMessage(message);
return result;
}
/**
* 对异常枚举进行封装
*/
public static ErrorResult fail(ResultCode resultCode, Throwable e) {
ErrorResult result = new ErrorResult();
result.setMessage(resultCode.message());
result.setStatus(resultCode.code());
result.setException(e.getClass().getName());
return result;
}
}
4. 全局异常捕获类
注:
类上面用了@RestControllerAdvice注解,表示所有的信息都会直接返回前端json格式。
在异常处理里面使用log的方式把出现异常的地址打印出来,能够方便我们及时排查出出现异常的位置。
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/**
* 处理运行时异常
*/
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Throwable.class)
public ErrorResult handleThrowable(Throwable e, HttpServletRequest request) {
//TODO 运行是异常,可以在这里记录,用于发异常邮件通知
ErrorResult error =ErrorResult.fail(ResultCode.SYSTEM_ERROR, e);
log.error("URL:{} ,系统异常: ",request.getRequestURI(), e);
return error;
}
/**
*处理@notNull绑定异常
*/
@ExceptionHandler(BindException.class)
public ErrorResult exceptionHandler(BindException e, HttpServletRequest request) {
String failMsg = e.getBindingResult().getFieldError().getDefaultMessage();
ErrorResult error = ErrorResult.fail(ResultCode.SYSTEM_ERROR, e, failMsg);
log.error("URL:{} ,绑定异常:{} ", request.getRequestURI(),failMsg);
return error;
}
/**
* 处理自定义异常
*/
//捕获指定异常
@ExceptionHandler(BusinessException.class)
public ErrorResult handleBusinessException(BusinessException e, HttpServletRequest request) {
ErrorResult error = ErrorResult.builder().status(e.code)
.message(e.message)
.exception(e.getClass().getName())
.build();
//在控制台会打印出异常的url地址和详细信息
log.warn("URL:{} ,业务异常:{}", request.getRequestURI(),error);
return error;
}
/**
* validator 统一异常封装
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
String msgs = this.handle(e.getBindingResult().getFieldErrors());
ErrorResult error = ErrorResult.fail(ResultCode.PARAM_IS_INVALID, e, msgs);
log.warn("URL:{} ,参数校验异常:{}", request.getRequestURI(),msgs);
return error;
}
private String handle(List<FieldError> fieldErrors) {
StringBuilder sb = new StringBuilder();
for (FieldError obj : fieldErrors) {
sb.append(obj.getField());
sb.append("=[");
sb.append(obj.getDefaultMessage());
sb.append("] ");
}
return sb.toString();
}
/**
* Assert的异常统一封装
*/
@ExceptionHandler(IllegalArgumentException.class)
public ErrorResult illegalArgumentException(IllegalArgumentException e, HttpServletRequest request) {
ErrorResult error = ErrorResult.builder().status(4000)
.message(e.getMessage())
.exception(e.getClass().getName())
.build();
log.warn("URL:{} ,业务校验异常:{}", request.getRequestURI(),e);
return error;
}
}
除了全局异常捕获异常外,也可以在controller里面自定义捕获service里面的异常,拿到异常code和message,然后返回给前端
@PostMapping("/login")
@ResponseBody
public AjaxResult ajaxLogin(String username, String password, Boolean rememberMe) {
UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return success();
} catch (AuthenticationException e) {
String msg = "用户或密码错误";
if (StringUtils.isNotEmpty(e.getMessage())) {
msg = e.getMessage();
}
return error(msg);
}
}