springboot統一接口返回數據

一,沒有異常的情況,正常返回數據

希望接口統一返回的數據格式如下:

{
    "status": 0,
    "msg": "成功",
    "data": null
}

和接口數據對應的bean

/**
 * 統一返回結果的實體
 * @param <T>
 */
public class Result<T> implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 錯誤碼
     */
    private int status;

    /**
     * 提示消息
     */
    private String msg;

    /**
     * 返回的數據體
     */
    private T data;

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

操作Result實體的工具類

/**
 * 生成result的工具類,避免重複代碼
 */
public class ResultUtils {

    /**
     * 成功時生成result的方法,有返回數據
     */
    public static <T> Result<T> success(T t){
        Result<T> result = new Result<>();
        result.setStatus(ResultEnum.SUCCESS.getCode());
        result.setMsg(ResultEnum.SUCCESS.getMsg());
        result.setData(t);
        return result;
    }

    /**
     * 成功時生成result的方法,無返回數據
     */
    public static <T> Result<T> success(){
        return success(null);
    }

    /**
     * 失敗時生成result的方法
     */
    public static <T> Result<T> error(int status, String msg){
        Result<T> result = new Result<>();
        result.setStatus(status);
        result.setMsg(msg);
        return result;
    }

}

封裝錯誤碼和錯誤消息的枚舉類

/**
 * 所有返回結果的枚舉
 */
public enum ResultEnum {

    UNKNOWN_ERROR(-1, "未知錯誤"),
    SUCCESS(0, "成功"),
    BASIC_INFO_ID_IS_EMPTY(600, "基本信息中BasicInfoId爲空"),
    BASIC_INFO_ADD_TO_DATABASE_FAILURE(601, "向數據庫添加基本信息失敗"),
    DETAILS_DATA_BASIC_INFO_ID_IS_EMPTY(602, "測試數據中BasicInfoId爲空"),
    DETAILS_DATA_ADD_TO_DATABASE_FAILURE(603, "向數據庫添加測試數據失敗");

    ResultEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    private int code;

    private String msg;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    @Override
    public String toString() {
        return "ResultEnum{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                '}';
    }

}

統一封裝返回結果的切面

之所以需要這個切面,是爲了避免每個Controller方法中都要調用ResultUtils.success()。有了這個切面,Controller可以和原來一樣正常返回對象,字符串,void,在切面裏面將結果封裝成Result實體,而不需要每個Controller方法都返回Result實體。

/**
 * 統一處理返回結果的切面,避免每個controller方法裏面都要調用ResultUtils.success()這句話
 * 統一在這個切面裏面調用
 */
