前言
現在我們做項目基本上中大型項目都是選擇前后端分離,前后端分離已經成了一個趨勢了,所以總這樣·我們就要和前端約定統一的api 接口返回json 格式,
這樣我們需要封裝一個統一通用全局 模版api返回格式,下次再寫項目時候直接拿來用就可以了
約定JSON格式
一般我們和前端約定json格式是這樣的
{
"code": 200,
"message": "成功",
"data": {
}
}
- code: 返回狀態碼
- message: 返回信息的描述
- data: 返回值
封裝java bean
定義狀態枚舉
package cn.soboys.core.ret;
import lombok.Data;
import lombok.Getter;
/**
* @author kenx
* @version 1.0
* @date 2021/6/17 15:35
* 響應碼枚舉,對應HTTP狀態碼
*/
@Getter
public enum ResultCode {
SUCCESS(200, "成功"),//成功
//FAIL(400, "失敗"),//失敗
BAD_REQUEST(400, "Bad Request"),
UNAUTHORIZED(401, "認證失敗"),//未認證
NOT_FOUND(404, "接口不存在"),//接口不存在
INTERNAL_SERVER_ERROR(500, "系統繁忙"),//服務器內部錯誤
METHOD_NOT_ALLOWED(405,"方法不被允許"),
/*參數錯誤:1001-1999*/
PARAMS_IS_INVALID(1001, "參數無效"),
PARAMS_IS_BLANK(1002, "參數為空");
/*用戶錯誤2001-2999*/
private Integer code;
private String message;
ResultCode(int code, String message) {
this.code = code;
this.message = message;
}
}
定義返回狀態碼,和信息一一對應,我們可以約定xxx~xxx 為什么錯誤碼,防止后期錯誤碼重復,使用混亂不清楚,
定義返回體結果體
package cn.soboys.core.ret;
import lombok.Data;
import java.io.Serializable;
/**
* @author kenx
* @version 1.0
* @date 2021/6/17 15:47
* 統一API響應結果格式封裝
*/
@Data
public class Result<T> implements Serializable {
private static final long serialVersionUID = 6308315887056661996L;
private Integer code;
private String message;
private T data;
public Result setResult(ResultCode resultCode) {
this.code = resultCode.getCode();
this.message = resultCode.getMessage();
return this;
}
public Result setResult(ResultCode resultCode,T data) {
this.code = resultCode.getCode();
this.message = resultCode.getMessage();
this.setData(data);
return this;
}
}
code,和message都從定義的狀態枚舉中獲取
這里有兩個需要注意地方我的數據類型
T data
返回的是泛型
類型而不是object
類型而且我的結果累實現了Serializable
接口
我看到網上有很多返回object,最后返回泛型因為泛型效率要高於object,object需要強制類型轉換,還有最后實現了Serializable
接口因為通過流bytes傳輸方式web傳輸,速率更塊
定義返回結果方法
一般業務返回要么是 success
成功,要么就是failure
失敗,所以我們需要單獨定義兩個返回實體對象方法,
package cn.soboys.core.ret;
/**
* @author kenx
* @version 1.0
* @date 2021/6/17 16:30
* 響應結果返回封裝
*/
public class ResultResponse {
private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";
// 只返回狀態
public static Result success() {
return new Result()
.setResult(ResultCode.SUCCESS);
}
// 成功返回數據
public static Result success(Object data) {
return new Result()
.setResult(ResultCode.SUCCESS, data);
}
// 失敗
public static Result failure(ResultCode resultCode) {
return new Result()
.setResult(resultCode);
}
// 失敗
public static Result failure(ResultCode resultCode, Object data) {
return new Result()
.setResult(resultCode, data);
}
}
注意這里我定義的是靜態工具方法,因為使用構造方法進行創建對象調用太麻煩了, 我們使用靜態方法來就直接類調用很方便
這樣我們就可以在controller中很方便返回統一api格式了
package cn.soboys.mallapi.controller;
import cn.soboys.core.ret.Result;
import cn.soboys.core.ret.ResultResponse;
import cn.soboys.mallapi.bean.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author kenx
* @version 1.0
* @date 2021/7/2 20:28
*/
@RestController //默認全部返回json
@RequestMapping("/user")
public class UserController {
@GetMapping("/list")
public Result getUserInfo(){
User u=new User();
u.setUserId("21");
u.setUsername("kenx");
u.setPassword("224r2");
return ResultResponse.success(u);
}
}
返回結果符合我們預期json格式
但是這個代碼還可以優化,不夠完善,比如,每次controller中所有的方法的返回必須都是要Result類型,我們想返回其他類型格式怎么半,還有就是不夠語義化,其他開發人員看你方法根本就不知道具體返回什么信息
如果改成這個樣子就完美了如
@GetMapping("/list")
public User getUserInfo() {
User u = new User();
u.setUserId("21");
u.setUsername("kenx");
u.setPassword("224r2");
return u;
}
其他開發人員一看就知道具體是返回什么數據。但這個格式要怎么去統一出來?
其實我們可以這么去優化,通過SpringBoot提供的ResponseBodyAdvice
進行統一響應處理
- 自定義注解@ResponseResult來攔截有此controller注解類的代表需要統一返回json格式,沒有就安照原來返回
package cn.soboys.core.ret;
import java.lang.annotation.*;
/**
* @author kenx
* @version 1.0
* @date 2021/6/17 16:43
* 統一包裝接口返回的值 Result
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseResult {
}
- 定義請求攔截器通過反射獲取到有此注解的HandlerMethod設置包裝攔截標志
package cn.soboys.core.ret;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
/**
* @author kenx
* @version 1.0
* @date 2021/6/17 17:10
* 請求攔截
*/
public class ResponseResultInterceptor implements HandlerInterceptor {
//標記名稱
public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//請求方法
if (handler instanceof HandlerMethod) {
final HandlerMethod handlerMethod = (HandlerMethod) handler;
final Class<?> clazz = handlerMethod.getBeanType();
final Method method = handlerMethod.getMethod();
//判斷是否在對象上加了注解
if (clazz.isAnnotationPresent(ResponseResult.class)) {
//設置此請求返回體需要包裝,往下傳遞,在ResponseBodyAdvice接口進行判斷
request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class));
//方法體上是否有注解
} else if (method.isAnnotationPresent(ResponseResult.class)) {
//設置此請求返回體需要包裝,往下傳遞,在ResponseBodyAdvice接口進行判斷
request.setAttribute(RESPONSE_RESULT_ANN, clazz.getAnnotation(ResponseResult.class));
}
}
return true;
}
}
- 實現
ResponseBodyAdvice<Object>
接口自定義json返回解析器根據包裝攔截標志判斷是否需要自定義返回類型返回類型
package cn.soboys.core.ret;
import cn.soboys.core.utils.HttpContextUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
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.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import javax.servlet.http.HttpServletRequest;
/**
* @author kenx
* @version 1.0
* @date 2021/6/17 16:47
* 全局統一響應返回體處理
*/
@Slf4j
@ControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";
/**
* @param methodParameter
* @param aClass
* @return 此處如果返回false , 則不執行當前Advice的業務
* 是否請求包含了包裝注解 標記,沒有直接返回不需要重寫返回體,
*/
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
HttpServletRequest request = HttpContextUtil.getRequest();
//判斷請求是否有包裝標志
ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN);
return responseResultAnn == null ? false : true;
}
/**
* @param body
* @param methodParameter
* @param mediaType
* @param aClass
* @param serverHttpRequest
* @param serverHttpResponse
* @return 處理響應的具體業務方法
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if (body instanceof Result) {
return body;
} else if (body instanceof String) {
return JSON.toJSONString(ResultResponse.success(body));
} else {
return ResultResponse.success(body);
}
}
}
注意這里string類型返回要單獨json序列化返回一下,不然會報轉換異常
這樣我們就可以在controler中返回任意類型,了不用每次都必須返回 Result
如
package cn.soboys.mallapi.controller;
import cn.soboys.core.ret.ResponseResult;
import cn.soboys.core.ret.Result;
import cn.soboys.core.ret.ResultResponse;
import cn.soboys.mallapi.bean.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author kenx
* @version 1.0
* @date 2021/7/2 20:28
*/
@RestController //默認全部返回json
@RequestMapping("/user")
@ResponseResult
public class UserController {
@GetMapping("/list")
public User getUserInfo() {
User u = new User();
u.setUserId("21");
u.setUsername("kenx");
u.setPassword("224r2");
return u;
}
@GetMapping("/test")
public String test() {
return "ok";
}
@GetMapping("/test2")
public Result test1(){
return ResultResponse.success();
}
}
這里還有一個問題?正常情況返回成功的話是統一json 格式,但是返回失敗,或者異常了,怎么統一返回錯誤json 格式,sprinboot有自己的錯誤格式?
請參考我上一篇,SpringBoot優雅的全局異常處理
掃碼關注公眾號猿人生了解更多好文