Spring Boot 接口封裝統一返回格式

前幾天搬磚的時候,發現全部接口方法都定義了同樣的返回值,不能真正地將業務邏輯表達出來,沒有達到「望文生意」的效果。着手改造一下。java

雖然標題是Spring Boot,可是這個接口在包spring-webmvc.jar下(請原諒我這個標題黨)。ResponseBodyAdvice接口類路徑:web

org.springframework.web.servlet.mvc.method.annotation

首先,咱們須要定義一個統一的狀態碼ResultCode,來統一工程中異常狀況的描述:spring

/**
 * 統一狀態返回碼
 * 三級狀態碼不知足時,可返回二級宏觀碼,依次類推
 * 狀態碼參考 alibaba 《JAVA開發手冊-泰山版》
 */
public enum ResultCode {

    OK("00000", "成功"),

    /** 一級宏觀錯誤碼 */
    CLIENT_ERROR("A0001", "用戶端錯誤 "),

    /** 二級宏觀錯誤碼 */
    USER_REGISTER_ERROR("A0100", "用戶註冊錯誤"),

    USER_DISAGREE_PRIVACY_PROTOCOL("A0101", "用戶未贊成隱私協議"),

    REGION_REGISTER_LIMITED("A0102","註冊國家或地區受限"),

    VALIDATE_USERNAME_FAILED("A0110","用戶名校驗失敗"),

    USERNAME_EXISTED("A0111","用戶名已存在"),

    /* 中間還有好多,鑑於篇幅,就不貼出來了 */
    
    MAIL_NOTICE_FAILED("C0503", "郵件提醒服務失敗");

    private String status;
    private String message;

    ResultCode(String status, String message) {
        this.status = status;
        this.message = message;
    }

    public String getStatus() {
        return status;
    }

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

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "ResultCode{" +
                "status='" + status + '\'' +
                ", message='" + message + '\'' +
                '}';
    }
}

枚舉的好處我就很少說了,想必你們也常常使用HttpStatus這個枚舉。後端

其次,咱們封裝一個數據傳輸對象Result,這個類就用來封裝咱們統一的返回格式:mvc

import com.jason.enums.ResultCode;
import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.springframework.data.domain.Page;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * 統一返回DTO
 * 經過 JsonView 儘量控制返回格式的精簡,酌情使用
 */
@ApiModel
public class Result {

    public interface commonResult{};

    public interface standardResult extends commonResult{};

    @ApiModelProperty(value = "status", name = "響應狀態碼")
    private String status;

    @ApiModelProperty(value = "message", name = "響應信息")
    private String message;

    @ApiModelProperty(value = "body", name = "響應內容")
    private Object body;

    public Result() {
        this.status = ResultCode.OK.getStatus();
        this.message = ResultCode.OK.getMessage();
    }

    public Result(ResultCode resultCode) {
        this.status = resultCode.getStatus();
        this.message = resultCode.getMessage();
    }

    public Result(ResultCode resultCode, String message) {
        this(resultCode);
        this.message = message;
    }

    public Result(Object body) {
        this.status = ResultCode.OK.getStatus();
        this.message = ResultCode.OK.getMessage();
        this.body = body;
    }

    public Result(ResultCode resultCode, Object body) {
        this(body);
        this.status = resultCode.getStatus();
        this.message = resultCode.getMessage();
    }

    public Result(Collection collection) {
        this.status = ResultCode.OK.getStatus();
        this.message = ResultCode.OK.getMessage();
        this.body = collection;
    }

    public Result(ResultCode resultCode, Collection collection) {
        this(collection);
        this.status = resultCode.getStatus();
        this.message = resultCode.getMessage();
    }

    public Result(Page page) {
        this.status = ResultCode.OK.getStatus();
        this.message = ResultCode.OK.getMessage();
        Map<String, Object> info = new HashMap<>(8);
        info.put("totalItem", page.getTotalElements());
        info.put("pageSize", page.getNumber());
        info.put("pageNum", page.getSize());
        info.put("totalPage", page.getTotalPages());
        info.put("item", page.getContent());
        this.body = info;
    }

    public Result(ResultCode resultCode, Page page) {
        this(page);
        this.status = resultCode.getStatus();
        this.message = resultCode.getMessage();
    }

    @JsonView(commonResult.class)
    public String getStatus() {
        return status;
    }

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

    @JsonView(commonResult.class)
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @JsonView(standardResult.class)
    public Object getBody() {
        return body;
    }

    public void setBody(Object body) {
        this.body = body;
    }

    @Override
    public String toString() {
        return "Result{" +
                "status='" + status + '\'' +
                ", message='" + message + '\'' +
                ", body=" + body +
                '}';
    }
}

這個類每一個人定義的方式不同,包括構造方法或者是否用static修飾,你們均可以根據自身狀況實現。前後端分離

最後,咱們再實現接口ResponseBody,能夠根據一些條件來控制:dom

import com.jason.dto.Result;
import com.jason.enums.ResultCode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * 統一返回對象配置類
 * create by Jason
 */
@ControllerAdvice
public class ResultBodyConfig implements ResponseBodyAdvice<Object> {

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

    private final static String PACKAGE_PATH = "com.jason.component";

    /**
     * 針對如下狀況 不作 統一包裝處理
     * 1.返回值爲 void 的方法
     * 2.返回值爲 String 類型的方法
     * 3.返回值爲 Result 類型的方法
     * 4.在包路徑 PACKAGE_PATH 之外的方法
     * @param methodParameter
     * @param aClass
     * @return
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return !methodParameter.getMethod().getReturnType().isAssignableFrom(Void.TYPE)
                && !methodParameter.getMethod().getReturnType().isAssignableFrom(String.class)
                && !methodParameter.getMethod().getReturnType().isAssignableFrom(Result.class)
                && methodParameter.getDeclaringClass().getPackage().getName().contains(PACKAGE_PATH);
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof Result) {
            return body;
        }
        return new Result(ResultCode.OK, body);
    }
}

方法supports返回true則會執行beforeBodyWrite方法,不然會跳過,繼續按照原來接口方法的返回值進行返回。ide

這裏談一下我過濾這些條件的想法:this

  • 返回值爲 Void 的方法:返回值爲void,嗯。
  • 返回值爲 String 的方法: 在SpringMVC中咱們會返回字符串來匹配模板,雖然如今都是先後端分離的項目,可是仍是按照約定俗成或者是第一反應,將String排除。
  • 返回 Result 的方法:咱們已經作了封裝,不須要在封裝一次了。
  • 不在指定包路徑下的方法:用來規避其餘組件,好比Swagger

通過以上這種處理,咱們在寫接口的時候就能夠放心大膽的定義業務須要的返回值了,真正的實現了接口方法就能夠描述業務的初衷。code

可是這樣作會出現一個問題,好比Spring Data Jpa實體使用@JsonView時,接口就會返回空,不知道哪位路過的大神能夠指導我一下。

除了這種方式,你們還能夠經過過濾器來實現這個功能,這裏就不作說明了。

相關文章
相關標籤/搜索