SpringBoot对Controller层做全局异常处理记录
开发环境
PS:该示例仅能处理 Controller 层未捕获(往外抛)的异常,不能处理Interceptor(拦截器)层的异常,Spring框架层的异常
封装统一API返回结果
package com.lyc.common;
import com.lyc.util.DateTimeUtil;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
/**
* @author 茶轴的青春
* @Description 统一API响应结果封装
* @date 2020/7/22 18:46
*/
@ToString
@NoArgsConstructor
@Data
public class R<T> implements Serializable {
private static final long serialVersionUID = 1L;
private int Code;
private boolean Success;
private T data;
private String msg;
private String respTime = DateTimeUtil.getNowTimeForString();
}
创建返回体报文的实体类
package com.lyc.exception;
import lombok.Data;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import java.time.LocalDateTime;
/**
* @author 茶轴的青春
* @Description 异常处理实体
* @date 2020/7/22 11:20
*/
@Document(collection = "exceptionLog")
@Data
public class LogException {
/**
*
*/
private static final long serialVersionUID = 4531499444309419351L;
@Field("error_code")
private int errorCode;
@Field("msg")
private String msg;
@Field("error_log")
private String errorLog;
@Field("ip")
private String ip;
@Field("operate_time")
private LocalDateTime operateTime;
}
创建异常实体 继承RuntimeException获取异常信息
一般系统抛出的错误是不含错误代码的,除去部分的404,400,500错误之外,我们如果想把错误代码定义的更细致,就需要自己继承RuntimeException这个类后重新定义一个构造方法来定义我们自己的错误信息
package com.lyc.exception;
/**
* @author 茶轴的青春
* @Description 异常信息捕获
* @date 2020/7/22 11:25
*/
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 3034121940056795549L;
private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public BusinessException() {
}
public BusinessException(BusinessStatus status) {
super(status.getMessage());
this.code = status.getCode();
}
public BusinessException(String message) {
super(message);
this.code = 500;
}
public BusinessException(String message, Integer code) {
super(message);
this.code = code;
}
}
创建一个枚举类 自定义异常信息
根据需求自定义已知的异常信息
package com.lyc.exception;
/**
* @author 茶轴的青春
* @Description 全局业务状态码
* @date 2020/7/22 11:25
*/
public enum BusinessStatus {
/**
* 管理员 - 账号密码错误
*/
ADMIN_PASSWORD(100101, "账号密码错误"),
/**
* 未知错误
*/
UNKNOWN(-1, "未知错误"),
/**
* 请求成功
*/
OK(20000, "成功"),
/**
* 请求失败
*/
FAIL(20001, "失败"),
/**
* 熔断请求
*/
BREAKING(20002, "熔断"),
/**
* 非法请求
*/
ILLEGAL_REQUEST(50000, "非法请求"),
/**
* 非法令牌
*/
ILLEGAL_TOKEN(50008, "非法令牌"),
/**
* 其他客户登录
*/
OTHER_CLIENTS_LOGGED_IN(50012, "其他客户登录"),
/**
* 令牌已过期
*/
TOKEN_EXPIRED(50014, "令牌已过期"),
/**
* 用户名已存在
*/
USER_NAME_EXISTS(50015, "用户名已存在"),
/**
* 用户锁定
*/
LOCK_USER(50017, "用户被锁定"),
/**
* 代表不存在
*/
FUND_SALES_USER_NOT_EXISTS(70001, "代表不存在"),
/**
* 没有获取到Token/Token过期
*/
JD_TOKEN_NOT_EXISTS(80001);
private Integer code;
private String message;
BusinessStatus(Integer code, String message) {
this.code = code;
this.message = message;
}
BusinessStatus(Integer code) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
public static String getMessage(int code) {
for (BusinessStatus status : values()) {
if (status.getCode().equals(code)) {
return status.getMessage();
}
}
return null;
}
}
自定义全局异常处理
@ExceptionHandler可以接受请求处理方法抛出的异常。但是他的作用范围仅限于当前控制器里的方法,而@ControllerAdvice注解,可以帮助我们将其应用到所有的控制器上。
本文示例使用MongoDB存储日志,简单使用可参考SpringBoot集成MongoDB
package com.lyc.exception;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.json.JSONUtil;
import com.lyc.common.R;
import com.lyc.util.GetClientIp;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolationException;
import java.time.LocalDateTime;
import java.util.List;
/**
* @author 茶轴的青春
* @Description 全局业务异常处理
* @date 2020/7/22 11:25
*/
@Slf4j
@ControllerAdvice
public class BusinessExceptionHandler {
@Resource
private MongoTemplate mongoTemplate;
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<?> handlerException(HttpServletRequest request, Exception ex) {
R error = new R();
// 业务异常
if (ex instanceof BusinessException) {
error.setCode(((BusinessException) ex).getCode());
error.setMsg(ex.getMessage());
log.warn("[全局业务异常]\r\n业务编码:{}\r\n异常记录:{}", error.getCode(), error.getMsg());
}
// 未知错误
else {
error.setCode(BusinessStatus.UNKNOWN.getCode());
error.setMsg(BusinessStatus.UNKNOWN.getMessage());
}
ex.printStackTrace();
this.saveLog(error, request, ex);
return new ResponseEntity<>(error, HttpStatus.OK);
}
@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<?> handlerViolationException(HttpServletRequest request, Exception ex) {
R error = new R();
List<String> errorMsgList = ListUtil.list(false);
if (ex instanceof ConstraintViolationException) {
error.setCode(500);
String errorMsg = ex.getMessage();
String[] errorArr = errorMsg.split(",");
for (String errorM : errorArr) {
String[] msgArr = errorM.split(":");
if (msgArr != null && msgArr.length >= 2) {
errorMsgList.add(msgArr[1]);
}
}
error.setMsg(JSONUtil.toJsonStr(errorMsgList));
log.warn("[全局业务异常]\r\n业务编码:{}\r\n异常记录:{}", error.getCode(), error.getMsg());
}
// 未知错误
else {
error.setCode(BusinessStatus.UNKNOWN.getCode());
error.setMsg(BusinessStatus.UNKNOWN.getMessage());
}
ex.printStackTrace();
this.saveLog(error, request, ex);
return new ResponseEntity<>(error, HttpStatus.OK);
}
/**
* 保存错误日志
*
* @param error
* @param request
* @param ex
*/
private void saveLog(R error, HttpServletRequest request, Exception ex) {
try {
LogException logException = new LogException();
logException.setErrorCode(error.getCode());
logException.setMsg(ex.getLocalizedMessage());
logException.setErrorLog(getExceptionAllInformation(ex));
logException.setIp(GetClientIp.getIpAddress(request));
// logException.setUserId(SecurityUtil.);
logException.setOperateTime(LocalDateTime.now());
ex.getLocalizedMessage();
log.info(ex.getLocalizedMessage());
new Thread(new Runnable() {
@Override
public void run() {
mongoTemplate.save(logException);
}
}).start();
} catch (Exception e) {
log.info("日志保存失败");
}
}
public static String getExceptionAllInformation(Exception ex) {
String sOut = "";
StackTraceElement[] trace = ex.getStackTrace();
for (StackTraceElement s : trace) {
sOut += "\tat " + s + "\r\n";
}
return sOut;
}
}
测试
手动定义异常测试
public R queryUserSystemById() {
int a = 0 / 0;
return new R();
}
后端返回错误 java.lang.ArithmeticException: / by zero
响应结果
{
"data": null,
"msg": "未知错误",
"respTime": "2020-05-27 17:05:18",
"code": -1,
"success": false
}
仅做记录 不足之处 请多指正 一起学习 互相进步!