如果還有接口test2、test3、test4…..,即每個接口都必須返回Result對象,這也是目前大多數項目正在使用的方式

優化一

1.繼承@ResponseBody

@Retention(RetentionPolicy.RUNTIME)  
@Target({ElementType.TYPE, ElementType.METHOD})  
@Documented  
@ResponseBody  
public @interface ResponseResultBody {  
  
}  

2.實現ResponseBodyAdvice

ResponseBodyAdvice 接口允許在執行 @ResponseBody 或 ResponseEntity 控制器方法之后,但在使用 HttpMessageConverter 寫入響應體之前自定義響應,進行功能增強。通常用于加密,簽名,統一數據格式等

相應的也有RequestBodyAdvice接口,針對所有以@RequestBody的參數,在讀取請求body之前或者在body轉換成對象之前可以做相應的增強

@Slf4j
@RestControllerAdvice
public class ResponseResultBodyAdvice implements ResponseBodyAdvice<Object> {

    private static final Class<? extends Annotation> ANNOTATION_TYPE = ResponseResultBody.class;

    /**
    * 1、選擇是否執行 beforeBodyWrite 方法,返回 true 執行,false 不執行.
    * 2、通過supports方法,可以選擇對哪些類或方法的 Response 進行處理,其余的則不處理。
    * @param returnType:返回類型
    * @param converterType:轉換器
    * @return :返回 true 則下面的 beforeBodyWrite  執行,否則不執行
    * /
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ANNOTATION_TYPE) || returnType.hasMethodAnnotation(ANNOTATION_TYPE);
    }

    /**
    * 對 Response 處理的具體執行方法
    * @param body:響應對象(response)中的響應體
    * @param returnType:控制器方法的返回類型
    * @param selectedContentType:通過內容協商選擇的內容類型
    * @param selectedConverterType:選擇寫入響應的轉換器類型
    * @param request:當前請求
    * @param response:當前響應
    * @return :返回傳入的主體或修改過的(可能是新的)主體
   * /
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        return convert(convert(body), selectedConverterType);
    }

    private Result<?> convert(Object body) {
        //兼容返回是Result類型
        if (body instanceof Result) {
            return (Result<?>) body;
        }
        return Result.success(body);
    }

    private Object convert(Result<?> result, Class<? extends HttpMessageConverter<?>> selectedConverterType) {
        //兼容返回是String類型
        if (selectedConverterType == StringHttpMessageConverter.class && result.getData() instanceof String) {
            return "{\"code\":\"" + result.getCode() + "\",\"message\":\"" + result.getMessage() + "\",\"data\":\"" + result.getData() + "\"}";
        }
        return result;
    }
}

3.控制層

@RestController
@ResponseResultBody
public class HelloController {

    @GetMapping("/test")
    public Result hello() {
        HashMap<String, Object> info = new HashMap<>();
        info.put("name","一安未來");
        info.put("addr","北京");
        return Result.success(ResultCode.SUCCESS,info);
    }

    @GetMapping("/test2")
    public Object hello2() {
        HashMap<String, Object> info = new HashMap<>();
        info.put("name","一安未來");
        info.put("addr","北京");
        return info;
    }
}

4.接口測試

至此,即使返回Object也可以統一JSON格式了, 不用每個返回都返回Result對象

優化二

使用@ExceptionHandler可以用來統一處理方法拋出的異常返回JSON格式

自定義異常處理類

@Data
public class ResultException extends Exception{

    ResultCode resultCode;

    public ResultException() {
        this(ResultCode.FAIL);
    }

    public ResultException(ResultCode resultCode) {
        super(resultCode.getMessage());
        this.resultCode = resultCode;
    }
}

改造ResponseResultBodyAdvice

ResponseBodyAdvice 接口允許在執行 @ResponseBody 或 ResponseEntity 控制器方法之后,但在使用 HttpMessageConverter 寫入響應體之前自定義響應,進行功能增強。通常用于加密,簽名,統一數據格式等

相應的也有RequestBodyAdvice接口,針對所有以@RequestBody的參數,在讀取請求body之前或者在body轉換成對象之前可以做相應的增強

@Slf4j
@RestControllerAdvice
public class ResponseResultBodyAdvice implements ResponseBodyAdvice<Object> {

    private static final Class<? extends Annotation> ANNOTATION_TYPE = ResponseResultBody.class;

    /**
    * 1、選擇是否執行 beforeBodyWrite 方法,返回 true 執行,false 不執行.
    * 2、通過supports方法,可以選擇對哪些類或方法的 Response 進行處理,其余的則不處理。
    * @param returnType:返回類型
    * @param converterType:轉換器
    * @return :返回 true 則下面的 beforeBodyWrite執行,否則不執行
    * /
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ANNOTATION_TYPE) || returnType.hasMethodAnnotation(ANNOTATION_TYPE);
    }

    /**
    * 對 Response 處理的具體執行方法
    * @param body:響應對象(response)中的響應體
    * @param returnType:控制器方法的返回類型
    * @param selectedContentType:通過內容協商選擇的內容類型
    * @param selectedConverterType:選擇寫入響應的轉換器類型
    * @param request:當前請求
    * @param response:當前響應
    * @return :返回傳入的主體或修改過的(可能是新的)主體
   * /
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        return convert(convert(body), selectedConverterType);
    }

    private Result<?> convert(Object body) {
        if (body instanceof Result) {
            return (Result<?>) body;
        }
        return Result.success(body);
    }

    private Object convert(Result<?> result, Class<? extends HttpMessageConverter<?>> selectedConverterType) {
        if (selectedConverterType == StringHttpMessageConverter.class && result.getData() instanceof String) {
            return "{\"code\":\"" + result.getCode() + "\",\"message\":\"" + result.getMessage() + "\",\"data\":\"" + result.getData() + "\"}";
        }
        return result;
    }


    /**
     * 提供對標準Spring MVC異常的處理
     */
    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Result<?>> exceptionHandler(Exception ex, WebRequest request) {
        log.error("ExceptionHandler: {}", ex.getMessage());
        HttpHeaders headers = new HttpHeaders();
        if (ex instanceof ResultException) {
            return this.handleResultException((ResultException) ex, headers, request);
        }
        // TODO:  這里可以自定義其他的異常攔截
        return this.handleException(ex, headers, request);
    }

    /**
     * 對ResultException類返回返回結果的處理
     */
    protected ResponseEntity<Result<?>> handleResultException(ResultException ex, HttpHeaders headers, WebRequest request) {
        Result<?> body = Result.failure(ex.getResultCode());
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        return this.handleExceptionInternal(ex, body, headers, status, request);
    }

    /**
     * 異常類的統一處理
     */
    protected ResponseEntity<Result<?>> handleException(Exception ex, HttpHeaders headers, WebRequest request) {
        Result<?> body = Result.failure();
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        return this.handleExceptionInternal(ex, body, headers, status, request);
    }

    protected ResponseEntity<Result<?>> handleExceptionInternal(
            Exception ex, Result<?> body, HttpHeaders headers, HttpStatus status, WebRequest request) {

        if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
            request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
        }
        return new ResponseEntity<>(body, headers, status);
    }
}

文章轉自微信公眾號@一安未來

上一篇:

Spring Boot 2.X 實戰--RESTful API 全局異常處理

下一篇:

API協議設計的10種技術
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費