什么!!Qi-API接口开放平台仅需两步就可以动态调用新接口!!?😱

大家好我是柒木,想必做过接口开放平台的同学都会遇到 该怎么动态发布并调用新接口? 这个棘手的问题,这次呢就带大家带来解决这一大难题!😎

相关网址导航

发布操作步骤(仅需两步!)

  1. 在接口服务(interface)项目中开发新接口 (接口服务可以是独立的项目,但需要在网关中配置路由

    在接口服务开发一个测试接口:

    java
    复制代码
    @GetMapping("/test") public String test(String text) { return text; }
    image-20241231114908610
  2. 开发完成后重启接口项目后,在管理员后台发布接口,就可以在线调用了!!

    image-20241231115154224
  3. 在接口大厅找到并请求接口

    image-20241231115407585
  4. 恭喜发布成功!!

是不是非常简便!!实现这一功能全得意于 Qi-API-SDK 🛠

实现原理

回到正题,我们看一下 Qi-API 接口开放平台 🔗 是怎么动态调用接口的

java
复制代码
@PostMapping("/invoke") @Transactional(rollbackFor = Exception.class) public BaseResponse<Object> invokeInterface(@RequestBody InvokeRequest invokeRequest, HttpServletRequest request) { if (ObjectUtils.anyNull(invokeRequest, invokeRequest.getId()) || invokeRequest.getId() <= 0) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } Long id = invokeRequest.getId(); InterfaceInfo interfaceInfo = interfaceInfoService.getById(id); if (interfaceInfo == null) { throw new BusinessException(ErrorCode.NOT_FOUND_ERROR); } if (interfaceInfo.getStatus() != InterfaceStatusEnum.ONLINE.getValue()) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "接口未开启"); } // 构建请求参数 List<InvokeRequest.Field> fieldList = invokeRequest.getRequestParams(); String requestParams = "{}"; if (fieldList != null && fieldList.size() > 0) { JsonObject jsonObject = new JsonObject(); for (InvokeRequest.Field field : fieldList) { jsonObject.addProperty(field.getFieldName(), field.getValue()); } requestParams = gson.toJson(jsonObject); } Map<String, Object> params = new Gson().fromJson(requestParams, new TypeToken<Map<String, Object>>() { }.getType()); UserVO loginUser = userService.getLoginUser(request); String accessKey = loginUser.getAccessKey(); String secretKey = loginUser.getSecretKey(); try { QiApiClient qiApiClient = new QiApiClient(accessKey, secretKey); CurrencyRequest currencyRequest = new CurrencyRequest(); currencyRequest.setMethod(interfaceInfo.getMethod()); currencyRequest.setPath(interfaceInfo.getUrl()); currencyRequest.setRequestParams(params); ResultResponse response = apiService.request(qiApiClient, currencyRequest); return ResultUtils.success(response.getData()); } catch (Exception e) { throw new BusinessException(ErrorCode.SYSTEM_ERROR, e.getMessage()); } }

可以看到在调用前构建了一个通用请求,将请求参数、请求方法、请求地址都传递给sdk提供的request方法,request是一个接口

接收QiApiClient客户端和一个泛型的通用请求。

java
复制代码
/** * 通用请求 * * @param qiApiClient qi api客户端 * @param request 要求 * @return {@link T} * @throws ApiException 业务异常 */ <O, T extends ResultResponse> T request(QiApiClient qiApiClient, BaseRequest<O, T> request) throws ApiException;

CurrencyRequest通过继承BaseRequest约定参数调用接口

java
复制代码
public class CurrencyRequest extends BaseRequest<Object, ResultResponse> { private String method; private String path; /** * get方法 * * @return {@link String} */ @Override public String getMethod() { return method; } public void setMethod(String method) { this.method = method; } /** * 获取路径 * * @return {@link String} */ @Override public String getPath() { return path; } public void setPath(String path) { this.path = path; } /** * 获取响应类 * * @return {@link Class}<{@link ResultResponse}> */ @Override public Class<ResultResponse> getResponseClass() { return ResultResponse.class; } }

BaseRequest中通过@JsonAnyGetter将接收的参数进行转换,

java
复制代码
public abstract class BaseRequest<O, T extends ResultResponse> { private Map<String, Object> requestParams = new HashMap<>(); /** * get方法 * * @return {@link RequestMethodEnum} */ public abstract String getMethod(); /** * 获取路径 * * @return {@link String} */ public abstract String getPath(); /** * 获取响应类 * * @return {@link Class}<{@link T}> */ public abstract Class<T> getResponseClass(); @JsonAnyGetter public Map<String, Object> getRequestParams() { return requestParams; } public void setRequestParams(O params) { this.requestParams = new Gson().fromJson(JSONUtil.toJsonStr(params), new TypeToken<Map<String, Object>>() { }.getType()); } }