@ControllerAdvice
public class MyResponseAdvice implements ResponseBodyAdvice<Object> {

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * Whether this component supports the given controller method return type
     * and the selected {@code HttpMessageConverter} type.
     *
     * @param returnType    the return type
     * @param converterType the selected converter type
     * @return {@code true} if {@link #beforeBodyWrite} should be invoked;
     * {@code false} otherwise
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    /**
     * Invoked after an {@code HttpMessageConverter} is selected and just before
     * its write method is invoked.
     *
     * @param body                  the body to be written
     * @param returnType            the return type of the controller method
     * @param selectedContentType   the content type selected through content negotiation
     * @param selectedConverterType the converter type selected to write to the response
     * @param request               the current request
     * @param response              the current response
     * @return the body that was passed in or a modified (possibly new) instance
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if(body instanceof Result){ //發生異常之後,異常處理器裏面返回的已經是Result了
            return body;
        }else if(body instanceof String){ //String屬於特殊情況,需要單獨處理,否則會報錯
            try {
                return objectMapper.writeValueAsString(ResultUtils.success(body));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
                return ResultUtils.error(ResultEnum.UNKNOWN_ERROR.getCode(), e.getMessage());
            }
        }
        return ResultUtils.success(body);
    }
}

 


二,有異常的情況下

    service層爲了自動回滾事務,會拋出一些自定義的RuntimeException。默認情況下,只有RuntimeException纔會回滾事務。如果Controller裏面直接處理service層拋出的異常,則Controller裏面到處都是try catch塊,代碼會很難看。將異常集中在一個地方處理會好很多。
    springboot中是通過@ControllerAdvice和@ExceptionHandler來完成統一異常處理的。這2個註解只能處理Controller攔截器中拋出的異常,其他地方拋出的異常(比如Filter中拋出的異常),無法捕獲。其他地方拋出的異常會轉到/error的Controller方法來處理,默認是BasicErrorController來處理,爲了能處理其他地方拋出的異常,我們會自定義ErrorController。

統一的異常處理類,處理Controller和攔截器拋出的異常

/**
 * 統一的異常處理類
 */
@ControllerAdvice
public class MyExceptionHandler {

    /**
     * 轉發到/error,表示由BasicErrorController處理,
     * BasicErrorController是由springboot自動裝配到容器中的
     */
    /*@ExceptionHandler(BasicInfoException.class)
    public String handleException(Exception ex, HttpServletRequest request){
        request.setAttribute("javax.servlet.error.status_code", 401);
        request.setAttribute("exMsg", ex.getMessage());
        return "forward:/error";
    }*/


    /**
     * 處理基本信息相關的異常
     */
    @ExceptionHandler(BasicInfoException.class)
    @ResponseBody
    public Result handleBasicInfoException(BasicInfoException ex){
        return ResultUtils.error(ex.getCode(), ex.getMessage());
    }

    /**
     * 處理測試數據相關的異常
     */
    @ExceptionHandler(DetailsDataException.class)
    @ResponseBody
    public Result handleDetailsDataException(DetailsDataException ex){
        return ResultUtils.error(ex.getCode(), ex.getMessage());
    }


    /**
     * 處理未知異常
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Result handleUnKnowException(Exception ex){
        return ResultUtils.error(ResultEnum.UNKNOWN_ERROR.getCode(), ex.getMessage());
    }

}

自定義的異常類示例

public class BasicInfoException extends RuntimeException {

    private int code;

    public BasicInfoException(int code, String msg){
        super(msg);
        this.code = code;
    }

    public int getCode() {
        return code;
    }
}

處理其他地方拋出的異常(不是Controller和攔截器拋出的異常),自定義ErrorController


/**
 * 自定義ErrorController,處理其他地方拋出的異常(不是Controller和攔截器拋出的異常)
 */
@Controller
public class MyBasicErrorController extends AbstractErrorController {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 可以通過@Value獲取到
     */
    @Value("${server.error.path}")
    private String myPath;

    private final ErrorProperties errorProperties;

    private ErrorAttributes mErrorAttributes;

    public MyBasicErrorController(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
        super(errorAttributes);
        this.errorProperties = serverProperties.getError();
        this.mErrorAttributes = errorAttributes;
    }

    //@RequestMapping(value = "/error")
    @RequestMapping("${server.error.path}") //從properties文件中獲取
    @ResponseBody
    public Result<Object> error(HttpServletRequest request) throws Throwable {
        logger.debug("myPath = " + myPath);

        //發生錯誤之後直接將異常拋出去,異常會到統一異常處理器中處理
        WebRequest webRequest = new ServletWebRequest(request);
        Throwable throwable = this.mErrorAttributes.getError(webRequest).getCause();
        throw throwable;
        /*UserException ex;
        if(throwable instanceof UserException){
            ex = (UserException) throwable;
            throw ex;
        }else{
            throw throwable;
        }*/
        /*HttpStatus status = getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return ResultUtils.error(status.value(), status.name());
        }
        Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        return ResultUtils.error((Integer) body.get("status"), (String)body.get("message"));*/
    }

    /**
     * Determine if the stacktrace attribute should be included.
     * @param request the source request
     * @param produces the media type produced (or {@code MediaType.ALL})
     * @return if the stacktrace attribute should be included
     */
    private boolean isIncludeStackTrace(HttpServletRequest request, MediaType produces) {
        ErrorProperties.IncludeStacktrace include = getErrorProperties().getIncludeStacktrace();
        if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {
            return true;
        }
        if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {
            return getTraceParameter(request);
        }
        return false;
    }

    /**
     * Provide access to the error properties.
     * @return the error properties
     */
    private ErrorProperties getErrorProperties() {
        return this.errorProperties;
    }


    /**
     * Returns the path of the error page.
     *
     * @return the error path
     */
    @Override
    public String getErrorPath() {
        return this.errorProperties.getPath();
    }
}

 

自定義ErrorController中錯誤處理的方法中,也可以直接將異常拋出,這樣異常就會交給統一異常處理器進行處理。

 //@RequestMapping(value = "/error")
    @RequestMapping("${server.error.path}") //從properties文件中獲取
    @ResponseBody
    public Result<Object> error(HttpServletRequest request) throws Throwable {
        logger.debug("myPath = " + myPath);

        //發生錯誤之後直接將異常拋出去,異常會到統一異常處理器中處理
        WebRequest webRequest = new ServletWebRequest(request);
        Throwable throwable = this.mErrorAttributes.getError(webRequest).getCause();
        UserException ex;
        if(throwable instanceof UserException){
            ex = (UserException) throwable;
            throw ex;
        }else{
            throw throwable;
        }
        /*HttpStatus status = getStatus(request);
        if (status == HttpStatus.NO_CONTENT) {
            return ResultUtils.error(status.value(), status.name());
        }
        Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        return ResultUtils.error((Integer) body.get("status"), (String)body.get("message"));*/
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章