設計 API 接口 實現統一格式返回


返回格式

后端返回給前端我們一般用 JSON 體方式,定義如下:

{
	#返回狀態碼
	code:integer,		
	#返回信息描述
	message:string,
	#返回值
	data:object
}

CODE 狀態碼

code 返回狀態碼,一般小伙伴們是在開發的時候需要什么,就添加什么

如接口要返回用戶權限異常,我們加一個狀態碼為 101 吧,下一次又要加一個數據參數異常,就加一個 102 的狀態碼。這樣雖然能夠照常滿足業務,但狀態碼太凌亂了

我們應該可以參考 HTTP 請求返回的狀態碼

:下面是常見的HTTP狀態碼:
200 - 請求成功
301 - 資源(網頁等)被永久轉移到其它URL
404 - 請求的資源(網頁等)不存在
500 - 內部服務器錯誤

HMJ7hW

我們可以參考這樣的設計,這樣的好處就把錯誤類型歸類到某個區間內,如果區間不夠,可以設計成 4 位數。

#1000~1999 區間表示參數錯誤
#2000~2999 區間表示用戶錯誤
#3000~3999 區間表示接口異常

這樣前端開發人員在得到返回值后,根據狀態碼就可以知道,大概什么錯誤,再根據 message 相關的信息描述,可以快速定位。

Message

這個字段相對理解比較簡單,就是發生錯誤時,如何友好的進行提示。一般的設計是和 code 狀態碼一起設計,如

package com.xxtsoft.enumeration;

/**
 * 狀態碼枚舉
 * <p>
 * 常見的 http 狀態碼
 * 200 - 請求成功
 * 301 - 資源(網頁等)被永久轉移到其它URL
 * 404 - 請求的資源(網頁等)不存在
 * 500 - 內部服務器錯誤
 * <p>
 * <p>
 * #1000~1999 區間表示參數錯誤
 * #2000~2999 區間表示用戶錯誤
 * #3000~3999 區間表示接口異常
 *
 * @author yang
 * @version 1.0.0
 * @date 2020-11-18 09:22
 */
public enum ResultCode {

    
    private Integer code;
    private String message;
    ResultCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}

再在枚舉中定義,狀態碼

package com.xxtsoft.enumeration;

/**
 * 狀態碼枚舉
 * <p>
 * 常見的 http 狀態碼
 * 200 - 請求成功
 * 301 - 資源(網頁等)被永久轉移到其它URL
 * 404 - 請求的資源(網頁等)不存在
 * 500 - 內部服務器錯誤
 * <p>
 * <p>
 * #1000~1999 區間表示參數錯誤
 * #2000~2999 區間表示用戶錯誤
 * #3000~3999 區間表示接口異常
 *
 * @author yang
 * @version 1.0.0
 * @date 2020-11-18 09:22
 */
public enum ResultCode {

    /**
     * 成功狀態碼
     */
    SUCCESS(1, "成功"),

    /**
     * 參數錯誤 1001-1999
     */
    PARAM_IS_INVALID(1001, "參數無效"),
    /**
     * 參數錯誤 1001-1999
     */
    PARAM_IS_BLANK(1002, "參數為空"),
    /**
     * 參數錯誤 1001-1999
     */
    PARAM_TYPE_BIND_ERROR(1003, "參數類型錯誤"),
    /**
     * 參數錯誤 1001-1999
     */
    PARAM_NOT_COMPLETE(1004, "參數缺失"),

    /**
     * 用戶錯誤 2001-2999
     */
    USER_NOT_LOGGED_IN(2001, "用戶未登錄,訪問的路徑需要驗證,請登錄"),

    /**
     * 用戶錯誤 2001-2999
     */
    USER_L0GIN_ERROR(2002, "賬號不存在或密碼錯誤"),

    /**
     * 用戶錯誤 2001-2999
     */
    USER_ACCOUNT_FORBIDDEN(2003, "賬號已被禁用"),

    /**
     * 用戶錯誤 2001-2999
     */
    USER_NOT_EXIST(2004, "用戶不存在"),
    /**
     * 用戶錯誤 2001-2999
     */
    USER_HAS_EXISTED(2005, "用戶已存在"),
    /**
     * 服務器錯誤 沒有此ID 3001
     */
    SERVER_NO_SUCH_ID(3001, "沒有此ID");

    private Integer code;
    private String message;

    ResultCode() {
    }

    ResultCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer code() {
        return this.code;
    }

    public String message() {
        return this.message;
    }
}

狀態碼和信息就會一一對應,比較好維護。

Data

返回數據體,JSON 格式,根據不同的業務又不同的 JSON 體。

我們要設計一個返回體類 Result

package com.xxtsoft.entity;

import com.xxtsoft.enumeration.ResultCode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.core.serializer.Serializer;

import java.io.Serializable;

/**
 * 返回體
 *
 * @author yang
 * @version 1.0.0
 * @date 2020-11-18 09:43
 */
@Data
public class Result implements Serializable {
    private Integer code;
    private String message;
    private Object data;

    public Result(Integer code, String message, Object data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public Result() {
    }

    public Result(ResultCode resultCode, Object data) {
        this.code = resultCode.code();
        this.message = resultCode.message();
        this.data = data;
    }

    public Result(ResultCode resultCode) {
        this.code = resultCode.code();
        this.message = resultCode.message();
    }

    public Result(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    /**
     * 返回成功!
     *
     * @return 成功
     */
    public static Result success() {
        final Result result = new Result();
        result.setResultCode(ResultCode.SUCCESS);
        return result;
    }

    /**
     * 返回成功!
     *
     * @param data 數據
     * @return Result,成功!
     */
    public static Result success(Object data) {
        final Result result = new Result();
        result.setResultCode(ResultCode.SUCCESS);
        result.setData(data);
        return result;
    }

    /**
     * 返回失敗!
     *
     * @param resultCode 失敗枚舉
     * @return 失敗!
     */
    public static Result failure(ResultCode resultCode) {
        final Result result = new Result();
        result.setResultCode(resultCode);
        return result;
    }

    public static Result failure(Integer code, String msg, Object data) {
        return new Result(code, msg, data);
    }

    public static Result failure(Integer code, String msg) {
        return new Result(code, msg);
    }

    /**
     * 返回失敗!
     *
     * @param resultCode 失敗枚舉
     * @param data       失敗數據
     * @return 失敗!
     */
    public static Result failure(ResultCode resultCode, Object data) {
        final Result result = new Result();
        result.setResultCode(resultCode);
        result.setData(data);
        return result;
    }


    public void setResultCode(ResultCode resultCode) {
        this.code = resultCode.code();
        this.message = resultCode.message();
    }


}

優雅優化

上面我們看到在 Result 類中增加了靜態方法,使得業務處理代碼簡潔了。但小伙伴們有沒有發現這樣有幾個問題:

1、每個方法的返回都是 Result 封裝對象,沒有業務含義

2、在業務代碼中,成功的時候我們調用 Result.success,異常錯誤調用 Result.failure。是不是很多余

3、上面的代碼,判斷 id 是否為 null,其實我們可以使用 hibernate validate 做校驗,沒有必要在方法體中做判斷。

我們最好的方式直接返回真實業務對象,最好不要改變之前的業務方式,如下圖

@RestController
@RequestMapping("/RoleManagementController")
@Validated
@Slf4j
public class RoleManagementController {
   @Autowired
    private ISysUserRoleService iSysUserRoleService;
     /**
     * 根據 角色 id,獲取擁有所有該角色的用戶
     *
     * @param id 角色 id,角色 id 必須是數字,且大於 0
     * @return 所有用戶
     */
    @GetMapping("/listUsersByRoleId/{id}")
    public List<SysUser> listUsersByRoleId(@DecimalMin(value = "0", message = "角色 id 必須是數字,且大於 0") @PathVariable("id") Integer id) {
        final List<SysUser> sysUsers = iSysUserRoleService.listUsersByRoleId(id);
        if (Validator.isNull(sysUsers) || CollUtil.isEmpty(sysUsers)) {
            throw new ResultException(ResultCode.SERVER_NO_SUCH_ID);
        }
        return sysUsers;
    }
}

實現方案

1、定義一個注解 @ResponseResult,表示這個接口返回的值需要包裝一下

2、攔截請求,判斷此請求是否需要被 @ResponseResult 注解

3、核心步驟就是實現接口 ResponseBodyAdvice 和 @ControllerAdvice,判斷是否需要包裝返回值,如果需要,就把 Controller 接口的返回值進行重寫。

注解類

用來標記方法的返回值,是否需要包裝

package com.xxtsoft.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.ElementType.*;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * 用來標記方法的返回值,是否需要包裝
 *
 * @author yang
 * @version 1.0.0
 * @date 2020-11-18 09:58
 */
@Retention(RUNTIME)
@Target({TYPE, METHOD})
@Documented
public @interface ResponseResult {
}

攔截器

攔截請求,是否此請求返回的值需要包裝,其實就是運行的時候,解析 @ResponseResult 注解

package com.xxtsoft.interceptor;

import cn.hutool.core.lang.Console;
import com.xxtsoft.annotation.ResponseResult;
import lombok.extern.slf4j.Slf4j;
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;

/**
 * 攔截器
 * 攔截請求,是否此請求返回的值需要包裝,其實就是運行的時候,解析 @ResponseResult 注解
 *
 * @author yang
 * @version 1.0.0
 * @date 2020-11-18 10:01
 */
@Slf4j
@Component
public class ResponseResultInterceptor implements HandlerInterceptor {
    /**
     * 標記名稱
     */
    public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";

    /**
     * 此代碼核心思想,就是獲取此請求,是否需要返回值包裝,設置一個屬性標記。
     *
     * @param request  request
     * @param response response
     * @param handler  handler
     * @return 包裝
     * @throws Exception 異常
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        log.debug("進入 preHandle 方法");
        // 請求的方法
        if (handler instanceof HandlerMethod) {
            final HandlerMethod handlerMethod = (HandlerMethod) handler;
            final Class<?> beanType = handlerMethod.getBeanType();
            final Method method = handlerMethod.getMethod();
            //   判斷是否在類對象上加了注解
            if (beanType.isAnnotationPresent(ResponseResult.class)) {
                // 設置此請求返回體,需要包裝,往下傳遞,在 ResponseBodyAdvice 接口進行判斷
                log.debug("此類有 ResponseResult 注解");
                request.setAttribute(RESPONSE_RESULT_ANN, beanType.getAnnotation(ResponseResult.class));
                // 方法上是否有注解
            } else if (method.isAnnotationPresent(ResponseResult.class)) {
                log.debug("此方法有 ResponseResult 注解");
                // 設置此請求返回體,需要包裝,往下傳遞,在 ResponseBodyAdvice 接口進行判斷
                request.setAttribute(RESPONSE_RESULT_ANN, method.getAnnotation(ResponseResult.class));
            }
        }
        return true;
    }
}

此代碼核心思想,就是獲取此請求,是否需要返回值包裝,設置一個屬性標記。

配置攔截器

package com.xxtsoft.config;

import com.xxtsoft.interceptor.ResponseResultInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * web 配置
 *
 * @author yang
 * @version 1.0.0
 * @date 2020-11-18 11:13
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private ResponseResultInterceptor responseResultInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 添加攔截器,配置攔截地址
        // 其中 /** 表示當前目錄以及所有子目錄(遞歸),/* 表示當前目錄,不包括子目錄。
        registry.addInterceptor(responseResultInterceptor).addPathPatterns("/**");
    }
}

重寫返回體

package com.xxtsoft.handler;

import com.xxtsoft.annotation.ResponseResult;
import com.xxtsoft.entity.Result;
import com.xxtsoft.exception.ResultException;
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 yang
 * @version 1.0.0
 * @date 2020-11-18 10:08
 */
@Slf4j
@ControllerAdvice
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {
    /**
     * 標記名稱
     */
    public static final String RESPONSE_RESULT_ANN = "RESPONSE-RESULT-ANN";

    /**
     * 是否請求 包含了 包裝注解標記
     * ,沒有就直接返回,不需要重寫返回體
     *
     * @param returnType    returnType
     * @param converterType converterType
     * @return boolean
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        log.debug("進入 supports 方法");
        final ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        assert sra != null;
        final HttpServletRequest request = sra.getRequest();
        // 判斷請求是否有包裝標記
        final ResponseResult responseResultAnn = (ResponseResult) request.getAttribute(RESPONSE_RESULT_ANN);
        return responseResultAnn != null;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        log.debug("進入 返回體,重寫格式 ,處理中!!!body {}", body);
        if (body instanceof Result) {
            // 是 Result 包轉好的,說明是處理過異常的,直接返回
            return body;
        }
        return Result.success(body);


    }
}

上面代碼就是判斷是否需要返回值包裝,如果需要就直接包裝

異常類

package com.xxtsoft.exception;

import com.xxtsoft.enumeration.ResultCode;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 返回體 異常類
 *
 * @author yang
 * @version 1.0.0
 * @date 2020-11-18 10:25
 */
@EqualsAndHashCode(callSuper = true)
@Data
public class ResultException extends RuntimeException {
    private Integer code;
    private String message;
    private Object data;

    public ResultException(ResultCode resultCode) {
        this.code = resultCode.code();
        this.message = resultCode.message();
    }

    public ResultException(ResultCode resultCode, Object data) {
        this.code = resultCode.code();
        this.message = resultCode.message();
        this.data = data;
    }
}

全局異常處理

package com.xxtsoft.controller;

import com.xxtsoft.entity.ResponseJson;
import com.xxtsoft.entity.Result;
import com.xxtsoft.enumeration.ResultCode;
import com.xxtsoft.exception.ResultException;
import com.xxtsoft.exception.SysDeptException;
import com.xxtsoft.exception.SysRoleException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.validation.ValidationException;

/**
 * 系統級別的異常處理
 *
 * @author yang
 * @version 1.0.0
 * @date 2020-11-18 10:55
 */
@RestControllerAdvice
@Slf4j
public class SystemExceptionHandler {
    /**
     * 捕獲
     * ResultException
     *
     * @param exception ResultException
     * @return 對應的信息
     */
    @ExceptionHandler(value = {ResultException.class})
    public Result sysDeptException(ResultException exception) {
        log.debug("code {},message {},data {} ", exception.getCode(), exception.getMessage(), exception.getData());
        return Result.failure(exception.getCode(), exception.getMessage(), exception.getData());
    }

    /**
     * 處理 參數不合法異常
     *
     * @param validator ValidationException
     * @return Result
     */
    @ExceptionHandler(value = {ValidationException.class})
    public Result validationException(Exception validator) {
        log.debug("{}", validator.toString());
        return Result.failure(ResultCode.PARAM_IS_INVALID, validator.getMessage());
    }
}

重寫 Controller

package com.xxtsoft.controller.system.management;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.lang.tree.Tree;
import com.xxtsoft.annotation.ResponseResult;
import com.xxtsoft.entity.*;
import com.xxtsoft.enumeration.ResultCode;
import com.xxtsoft.exception.ResultException;
import com.xxtsoft.exception.SysDeptException;
import com.xxtsoft.exception.SysRoleException;
import com.xxtsoft.service.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.annotations.Delete;
import org.hibernate.validator.constraints.Length;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotBlank;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

/**
 * 角色管理控制器
 * <p>
 * 獲取權限樹
 * <p>
 * 獲取所有角色
 * <p>
 * 添加角色
 * 刪除角色
 *
 * @author yang
 * @version 1.0.0
 * @date 2020-11-10 10:03
 */
@RestController
@RequestMapping("/RoleManagementController")
@Validated
@Slf4j
public class RoleManagementController {

    @Autowired
    private ISysMenuService iSysMenuService;

    @Autowired
    private ISysUserService iSysUserService;

    @Autowired
    private ISysRoleService iSysRoleService;

    @Autowired
    private ISysRoleFunctionService iSysRoleFunctionService;

    @Autowired
    private ISysUserRoleService iSysUserRoleService;


    /**
     * 獲取 菜單樹
     *
     * @return 菜單樹
     */
    @PostMapping("/treeList")
    public ResponseJson treeList() {
        final List<Tree<Integer>> trees = iSysMenuService.treeList();
        log.debug("trees {}", trees);
        return new ResponseJson().ok(trees).message("獲取成功");
    }

    /**
     * 獲取 所有的角色
     *
     * @return 所有的角色
     */
    @GetMapping("/roleList")
    public ResponseJson roleListResponseJson() {
        final List<SysRole> sysRoleList = iSysRoleService.list();
        log.debug("{}", sysRoleList);
        return new ResponseJson().ok(sysRoleList).message("獲取成功");
    }

    /**
     * 根據 角色 id 獲取對應的 (功能)權限
     *
     * @param id 角色 id
     * @return 功能(權限)
     */
    @GetMapping("/listFunctionByRoleId/{id}")
    public ResponseJson getJurisdiction(@DecimalMin(value = "0", message = "角色 id 必須是數字,且大於 0") @PathVariable("id") Integer id) {
        final List<SysFunction> sysFunctionList = iSysRoleFunctionService.listFunctionByRoleId(id);
        log.debug("id {},list {}", id, sysFunctionList);
        if (Validator.isNull(sysFunctionList) || CollUtil.isEmpty(sysFunctionList)) {
            throw new SysRoleException("沒有此 id");
        }
        return new ResponseJson().ok(sysFunctionList).message("獲取成功");
    }

    /**
     * 根據 角色 id,獲取擁有所有該角色的用戶
     *
     * @param id 角色 id,角色 id 必須是數字,且大於 0
     * @return 所有用戶
     */
    @ResponseResult
    @GetMapping("/listUsersByRoleId/{id}")
    public List<SysUser> listUsersByRoleId(@DecimalMin(value = "0", message = "角色 id 必須是數字,且大於 0") @PathVariable("id") Integer id) {
        final List<SysUser> sysUsers = iSysUserRoleService.listUsersByRoleId(id);
        if (Validator.isNull(sysUsers) || CollUtil.isEmpty(sysUsers)) {
            throw new ResultException(ResultCode.SERVER_NO_SUCH_ID);
        }
        return sysUsers;
    }


    /**
     * 根據 角色 id 和 用戶 ID 取消一條權限
     *
     * @param role 角色 id
     * @param user 用戶 ID
     * @return 是否取成功
     */
    @ResponseResult
    @DeleteMapping("/cancelRoleByRoleIdAndUserId/{role}/{user}")
    public Boolean cancelRoleByRoleIdAndUserId(@DecimalMin(value = "0", message = "角色 id 必須是數字,且大於 0") @PathVariable("role") Integer role, @DecimalMin(value = "0", message = "用戶 id 必須是數字,且大於 0") @PathVariable("user") Integer user) {
        log.debug(" role {},user {}", role, user);
        return iSysUserRoleService.cancelRoleByRoleIdAndUserId(role, user);
    }

    /**
     * 根據 角色 id
     * 刪除一個角色
     *
     * @param roleId 角色 id
     * @return 是否成功!
     */
    @ResponseResult
    @DeleteMapping("/delRoleByRoleId/{roleId}")
    public Boolean delRoleByRoleId(@PathVariable("roleId") Integer roleId) {
        log.debug("roleId {}", roleId);
        // 刪除一個角色
        final boolean b = iSysRoleService.removeById(roleId);
        // 根據 角色 去 角色功能表 刪除角色對應的功能
        iSysRoleFunctionService.deleteByRoleId(roleId);
        return b;
    }

    /**
     * 添加一個角色
     *
     * @param roleName 角色名
     * @param roleDes  角色描述
     * @return 添加成功后的角色
     */
    @ResponseResult
    @PutMapping("/addRole/{roleName}/{roleDes}")
    public Boolean addRole(@NotBlank(message = "角色名稱不能為空")
                           @Length(max = 50, min = 1, message = "角色名稱長度限制 1 ~ 50") @PathVariable String roleName, @NotBlank(message = "角色描述不能為空")
                           @Length(max = 50, min = 1, message = "角色描述長度限制 1 ~ 50") @PathVariable String roleDes) {
        log.debug(" 角色名稱 {} 角色描述 {}", roleName, roleDes);


        final SysRole sysRole = SysRole.builder()
                .froleName(roleName)
                .froleDesc(roleDes)
                .fcreateLn(iSysUserService.getLoginUserName())
                .fcreateDate(LocalDateTime.now())
                .flastModifyLn(iSysUserService.getLoginUserName())
                .flastModifyDate(LocalDateTime.now())
                .fversion(LocalDate.now())
                .build();

        return iSysRoleService.save(sysRole);

    }

    /**
     * 根據角色 id 變更權限
     *
     * @param roleId       角色 id
     * @param functionList 權限 數組
     * @return 是否成功
     */
    @ResponseResult
    @PutMapping("/AddPermissionByRoleId/{roleId}")
    public boolean addPermissionByRoleId(@PathVariable("roleId") Integer roleId, @RequestBody List<Integer> functionList) {
        log.debug(" roleId {}, functions {}", roleId, functionList);
        return iSysRoleFunctionService.addPermissionByRoleId(roleId, functionList);
    }

}

在控制器類上或者方法體上加上 @ResponseResult 注解,這樣就 ok 了,簡單吧。到此返回的設計思路完成,是不是又簡潔,又優雅。

總結

這個方案還有沒有別的優化空間,當然是有的。如:每次請求都要反射一下,獲取請求的方法是否需要包裝,其實可以做個緩存,不需要每次都需要解析


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM