全局統一返回實現方案

前言

當我們提供數據接口給前端時,一般需要告知前端該接口的調用情況。

  • 如果調用成功,需要提供成功碼數據
  • 如果調用失敗,需要提供失敗原因,大致包括錯誤碼失敗具體原因

所以一個返回體至少應該包括 codemessagedata 3 個字段。

本文使用的返回體在這 3 個基礎上增加了 successstatustimestamp,實際使用請根據團隊規範的來。

配置

本實驗環境爲 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, "啊,報錯了");
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章