前幾天搬磚的時候,發現全部接口方法都定義了同樣的返回值,不能真正地將業務邏輯表達出來,沒有達到「望文生意」的效果。着手改造一下。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
SpringMVC
中咱們會返回字符串來匹配模板,雖然如今都是先後端分離的項目,可是仍是按照約定俗成或者是第一反應,將String
排除。Result
的方法:咱們已經作了封裝,不須要在封裝一次了。Swagger
。通過以上這種處理,咱們在寫接口的時候就能夠放心大膽的定義業務須要的返回值了,真正的實現了接口方法就能夠描述業務的初衷。code
可是這樣作會出現一個問題,好比Spring Data Jpa實體使用@JsonView時,接口就會返回空,不知道哪位路過的大神能夠指導我一下。
除了這種方式,你們還能夠經過過濾器來實現這個功能,這裏就不作說明了。