前言
當我們提供數據接口給前端時,一般需要告知前端該接口的調用情況。
- 如果調用成功,需要提供成功碼和數據。
- 如果調用失敗,需要提供失敗原因,大致包括錯誤碼和失敗具體原因。
所以一個返回體至少應該包括 code
、message
、data
3 個字段。
本文使用的返回體在這 3 個基礎上增加了 success
、status
、timestamp
,實際使用請根據團隊規範的來。
配置
本實驗環境爲 Spring Boot 2.2.5.RELEASE,也就是 Spring 5.2.4.RELEASE
返回體類
/**
* 響應消息,響應請求結果給客戶端。
* @param <T>
* @author luoxc
*/
@Setter
@Getter
public class CommonResult<T> implements Serializable {
/**
* 是否成功:true,成功;false,失敗
*/
private Boolean success;
/**
* 狀態碼
*/
private Integer status;
/**
* 業務代碼,失敗時返回具體錯誤碼
*/
private Integer code;
/**
* 成功時返回 null,失敗時返回具體錯誤消息
*/
private String message;
/**
* 成功時具體返回值,失敗時爲 null
*/
private T data;
/**
* 時間戳
*/
private Long timestamp;
public CommonResult() {}
public CommonResult<T> success(Boolean success) {
this.success = success;
return this;
}
public CommonResult<T> status(Integer status) {
this.status = status;
return this;
}
public CommonResult<T> code(Integer code) {
this.code = code;
return this;
}
public CommonResult<T> message(String message) {
this.message = message;
return this;
}
public CommonResult<T> data(T data) {
this.data = data;
return this;
}
public CommonResult<T> putTimeStamp() {
this.timestamp = System.currentTimeMillis();
return this;
}
}
異常枚舉類
public interface BaseExceptionEnum {
/**
* 獲取異常編碼
* @return code
*/
Integer getCode();
/**
* 獲取異常信息
* @return message
*/
String getMessage();
}
返回體工具類
public class RestHelper {
/**
* 請求處理成功
* @param <T>
* @return
*/
public static <T> CommonResult<T> ok() {
return ok(null);
}
/**
* 請求處理成功
* @param <T>
* @return
*/
public static <T> CommonResult<T> ok(T data) {
return new CommonResult<T>()
.success(Boolean.TRUE)
.status(HttpStatus.OK.value())
.data(data)
.putTimeStamp();
}
/**
* 請求處理失敗(默認500錯誤)
* @param message 調用結果消息
* @param <T>
* @return
*/
public static <T> CommonResult<T> error(String message) {
return error(HttpStatus.INTERNAL_SERVER_ERROR.value(), message);
}
/**
* 請求處理失敗
* @param status 狀態碼
* @param message 調用結果消息
* @param <T>
* @return
*/
public static <T> CommonResult<T> error(Integer status, String message) {
return error(status, null, message);
}
/**
* 業務處理失敗(默認500錯誤)
* @param code 業務代碼
* @param message 調用結果消息
* @param <T>
* @return
*/
public static <T> CommonResult<T> bizError(Integer code, String message) {
return error(HttpStatus.INTERNAL_SERVER_ERROR.value(), code, message);
}
/**
* 業務處理失敗(默認500錯誤)
* @param baseExceptionEnum
* @param <T>
* @return
*/
public static <T> CommonResult<T> bizError(BaseExceptionEnum baseExceptionEnum) {
return bizError(baseExceptionEnum.getCode(), baseExceptionEnum.getMessage());
}
/**
* 請求處理失敗
* @param status 狀態碼
* @param code 業務代碼
* @param message 調用結果消息
* @param <T>
* @return
*/
public static <T> CommonResult<T> error(Integer status, Integer code, String message) {
return new CommonResult<T>()
.success(Boolean.FALSE)
.status(status)
.code(code)
.message(message)
.putTimeStamp();
}
/**
* 判斷是否是ajax請求
*/
public static boolean isAjax(HttpServletRequest request) {
return (request.getHeader("X-Requested-With") != null && "XMLHttpRequest".equals(request.getHeader("X-Requested-With")));
}
}
統一返回體自動包裝類
通過實現 ResponseBodyAdvice
接口,並在類上添加 @ControllerAdvice
註解,可以攔截 Handler(Controller) 的返回結果。
@ControllerAdvice
public class GlobalResponseBodyAdvice implements ResponseBodyAdvice {
/**
* 這個方法表示對於哪些請求要執行 beforeBodyWrite,返回 true 執行,返回 false 不執行
* @param returnType, converterType
* @return boolean
*/
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return true;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 如果返回體已經是 CommonResult 類型,則不必再次包裝
if ((body instanceof CommonResult)) {
return body;
}
return ok(body);
}
}
可以通過設置
@ControllerAdvice(basePackages="...")
來指定返回體攔截的範圍。
supports(MethodParameter returnType, Class converterType)
方法返回 true,表示攔截 Handler(Controller) 的返回結果。
beforeBodyWrite(...)
在這個方法中可對返回體進行邏輯處理。
測試
@RestController
@RequestMapping("/response")
public class ResponseController {
@GetMapping("/test")
public Map test() {
Map map = new HashMap();
map.put("name", "xiaoming");
map.put("age", 18);
return map;
}
@GetMapping("/test1")
public CommonResult test1() {
return RestHelper.ok("成功");
}
@GetMapping(value = "/test2")
public CommonResult test2() {
return RestHelper.bizError(1, "啊,報錯了");
}
}
調用 localhost/demo/response/test
接口時的返回體
{
"success": true,
"status": 200,
"code": null,
"message": null,
"data": {
"name": "xiaoming",
"age": 18
},
"timestamp": 1585630308124
}
調用 localhost/demo/response/test1
接口時的返回體
{
"success": true,
"status": 200,
"code": null,
"message": null,
"data": "成功",
"timestamp": 1585630638916
}
調用 localhost/demo/response/test2
接口時的返回體
{
"success": false,
"status": 500,
"code": 1,
"message": "啊,報錯了",
"data": null,
"timestamp": 1585630611703
}
可能有些小夥伴獲取到的返回體信息與上面有所區別,例如有的調用
localhost/demo/response/test1
請求返回體中的"message": ""
,這是因爲 Java 對象與 JSon 消息轉換階段配置的序列化策略不同導致的。
到這一步,返回體統一包裝配置就結束了。
下面的是通過註解來開啓返回體包裝,這樣設置不知道有沒有實際應用場景。
其他
通過使用 @RestResponseResult
註解類或者使用 @ResponseResult
註解方法來開啓返回體自動包裝。
ResponseResult 註解
/**
* 自定義標識註解,用於標識該類或方法需要被 CommonResult 包裝,
* 常標記於方法上,可代替 @ResponseBody 使用
* @author luoxc
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ResponseBody
public @interface ResponseResult {
}
RestResponseResult 註解
/**
* 自定義標識註解,用於標識該接口需要被 CommonResult 包裝
* 常標記於類上,代替 @Controller,@ResponseBody,@ResponseResult
* @author luoxc
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseResult
public @interface RestResponseResult {
@AliasFor(
annotation = Controller.class
)
String value() default "";
}
ResponseResultProperties 配置類
/**
* 返回體統一包裝配置類
* @author luoxc
*/
@Setter
@Getter
@ConfigurationProperties(prefix = "api.result")
public class ResponseResultProperties {
/**
* 是否啓用返回體自動包裝
*/
private boolean enabled;
/**
* 啓動註解配置:true,啓用註解;false,禁用註解
*/
private boolean ann;
}
統一返回體自動包裝類
/**
* 返回體包裝
* @author luoxc
*/
@ControllerAdvice
@EnableConfigurationProperties(ResponseResultProperties.class)
public class ResultResponseBodyAdvice implements ResponseBodyAdvice {
@Autowired
private ResponseResultProperties responseResultProperties;
/**
* 這個方法表示對於哪些請求要執行 beforeBodyWrite,返回 true 執行,返回 false 不執行
* @param returnType, converterType
* @return boolean
*/
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
// 根據配置是否使用 @RestResponseResult 或 @ResponseResult 啓用返回體包裝
if (responseResultProperties.isAnn()) {
Class clazz = returnType.getContainingClass();
return clazz.isAnnotationPresent(RestResponseResult.class) ||
clazz.isAnnotationPresent(ResponseResult.class) ||
returnType.hasMethodAnnotation(ResponseResult.class);
}
return true;
}
/**
* 重寫返回體
* @param body, returnType, selectedContentType, selectedConverterType, request, response
* @return java.lang.Object
*/
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
// 如果返回體已經是 CommonResult 類型,則不必再次包裝
if ((body instanceof CommonResult)) {
return body;
}
return ok(body);
}
}
開啓配置
在 appliation.yaml
中添加以下內容
api:
result:
ann: true # true,表示開啓註解配置;false,表示禁用
測試
@RestResponseResult // 類級別
@RequestMapping("/response")
public class ResponseController {
@ResponseResponse // 方法級別
@GetMapping(value = "/test")
public CommonResult test() {
return RestHelper.bizError(1, "啊,報錯了");
}
}