ServerResponse(服務器統一響應數據格式)


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也不一定是最好的。即使是最好的,也不一定是最適合你的。

往往我們在項目中需要一些工具實現一些特定功能,在實現功能之后,都或多或少會對現有的工具做一些調整,使得其更適合自己現有項目。

所以說,最好的不一定最適合。我們需要根據現有的情況,進行調整,重構,乃至自研。

題外話:

偷偷地推薦一下自己的個人博客。目前這個博客還處於測試階段。還有很多的調整,之后還會與我自己微信公眾號綁定,目測需要到今年下半年,才能全部完成。囧。有什么意見也可以提一提。

另外,由於還在測試階段,所以如果哪天看不了,實屬正常。囧


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM