腾讯云SDK并发调用优化方案

目录

一、概述

二、 网关的使用

2.1 核心代码

三、腾讯云SDK依赖包的改造


一、概述

     此网关主要用于协调腾讯云SDK调用的QPS消耗,使得多个腾讯云用户资源能得到最大限度的利用。避免直接使用腾讯云SDK 时,在较大并发情况下导致接口调用异常。网关的工作流程如下图所示:

     如上图所示,各个客户端在发起腾讯云SDK调用时,请求统一先发到网关,网关会根据现有的腾讯云账户资源使用情况,通过负载均衡算法,选择一个合适的腾讯云账户来执行请求,将请求转发到腾讯云服务,从而保证了腾讯云用户资源的最大利用。在这个过程中,如果暂时未找到可用的腾讯云用户,则会阻塞线程,直到有可用的账户时再将线程唤醒放行,避免了在较大并发量时直接调用SDK,而导致接口报错的情况发生。

二、 网关的使用

2.1 核心代码

   RequestLimitFilter.java

package com.tencentcloudapi.gateway.filter;


import com.tencentcloudapi.common.Sign;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.gateway.api.dto.UserInfo;
import com.tencentcloudapi.gateway.api.service.UserManageService;
import io.micrometer.common.util.StringUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.xml.bind.DatatypeConverter;
import java.nio.charset.StandardCharsets;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.Semaphore;

/**
 * @Description 限流过滤器
 * @Author miller.Lai
 * @Date 2023-11-06 10:23
 */
@Component
@Slf4j
public class RequestLimitFilter implements GlobalFilter, Ordered {


    @Resource
    private UserManageService userManageService;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // 打印请求路径
        String requestUri = request.getPath().pathWithinApplication().value();
        log.info("接受到请求,请求路径:{}", requestUri);

        List<String> authorizationList = request.getHeaders().get("authorization");
        List<String> actionList = request.getHeaders().get("X-TC-Action");
        // 如果请求头中存在 authorization 信息,则要进行替换,以适应现有的接口TPS限制策略
        if (!CollectionUtils.isEmpty(authorizationList) && StringUtils.isNotEmpty(authorizationList.get(0))&&
                !CollectionUtils.isEmpty(actionList) && StringUtils.isNotEmpty(actionList.get(0))) {
            UserInfo userInfo = new UserInfo();
            // 接口名称
            String action = null;
            try {
                action = actionList.get(0);
                log.info("当前调用API名称:{}", action);
                // 获取可用的腾讯秘钥,这是一个阻塞方法
                userInfo = userManageService.getAvailableUserInfo(action);
                String secretId = userInfo.getSecretInfo().getSecretId();
                String secretKey = userInfo.getSecretInfo().getSecretKey();
                // 根据可用的秘钥对请求头中的认证信息做重新生成
                String signedHeaders = "content-type;host";
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                sdf.setTimeZone(TimeZone.getTimeZone("UTC"));

                // 接口请求所在秒钟
                String timestamp = String.valueOf(System.currentTimeMillis() / 1000L);
                String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
                String service = request.getHeaders().get("service") != null ?  request.getHeaders().get("service").get(0) : "";
                String credentialScope = date + "/" + service + "/tc3_request";
                String stringToSign = request.getHeaders().get("stringToSign") != null ? new String(Base64.getDecoder().decode(Objects.requireNonNull(request.getHeaders().get("stringToSign")).get(0).getBytes())) : "";
                try {
                    byte[] secretDate = Sign.hmac256(("TC3" + secretKey).getBytes(StandardCharsets.UTF_8), date);
                    byte[] secretService = Sign.hmac256(secretDate, service);
                    byte[] secretSigning = Sign.hmac256(secretService, "tc3_request");
                    String signature = DatatypeConverter.printHexBinary(Sign.hmac256(secretSigning, stringToSign)).toLowerCase();
                    String authorization = "TC3-HMAC-SHA256 Credential=" + secretId + "/" + credentialScope + ", SignedHeaders=" + signedHeaders + ", Signature=" + signature;
                    exchange.getRequest().mutate().headers(httpHeaders -> {
                        // 去除自定义的请求头
                        httpHeaders.remove("service");
                        httpHeaders.remove("stringToSign");
                        // 去除不合法的认证信息
                        httpHeaders.remove("authorization");
                        // 塞入有效的认证信息
                        httpHeaders.add("authorization", authorization);

                    });
                } catch (TencentCloudSDKException e) {
                    throw new RuntimeException(e);
                }
                log.info("线程 {} 的请求时间:{} 毫秒,secretId:{}",Thread.currentThread().getName(),System.currentTimeMillis(),secretId);
                UserInfo finalUserInfo = userInfo;
                String finalAction = action;


                // 如果上述过着正常获取信号量的许可
                String hasAcquired = userManageService.getHasAcquiredThreadLocal().get();
                userManageService.getHasAcquiredThreadLocal().remove();
                return chain.filter(exchange).then( Mono.fromRunnable(() -> {
                    // 接口逻辑执行完毕后释放信号量锁
                    if("1".equals(hasAcquired)){
                        finalUserInfo.getInterfaceInfo(finalAction).getSemaphore().release();
                        log.info("线程 {} 已释放线程锁",Thread.currentThread().getName());
                    }
                }));

            } catch (Exception e) {
                // 在发生错误时释放信号量锁
                // 将当前线程标记为已获取许可
                String hasAcquired = userManageService.getHasAcquiredThreadLocal().get();
                if("1".equals(hasAcquired)){
                    userInfo.getInterfaceInfo(action).getSemaphore().release();
                    userManageService.getHasAcquiredThreadLocal().remove();
                    log.info("线程 {} 已释放线程锁",Thread.currentThread().getName());
                }

                throw new RuntimeException(e);
            }
        }else{
            return chain.filter(exchange);
        }
    }

    @Override
    public int getOrder() {
        return 1;
    }
}
UserManageServiceImpl.java
package com.tencentcloudapi.gateway.api.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tencentcloudapi.gateway.api.dto.UserInfo;
import com.tencentcloudapi.gateway.api.dto.InterfaceInfo;
import io.micrometer.common.util.StringUtils;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @author Miller.Lai
 * @description:  腾讯云用户管理实现类
 * @date 2024-01-25 17:13:10
 */
@Service
public class UserManageServiceImpl implements UserManageService {

    private  List<UserInfo>  userInfos;


    @Value("${tencentcloud.api.authorization.file:}")
    private String authorizationJsonUrl;


    private ThreadLocal<String> hasAcquiredThreadLocal ;


    public ThreadLocal<String> getHasAcquiredThreadLocal() {
        return hasAcquiredThreadLocal;
    }


    @PostConstruct
    public void init(){
        try {
            InputStream inputStreams = null;
            // 如果有指定authorization文件路径,则按指定的路径找配置文件
            if (StringUtils.isNotBlank(authorizationJsonUrl)) {
                inputStreams = new FileInputStream(authorizationJsonUrl);
            } else {
                // 从resource目录下加载JSON文件
                Resource resource = new ClassPathResource("authorization.json");
                inputStreams = resource.getInputStream();
            }
            // 使用Jackson的ObjectMapper将JSON数组内容映射为List对象
            ObjectMapper objectMapper = new ObjectMapper();
            userInfos = objectMapper.readValue(inputStreams, new TypeReference<>() {});
            hasAcquiredThreadLocal =new ThreadLocal<>();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 随机获取一个可用的用户
     * @param action
     * @return
     */
    @Override
    public UserInfo getAvailableUserInfo(String action) {

        // 可用的用户列表
        List<UserInfo> availableUserInfos = new ArrayList<>();

        // 计算总权重,即总并发量
        int totalWeight = 0;

        // 过滤可用的腾讯用户,过滤条件: action匹配
        for (int i = 0; i < userInfos.size(); i++) {
            UserInfo userInfo =userInfos.get(i);
            // 当前用户是否可用
            boolean available = false;
            for (int j = 0; j < userInfo.getInterfaces().size(); j++) {
                InterfaceInfo interfaceInfo  = userInfo.getInterfaces().get(j);
                if(interfaceInfo.getAction().equals(action)){
                    available = true;
                    totalWeight += interfaceInfo.getMaxTPS();
                    break;
                }
            }
            // 如果当前用户可用,则加入列表
            if(available){
                availableUserInfos.add(userInfo);
            }
        }

        // 如果没找到可用用户,说明用户接口配置文件存在问题
        if(availableUserInfos.size() == 0 ){
           throw new RuntimeException("未找到可用的腾讯用户,请检查接口配置文件");
        }

        // 根据总权重生成权重随机数,0 ~ totalWeight-1
        int randomWeight = new Random().nextInt(totalWeight);

        // 根据权重值选择对应的用户
        int currentWeight = 0;
        for (int i = 0; i < availableUserInfos.size(); i++) {
            UserInfo userInfo = availableUserInfos.get(i);
            for (int j = 0; j < userInfo.getInterfaces().size(); j++) {
                InterfaceInfo interfaceInfo = userInfo.getInterfaces().get(j);
                // 找到对应的接口
                if (interfaceInfo.getAction().equals(action)) {
                    currentWeight += interfaceInfo.getMaxTPS();
                    // 如果当前接口的当前权重 > 随机权重, 则使用当前用户
                    if (currentWeight > randomWeight) {
                        // 给线程加锁,这是一个阻塞方法,如果当前用户的并发数达到上限,则当前线程会被阻塞
                        try {
                            interfaceInfo.getSemaphore().tryAcquire(30, TimeUnit.SECONDS);
                            // 将当前线程标记为已获取许可
                            hasAcquiredThreadLocal.set("1");
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        } finally {
                            return userInfo;
                        }

                    }
                    break;
                }
            }
        }

        return null;

    }
}

authorization.json

[
  {
    "secretInfo": {
      "secretId": "AKIDpiYK2xxxxxxxxxxxxxxxxxxbUyOk2W",
      "secretKey": "dFlSKDBDXXXXXXXXXXXXXXXXXXYdyBE5"
    },
    "interfaces": [
      {
        "action": "RecognizeTableAccurateOCR",
        "maxTPS": 2
      },
      {
        "action": "VehicleLicenseOCR",
        "maxTPS": 10
      },
      {
        "action": "DriverLicenseOCR",
        "maxTPS": 10
      },
      {
        "action": "MLIDPassportOCR",
        "maxTPS": 5
      },
      {
        "action": "HmtResidentPermitOCR",
        "maxTPS": 20
      },
      {
        "action": "MainlandPermitOCR",
        "maxTPS": 20
      }
    ]
  },
  {
    "secretInfo": {
      "secretId": "AKIDJh9fxxxxxxxxxxxxxxxxxxxxxv8IOR",
      "secretKey": "00HaowzxxxxxxxxxxxxxxxxxxxxxjMp8b"
    },
    "interfaces": [
      {
        "action": "RecognizeTableAccurateOCR",
        "maxTPS": 2
      },
      {
        "action": "VehicleLicenseOCR",
        "maxTPS": 10
      },
      {
        "action": "DriverLicenseOCR",
        "maxTPS": 10
      },
      {
        "action": "MLIDPassportOCR",
        "maxTPS": 5
      },
      {
        "action": "HmtResidentPermitOCR",
        "maxTPS": 20
      },
      {
        "action": "MainlandPermitOCR",
        "maxTPS": 20
      }
    ]
  }
]

application-dev.yml

spring:
  application:
    name: uap-gateway
  cloud:
    gateway:
      routes:
        - id: tencentcloud-route
          uri: https://ocr.tencentcloudapi.com
          predicates:
            - Path=/tencentcloudapi/**
          filters:
            - StripPrefix=1
  main:
    web-application-type: reactive
server:
  port: 9000

# 腾讯云用户接口权限配置文件
#tencentcloud:
#  api:
#    authorization:
#      file: d://authorization.json

三、腾讯云SDK依赖包的改造

修改 com.tencentcloudapi.common.AbstractClient 类中 REMOTE_SERVER_ADDRESS 的值,指向实际的网关地址,如下所示:

/*
 * Copyright (c) 2018 THL A29 Limited, a Tencent company. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package com.tencentcloudapi.common;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.google.gson.reflect.TypeToken;
import com.tencentcloudapi.common.exception.TencentCloudSDKException;
import com.tencentcloudapi.common.http.HttpConnection;
import com.tencentcloudapi.common.profile.ClientProfile;
import com.tencentcloudapi.common.profile.HttpProfile;
import okhttp3.*;
import okhttp3.Headers.Builder;

import javax.crypto.Mac;
import javax.net.ssl.SSLContext;
import javax.xml.bind.DatatypeConverter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.*;

public abstract class AbstractClient {
  /**
   * 远程服务地址,根据实际情况修改
   * 格式为:http://ip:port/tencentcloudapi
   */
  public static String REMOTE_SERVER_ADDRESS = "http://localhost:9000/tencentcloudapi";
  public static final int HTTP_RSP_OK = 200;
  public static final String SDK_VERSION = "SDK_JAVA_3.1.699";

  private Credential credential;
  private ClientProfile profile;
  private String endpoint;
  private String service;
  private String region;
  private String path;
  private String sdkVersion;
  private String apiVersion;
  public Gson gson;
  private TCLog log;
  private HttpConnection httpConnection;
  public AbstractClient(String endpoint, String version, Credential credential, String region) {
    this(endpoint, version, credential, region, new ClientProfile());
  }

  static {
    String remoteServerAddress = System.getenv("REMOTE_SERVER_ADDRESS");
    if(remoteServerAddress != null){
      AbstractClient.REMOTE_SERVER_ADDRESS = remoteServerAddress;
    }
  }


  public AbstractClient(
      String endpoint,
      String version,
      Credential credential,
      String region,
      ClientProfile profile) {
    this.credential = credential;
    this.profile = profile;
    this.endpoint = endpoint;
    this.service = endpoint.split("\\.")[0];
    this.region = region;
    this.path = "/";
    this.sdkVersion = AbstractClient.SDK_VERSION;
    this.apiVersion = version;
    this.gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();
    this.log = new TCLog(getClass().getName(), profile.isDebug());
    this.httpConnection = new HttpConnection(
            this.profile.getHttpProfile().getConnTimeout(),
            this.profile.getHttpProfile().getReadTimeout(),
            this.profile.getHttpProfile().getWriteTimeout()
    );
    this.httpConnection.addInterceptors(this.log);
    this.trySetProxy(this.httpConnection);
    warmup();
  }

  public void setRegion(String region) {
    this.region = region;
  }

  public String getRegion() {
    return this.region;
  }

  public void setClientProfile(ClientProfile profile) {
    this.profile = profile;
  }

  public ClientProfile getClientProfile() {
    return this.profile;
  }

  public void setCredential(Credential credential) {
    this.credential = credential;
  }

  public Credential getCredential() {
    return this.credential;
  }

  /**
   * Use post/json with tc3-hmac-sha256 signature to call any action. Ignore request method and
   * signature method defined in profile.
   *
   * @param action Name of action to be called.
   * @param jsonPayload Parameters of action serialized in json string format.
   * @return Raw response from API if request succeeded, otherwise an exception will be raised
   *     instead of raw response
   * @throws TencentCloudSDKException
   */
  public String call(String action, String jsonPayload) throws TencentCloudSDKException {
    HashMap<String, String> headers = this.getHeaders();
    headers.put("X-TC-Action", action);
    headers.put("Content-Type", "application/json; charset=utf-8");
    byte[] requestPayload = jsonPayload.getBytes(StandardCharsets.UTF_8);
    String authorization = this.getAuthorization(headers, requestPayload);
    headers.put("Authorization", authorization);
    String url = REMOTE_SERVER_ADDRESS + this.path;
    return this.getResponseBody(url, headers, requestPayload);
  }

  /**
   * Use post application/octet-stream with tc3-hmac-sha256 signature to call specific action.
   * Ignore request method and signature method defined in profile.
   *
   * @param action Name of action to be called.
   * @param headers Parameters of the action, will be put in http header.
   * @param body octet-stream binary body.
   * @return Raw response from API if request succeeded, otherwise an exception will be raised
   *     instead of raw response
   * @throws TencentCloudSDKException
   */
  public String callOctetStream(String action, HashMap<String, String> headers, byte[] body)
      throws TencentCloudSDKException {
    headers.putAll(this.getHeaders());
    headers.put("X-TC-Action", action);
    headers.put("Content-Type", "application/octet-stream; charset=utf-8");
    String authorization = this.getAuthorization(headers, body);
    headers.put("Authorization", authorization);
    String url = REMOTE_SERVER_ADDRESS + this.path;
    return this.getResponseBody(url, headers, body);
  }

  private HashMap<String, String> getHeaders() {
    HashMap<String, String> headers = new HashMap<String, String>();
    String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
    headers.put("X-TC-Timestamp", timestamp);
    headers.put("X-TC-Version", this.apiVersion);
    headers.put("X-TC-Region", this.getRegion());
    headers.put("X-TC-RequestClient", SDK_VERSION);
    headers.put("Host", this.getEndpoint());
    String token = this.credential.getToken();
    if (token != null && !token.isEmpty()) {
      headers.put("X-TC-Token", token);
    }
    if (this.profile.isUnsignedPayload()) {
      headers.put("X-TC-Content-SHA256", "UNSIGNED-PAYLOAD");
    }
    if (null != this.profile.getLanguage()) {
      headers.put("X-TC-Language", this.profile.getLanguage().getValue());
    }
    return headers;
  }

  private String getAuthorization(HashMap<String, String> headers, byte[] body)
      throws TencentCloudSDKException {
    String endpoint = this.getEndpoint();
    // always use post tc3-hmac-sha256 signature process
    // okhttp always set charset even we don't specify it,
    // to ensure signature be correct, we have to set it here as well.
    String contentType = headers.get("Content-Type");
    byte[] requestPayload = body;
    String canonicalUri = "/";
    String canonicalQueryString = "";
    String canonicalHeaders = "content-type:" + contentType + "\nhost:" + endpoint + "\n";
    String signedHeaders = "content-type;host";

    String hashedRequestPayload = "";
    if (this.profile.isUnsignedPayload()) {
      hashedRequestPayload = Sign.sha256Hex("UNSIGNED-PAYLOAD".getBytes(StandardCharsets.UTF_8));
    } else {
      hashedRequestPayload = Sign.sha256Hex(requestPayload);
    }
    String canonicalRequest =
        HttpProfile.REQ_POST
            + "\n"
            + canonicalUri
            + "\n"
            + canonicalQueryString
            + "\n"
            + canonicalHeaders
            + "\n"
            + signedHeaders
            + "\n"
            + hashedRequestPayload;

    String timestamp = headers.get("X-TC-Timestamp");
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
    String service = endpoint.split("\\.")[0];
    String credentialScope = date + "/" + service + "/" + "tc3_request";
    String hashedCanonicalRequest =
        Sign.sha256Hex(canonicalRequest.getBytes(StandardCharsets.UTF_8));
    String stringToSign =
        "TC3-HMAC-SHA256\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;

    String secretId = this.credential.getSecretId();
    String secretKey = this.credential.getSecretKey();
    byte[] secretDate = Sign.hmac256(("TC3" + secretKey).getBytes(StandardCharsets.UTF_8), date);
    byte[] secretService = Sign.hmac256(secretDate, service);
    byte[] secretSigning = Sign.hmac256(secretService, "tc3_request");
    String signature =
        DatatypeConverter.printHexBinary(Sign.hmac256(secretSigning, stringToSign)).toLowerCase();
    return "TC3-HMAC-SHA256 "
        + "Credential="
        + secretId
        + "/"
        + credentialScope
        + ", "
        + "SignedHeaders="
        + signedHeaders
        + ", "
        + "Signature="
        + signature;
  }

  private String getResponseBody(String url, HashMap<String, String> headers, byte[] body)
      throws TencentCloudSDKException {
    Builder hb = new Builder();
    for (String key : headers.keySet()) {
      hb.add(key, headers.get(key));
    }
    Response resp = this.httpConnection.postRequest(url, body, hb.build());
    if (resp.code() != AbstractClient.HTTP_RSP_OK) {
      String msg = "response code is " + resp.code() + ", not 200";
      log.info(msg);
      throw new TencentCloudSDKException(msg, "", "ServerSideError");
    }
    String respbody = null;
    try {
      respbody = resp.body().string();
    } catch (IOException e) {
      String msg =
          "Cannot transfer response body to string, because Content-Length is too large, or Content-Length and stream length disagree.";
      log.info(msg);
      throw new TencentCloudSDKException(msg, "", e.getClass().getName());
    }
    JsonResponseModel<JsonResponseErrModel> errResp = null;
    try {
      Type errType = new TypeToken<JsonResponseModel<JsonResponseErrModel>>() {}.getType();
      errResp = gson.fromJson(respbody, errType);
    } catch (JsonSyntaxException e) {
      String msg = "json is not a valid representation for an object of type";
      log.info(msg);
      throw new TencentCloudSDKException(msg, "", e.getClass().getName());
    }
    if (errResp.response.error != null) {
      throw new TencentCloudSDKException(
          errResp.response.error.message, errResp.response.requestId, errResp.response.error.code);
    }
    return respbody;
  }

  private void trySetProxy(HttpConnection conn) {
    String host = this.profile.getHttpProfile().getProxyHost();
    int port = this.profile.getHttpProfile().getProxyPort();

    if (host == null || host.isEmpty()) {
      return;
    }
    Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
    conn.setProxy(proxy);

    final String username = this.profile.getHttpProfile().getProxyUsername();
    final String password = this.profile.getHttpProfile().getProxyPassword();
    if (username == null || username.isEmpty()) {
      return;
    }
    conn.setProxyAuthenticator(
        new Authenticator() {
          @Override
          public Request authenticate(Route route, Response response) throws IOException {
            String credential = Credentials.basic(username, password);
            return response
                    .request()
                    .newBuilder()
                    .header("Proxy-Authorization", credential)
                    .build();
          }
        });
  }

  protected String internalRequest(AbstractModel request, String actionName)
      throws TencentCloudSDKException {
    Response okRsp = null;
    String endpoint = this.getEndpoint();
    String[] binaryParams = request.getBinaryParams();
    String sm = this.profile.getSignMethod();
    String reqMethod = this.profile.getHttpProfile().getReqMethod();

    // currently, customized params only can be supported via post json tc3-hmac-sha256
    HashMap<String, Object> customizedParams = request.any();
    if (customizedParams.size() > 0) {
      if (binaryParams.length > 0) {
        throw new TencentCloudSDKException(
            "WrongUsage: Cannot post multipart with customized parameters.");
      }
      if (sm.equals(ClientProfile.SIGN_SHA1) || sm.equals(ClientProfile.SIGN_SHA256)) {
        throw new TencentCloudSDKException(
            "WrongUsage: Cannot use HmacSHA1 or HmacSHA256 with customized parameters.");
      }
      if (reqMethod.equals(HttpProfile.REQ_GET)) {
        throw new TencentCloudSDKException(
            "WrongUsage: Cannot use get method with customized parameters.");
      }
    }

    if (binaryParams.length > 0 || sm.equals(ClientProfile.SIGN_TC3_256)) {
      okRsp = doRequestWithTC3(endpoint, request, actionName);
    } else if (sm.equals(ClientProfile.SIGN_SHA1) || sm.equals(ClientProfile.SIGN_SHA256)) {
      okRsp = doRequest(endpoint, request, actionName);
    } else {
      throw new TencentCloudSDKException(
          "Signature method " + sm + " is invalid or not supported yet.");
    }

    if (okRsp.code() != AbstractClient.HTTP_RSP_OK) {
      String msg = "response code is " + okRsp.code() + ", not 200";
      log.info(msg);
      throw new TencentCloudSDKException(msg, "", "ServerSideError");
    }
    String strResp = null;
    try {
      strResp = okRsp.body().string();
    } catch (IOException e) {
      String msg = "Cannot transfer response body to string, because Content-Length is too large, or Content-Length and stream length disagree.";
      log.info(msg);
      throw new TencentCloudSDKException(msg, "", endpoint.getClass().getName());
    }

    JsonResponseModel<JsonResponseErrModel> errResp = null;
    try {
      Type errType = new TypeToken<JsonResponseModel<JsonResponseErrModel>>() {}.getType();
      errResp = gson.fromJson(strResp, errType);
    } catch (JsonSyntaxException e) {
      String msg = "json is not a valid representation for an object of type";
      log.info(msg);
      throw new TencentCloudSDKException(msg, "", e.getClass().getName());
    }
    if (errResp.response.error != null) {
      throw new TencentCloudSDKException(
          errResp.response.error.message,
          errResp.response.requestId,
          errResp.response.error.code);
    }

    return strResp;
  }

  private Response doRequest(String endpoint, AbstractModel request, String action)
      throws TencentCloudSDKException {
    HashMap<String, String> param = new HashMap<String, String>();
    request.toMap(param, "");
    String strParam = this.formatRequestData(action, param);
    String reqMethod = this.profile.getHttpProfile().getReqMethod();
    String url = REMOTE_SERVER_ADDRESS + this.path;
    if (reqMethod.equals(HttpProfile.REQ_GET)) {
      return this.httpConnection.getRequest(url + "?" + strParam);
    } else if (reqMethod.equals(HttpProfile.REQ_POST)) {
      return this.httpConnection.postRequest(url, strParam);
    } else {
      throw new TencentCloudSDKException("Method only support (GET, POST)");
    }
  }

  private Response doRequestWithTC3(String endpoint, AbstractModel request, String action)
      throws TencentCloudSDKException {
    String httpRequestMethod = this.profile.getHttpProfile().getReqMethod();
    if (httpRequestMethod == null) {
      throw new TencentCloudSDKException(
          "Request method should not be null, can only be GET or POST");
    }
    String contentType = "application/x-www-form-urlencoded";
    byte[] requestPayload = "".getBytes(StandardCharsets.UTF_8);
    HashMap<String, String> params = new HashMap<String, String>();
    request.toMap(params, "");
    String[] binaryParams = request.getBinaryParams();
    if (binaryParams.length > 0) {
      httpRequestMethod = HttpProfile.REQ_POST;
      String boundary = UUID.randomUUID().toString();
      // okhttp always set charset even we don't specify it,
      // to ensure signature be correct, we have to set it here as well.
      contentType = "multipart/form-data; charset=utf-8" + "; boundary=" + boundary;
      try {
        requestPayload = getMultipartPayload(request, boundary);
      } catch (Exception e) {
        throw new TencentCloudSDKException("Failed to generate multipart. because: " + e);
      }
    } else if (httpRequestMethod.equals(HttpProfile.REQ_POST)) {
      requestPayload = AbstractModel.toJsonString(request).getBytes(StandardCharsets.UTF_8);
      // okhttp always set charset even we don't specify it,
      // to ensure signature be correct, we have to set it here as well.
      contentType = "application/json; charset=utf-8";
    }
    String canonicalUri = "/";
    String canonicalQueryString = this.getCanonicalQueryString(params, httpRequestMethod);
    String canonicalHeaders = "content-type:" + contentType + "\nhost:" + endpoint + "\n";
    String signedHeaders = "content-type;host";

    String hashedRequestPayload = "";
    if (this.profile.isUnsignedPayload()) {
      hashedRequestPayload = Sign.sha256Hex("UNSIGNED-PAYLOAD".getBytes(StandardCharsets.UTF_8));
    } else {
      hashedRequestPayload = Sign.sha256Hex(requestPayload);
    }
    String canonicalRequest =
        httpRequestMethod
            + "\n"
            + canonicalUri
            + "\n"
            + canonicalQueryString
            + "\n"
            + canonicalHeaders
            + "\n"
            + signedHeaders
            + "\n"
            + hashedRequestPayload;

    String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
    String date = sdf.format(new Date(Long.valueOf(timestamp + "000")));
    String service = endpoint.split("\\.")[0];
    String credentialScope = date + "/" + service + "/" + "tc3_request";
    String hashedCanonicalRequest =
        Sign.sha256Hex(canonicalRequest.getBytes(StandardCharsets.UTF_8));
    String stringToSign =
        "TC3-HMAC-SHA256\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest;

    String secretId = this.credential.getSecretId();
    String secretKey = this.credential.getSecretKey();
    byte[] secretDate = Sign.hmac256(("TC3" + secretKey).getBytes(StandardCharsets.UTF_8), date);
    byte[] secretService = Sign.hmac256(secretDate, service);
    byte[] secretSigning = Sign.hmac256(secretService, "tc3_request");
    String signature =
        DatatypeConverter.printHexBinary(Sign.hmac256(secretSigning, stringToSign)).toLowerCase();
    String authorization =
        "TC3-HMAC-SHA256 "
            + "Credential="
            + secretId
            + "/"
            + credentialScope
            + ", "
            + "SignedHeaders="
            + signedHeaders
            + ", "
            + "Signature="
            + signature;

    String url = REMOTE_SERVER_ADDRESS + this.path;
    Builder hb = new Builder();
    hb.add("Content-Type", contentType)
        .add("Host", endpoint)
        .add("Authorization", authorization)
        .add("X-TC-Action", action)
        .add("X-TC-Timestamp", timestamp)
        .add("X-TC-Version", this.apiVersion)
        .add("X-TC-RequestClient", SDK_VERSION);
    if (null != this.getRegion()) {
      hb.add("X-TC-Region", this.getRegion());
    }
    String token = this.credential.getToken();
    if (token != null && !token.isEmpty()) {
      hb.add("X-TC-Token", token);
    }
    if (this.profile.isUnsignedPayload()) {
      hb.add("X-TC-Content-SHA256", "UNSIGNED-PAYLOAD");
    }
    if (null != this.profile.getLanguage()) {
      hb.add("X-TC-Language", this.profile.getLanguage().getValue());
    }
    if (null != service ) {
      hb.add("service", service);
    }
    if (null != stringToSign ) {
      hb.add("stringToSign", new String(Base64.getEncoder().encode(stringToSign.getBytes())));
    }
    Headers headers = hb.build();
    if (httpRequestMethod.equals(HttpProfile.REQ_GET)) {
      return this.httpConnection.getRequest(url + "?" + canonicalQueryString, headers);
    } else if (httpRequestMethod.equals(HttpProfile.REQ_POST)) {
      return this.httpConnection.postRequest(url, requestPayload, headers);
    } else {
      throw new TencentCloudSDKException("Method only support GET, POST");
    }
  }

  private byte[] getMultipartPayload(AbstractModel request, String boundary) throws Exception {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    String[] binaryParams = request.getBinaryParams();
    for (Map.Entry<String, byte[]> entry : request.getMultipartRequestParams().entrySet()) {
      baos.write("--".getBytes(StandardCharsets.UTF_8));
      baos.write(boundary.getBytes(StandardCharsets.UTF_8));
      baos.write("\r\n".getBytes(StandardCharsets.UTF_8));
      baos.write("Content-Disposition: form-data; name=\"".getBytes(StandardCharsets.UTF_8));
      baos.write(entry.getKey().getBytes(StandardCharsets.UTF_8));
      if (Arrays.asList(binaryParams).contains(entry.getKey())) {
        baos.write("\"; filename=\"".getBytes(StandardCharsets.UTF_8));
        baos.write(entry.getKey().getBytes(StandardCharsets.UTF_8));
        baos.write("\"\r\n".getBytes(StandardCharsets.UTF_8));
      } else {
        baos.write("\"\r\n".getBytes(StandardCharsets.UTF_8));
      }
      baos.write("\r\n".getBytes(StandardCharsets.UTF_8));
      baos.write(entry.getValue());
      baos.write("\r\n".getBytes(StandardCharsets.UTF_8));
    }
    if (baos.size() != 0) {
      baos.write("--".getBytes(StandardCharsets.UTF_8));
      baos.write(boundary.getBytes(StandardCharsets.UTF_8));
      baos.write("--\r\n".getBytes(StandardCharsets.UTF_8));
    }
    byte[] bytes = baos.toByteArray();
    baos.close();
    return bytes;
  }

  private String getCanonicalQueryString(HashMap<String, String> params, String method)
      throws TencentCloudSDKException {
    if (method != null && method.equals(HttpProfile.REQ_POST)) {
      return "";
    }
    StringBuilder queryString = new StringBuilder("");
    for (Map.Entry<String, String> entry : params.entrySet()) {
      String v;
      try {
        v = URLEncoder.encode(entry.getValue(), "UTF8");
      } catch (UnsupportedEncodingException e) {
        throw new TencentCloudSDKException("UTF8 is not supported." + e.getMessage());
      }
      queryString.append("&").append(entry.getKey()).append("=").append(v);
    }
    if (queryString.length() == 0) {
      return "";
    } else {
      return queryString.toString().substring(1);
    }
  }

  private String formatRequestData(String action, Map<String, String> param)
      throws TencentCloudSDKException {
    param.put("Action", action);
    param.put("RequestClient", this.sdkVersion);
    param.put("Nonce", String.valueOf(Math.abs(new SecureRandom().nextInt())));
    param.put("Timestamp", String.valueOf(System.currentTimeMillis() / 1000));
    param.put("Version", this.apiVersion);

    if (this.credential.getSecretId() != null && (!this.credential.getSecretId().isEmpty())) {
      param.put("SecretId", this.credential.getSecretId());
    }

    if (this.region != null && (!this.region.isEmpty())) {
      param.put("Region", this.region);
    }

    if (this.profile.getSignMethod() != null && (!this.profile.getSignMethod().isEmpty())) {
      param.put("SignatureMethod", this.profile.getSignMethod());
    }

    if (this.credential.getToken() != null && (!this.credential.getToken().isEmpty())) {
      param.put("Token", this.credential.getToken());
    }

    if (null != this.profile.getLanguage()) {
      param.put("Language", this.profile.getLanguage().getValue());
    }

    String endpoint = this.getEndpoint();

    String sigInParam =
        Sign.makeSignPlainText(
            new TreeMap<String, String>(param),
            this.profile.getHttpProfile().getReqMethod(),
            endpoint,
            this.path);
    String sigOutParam =
        Sign.sign(this.credential.getSecretKey(), sigInParam, this.profile.getSignMethod());

    String strParam = "";
    try {
      for (Map.Entry<String, String> entry : param.entrySet()) {
        strParam +=
            (URLEncoder.encode(entry.getKey(), "utf-8")
                + "="
                + URLEncoder.encode(entry.getValue(), "utf-8")
                + "&");
      }
      strParam += ("Signature=" + URLEncoder.encode(sigOutParam, "utf-8"));
    } catch (UnsupportedEncodingException e) {
      throw new TencentCloudSDKException(e.getClass().getName() + "-" + e.getMessage());
    }
    return strParam;
  }

  /** warm up, try to avoid unnecessary cost in the first request */
  private void warmup() {
    try {
      // it happens in SDK signature process.
      // first invoke costs around 250 ms.
      Mac.getInstance("HmacSHA1");
      Mac.getInstance("HmacSHA256");
      // it happens inside okhttp, but I think any https framework/package will do the same.
      // first invoke costs around 150 ms.
      SSLContext sslContext = SSLContext.getInstance("TLS");
      sslContext.init(null, null, null);
    } catch (Exception e) {
      // ignore but print message to console
      e.printStackTrace();
    }
  }

  private String getEndpoint() {
    // in case user has reset endpoint after init this client
    if (null != this.profile.getHttpProfile().getEndpoint()) {
      return this.profile.getHttpProfile().getEndpoint();
    } else {
      // protected abstract String getService();
      // use this.getService() from overrided subclass will be better
      return this.service + "." + this.profile.getHttpProfile().getRootDomain();
    }
  }

  /**
   * 请注意购买类接口谨慎调用,可能导致多次购买
   * 仅幂等接口推荐使用
   *
   * @param req
   * @param retryTimes
   * @throws TencentCloudSDKException
   */
  public Object retry(AbstractModel req, int retryTimes) throws TencentCloudSDKException {
    if (retryTimes < 0 || retryTimes > 10) {
      throw new TencentCloudSDKException("The number of retryTimes supported is 0 to 10.", "", "ClientSideError");
    }
    Class cls = this.getClass();
    String methodName = req.getClass().getSimpleName().replace("Request", "");
    Method method;
    try {
      method = cls.getMethod(methodName, req.getClass());
    } catch (NoSuchMethodException e) {
      throw new TencentCloudSDKException(e.toString(), "", "ClientSideError");
    }
    do {
      try {
        return method.invoke(this, req);
      } catch (IllegalAccessException e) {
        throw new TencentCloudSDKException(e.toString(), "", "ClientSideError");
      } catch (InvocationTargetException e) {
        if (retryTimes == 0) {
          throw (TencentCloudSDKException) e.getTargetException();
        }
      }
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        throw new TencentCloudSDKException(e.toString(), "", "ClientSideError");
      }
    } while (--retryTimes >= 0);
    return null;
  }
}

  将源码中上述类修改后重新达成jar包即可。

       本人近十年JAVA架构设计经验,长期从事IT技术资源整合。有志于自我技术提升、需要最新IT技术课程的小伙伴,可私信联系我 ,粉丝一律白菜价 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/358230.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AtCoder Beginner Contest 338 A~F

