SpringBoot工具篇--统一数据结构及返回(controller & exception)

前言: 本文通过自定义注解方式实现对后端接口统一进行数据的封装和返回;

1 自定义数据返回类:

1.1 定义注解,标识在方法和类上,只要加了这个注解就进行统一结构数据的返回

import org.springframework.web.bind.annotation.ResponseBody;
import java.lang.annotation.*;
/**
 * 统一返回
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@ResponseBody
public @interface CommonResponseBody {
}

1.2 定义返回的结构体:
BizHttpStatus :

public enum BizHttpStatus {
    HTTP_STATUS_200(200),
    HTTP_STATUS_400(400),
    HTTP_STATUS_401(401),
    HTTP_STATUS_4011(4011), // 没权限
    HTTP_STATUS_4012(4012), // 小程序没权限
    HTTP_STATUS_403(403),
    HTTP_STATUS_404(404),  // 未找到
    HTTP_STATUS_405(405),   // 黑名单
    HTTP_STATUS_406(406),   // 休息一会
    HTTP_STATUS_500(500);
    private int status;

    BizHttpStatus(int status) {
        this.status = status;
    }

    public int getStatus() {
        return status;
    }
}

ResponseDTO:

@Data
@Accessors(chain = true)
public class ResponseDTO<T> {
private static final long serialVersionUID = 3918877423924837166L;

    private int code = 200;
    private T body;
    private String msg;
    private boolean redirect = true;
    private List<String> message = new ArrayList<>();
    private int errNum;
    private String errorMsg;
    private Object errorObject;

    private boolean success = true;
    
public static <T> ResponseDTO defaultResponse(T t) {
        return new ResponseDTO().setCode(BizHttpStatus.HTTP_STATUS_200.getStatus())
        .setSuccess(Boolean.TRUE).setBody(t);
    }
}

2 数据处理:
2.1 数据封装和返回:

import com.alibaba.fastjson.JSON;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.lang.annotation.Annotation;

/**
 * @desc 统一处理返回结果
 */
@RestControllerAdvice
public class CommonResponseBodyAdvice implements ResponseBodyAdvice {
    private static final Class<? extends Annotation> ANNOTATION_TYPE = CommonResponseBody.class;

    @Override
    public boolean supports(MethodParameter returnType, @Nullable Class aClass) {
        return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ANNOTATION_TYPE) || returnType.hasMethodAnnotation(ANNOTATION_TYPE);
    }

    @Override
    public Object beforeBodyWrite(Object body, @Nullable MethodParameter methodParameter,
                                  @Nullable MediaType mediaType, @Nullable Class aClass,
                                  @Nullable ServerHttpRequest serverHttpRequest, @Nullable ServerHttpResponse serverHttpResponse) {
        if (body instanceof ResponseDTO) {
            return body;
        }
        if (body instanceof String) {
            return JSON.toJSONString(ResponseDTO.defaultResponse(body));
        }
        return ResponseDTO.defaultResponse(body);
    }
}

3 异常统一处理和返回:
3.1 定义异常枚举类BizErrorEnum:

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum BizErrorEnum {
    //系统通用错误
    暂无权限(30001001, "暂无权限"),
    业务配置缺失(30001002, "业务配置缺失"),
    系统配置缺失(30001003, "系统配置缺失"),
    系统内部错误(30001004, "系统内部错误!"),
    空指针错误(30001005, "空指针错误!"),
    类型转换错误(30001006, "类型转换错误!"),
    文件未找到错误(30001007, "文件未找到错误!"),
    数字格式错误(30001008, "数字格式错误!"),
    sql错误(30001009, "sql错误!"),
    类型不存在错误(30001010, "类型不存在错误!"),
    IO错误(30001011, "IO错误!"),
    未知方法错误(30001012, "未知方法错误!"),
    越界错误(30001013, "越界错误!"),
    sql语法错误(30001014, "sql语法错误!"),
    无法注入bean错误(30001015, "无法注入bean错误!"),
    Http消息不可读错误(30001016, "Http消息不可读错误!"),
    服务器异常400错误(30001017, "服务器异常400错误!"),
    服务器异常500错误(30001018, "服务器异常500错误!"),
    栈溢出错误(30001019, "栈溢出错误!"),
    除数错误(30001020, "除数错误!"),
    安全异常(30001021, "安全异常!"),
    其他错误(30001022, "其他错误!"),
    文件路径错误(30001023, "文件路径错误!"),
    参数缺失(30001024, "参数缺失"),

    token验证失败_不存在(401001, "token验证失败,token不存在"),
    ILLEGAL_PARAMS(403004, "数据不合法"),
    ILLEGAL_PARAMS_NOT_EXITST(404001, "请求参数不合法"),
    user_is_not_auth(401003, "未授权用户!"),
    url_is_not_auth(401004, "活动链接不存在!"),

    权限变更(401005, "权限变更!"),
    
    // 默认错误
    DEFAULT(500_00_001, "系统内部错误"),
    ILLEGAL_CORP_NOT_EXITST(403005, "数据不合法,当前企业应用不存在"),
    ILLEGAL_SECRET_DATA_NOT_EXITST(403006, "数据不合法,解密数据存在错误,请核对参数"),

    // 鉴权
    NOOPENID(403_00_001, "用户未默认授权"),
    NOAUTHOPHONE(403_00_002, "用户未授权手机号"),

    ;
    private int errorCode;
    private String errorMessage;

    public static BizErrorEnum getBizErrorEnumByErrorMessage(String errorMessage) {
        BizErrorEnum value = null;
        for (BizErrorEnum v1 : BizErrorEnum.values()) {
            if (errorMessage.equals(v1.getErrorMessage())) {
                value = v1;
                break;
            }
        }
        return value;
    }
}