每一个继承BaseRequest都会转换,子类来明确sdk用户调用所需要传递的参数,而不是用户谁便传。

例如获取用户输入name接口,规定用户的请求和响应。用户只需要调用我们提供的方法即可完成调用

更多示例:sdk-request-demo

java
复制代码
NameRequest nameRequest = new NameRequest(); NameParams nameRequest = new NameParams(); nameRequest.setName("123"); nameRequest.setRequestParams(nameRequest); NameResponse name = apiService.getName(nameRequest);
java
复制代码
@Data @Accessors(chain = true) public class NameParams implements Serializable { private static final long serialVersionUID = 3815188540434269370L; private String name; }
java
复制代码
@Accessors(chain = true) public class NameRequest extends BaseRequest<NameParams, NameResponse> { @Override public String getPath() { return "/name"; } /** * 获取响应类 * * @return {@link Class}<{@link NameResponse}> */ @Override public Class<NameResponse> getResponseClass() { return NameResponse.class; } @Override public String getMethod() { return RequestMethodEnum.GET.getValue(); } }

而CurrencyRequest类的请求参数泛型是Object类,也就是说用户可以自己传递参数,刚好我们可以在接口后台设置哪些参数可以请求,这一点实现了动态传参。

之后会调用抽象类中的request,使用模板方法构建必须的设置和检查