A.Capitalized?&#xff08;模拟&#xff09; 题意&#xff1a; 给一个字符串 s s s&#xff0c;询问 s s s的第一个字母是不是大写&#xff0c;并且其他字母都是小写。 分析&#xff1a; 使用 A S C I I ASCII ASCII码&#xff0c;单独判断第一个字母&#xff0c;循环判断…

三步万能公式解决软件各种打不开异常

程序员都知道,辛苦做的软件发给客户打不开那是一个大写的尴尬,尴尬归尴尬还是要想办法解决问题. 第一步清理环境. 目标机台有环境和没有运行环境的,统统把vs环境卸载了,让目标机台缺少环境.第二步打包环境 源代码添加打包工程,setup,重新编译.![添加setup ](https://img-blo…

vue3项目中让echarts适应div的大小变化,跟随div的大小改变图表大小

目录如下 我的项目环境如下利用element-resize-detector插件监听元素大小变化element-resize-detector插件的用法完整代码如下&#xff1a;结果如下 在做项目的时候&#xff0c;经常会使用到echarts&#xff0c;特别是在做一些大屏项目的时候。有时候我们是需要根据div的大小改…

一文说清楚仿真与数字孪生的关系

获取更多资讯&#xff0c;赶快关注上面的公众号吧&#xff01; 文章目录 何为仿真何为数字孪生 最近看群里的小伙伴在疯狂讨论数字孪生&#xff0c;今天我也谈谈自己的理解。 之前还在北航读博的时候&#xff0c;北航陶飞教授已经算是数字孪生领域的领军人物&#xff0c;也专门…

【C++】2024.01.29 克隆机

题目描述 有一台神奇的克隆机&#xff0c;可以克隆任何东西。将样品放进克隆机&#xff0c;可以克隆出一份一样的“复制品”。小明得到了 k 种珍贵的植物种子&#xff0c;依次用 A,B,C,D,...,Z 表示&#xff08;1≤k≤26&#xff09;。一开始&#xff0c;每种植物种子只有…

PyFlink使用教程,Flink,Python,Java

环境准备 环境要求 Java 11 Python 3.7, 3.8, 3.9 or 3.10文档&#xff1a;https://nightlies.apache.org/flink/flink-docs-release-1.17/zh/docs/dev/python/installation/ 打开 Anaconda3 Prompt > java -version java version "11.0.22" 2024-01-16 LTS J…

信息安全考证攻略

&#x1f525;在信息安全领域&#xff0c;拥有相关的证书不仅能提升自己的专业技能&#xff0c;更能为职业生涯增添不少光彩。下面为大家盘点了一些国内外实用的信息安全证书&#xff0c;让你一睹为快&#xff01; &#x1f31f;国内证书&#xff08;认证机构&#xff1a;中国信…

网工,这才是跳纤的正确姿势!

晚上好&#xff0c;我的网工朋友。 当你们看到下面这张图&#xff0c;内心是什么感想&#xff1f; 这时你是不是巴不得把所有线全部拔了&#xff0c;来重新整一遍哈哈哈哈。那话说到这&#xff0c;到底该如何跳纤呢&#xff1f;有没有什么秘诀呢&#xff1f;遵循什么原则&#…

GLOBALCHIP GC3909Pin to Pin兼容A3909/allegro电机驱动芯片产品参数分析,应用于摇头机,舞台灯,打印机,白色家电等

GLOBALCHIP GC3909 12V H 桥驱动器芯片替代A3909/Allegro产品概述: GC3909是一款双通道12V直流电机驱动芯片&#xff0c;为摄像机、消费类产品、玩具和其他低压或者电池供电的运动控制类应用提供了集成的电机驱动解决方案。芯片一般用来驱动两个直流电机或者驱动一个步进电机。…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之DataPanel组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之DataPanel组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、DataPanel组件 数据面板组件&#xff0c;用于将多个数据占比情况使用占比图进…

网络安全全栈培训笔记(59-服务攻防-中间件安全CVE复现lSApacheTomcataNginx)

第59天 服务攻防-中间件安全&CVE复现&lS&Apache&Tomcata&Nginx 知识点&#xff1a; 中间件及框架列表&#xff1a; lIS,Apache,Nginx,Tomcat,Docker,Weblogic,JBoos,WebSphere,Jenkins, GlassFish,Jira,Struts2,Laravel,Solr,Shiro,Thinkphp,Sprng,Flask,…

Linux实验记录:使用iptables

前言&#xff1a; 本文是一篇关于Linux系统初学者的实验记录。 参考书籍&#xff1a;《Linux就该这么学》 实验环境&#xff1a; VmwareWorkStation 17——虚拟机软件 RedHatEnterpriseLinux[RHEL]8——红帽操作系统 备注&#xff1a; 防火墙作为公网与内网的屏障&#…

【linux】磁盘空间不足-常用排查和处理命令

【linux】磁盘空间不足-常用排查和处理命令 1.通查一下 df -h #查看服务器磁盘空间情况 du -hs * 2>/dev/null #列出各目录所占空间大小 或 du -h -d 1 2>/dev/null #列出各目录所占空间大小 1.1情况一 df 磁盘空间和du 目录空间占用相等&#xff0c…

C++中 this指针、构造函数、析构函数

1.this指针 我们定义一个日期类来举例子 对于上述类&#xff0c;有这样一个问题&#xff0c;Date类中有Init和Print这两个成员函数&#xff0c;函数体中没有关于不同对象的区分&#xff0c;那d1调用函数的时候&#xff0c;编译器是如和来确定d1而不是d2呢&#xff1f;C通过引入…

Linux线程安全

Linux线程安全 Linux线程互斥进程线程间的互斥相关背景概念互斥量mutex互斥量的接口 可重入VS线程安全常见锁概念死锁的四个必要条件 Linux线程同步条件变量 Linux线程互斥 进程线程间的互斥相关背景概念 临界资源和临界区 进程之间如果要进行通信我们需要先创建第三方资源&a…

虚拟机安装Centos8.5

记得看目录哦&#xff01; 附件1. 新建虚拟机2. 安装Centos8.5 附件 安装包自行下载 https://mirrors.aliyun.com/centos/8/isos/x86_64/ 1. 新建虚拟机 2. 安装Centos8.5 启动虚拟机–选择第一个install Centos8.5 记得接收许可证

VUE3子表格嵌套分页查询互相干扰的问题解决

VUE3在表格中嵌套子表格子表格的分页查询互相干扰的问题解决 简单嵌套 如果不需要做子表格的分页查询&#xff0c;那么可以直接在主表格中嵌套子表格&#xff0c;有两种方式&#xff1b;一种是主表格加载的同时加载子表格数据&#xff0c;另一种是点击展开时加载子表格数据&…

(2024,初始化原型嵌入,扩散模型微调,类别特征正则化,对象特定损失)使用原型嵌入对文本到图像扩散进行对象驱动的单样本微调

Object-Driven One-Shot Fine-tuning of Text-to-Image Diffusion with Prototypical Embedding 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 3. 方法 3.1 概述 3.2 LDM …

面试官要你介绍项目,怎么说?

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

Golang数据结构性能优化实践

仅仅通过对struct字段重新排序&#xff0c;优化内存对齐方式&#xff0c;就可以获得明显的内存和执行效率提升。原文: How to Speed Up Your Struct in Golang Mike Pexels 如果你有Golang开发经验&#xff0c;一定定义过struct类型。 但可能你不知道&#xff0c;通过简单的重新…