3.2 定义异常类 BizException:

import java.util.Map;
public class BizException extends RuntimeException {

    private static final long serialVersionUID = 1L;
    private int errorCode;
    private String errorMessage;
    private Map errorMap;

    public BizException() {
        super();
    }

    public BizException(String errorMessage) {
        super();
        this.errorMessage = errorMessage;
    }

    public BizException(int errorCode, String errorMessage) {
        super();
        this.errorCode = errorCode;
        this.errorMessage = errorMessage;
    }

    public BizException(BizErrorEnum bizError) {
        super();
        this.errorCode = bizError.getErrorCode();
        this.errorMessage = bizError.getErrorMessage();
    }

    public BizException(BizErrorEnum bizError, String detailMessage) {
        super();
        this.errorCode = bizError.getErrorCode();
        this.errorMessage = bizError.getErrorMessage() + detailMessage;
    }

    public BizException(BizErrorEnum bizError, String detailMessage, Map errorMap) {
        super();
        this.errorCode = bizError.getErrorCode();
        this.errorMessage = bizError.getErrorMessage() + detailMessage;
        this.errorMap = errorMap;
    }

    public int getErrorCode() {
        return errorCode;
    }

    public void setErrorCode(int errorCode) {
        this.errorCode = errorCode;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }

    public Map getErrorMap() {
        return errorMap;
    }

    @Override
    public String toString() {
        return "BizException [ errorCode-" + errorCode + ", errorMessage-" + errorMessage + "]";
    }
}

3.3 定义异常处理类ExceptionsHandler:
ExceptionFormatUtil 异常解析工具类:

public class ExceptionFormatUtil {
    //打印异常堆栈信息
    public static String getStackTraceString(Throwable ex) {//(Exception ex) {
        StackTraceElement[] traceElements = ex.getStackTrace();

        StringBuilder traceBuilder = new StringBuilder();

        if (traceElements != null && traceElements.length > 0) {
            for (StackTraceElement traceElement : traceElements) {
                traceBuilder.append(traceElement.toString());
                traceBuilder.append("\n");
            }
        }

        return traceBuilder.toString();
    }

    //构造异常堆栈信息
    public static String buildErrorMessage(Exception ex) {

        String result;
        String stackTrace = getStackTraceString(ex);
        String exceptionType = ex.toString();
        String exceptionMessage = ex.getMessage();

        result = String.format("%s : %s \r\n %s", exceptionType, exceptionMessage, stackTrace);

        return result;
    }
}

ExceptionsHandler:

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

@Slf4j
@RestControllerAdvice
public class ExceptionsHandler {
    /**
     * 自定义异常
     *
     * @param exception
     * @return
     */
    @ExceptionHandler(BizException.class)
    public ResponseDTO CommonException(BizException exception) {
        log.error("业务异常:{}", exception.toString(), exception);
        return new ResponseDTO()
                .setCode(exception.getErrorCode())
                .setMsg(exception.getErrorMessage())
                .setSuccess(false)
                .setErrorObject(exception.getErrorMap());
    }

