1. 程式人生 > 其它 >SpringBoot統一Api介面返回格式

SpringBoot統一Api介面返回格式

在最流行的前後端互動的專案中,後端一般都是返回指定格式的資料供前端解析,本文使用註解方式返回統一格式的資料,那麼下面就看看是如何使用的吧

1)返回響應碼實體

package com.zxh.example.entity.model;

import lombok.Data;

public enum ResultCode {

    SUCCESS(200, "處理成功"),
    FAILURE(201, "處理失敗"),
    PARAM_ERROR(400, "引數錯誤"),
    NOT_PERMISSION(402, "未授權,無法訪問"),
    WRONG_PASSWORD(403
, "使用者名稱或密碼錯誤"), NOT_FOUND(404, "檔案不存在或已被刪除"), METHOD_NOT_SUPPORT(405, "方法不被允許"), SERVER_ERROR(500, "伺服器異常,請聯絡管理員"); private Integer code; private String message; ResultCode(Integer code, String message) { this.code = code; this.message = message; } public
Integer code(){ return this.code; } public String message(){ return this.message; } }

2)返回資料實體

package com.zxh.example.entity.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain 
= true) public class ResultData implements Serializable { //請求狀態碼 private Integer code; //返回資訊描述 private String message; //返回內容 private Object data; public ResultData(Integer code, String message) { this.code = code; this.message = message; } public ResultData(ResultCode resultCode, Object data) { this.code = resultCode.code(); this.message = resultCode.message(); this.data = data; } public ResultData(ResultCode resultCode) { this.code = resultCode.code(); this.message = resultCode.message(); } public static ResultData success() { return new ResultData(ResultCode.SUCCESS); } public static ResultData success(Object data) { return new ResultData(ResultCode.SUCCESS, data); } public static ResultData failure(Integer code, String msg) { return new ResultData(code, msg); } public static ResultData failure(ResultCode resultCode) { return new ResultData(resultCode); } public static ResultData failure(ResultCode resultCode, Object data) { return new ResultData(resultCode, data); } }

若上述靜態方法還不滿足,可自定義新增。

3)定義註解

package com.zxh.example.anno;

import java.lang.annotation.*;

/**
 * 返回體標記註解
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD, ElementType.TYPE})
public @interface ResponseResult {
}

此註解用在類或方法上,用來標記是否需要統一格式返回。

4)定義系統常量

package com.zxh.example.config;

public class SysConst {

    public static final String RESPONSE_KEY = "response_result";
}

對於多個地方共有的方法或變數,應公共定義。

5)配置響應攔截器

package com.zxh.example.config;

import com.zxh.example.anno.ResponseResult;
import org.springframework.stereotype.Component;
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;

/**
 * 響應攔截器
 */
@Component
public class ResponseResultInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        //是否是請求的方法,若在方法或類上使用了註解,就設定到request物件中
        if (handler instanceof HandlerMethod) {
            final HandlerMethod handlerMethod = (HandlerMethod) handler;
            final Class<?> clazz = handlerMethod.getBeanType();
            final Method method = handlerMethod.getMethod();
            Class<ResponseResult> aClass = ResponseResult.class;
            //判斷是否需要轉換,在類上或者方法上
            if (clazz.isAnnotationPresent(aClass)) {
                request.setAttribute(SysConst.RESPONSE_KEY, clazz.getAnnotation(aClass));
            } else if (method.isAnnotationPresent(aClass)) {
                request.setAttribute(SysConst.RESPONSE_KEY, method.getAnnotation(aClass));
            }
        }
        return true;

    }
}

此攔截器的主要作用是判斷是否在類或方法上添加了上述定義的註解,若添加了則給request屬性設定引數,以便在響應體返回時判斷是否需要重寫。

6)重寫響應體  

package com.zxh.example.config;

import com.zxh.example.anno.ResponseResult;
import com.zxh.example.entity.model.ResultData;
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;

@ControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {

    /**
     * 是否使用了包裝註解,若使用了則會走 beforeBodyWrite方法
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        ResponseResult responseResult = (ResponseResult) request.getAttribute(SysConst.RESPONSE_KEY);
        //若返回false則下面的配置不生效
        return responseResult == null ? false : true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, 
ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if (body instanceof ResultData) { return body; } return ResultData.success(body); } }

此類用於判斷是否需要統一返回,若需要則按格式重寫響應體並返回,否則不重寫。

7)配置mvc攔截器

package com.zxh.example.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

/**
 * mvc配置類,配置攔截器
 */
@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {

    @Autowired
    private ResponseResultInterceptor interceptor;

    /**
     * 配置攔截器
     * @param registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(interceptor).addPathPatterns("/**");
    }

    /**
     * 配置訊息轉換器
     * @param converters
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        //去掉String型別轉換器。否則當controller返回String型別時會出現型別轉換異常ClassCastException
        converters.removeIf(httpMessageConverter -> httpMessageConverter.getClass() == StringHttpMessageConverter.class);
    }
}

將響應攔截器加入mvc攔截器中,使其生效。若不配置則響應攔截器不會生效。

需要注意的是,必須要配置訊息轉換器,用來去掉String型別轉換器,否則當controller中方法返回型別是String時會報錯!

8)建立controller介面

package com.zxh.example.controller;

import com.zxh.example.anno.ResponseResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api")
@Slf4j
//@ResponseResult
public class TestController {

    @ResponseResult
    @GetMapping("/test")
    public String test(String name) {
        return "hello" + name;
    }

    @GetMapping("/test2")
    public Map test2() {
        Map<String, Object> map = new HashMap<>();
        map.put("name", "張三");
        map.put("age", 20);
        return map;
    }


}

此介面中有兩個方法,一個使用註解標記了,另一個直接返回相應的物件。

9)自定義異常類(需要時可建立)

package com.zxh.example.config;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * 自定義異常類
 */
@Setter
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class ApiExceptionHandler extends RuntimeException {

    /**
     * 錯誤碼
     */
    protected Integer errorCode;
    /**
     * 錯誤資訊
     */
    protected String errorMsg;


    public ApiExceptionHandler(String errorMsg) {
        super(errorMsg);
        this.errorMsg = errorMsg;
    }

    public ApiExceptionHandler(Integer errorCode, String errorMsg, Throwable cause) {
        super(errorCode.toString(), cause);
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }

    @Override
    public synchronized Throwable fillInStackTrace() {
        return this;
    }
}

10)全域性異常處理(需要時可建立)

package com.zxh.example.config;


import com.zxh.example.entity.model.ResultCode;
import com.zxh.example.entity.model.ResultData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.FileNotFoundException;

/**
 * 全域性異常處理
 */
@ControllerAdvice
@Slf4j
@RestController
public class GlobalExceptionHandler {


    /**
     * 處理自定義的業務異常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = ApiExceptionHandler.class)
    public ResultData apiExceptionHandler(ApiExceptionHandler e) {
        log.error("發生業務異常!原因是:{}", e.getErrorMsg());
        return ResultData.failure(e.errorCode, e.errorMsg);
    }


    /**
     * 處理空指標的異常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = NullPointerException.class)
    public ResultData exceptionHandler(NullPointerException e) {
        log.error("發生空指標異常!原因是:", e);
        return ResultData.failure(ResultCode.PARAM_ERROR);
    }

    /**
     * 處理檔案找不到的異常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = FileNotFoundException.class)
    public ResultData exceptionHandler(FileNotFoundException e) {
        log.error("發生檔案找不到異常!原因是:", e);
        return ResultData.failure(ResultCode.NOT_FOUND);
    }

    /**
     * 索引越界的異常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = ArrayIndexOutOfBoundsException.class)
    public ResultData exceptionHandler(ArrayIndexOutOfBoundsException e) {
        log.error("發生陣列索引越界異常!原因是:", e);
        return ResultData.failure(ResultCode.SERVER_ERROR.code(), "陣列越界,請檢查資料");
    }

    /**
     * 處理其他異常
     *
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    public ResultData exceptionHandler(Exception e, HttpServletResponse response) {
        response.setCharacterEncoding("UTF-8");
        log.error("發生未知異常!原因是:", e);
        return ResultData.failure(ResultCode.SERVER_ERROR);
    }

}

異常處理是必不可少的,在異常中也需要統一返回格式。

11)啟動測試

啟動專案,分別訪問介面 localhost:8080/api/test?name=11112 和 localhost:8080/api/test2,返回結果如下

圖1

圖2

上述就是在方法上使用註解 @ResponseResult 進行統一返回格式,與此同時controller中的方法也都根據自己的特性返回了對應實體。

若此controller中所有方法都需要統一格式,則可直接在類加上註解 @ResponseResult 即可,無需在每個方法上都加此註解。

12)分頁補充

對於需要分頁返回的格式,則可再新建一個實體類,用於儲存分頁的資料,

package com.zxh.example.entity.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class ResultPage implements Serializable {
    //資料內容
    private Object data;
    //資料總條數
    private Long total;
}

在controller中新建一個方法模擬分頁返回的資料,添加註解進行標記即可

    @ResponseResult
    @GetMapping("/test3")
    public ResultPage test3() {
        List<String> list = Arrays.asList("123", "23444", "8544");
        return new ResultPage(list, 3L);
    }

返回資料如下圖