ServerResponse(伺服器統一響應資料格式)
前言:
其實嚴格來說,ServerResponse應該歸類到common包中。但是我實在太喜歡這玩意兒了。而且用得也非常頻繁,是以忍不住推薦一下。
借此機會,申明一點,這個系列的類并不是都是我原創的,都是我從各個項目中看到的,感覺非常贊,一點點攢起來的。當然後面也有我自己寫的一些工具。重要的是學習,從中學習到知識,就算脫離了這些工具,我們也可以自己寫一個。
場景:
這個場景我真的覺得隻要寫過接口的,都需要這個。
其實,在剛剛接觸代碼的時候,看到大佬接口傳回的JSON。JSON裡面除了必要的data外,還有各種狀态碼,狀态說明什麼的,感覺很厲害。後來漸漸明白了,這個東西是必須的,你不寫試試,看與你互動的大佬會不會把你拍成肉餅。
演進:
1.直接傳回請求的資料:
後端:呀,前端發來的這個請求,資料庫沒有對應資料啊。傳回一個null吧。
前端:大哥,你傳回給我一個null,是不是接口有問題啊?
後端:那是你請求的資料在資料庫中沒有。
前端:哦。那我知道了。
後端:呀,前端發來的這個請求,參數不對啊(可能必要參數為空什麼的)。我要傳回null。
前端:大哥,你給我傳回個null,是資料庫沒有對應資料嘛?但是這個條件應該有資料啊。
後端:不是的,你請求的參數有問題啊。
前端:大哥,那你倒是給我要給回饋啊。否則,我還以為是你接口沒資料呢。
後端:好的吧。讓我想想。
2.傳回一個對象ResultVo(包含data與code,data為請求的資料,code為狀态碼):
後端:嘿,兄弟。我想到了一個好辦法,我寫了一個ResultVo,它是這樣的……%¥&¥……。
前端:好的。我了解了。
後端:呀,前端發來的這個請求,沒有足夠的權限啊。我要傳回data=null&code=10。然後在常量表中設定一下。
前端:我剛剛無意間發現,你的code又增加了10,什麼意思?
後端:啊。忘了告訴你了。code=10表示權限不足。
前端:那我需要就這個情況,給使用者提供專門的說明呀。
後端:這樣效率太低了。而且以後可能會有更複雜多變的情況。我得想想辦法。
3.傳回一個對象ResultVo2(新增msg屬性,充當響應的說明):
後端:嘿,兄弟。我将原來的ResultVo進行了更新,它是這樣的&……%&%&……。
前端:這挺不錯的,以後很多地方,我可以直接顯示msg就行了。但是,現在有一個問題,現在的code太多了。我每次進行處理時都要周遊判斷,而我常常隻需要判斷這個響應是否成功了。
後端:這樣啊。我還得再改進一下。
4.ServerResponse:
後端:請教大佬後,我得到了非常棒的解決方案。并且,我根據自己的業務情況,進行細微的調整,這下就沒什麼問題了。
前端&後端:我們感受到了效率的顯著提升,以及最為重要的代碼規範(契約)。
作用:
ServerResponse就是用來統一伺服器接口調用的響應
代碼:
package tech.jarry.learning;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.map.annotate.JsonSerialize;
import java.io.Serializable;
/**
* @Author: jarry
*/
// 確定序列化JSON時,如果是null對象,其key也會消失。
@JsonSerialize(include = JsonSerialize.Inclusion.NON_NULL)
// 生成無參構造,確定在RPC調用時,不會出現反序列失敗
@NoArgsConstructor
public class ServerResponse<T> implements Serializable {
private int status;
private String msg;
private T data;
private ServerResponse(int status) {
this.status = status;
}
private ServerResponse(int status, String msg) {
this.status = status;
this.msg = msg;
}
// 這裡存在一個問題,如果構造函數傳入的參數清單為(int,String),那麼是調用上面的(int,String),還是這裡的(int,T),畢竟T作為泛型是可以表示String的
// 答案是調用上面的(int,String)(可以了解為上面的是專業的)。那麼有時候data作為T類型傳入的就是String啊,豈不是就出問題了。這裡會在下方對應的public函數處理
private ServerResponse(int status, T data) {
this.status = status;
this.data = data;
}
private ServerResponse(int status, String msg, T data) {
this.status = status;
this.msg = msg;
this.data = data;
}
// 使之不在JSON序列化結果當中
@JsonIgnore
// 可以快速進行成功與否的條件判斷
public boolean isSuccess() {
return this.status == ResponseCode.SUCCESS.getCode();
}
@JsonIgnore
// 可以快速進行成功與否的條件判斷,判斷false時,不用加!。囧
public boolean isFail() {
return this.status != ResponseCode.SUCCESS.getCode();
}
public int getStatus() {
return status;
}
public String getMsg() {
return msg;
}
public T getData() {
return data;
}
// 快速建構傳回結果
// 成功時的調用
public static <T> ServerResponse<T> createBySuccess() {
return new ServerResponse<T>(ResponseCode.SUCCESS.getCode());
}
public static <T> ServerResponse<T> createBySuccessMessage(String msg) {
return new ServerResponse<T>(ResponseCode.SUCCESS.getCode(), msg);
}
public static <T> ServerResponse<T> createBySuccess(T data) {
return new ServerResponse<T>(ResponseCode.SUCCESS.getCode(), data);
}
public static <T> ServerResponse<T> createBySuccess(String msg, T data) {
return new ServerResponse<T>(ResponseCode.SUCCESS.getCode(), msg, data);
}
// 失敗時的調用
public static <T> ServerResponse<T> createByError() {
return new ServerResponse<T>(ResponseCode.ERROR.getCode(), ResponseCode.ERROR.getDesc());
}
public static <T> ServerResponse<T> createByErrorMessage(String errorMessage) {
return new ServerResponse<T>(ResponseCode.ERROR.getCode(), errorMessage);
}
public static <T> ServerResponse<T> createByErrorCodeMessage(int errorCode, String errorMessage) {
return new ServerResponse<T>(errorCode, errorMessage);
}
}
依賴:
lombok(絕對的效率工具,值得推薦)
應用:
package tech.jarry.learning.terminal.client;
import com.renewable.terminal.terminal.common.ServerResponse;
import com.renewable.terminal.terminal.entity.Terminal;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
/**
* @Description:通過feign,對外提供termina服務的調用接口
* @Author: jarry
*/
@FeignClient(name = "terminal", fallback = TerminalClient.TerminalClientFallback.class)
public interface TerminalClient {
@PostMapping("/terminal/update_from_center.do")
ServerResponse updateTerminalFromCenter(@RequestBody Terminal terminal);
@PostMapping("/terminal/update.do")
ServerResponse updateTerminal(@RequestBody Terminal terminal);
@GetMapping("/terminal/refresh.do")
ServerResponse refreshTerminal();
@Component
public static class TerminalClientFallback implements TerminalClient {
@Override
public ServerResponse updateTerminalFromCenter(@RequestBody Terminal terminal){
return ServerResponse.createByErrorMessage("Busy service about Terminal/updateTerminalFromCenter().");
}
@Override
public ServerResponse updateTerminal(Terminal terminal) {
return ServerResponse.createByErrorMessage("Busy service about Terminal/updateTerminal().");
}
@Override
public ServerResponse refreshTerminal(){
return ServerResponse.createByErrorMessage("Busy service about Terminal/refreshTerminal().");
}
}
}
package tech.jarry.learning.terminal.controller;
import com.renewable.terminal.message.client.TerminalMessageClient;
import com.renewable.terminal.terminal.common.ServerResponse;
import com.renewable.terminal.terminal.entity.Terminal;
import com.renewable.terminal.terminal.service.ITerminalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* <p>
* 前端控制器
* </p>
*
* @author jarry
* @since 2019-07-22
*/
@RestController
@RequestMapping("/terminal/")
public class TerminalController {
@Autowired
private ITerminalService iTerminalService;
@GetMapping("get_terminal.do")
@ResponseBody
public ServerResponse getTerminal(){
return iTerminalService.getTerminal();
}
@PostMapping("update.do")
@ResponseBody
public ServerResponse updateTerminal(@RequestBody Terminal terminal){
boolean result = iTerminalService.updateById(terminal);
iTerminalService.refresh();
if (!result){
return ServerResponse.createByErrorMessage("fail !");
}
return ServerResponse.createBySuccess(terminal);
}
@PostMapping("update_from_center.do")
@ResponseBody
public ServerResponse updateTerminalFromCenter(@RequestBody Terminal terminal){
boolean result = iTerminalService.updateById(terminal);
if (!result){
return ServerResponse.createByErrorMessage("fail !");
}
return ServerResponse.createBySuccessMessage("success");
}
@GetMapping("refresh.do")
@ResponseBody
public ServerResponse refreshTerminal(){
return iTerminalService.refresh();
}
}
問題:
在使用ServerResponse的過程中,曾經遇到一個問題。
那就是ServerResponse在SpringCloud架構中的Feign中的RPC調用中,無法進行反序列化。
找到的解釋是,缺乏無參構造器(如果類中具有任意構造器,JVM就不會提供預設的無參構造器)。
是以在類的開頭增加了@NoArgsConstructor,使得類具備無參構造器,問題解決。
總結:
作為伺服器響應的統一資料格式,網上有很多的寫法。這個ServerResponse也不一定是最好的。即使是最好的,也不一定是最适合你的。
往往我們在項目中需要一些工具實作一些特定功能,在實作功能之後,都或多或少會對現有的工具做一些調整,使得其更适合自己現有項目。
是以說,最好的不一定最适合。我們需要根據現有的情況,進行調整,重構,乃至自研。
題外話:
偷偷地推薦一下自己的個人部落格。目前這個部落格還處于測試階段。還有很多的調整,之後還會與我自己微信公衆号綁定,目測需要到今年下半年,才能全部完成。囧。有什麼意見也可以提一提。
另外,由于還在測試階段,是以如果哪天看不了,實屬正常。囧