    /**
     * 运行时异常
     */
    @ExceptionHandler(RuntimeException.class)
    public ResponseDTO runtimeExceptionHandler(RuntimeException ex) {
        log.error("运行时异常:{}", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.系统内部错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    /**
     * 空指针异常
     */
    @ExceptionHandler(NullPointerException.class)
    public ResponseDTO nullPointerExceptionHandler(NullPointerException ex) {
        log.error("空指针异常:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.空指针错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    /**
     * 类型转换异常
     */
    @ExceptionHandler(ClassCastException.class)
    public ResponseDTO classCastExceptionHandler(ClassCastException ex) {
        log.error("类型转换异常:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.类型转换错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    /**
     * 文件未找到异常
     */
    @ExceptionHandler(FileNotFoundException.class)
    public ResponseDTO FileNotFoundException(FileNotFoundException ex) {
        log.error("文件未找到异常:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.文件未找到错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    /**
     * 数字格式异常
     */
    @ExceptionHandler(NumberFormatException.class)
    public ResponseDTO NumberFormatException(NumberFormatException ex) {
        log.error("数字格式异常:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.数字格式错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    /**
     * 安全异常
     */
    @ExceptionHandler(SecurityException.class)
    public ResponseDTO SecurityException(SecurityException ex) {
        log.error("安全异常:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.安全异常, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    /**
     * sql异常
     */
    @ExceptionHandler(SQLException.class)
    public ResponseDTO SQLException(SQLException ex) {
        log.error("sql异常:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.sql错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    /**
     * 类型不存在异常
     */
    @ExceptionHandler(TypeNotPresentException.class)
    public ResponseDTO TypeNotPresentException(TypeNotPresentException ex) {
        log.error("类型不存在异常:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.类型不存在错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    /**
     * IO异常
     */
    @ExceptionHandler({IOException.class})
    public ResponseDTO iOExceptionHandler(IOException ex) {
        log.error("IO异常:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.IO错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }


    /**
     * 未知方法异常
     */
    @ExceptionHandler(NoSuchMethodException.class)
    public ResponseDTO noSuchMethodExceptionHandler(NoSuchMethodException ex) {
        log.error("未知方法异常:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.未知方法错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    /**
     * 数组越界异常
     */
    @ExceptionHandler(IndexOutOfBoundsException.class)
    public ResponseDTO indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException ex) {
        log.error("越界异常:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.越界错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    /**
     * sql语法错误异常
     */
    @ExceptionHandler(BadSqlGrammarException.class)
    public ResponseDTO BadSqlGrammarException(BadSqlGrammarException ex) {
        log.error("sql语法错误异常:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.sql语法错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    /**
     * 无法注入bean异常
     */
    @ExceptionHandler(NoSuchBeanDefinitionException.class)
    public ResponseDTO NoSuchBeanDefinitionException(NoSuchBeanDefinitionException ex) {
        log.error("无法注入bean异常 :{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.无法注入bean错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    /**
     * Http消息不可读异常
     */
    @ExceptionHandler({HttpMessageNotReadableException.class})
    public ResponseDTO requestNotReadable(HttpMessageNotReadableException ex) {
        log.error("400错误..requestNotReadable:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.Http消息不可读错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    /**
     * 400错误
     */
    @ExceptionHandler({TypeMismatchException.class})
    public ResponseDTO requestTypeMismatch(TypeMismatchException ex) {
        log.error("400错误..TypeMismatchException:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.服务器异常400错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    /**
     * 500错误
     */
    @ExceptionHandler({ConversionNotSupportedException.class, HttpMessageNotWritableException.class})
    public ResponseDTO server500(RuntimeException ex) {
        log.error("500错误:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.服务器异常500错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    /**
     * 栈溢出
     */
    @ExceptionHandler({StackOverflowError.class})
    public ResponseDTO requestStackOverflow(StackOverflowError ex) {
        log.error("栈溢出:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.栈溢出错误, Collections.singletonList(ex.getMessage()));
    }

    /**
     * 除数不能为0
     */
    @ExceptionHandler({ArithmeticException.class})
    public ResponseDTO arithmeticException(ArithmeticException ex) {
        log.error("除数不能为0:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.除数错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    /**
     * 其他错误
     */
    @ExceptionHandler({Exception.class})
    public ResponseDTO exception(Exception ex) {
        log.error("其他错误:{} ", ex.getMessage(), ex);
        return ResponseDTO.errorResponse(BizErrorEnum.其他错误, Collections.singletonList(ExceptionFormatUtil.buildErrorMessage(ex)));
    }

    @ExceptionHandler(value = BindException.class)
    public ResponseDTO handleBindException(BindException ex) {
        return handleBindingResultError(ex.getBindingResult());
    }

    private ResponseDTO handleBindingResultError(BindingResult bindingResult) {
        List<String> message = new ArrayList<>();
        for (FieldError item : bindingResult.getFieldErrors()) {
            String itemMessage = item.getDefaultMessage();
            message.add(itemMessage);
        }
        return ResponseDTO.response500().setMsg(message.toString()).setSuccess(false);
    }

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResponseDTO handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        return handleBindingResultError(ex.getBindingResult());
    }

    @ExceptionHandler(value = ConstraintViolationException.class)
    public ResponseDTO handleConstraintViolationException(ConstraintViolationException ex) {
        List<String> message = new ArrayList<>();
        Set<ConstraintViolation<?>> constraintViolations = ex.getConstraintViolations();
        if (!CollectionUtils.isEmpty(constraintViolations)) {
            constraintViolations.forEach(v -> message.add(v.getMessage()));
        }

        return ResponseDTO.errorResponse(BizErrorEnum.ILLEGAL_PARAMS, message);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值