java
复制代码
@Slf4j @Data public abstract class BaseService implements ApiService { private QiApiClient qiApiClient; /** * 网关HOST */ private String gatewayHost = "https://gateway.qimuu.icu/api"; /** * 检查配置 * * @param qiApiClient qi api客户端 * @throws ApiException 业务异常 */ public void checkConfig(QiApiClient qiApiClient) throws ApiException { if (qiApiClient == null && this.getQiApiClient() == null) { throw new ApiException(ErrorCode.NO_AUTH_ERROR, "请先配置密钥AccessKey/SecretKey"); } if (qiApiClient != null && !StringUtils.isAnyBlank(qiApiClient.getAccessKey(), qiApiClient.getSecretKey())) { this.setQiApiClient(qiApiClient); } } /** * 执行请求 * * @param request 请求 * @return {@link HttpResponse} * @throws ApiException 业务异常 */ private <O, T extends ResultResponse> HttpResponse doRequest(BaseRequest<O, T> request) throws ApiException { try (HttpResponse httpResponse = getHttpRequestByRequestMethod(request).execute()) { return httpResponse; } catch (Exception e) { throw new ApiException(ErrorCode.OPERATION_ERROR, e.getMessage()); } } /** * 通过请求方法获取http响应 * * @param request 要求 * @return {@link HttpResponse} * @throws ApiException 业务异常 */ private <O, T extends ResultResponse> HttpRequest getHttpRequestByRequestMethod(BaseRequest<O, T> request) throws ApiException { if (ObjectUtils.isEmpty(request)) { throw new ApiException(ErrorCode.OPERATION_ERROR, "请求参数错误"); } String path = request.getPath().trim(); String method = request.getMethod().trim().toUpperCase(); if (ObjectUtils.isEmpty(method)) { throw new ApiException(ErrorCode.OPERATION_ERROR, "请求方法不存在"); } if (StringUtils.isBlank(path)) { throw new ApiException(ErrorCode.OPERATION_ERROR, "请求路径不存在"); } if (path.startsWith(gatewayHost)) { path = path.substring(gatewayHost.length()); } log.info("请求方法:{},请求路径:{},请求参数:{}", method, path, request.getRequestParams()); HttpRequest httpRequest; switch (method) { case "GET": { httpRequest = HttpRequest.get(splicingGetRequest(request, path)); break; } case "POST": { Map<String, Object> requestParams = request.getRequestParams(); String s = JSONUtil.toJsonStr(requestParams); System.err.println(s); httpRequest = HttpRequest.post(gatewayHost + path).body(JSONUtil.toJsonStr(request.getRequestParams())); break; } default: { throw new ApiException(ErrorCode.OPERATION_ERROR, "不支持该请求"); } } return httpRequest.addHeaders(getHeaders(JSONUtil.toJsonStr(request), qiApiClient)); } /** * 获取响应数据 * * @param request 要求 * @return {@link T} * @throws ApiException 业务异常 */ public <O, T extends ResultResponse> T res(BaseRequest<O, T> request) throws ApiException { if (qiApiClient == null || StringUtils.isAnyBlank(qiApiClient.getAccessKey(), qiApiClient.getSecretKey())) { throw new ApiException(ErrorCode.NO_AUTH_ERROR, "请先配置密钥AccessKey/SecretKey"); } T rsp; try { Class<T> clazz = request.getResponseClass(); rsp = clazz.newInstance(); } catch (Exception e) { throw new ApiException(ErrorCode.OPERATION_ERROR, e.getMessage()); } HttpResponse httpResponse = doRequest(request); String body = httpResponse.body(); Map<String, Object> data = new HashMap<>(); if (httpResponse.getStatus() != 200) { ErrorResponse errorResponse = JSONUtil.toBean(body, ErrorResponse.class); data.put("errorMessage", errorResponse.getMessage()); data.put("code", errorResponse.getCode()); } else { try { // 尝试解析为JSON对象 data = new Gson().fromJson(body, new TypeToken<Map<String, Object>>() { }.getType()); } catch (JsonSyntaxException e) { // 解析失败,将body作为普通字符串处理 data.put("value", body); } } rsp.setData(data); return rsp; } /** * 拼接Get请求 * * @param request 要求 * @param path 路径 * @return {@link String} */ private <O, T extends ResultResponse> String splicingGetRequest(BaseRequest<O, T> request, String path) { StringBuilder urlBuilder = new StringBuilder(gatewayHost); // urlBuilder最后是/结尾且path以/开头的情况下,去掉urlBuilder结尾的/ if (urlBuilder.toString().endsWith("/") && path.startsWith("/")) { urlBuilder.setLength(urlBuilder.length() - 1); } urlBuilder.append(path); if (!request.getRequestParams().isEmpty()) { urlBuilder.append("?"); for (Map.Entry<String, Object> entry : request.getRequestParams().entrySet()) { String key = entry.getKey(); String value = entry.getValue().toString(); urlBuilder.append(key).append("=").append(value).append("&"); } urlBuilder.deleteCharAt(urlBuilder.length() - 1); } log.info("GET请求路径:{}", urlBuilder); return urlBuilder.toString(); } /** * 获取请求头 * * @param body 请求体 * @param qiApiClient qi api客户端 * @return {@link Map}<{@link String}, {@link String}> */ private Map<String, String> getHeaders(String body, QiApiClient qiApiClient) { Map<String, String> hashMap = new HashMap<>(4); hashMap.put("accessKey", qiApiClient.getAccessKey()); String encodedBody = SecureUtil.md5(body); hashMap.put("body", encodedBody); hashMap.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); hashMap.put("sign", SignUtils.getSign(encodedBody, qiApiClient.getSecretKey())); return hashMap; } @Override public <O, T extends ResultResponse> T request(BaseRequest<O, T> request) throws ApiException { try { return res(request); } catch (Exception e) { throw new ApiException(ErrorCode.OPERATION_ERROR, e.getMessage()); } } @Override public <O, T extends ResultResponse> T request(QiApiClient qiApiClient, BaseRequest<O, T> request) throws ApiException { checkConfig(qiApiClient); return request(request); } }

之后就会请求对应的接口,返回数据后尝试将数据转换为规定的格式,不成功就转换为普通值

java
复制代码
* 获取响应数据 * * @param request 要求 * @return {@link T} * @throws ApiException 业务异常 */ public <O, T extends ResultResponse> T res(BaseRequest<O, T> request) throws ApiException { if (qiApiClient == null || StringUtils.isAnyBlank(qiApiClient.getAccessKey(), qiApiClient.getSecretKey())) { throw new ApiException(ErrorCode.NO_AUTH_ERROR, "请先配置密钥AccessKey/SecretKey"); } T rsp; try { Class<T> clazz = request.getResponseClass(); rsp = clazz.newInstance(); } catch (Exception e) { throw new ApiException(ErrorCode.OPERATION_ERROR, e.getMessage()); } HttpResponse httpResponse = doRequest(request); String body = httpResponse.body(); Map<String, Object> data = new HashMap<>(); if (httpResponse.getStatus() != 200) { ErrorResponse errorResponse = JSONUtil.toBean(body, ErrorResponse.class); data.put("errorMessage", errorResponse.getMessage()); data.put("code", errorResponse.getCode()); } else { try { // 尝试解析为JSON对象 data = new Gson().fromJson(body, new TypeToken<Map<String, Object>>() { }.getType()); } catch (JsonSyntaxException e) { // 解析失败,将body作为普通字符串处理 data.put("value", body); } } rsp.setData(data); return rsp; }

这样就能够实现动态调用接口啦!!!

image-20241231125721180
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
柒木
下载 APP