腾讯云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 {@Resourceprivate UserManageService userManageService;@Overridepublic 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);}}@Overridepublic 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;}@PostConstructpublic 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*/@Overridepublic 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-1int 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-gatewaycloud:gateway:routes:- id: tencentcloud-routeuri: https://ocr.tencentcloudapi.compredicates:- Path=/tencentcloudapi/**filters:- StripPrefix=1main: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() {@Overridepublic 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-sha256HashMap<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 consolee.printStackTrace();}}private String getEndpoint() {// in case user has reset endpoint after init this clientif (null != this.profile.getHttpProfile().getEndpoint()) {return this.profile.getHttpProfile().getEndpoint();} else {// protected abstract String getService();// use this.getService() from overrided subclass will be betterreturn 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.mzph.cn/news/657364.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Yolo v8 入门学习之采用 coco128 数据集进行图片检测测试

示例入门代码 from ultralytics import YOLO import cv2 import matplotlib.pyplot as plt import matplotlib.image as mpimgdef test():# Create a new YOLO model from scratchmodel YOLO(yolov8n.yaml)# Load a pretrained YOLO model (recommended for training)model …

Redis -- 开篇热身,常用的全局命令

目录 Redis重要文件 启动停止脚本 配置文件 持久化文件存储目录 核心命令 set get 全局命令 keys exists del expire ttl 过期策略是如何实现的 定时器 type 小结 Redis重要文件 启动停止脚本 /usr/bin/redis-benchmark &#xff1a; 用于对Redis做性能基准…

SpringCloud_学习笔记_1

SpringCloud01 1.认识微服务 随着互联网行业的发展&#xff0c;对服务的要求也越来越高&#xff0c;服务架构也从单体架构逐渐演变为现在流行的微服务架构。这些架构之间有怎样的差别呢&#xff1f; 1.0.学习目标 了解微服务架构的优缺点 1.1.单体架构 单体架构&#xff…

opencv学习 特征提取

内容来源于《opencv4应用开发入门、进阶与工程化实践》 图像金字塔 略 拉普拉斯金字塔 对输入图像进行reduce操作会生成不同分辨率的图像&#xff0c;对这些图像进行expand操作&#xff0c;然后使用reduce减去expand之后的结果&#xff0c;就会得到拉普拉斯金字塔图像。 …

【开源操作系统】上海道宁为您带来稳定、安全、开源和易用的操作系统——Ubuntu,为您的数字化生活保驾护航

Ubuntu是 源于非洲的一种传统价值观 意为“人性、关爱和共享” 这种价值观在 开源、稳定、安全、易用的 Ubuntu操作系统中 得到了完美的体现 除此之外&#xff0c;Ubuntu还具有 强大的安全性 它自带了诸多安全功能 如防火墙、加密文件系统等 可以有效地保护用户的隐私…

你ping一下,服务器累成狗--第二篇

你ping一下&#xff0c;服务器累成狗-目录篇文章浏览阅读1.7k次&#xff0c;点赞65次&#xff0c;收藏20次。我们的电脑怎么干活的https://blog.csdn.net/u010187815/article/details/135796967 你ping一下&#xff0c;服务器累成狗--第一篇文章浏览阅读62次&#xff0c;点赞6…

C#用正则表达式验证格式:电话号码、密码、邮编、手机号码、身份证、指定的小数点后位数、有效月

正则表达式在程序设计中有着重要的位置&#xff0c;经常被用于处理字符串信息。 用Regex类的IsMatch方法&#xff0c;使用正则表达式可以验证电话号码是否合法。 一、涉及到的知识点 Regex类的IsMatch方法用于指示正则表达式使用pattern参数中指定的正则表达式是否在输入字符串…

IntersectionObserver、MutationObserver应用,监听项目中指定属性数据,点击或模块显示时

当项目中&#xff0c;需要获取某个页面上、某个标签上、有指定自定义属性时&#xff0c;需要在点击该元素时进行公共逻辑处理&#xff0c;或该元素在显示的时候进行逻辑处理&#xff0c;这时可以定义一个公共的方法&#xff0c;在每个页面引用&#xff0c;并写入数据即可 &…

OSPF的优化

一&#xff1a;OSPF的优化&#xff1a;---lsa的优化 1、汇总 --- 减少骨干区域LSA更新量 2、特殊区域 --- 减少非骨干区域LSA更新量 二&#xff1a;汇总 1、区域汇总&#xff1a;OSPF的汇总被称为区域汇总 域间路由汇总---针对OSPF区域之间的路由进行汇总&#xff0c;针对…

【机器学习300问】21、什么是激活函数?常见激活函数都有哪些?

在我写的上一篇文章中介绍了感知机&#xff08;单个神经元&#xff09;的构成&#xff0c;其中就谈到了神经元会计算传送过来的信号的总和&#xff0c;只有当这个总和超过了某个界限值时&#xff0c;才会输出值。这也称为“神经元被激活”。如果想对神经网络是什么有更多了解的…

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

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

集成学习之Boosting方法系列_XGboost

文章目录 【文章系列】【前言】【算法简介】【正文】&#xff08;一&#xff09;XGBoost前身&#xff1a;梯度提升树&#xff08;二&#xff09;XGBoost的特点&#xff08;三&#xff09;XGBoost实际操作1. 前期准备&#xff08;1&#xff09;数据格式&#xff08;2&#xff09…

小程序定制开发:解析定制化移动应用的未来

引言 在当今数字化时代&#xff0c;移动应用已经成为人们生活不可或缺的一部分。随着智能手机的普及&#xff0c;移动应用的需求呈现出爆发式增长&#xff0c;企业们也纷纷投身于这场数字化浪潮。然而&#xff0c;众多企业在竞争激烈的市场中&#xff0c;如何突显个性、提高用…

使用Eclipse搞Android项目报错

相信现在都没什么人还会用Eclipse来开发的了。 不过安装完后&#xff0c;打开Eclipse会提示我的Jdk版本不符合 --------------------------- Incompatible JVM --------------------------- Version 1.8.0_391 of the JVM is not suitable for this product. Version: 17 or g…

python之poetry模块,项目管理

一、简介 Poetry 是一个用于管理 Python 项目依赖关系和构建工具的工具。它提供了一个简单的命令行界面&#xff0c;可以帮助您创建、管理和发布 Python 项目&#xff0c;使用方法&#xff1a;command [options] [arguments] 官网&#xff1a;https://python-poetry.org/docs/…

详解SpringCloud微服务技术栈:深入ElasticSearch(1)——数据聚合

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;详解SpringCloud微服务技术栈&#xff1a;ElasticSearch实战&#xff08;旅游类项目&#xff09; &#x1f4da;订阅专栏&#x…

软件设计不是CRUD(11):低耦合模块设计理论——业务抽象:规划模块分层

上一篇文章《软件设计不是CRUD(10):低耦合模块设计理论——业务抽象:从需求中提取业务维度》本专题详细讲解了业务抽象的一个重要步骤:提取业务维度。本篇文章内容主要讲解在提取业务维度后,如何对应用程序中初步划分的各个功能模块进行分层规划。 1、为什么要进行模块分…

【lesson2】定长内存池的实现

文章目录 介绍定长内存池的设计定长内存池的实现需要成员变量需要的成员函数定长内存池结构定长内存池Delete&#xff08;释放空间&#xff09;的实现定长内存池New&#xff08;申请空间&#xff09;的实现 定长内存池的实现完整版 介绍 作为程序员(C/C)我们知道申请内存使用的…

Zookeeper实现分布式队列

目录 Zookeeper分布式队列 普通方式实现 设计思路 具体实现 使用Curator实现 具体实现 注意事项 Zookeeper分布式队列 常见的消息队列有:RabbitMQ&#xff0c;RocketMQ&#xff0c;Kafka等。Zookeeper作为一个分布式的小文件管理系统&#xff0c;同样能实现简单的队列功…

【python】图形化开发pyqt6基本写法模板与基础控件属性方法整理

pyqt6的简介 首先呢Python有许多可以编写图形化界面的库&#xff0c;我们通常跟着教程的话最初会接触的tkinter&#xff0c;但是学习中会发现编写的图形化跟我们平常接触的软件有很大区别&#xff08;简单来说就是丑&#xff09;。 pyqt则是第三方库&#xff0c;在Python中算…