项目中我们是一定要碰到的情况就是无论在控制层,业务层还是Dao层都需要校验一些数据,无论是前端传过来的,还是经过业务处理判断的,如果不合法需要友好的提示给用户,否则用户收到一个NullPointerException这玩意,他可能会很懵逼,再说直接将错误的信息直接暴露给用户,这样的体验也不是太好。通过统一异常拦截,可以手动抛出异常,异常拦截格式化异常返回数据。
一、集成统一异常处理
- 响应结果统一封装类
package com.hld.free.common;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import java.io.Serializable;
@Data
@ApiModel("Http结果响应")
public class HttpResult<T> implements Serializable {
private static String successMessage = "操作成功";
private static String errorMessage = "操作失败";
@ApiModelProperty("响应码")
private int code;
@ApiModelProperty("响应数据")
private T data;
@ApiModelProperty("响应消息")
private String msg;
public HttpResult(){ }
public HttpResult(int code, String msg) {
this.code=code;
this.msg=msg;
}
public HttpResult(int code, String msg, T data) {
this.code=code;
this.msg=msg;
this.data=data;
}
public static HttpResult successResult() {
return new HttpResult(HttpStatus.OK.value(), successMessage);
}
public static HttpResult successResult(String msg) {
return new HttpResult(DmStatus.OK.getHttpStatus().value(), defaultSuccessMsg(msg));
}
public static HttpResult successResult(Object obj) {
return new HttpResult(DmStatus.OK.getHttpStatus().value(), successMessage, obj);
}
public static HttpResult successResult(String msg, Object obj) {
return new HttpResult(DmStatus.OK.getHttpStatus().value(), defaultSuccessMsg(msg), obj);
}
public static HttpResult failureResult() {
return new HttpResult(DmStatus.INTERNAL_ERROR.getHttpStatus().value(), errorMessage);
}
public static HttpResult failureResult(String msg) {
return new HttpResult(DmStatus.INTERNAL_ERROR.getHttpStatus().value(), defautlErrorMsg(msg));
}
public static HttpResult failureResult(Integer code, String msg) {
return new HttpResult(code, defautlErrorMsg(msg));
}
public static HttpResult failureResult(Integer code, String msg,Object obj) {
return new HttpResult(code, defautlErrorMsg(msg),obj);
}
public static HttpResult failureResult(String msg, Object obj) {
return new HttpResult(DmStatus.INTERNAL_ERROR.getHttpStatus().value(), defaultSuccessMsg(msg), obj);
}
private static String defautlErrorMsg(String msg) {
return StringUtils.isEmpty(msg)?errorMessage:msg;
}
private static String defaultSuccessMsg(String msg) {
return StringUtils.isEmpty(msg)?successMessage:msg;
}
}
- 异常状态信息枚举
package com.hld.free.common;
import java.text.MessageFormat;
import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
public enum DmStatus {
OK(HttpStatus.OK, "DM-200-00000", "数据返回正常"),
BAD_REQUEST(HttpStatus.BAD_REQUEST, "DM-400-A0001", "{0}"),
INTERNAL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "DM-500-B0001", "后台服务异常,请联系管理员!"),
NO_REGISTER_ERROR(HttpStatus.UNAUTHORIZED, "DM-500-B0001", "账号未注册"),
NO_BINDING_ERROR(HttpStatus.FORBIDDEN, "DM-500-B0001", "账号未注册"),
SYS_RUNTIME_ERROR(HttpStatus.REQUEST_TIMEOUT, "DM-400-B0002", "系统运行时异常,请联系管理员!"),
DATA_OPT_ERROR(HttpStatus.BAD_REQUEST, "DM-400-B0003", "数据操作异常!{0}"),
TARGET_OBJ_EXCEPTION(HttpStatus.BAD_REQUEST, "DM-400-B0004", "目标对象异常!"),
GATEWAY_TIMEOUT(HttpStatus.GATEWAY_TIMEOUT, "DM-504-B0100", "网关超时【{0}】"),
REQUEST_TIMEOUT(HttpStatus.REQUEST_TIMEOUT, "DM-408-B0101", "系统请求超时【{0}】"),
PARAM_IS_INVALID(HttpStatus.BAD_REQUEST, "DM-400-B0200", "请求参数异常错误!【{0}】"),
CONSTRAINT_VIOLATION_ERROR(HttpStatus.BAD_REQUEST, "DM-400-B0201", "未绑定对象!【{0}】"),
SERVLET_REQUEST_BINDING_ERROR(HttpStatus.BAD_REQUEST, "DM-400-B0202", "缺失必要参数!【{0}】"),
HTTP_METHOD_NOT_ALLOW_ERROR(HttpStatus.METHOD_NOT_ALLOWED, "DM-405-B0203", "HTTP请求方法不被允许【{0}】"),
HTTP_MEDIA_TYPE_NOT_SUPPORTED_ERROR(HttpStatus.UNSUPPORTED_MEDIA_TYPE, "DM-415-B0203", "不支持的媒体类型"),
MAX_UPLOAD_SIZE_EXCEEDED(HttpStatus.BAD_REQUEST, "DM-400-B0300", "文件大小超出【{0}】限制, 请压缩或降低文件质量!!"),
MAX_UPLOAD_TYPE_ERROR(HttpStatus.BAD_REQUEST, "DM-400-B0301", "文件上传类型错误!【{0}】"),
THIRD_SERVER_ERROR(HttpStatus.BAD_REQUEST, "DM-400-C0001", "调用第三方服务出错"),
MIDDLE_CALL_ERROR(HttpStatus.BAD_REQUEST, "DM-400-C0100", "中间服务调用出错"),
MESSAGE_SEND_ERROR(HttpStatus.BAD_REQUEST, "DM-400-C0120", "消息发送失败");
private static final Logger LOG = LoggerFactory.getLogger(DmStatus.class);
private final String errorMessage;
private final String errorCode;
private final HttpStatus httpStatus;
private DmStatus(HttpStatus httpStatus, String errorCode, String errorMessage) {
this.errorMessage = errorMessage;
this.errorCode = errorCode;
this.httpStatus = httpStatus;
}
public String getFormattedErrorMessage(String... params) {
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("<== DmErrorCode.getMessage(%s)", Arrays.toString(params)));
}
MessageFormat mf = new MessageFormat(this.errorMessage);
String result = mf.format(params);
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("==> DmErrorCode.getMessage(%s): %s", Arrays.toString(params), result));
}
return result;
}
public String getErrorMessage() {
return this.errorMessage;
}
public String getErrorCode() {
return this.errorCode;
}
public HttpStatus getHttpStatus() {
return this.httpStatus;
}
}
- 自定义异常处理类
例如:自定义未注册异常
package com.hld.free.config;
import com.hld.free.common.CommonEnum;
import lombok.Data;
/**
* @description: 未注册异常
* @author: hld
* @create: 2020/10/24 13:09
* @update: 2020/10/24 13:09
*/
@Data
public class NoRegisterException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 描述:错误码
*/
private int errorCode;
/**
* 描述:错误信息
*/
private String errorMsg;
public NoRegisterException() {
}
public NoRegisterException(String errorMsg) {
super(errorMsg);
this.errorMsg = errorMsg;
}
public NoRegisterException(int errorCode, String errorMsg) {
super(String.valueOf(errorCode));
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
public NoRegisterException(int errorCode, String errorMsg, Throwable cause) {
super(String.valueOf(errorCode), cause);
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
}
- 统一异常拦截
统一异常拦截主要由@ControllerAdvice 注解实现
package com.hld.free.config;
import com.hld.free.common.DmStatus;
import com.hld.free.common.HttpResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import javax.validation.ConstraintViolationException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.List;
/**
* @description: 统一异常处理
* @author: fangzhao
* @create: 2020/3/24 13:09
* @update: 2020/3/24 13:09
*/
@Slf4j
@RestControllerAdvice
public class GlobalExceptionAdvice {
/**
* 需指定自定义异常处理类
* @param exception
* @return
*/
@ExceptionHandler(NoRegisterException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public HttpResult<Object> sendErrorResponse(NoRegisterException exception){
log.error("未注册账号异常: {}", getStackTrace(exception));
return HttpResult.failureResult(exception.getErrorCode(),exception.getErrorMsg());
}
/**
* 系统运行时异常
* @param exception RuntimeException
* @return 返回消息
*/
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public HttpResult<Object> sendErrorResponse(RuntimeException exception){
log.error("系统运行时异常: {}", getStackTrace(exception));
return HttpResult.failureResult(DmStatus.SYS_RUNTIME_ERROR.getHttpStatus().value(),exception.getLocalizedMessage());
}
/**
* 单个参数校验(没有绑定对象)
* @param e ConstraintViolationException
* @return 返回的信息
*/
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public HttpResult<Object> otherValidException(ConstraintViolationException e) {
log.error("参数校验异常: {}", getStackTrace(e));
return HttpResult.failureResult(DmStatus.CONSTRAINT_VIOLATION_ERROR.getHttpStatus().value(),e.getLocalizedMessage());
}
/**
* 必填参数缺失
* @param e ServletRequestBindingException
* @return 返回的信息
*/
@ExceptionHandler(ServletRequestBindingException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public HttpResult<Object> servletRequestBinding(ServletRequestBindingException e) {
log.error("必填参数缺失异常: {}", getStackTrace(e));
return HttpResult.failureResult(DmStatus.SERVLET_REQUEST_BINDING_ERROR.getHttpStatus().value(),e.getLocalizedMessage());
}
/**
* 参数错误异常
* @param e 异常参数
* @return 返回的信息
*/
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public HttpResult<Object> handleException(Exception e) {
StringBuilder errorMsg = new StringBuilder();
if (e instanceof MethodArgumentNotValidException) {
MethodArgumentNotValidException validException = (MethodArgumentNotValidException) e;
BindingResult result = validException.getBindingResult();
if (result.hasErrors()) {
List<ObjectError> errors = result.getAllErrors();
errors.forEach(p ->{
FieldError fieldError = (FieldError) p;
errorMsg.append(fieldError.getDefaultMessage()).append(",");
});
}
} else if (e instanceof BindException) {
BindException bindException = (BindException)e;
if (bindException.hasErrors()) {
log.error("请求参数错误: {}", bindException.getAllErrors());
errorMsg.append(bindException.getAllErrors());
}
}
log.error("参数校验异常: {}", getStackTrace(e));
return HttpResult.failureResult(DmStatus.PARAM_IS_INVALID.getHttpStatus().value(),errorMsg.toString());
}
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
public HttpResult<Object> sendErrorResponse(HttpRequestMethodNotSupportedException exception){
log.error("http方法不允许: {}", getStackTrace(exception));
return HttpResult.failureResult(DmStatus.HTTP_METHOD_NOT_ALLOW_ERROR.getHttpStatus().value(), Arrays.toString(exception.getSupportedMethods()));
}
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
@ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
public HttpResult<Object> sendErrorResponse(HttpMediaTypeNotSupportedException exception){
return HttpResult.failureResult(DmStatus.HTTP_MEDIA_TYPE_NOT_SUPPORTED_ERROR.getHttpStatus().value(),exception.getLocalizedMessage());
}
@ExceptionHandler(MaxUploadSizeExceededException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public HttpResult<Object> sendErrorResponse(MaxUploadSizeExceededException exception){
return HttpResult.failureResult(DmStatus.MAX_UPLOAD_SIZE_EXCEEDED.getHttpStatus().value(),exception.getLocalizedMessage());
}
public static String getStackTrace(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw, true);
throwable.printStackTrace(pw);
return sw.getBuffer().toString();
}
}
二、测试统一异常
- 手动抛出异常
/**
*这里只写出部分使用的代码片段
*/
//如果有@IgnoreAuth注解,则不验证token
if (annotation != null) {
return true;
}
//从header中获取token
String token = request.getHeader(ShopCommon.TOKEN);
//token为空
if (StringUtils.isBlank(token)) {
throw new NoRegisterException(401,"请先登录");
}
- 接口返回示例