一、为什么要进行统一异常处理
- 如果发生了异常我们应该让接口可以返回统一的结果。有好的展示给接口调用方。
- 方便我们对异常进行记录,和错误排查。
- 我们可能对某些异常比较关注,比如说我们监控某个IP或者用户一天发送短信的数量,当超出一定数量后我们就不再发送然后抛出异常。这个时候我们通过统一异常处理进行全局拦截,然后记录日志甚至是数据库,并且发送短信通知相关负责人。
二、统一异常处理概述
1、统一异常处理介绍
Spring在3.2版本增加了一个注解@ControllerAdvice,可以与@ExceptionHandler、@InitBinder、@ModelAttribute 等注解注解配套使用。不过跟异常处理相关的只有注解@ExceptionHandler,从字面上看,就是异常处理器 的意思
2、原理和目标
简单的说,该注解可以把异常处理器应用到所有控制器,而不是单个控制器。借助该注解,我们可以实现:在独立的某个地方,比如单独一个类,定义一套对各种异常的处理机制,然后在类的签名加上注解@ControllerAdvice,统一对 不同阶段的、不同异常 进行处理。这就是统一异常处理的原理。
对异常按阶段进行分类,大体可以分成:进入Controller前的异常 和 Service 层异常
三、项目中的异常
1、制造异常
屏蔽IntegralGrade实体类 中的 @TableField注解
@ApiModelProperty(value = "逻辑删除(1:已删除,0:未删除)")
//@TableField("is_deleted")
@TableLogic
private Boolean deleted;
2、Swagger中测试
测试列表查询功能,查看结果,发生错误,显示响应失败,因为deleted在数据库找不到对应的列
四、实现统一异常处理
目标:我们想让异常结果也显示为统一的返回结果对象,并且统一处理系统的异常信息,那么需要进行统一异常处理。
1、创建自定义业务异常类
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
private int code;
private String msg;
public BusinessException(int code) {
this.code = code;
this.msg = MessageUtils.getMessage(code);
}
public BusinessException(int code, String... params) {
this.code = code;
this.msg = MessageUtils.getMessage(code, params);
}
public BusinessException(int code, Throwable e) {
super(e);
this.code = code;
this.msg = MessageUtils.getMessage(code);
}
public BusinessException(int code, Throwable e, String... params) {
super(e);
this.code = code;
this.msg = MessageUtils.getMessage(code, params);
}
public BusinessException(String msg) {
super(msg);
this.code = ErrorCode.INTERNAL_SERVER_ERROR;
this.msg = msg;
}
public BusinessException(String msg, Throwable e) {
super(msg, e);
this.code = ErrorCode.INTERNAL_SERVER_ERROR;
this.msg = msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}
异常工具类
public class ExceptionUtils {
/**
* 获取异常信息
* @param ex 异常
* @return 返回异常信息
*/
public static String getErrorStackTrace(Exception ex){
StringWriter sw = null;
PrintWriter pw = null;
try {
sw = new StringWriter();
pw = new PrintWriter(sw, true);
ex.printStackTrace(pw);
}finally {
try {
if(pw != null) {
pw.close();
}
} catch (Exception e) {
}
try {
if(sw != null) {
sw.close();
}
} catch (IOException e) {
}
}
return sw.toString();
}
}
异常代码类
public interface ErrorCode {
int INTERNAL_SERVER_ERROR = 500;
int UNAUTHORIZED = 401;
int NOT_NULL = 10001;
......
}
2、创建统一返回结果类
@ApiModel(value = "响应")
@Data
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 编码:0表示成功,其他值表示失败
*/
@ApiModelProperty(value = "编码:0表示成功,其他值表示失败")
private int code = 0;
/**
* 消息内容
*/
@ApiModelProperty(value = "消息内容")
private String msg = "success";
/**
* 响应数据
*/
@ApiModelProperty(value = "响应数据")
private T data;
public Result<T> ok(T data) {
this.setData(data);
return this;
}
public boolean success(){
return code == 0;
}
public Result<T> error() {
this.code = ErrorCode.INTERNAL_SERVER_ERROR;
this.msg = MessageUtils.getMessage(this.code);
return this;
}
public Result<T> error(int code) {
this.code = code;
this.msg = MessageUtils.getMessage(this.code);
return this;
}
public Result<T> error(int code, String msg) {
this.code = code;
this.msg = msg;
return this;
}
public Result<T> error(String msg) {
this.code = ErrorCode.INTERNAL_SERVER_ERROR;
this.msg = msg;
return this;
}
}
3、创建异常日志表、实体类及实现
实体类
@Data
@EqualsAndHashCode(callSuper=false)
@TableName("sys_log_error")
public class SysLogErrorEntity extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 请求URI
*/
private String requestUri;
/**
* 请求方式
*/
private String requestMethod;
/**
* 请求参数
*/
private String requestParams;
/**
* 用户代理
*/
private String userAgent;
/**
* 操作IP
*/
private String ip;
/**
* 异常信息
*/
private String errorInfo;
}
实现
@Service
public class SysLogErrorServiceImpl extends BaseServiceImpl<SysLogErrorDao, SysLogErrorEntity> implements SysLogErrorService {
@Override
public PageData<SysLogErrorDTO> page(Map<String, Object> params) {
IPage<SysLogErrorEntity> page = baseDao.selectPage(
getPage(params, Constant.CREATE_DATE, false),
getWrapper(params)
);
return getPageData(page, SysLogErrorDTO.class);
}
@Override
public List<SysLogErrorDTO> list(Map<String, Object> params) {
List<SysLogErrorEntity> entityList = baseDao.selectList(getWrapper(params));
return ConvertUtils.sourceToTarget(entityList, SysLogErrorDTO.class);
}
private QueryWrapper<SysLogErrorEntity> getWrapper(Map<String, Object> params){
QueryWrapper<SysLogErrorEntity> wrapper = new QueryWrapper<>();
return wrapper;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void save(SysLogErrorEntity entity) {
insert(entity);
}
}
4、创建统一异常处理器,并保存系统异常信息
@RestControllerAdvice
@Slf4j
public class AdminExceptionHandler {
@Autowired
private SysLogErrorService sysLogErrorService;
/**
* 处理自定义异常
*/
@ExceptionHandler(BusinessException.class)
public Result handleRenException(BusinessException ex){
log.error(ex.getMessage(), ex);
Result result = new Result();
result.error(ex.getCode(), ex.getMsg());
return result;
}
@ExceptionHandler(DuplicateKeyException.class)
public Result handleDuplicateKeyException(DuplicateKeyException ex){
Result result = new Result();
result.error(ErrorCode.DB_RECORD_EXISTS);
return result;
}
@ExceptionHandler(Exception.class)
public Result handleException(Exception ex){
log.error(ex.getMessage(), ex);
saveLog(ex);
return new Result().error();
}
/**
* 保存异常日志
*/
private void saveLog(Exception ex){
SysLogErrorEntity errorEntity = new SysLogErrorEntity();
//请求相关信息
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
errorEntity.setIp(IpUtils.getIpAddr(request));
errorEntity.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
errorEntity.setRequestUri(request.getRequestURI());
errorEntity.setRequestMethod(request.getMethod());
Map<String, String> params = HttpContextUtils.getParameterMap(request);
if(MapUtil.isNotEmpty(params)){
errorEntity.setRequestParams(JsonUtils.toJsonString(params));
}
//异常信息
errorEntity.setErrorInfo(ExceptionUtils.getErrorStackTrace(ex));
//保存
sysLogErrorService.save(errorEntity);
}
}