惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recent Commits to openclaw:main
Recent Commits to openclaw:main
SecWiki News
SecWiki News
Webroot Blog
Webroot Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
N
News and Events Feed by Topic
Recent Announcements
Recent Announcements
Help Net Security
Help Net Security
Jina AI
Jina AI
O
OpenAI News
雷峰网
雷峰网
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
博客园 - 三生石上(FineUI控件)
W
WeLiveSecurity
Schneier on Security
Schneier on Security
T
Threat Research - Cisco Blogs
IT之家
IT之家
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Vercel News
Vercel News
N
News and Events Feed by Topic
T
The Exploit Database - CXSecurity.com
爱范儿
爱范儿
Recorded Future
Recorded Future
Google Online Security Blog
Google Online Security Blog
TaoSecurity Blog
TaoSecurity Blog
美团技术团队
Engineering at Meta
Engineering at Meta
Security Latest
Security Latest
V
V2EX
T
Tailwind CSS Blog
P
Privacy & Cybersecurity Law Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
S
Schneier on Security
B
Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园 - 【当耐特】
PCI Perspectives
PCI Perspectives
GbyAI
GbyAI
I
Intezer
Spread Privacy
Spread Privacy
Security Archives - TechRepublic
Security Archives - TechRepublic
Cloudbric
Cloudbric
V
Visual Studio Blog
MongoDB | Blog
MongoDB | Blog
Forbes - Security
Forbes - Security
The Last Watchdog
The Last Watchdog
aimingoo的专栏
aimingoo的专栏
C
CERT Recently Published Vulnerability Notes
A
About on SuperTechFans
罗磊的独立博客

二丫讲梵

学习周刊-总第258期-2026年第15周 学习周刊-总第257期-2026年第14周 学习周刊-总第256期-2026年第13周 学习周刊-总第255期-2026年第12周 学习周刊-总第254期-2026年第11周 学习周刊-总第253期-2026年第10周 临时插播一条羊毛,免费领取450元大模型API代金券 学习周刊-总第252期-2026年第09周 学习周刊-总第251期-2026年第08周 学习周刊-总第250期-2026年第07周 学习周刊-总第249期-2026年第06周 诚邀评论,聊聊你所知欲知的我 学习周刊-总第248期-2026年第05周 我的QQ动态之2015年 我的QQ动态之2014年 我的QQ动态之2013年 我的QQ动态之2012年 我的QQ动态-2010-2011年 我的QQ动态-创栏小叙 学习周刊-总第247期-2026年第04周 Nexus社区版权益阉割--一文告诉你有哪些版本可以选择 学习周刊-总第246期-2026年第03周 学习周刊-总第245期-2026年第02周 学习周刊-总第244期-2026年第01周 学习周刊-总第243期-2025年第52周 学习周刊-总第242期-2025年第51周 开源项目ZenOps:带你领略禅意运维 学习周刊-总第241期-2025年第50周 用京东金融,享负债人生 学习周刊-总第240期-2025年第49周 学习周刊-总第239期-2025年第48周 整理我在静态服务透明代理上的极致求索之路,最后一个你绝想不到 学习周刊-总第238期-2025年第47周 我们输了,但收获颇多 太多了,太多了 学习周刊-总第237期-2025年第46周 学习周刊-总第236期-2025年第45周 学习周刊-总第235期-2025年第44周 二五年国庆二三事 学习周刊-总第234期-2025年第43周 学习周刊-总第233期-2025年第42周 学习周刊-总第232期-2025年第41周 学习周刊-总第231期-2025年第40周 学习周刊-总第230期-2025年第39周 西湖毅行 2025年开源世界逸闻三则 学习周刊-总第229期-2025年第38周 手抄《与妻书》 CNB开发与构建基于docker-cache缓存复用的配置实践心得 学习周刊-总第228期-2025年第37周 父亲善行录 学习周刊-总第227期-2025年第36周 带你认识我之看看我的高中同学录(其二) 学习周刊-总第226期-2025年第35周 带你认识我之看看我的高中同学录(其一) 认识神级MCP工具系列--用anyquery和数据库交互 学习周刊-总第225期-2025年第34周 学习周刊-总第224期-2025年第33周 学习周刊-总第223期-2025年第32周 学习周刊-总第222期-2025年第31周 学习周刊-总第221期-2025年第30周 CNB云原生开发环境届的瑞士军刀,详解qifei项目 vuepress-vdoing主题配置自建不蒜子统计 学习周刊-总第220期-2025年第29周 写在博客发表文章1000篇的节点 从claude cli的体验聊聊最大的敌人是我们自己的成见 学习周刊-总第219期-2025年第28周 学习周刊-总第218期-2025年第27周 学习周刊-总第217期-2025年第26周 理论正确,事实错误 学习周刊-总第216期-2025年第25周 学习周刊-总第215期-2025年第24周 学习周刊-总第214期-2025年第23周 学习周刊-总第213期-2025年第22周 学习周刊-总第212期-2025年第21周 从赵心童世锦赛夺冠聊聊我的斯诺克情缘 学习周刊-总第211期-2025年第20周 记录二五年五一之短暂回归家庭 学习周刊-总第210期-2025年第19周 学习周刊-总第209期-2025年第18周 学习周刊-总第208期-2025年第17周 Go开发实践之Gin框架将前端的dist目录embed到二进制 学习周刊-总第207期-2025年第16周 近期关于cobra库的一些实践心得总结 学习周刊-总第206期-2025年第15周 我的嗜好--嗑瓜子 记大宝参加幼儿园组织的清明烈士陵园扫墓活动 学习周刊-总第205期-2025年第14周 学习周刊-总第204期-2025年第13周 前端开发小笔记--在数组中给字段添加值与删除字段的操作 学习周刊-总第203期-2025年第12周 人生实苦,何以自渡 学习周刊-总第202期-2025年第11周 近期发现的一些优秀的工具提名(一) 学习周刊-总第201期-2025年第10周 学习周刊-总第200期-2025年第09周 记一次女友喝醉使我忍术破功 爱情风波--一次分手又复合的经历 学习周刊-总第199期-2025年第08周 整理我的信息源
对接腾讯云未集成到SDK的接口开发实践小记
二丫讲梵 · 2024-03-19 · via 二丫讲梵

# 前言

日常工作中,与腾讯云打交道,为了提高效率,会调用它的接口来完成一些重复性工作。一般都是通过官方集成好的 SDK 来完成交互。

但有时候也存在一种情况,那就是这个接口还没有集成到 SDK 当中,又需要进行调用,此时可通过原生方式生成 curl 请求,以达到调用目的。

本文记录如何快速实现并完成这样一个流程,以此记录,便于后续再次查阅。

# 认识原生请求方法

通常在每个产品的 API 接口文档页面,都会在调用方式里边有一个 签名方法 v3 的页面,在这个页面,介绍了如何自助生成签名,完成接口的调用。

这里以云服务器的签名方法 v3 (opens new window) 的文档介绍为例,来简单认识一下这一思路的用法。

我这里选择其中提供的 Golang 代码示例来做演示:

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "os"
    "strings"
    "time"
)

func sha256hex(s string) string {
    b := sha256.Sum256([]byte(s))
    return hex.EncodeToString(b[:])
}

func hmacsha256(s, key string) string {
    hashed := hmac.New(sha256.New, []byte(key))
    hashed.Write([]byte(s))
    return string(hashed.Sum(nil))
}

func main() {
    // 需要设置环境变量 TENCENTCLOUD_SECRET_ID,值为示例的 AKIDz8krbsJ5yKBZQpn74WFkmLPx3*******
    secretId := os.Getenv("TENCENTCLOUD_SECRET_ID")
    // 需要设置环境变量 TENCENTCLOUD_SECRET_KEY,值为示例的 Gu5t9xGARNpq86cd98joQYCN3*******
    secretKey := os.Getenv("TENCENTCLOUD_SECRET_KEY")
    host := "cvm.tencentcloudapi.com"
    algorithm := "TC3-HMAC-SHA256"
    service := "cvm"
    version := "2017-03-12"
    action := "DescribeInstances"
    region := "ap-guangzhou"
    //var timestamp int64 = time.Now().Unix()
    var timestamp int64 = 1551113065

    // step 1: build canonical request string
    httpRequestMethod := "POST"
    canonicalURI := "/"
    canonicalQueryString := ""
    canonicalHeaders := fmt.Sprintf("content-type:%s\nhost:%s\nx-tc-action:%s\n",
        "application/json; charset=utf-8", host, strings.ToLower(action))
    signedHeaders := "content-type;host;x-tc-action"
    payload := `{"Limit": 1, "Filters": [{"Values": ["\u672a\u547d\u540d"], "Name": "instance-name"}]}`
    hashedRequestPayload := sha256hex(payload)
    canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s",
        httpRequestMethod,
        canonicalURI,
        canonicalQueryString,
        canonicalHeaders,
        signedHeaders,
        hashedRequestPayload)
    fmt.Println(canonicalRequest)

    // step 2: build string to sign
    date := time.Unix(timestamp, 0).UTC().Format("2006-01-02")
    credentialScope := fmt.Sprintf("%s/%s/tc3_request", date, service)
    hashedCanonicalRequest := sha256hex(canonicalRequest)
    string2sign := fmt.Sprintf("%s\n%d\n%s\n%s",
        algorithm,
        timestamp,
        credentialScope,
        hashedCanonicalRequest)
    fmt.Println(string2sign)

    // step 3: sign string
    secretDate := hmacsha256(date, "TC3"+secretKey)
    secretService := hmacsha256(service, secretDate)
    secretSigning := hmacsha256("tc3_request", secretService)
    signature := hex.EncodeToString([]byte(hmacsha256(string2sign, secretSigning)))
    fmt.Println(signature)

    // step 4: build authorization
    authorization := fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s",
        algorithm,
        secretId,
        credentialScope,
        signedHeaders,
        signature)
    fmt.Println(authorization)

    curl := fmt.Sprintf(`curl -X POST https://%s\
 -H "Authorization: %s"\
 -H "Content-Type: application/json; charset=utf-8"\
 -H "Host: %s" -H "X-TC-Action: %s"\
 -H "X-TC-Timestamp: %d"\
 -H "X-TC-Version: %s"\
 -H "X-TC-Region: %s"\
 -d '%s'`, host, authorization, host, action, timestamp, version, region, payload)
    fmt.Println(curl)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

如上命令运行之后,最后会打印该 curl 请求如下:

$ curl -X POST https://cvm.tencentcloudapi.com\
 -H "Authorization: TC3-HMAC-SHA256 Credential=***EG1Yac266rMcU6uZO****AhzRdZaRqMRG/2019-02-25/cvm/tc3_request, SignedHeaders=content-type;host;x-tc-action, Signature=b**cc7cd1c906c828ba52266b543816adf61a6df9af2e4da708fb790"\
 -H "Content-Type: application/json; charset=utf-8"\
 -H "Host: cvm.tencentcloudapi.com" -H "X-TC-Action: DescribeInstances"\
 -H "X-TC-Timestamp: 1551113065"\
 -H "X-TC-Version: 2017-03-12"\
 -H "X-TC-Region: ap-guangzhou"\
 -d '{"Limit": 1, "Filters": [{"Values": ["\u672a\u547d\u540d"], "Name": "instance-name"}]}'

1
2
3
4
5
6
7
8

整个代码里边每个步骤的操作也都有注释,就不一一解释了。这里简单概述一下,如上代码,除去生成签名的步骤之外,还有五个部分,来完成此命令的渲染:

  • host: 指定要请求的域名地址。
  • service: 指定服务类型
  • version::指定接口版本
  • action::指定请求的动作,即对应的接口
  • region::指定资源的区域
  • payload::指定请求的参数

# 集成官方提供的方法

如上方法对应的代码,一大块儿都是签名的处理,猛地一看这些代码,可能也很懵,我一开始还想着如何简化抽象一下签名的方法,就在简化的过程中,一检索,发现,事实上官方的 SDK 也提供了简化的方案。

比如 golang 的 SDK,看官方 readme,有专门的介绍:结合 common client 自定义请求 (opens new window)

其中也提供了代码示例:

package main

import (
	"fmt"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
	tchttp "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/regions"
	"log"
	"os"
)

// 目前只支持 签名v3+POST
func main() {
	credential := common.NewCredential(
		os.Getenv("TENCENTCLOUD_SECRET_ID"),
		os.Getenv("TENCENTCLOUD_SECRET_KEY"))
	cpf := profile.NewClientProfile()
	cpf.HttpProfile.Endpoint = "cvm.tencentcloudapi.com"
	cpf.HttpProfile.ReqMethod = "POST"
	//创建common client
	client := common.NewCommonClient(credential, regions.Guangzhou, cpf).WithLogger(log.Default())

	// 创建common request,依次传入产品名、产品版本、接口名称
	request := tchttp.NewCommonRequest("cvm", "2017-03-12", "DescribeZones")

	// 自定义请求参数:
	// SetActionParameters 函数支持三种数据类型的入参:
	// 1. map[string]interface{}
	// body:=map[string]interface{}{
	// "InstanceId":"crs-xxx",
	// "SpanType":2,
	// }
	body := map[string]interface{}{}

	// // 2. string
	// bodyStr := `{}`

	// // 3. []byte
	// bodyBytes := []byte(bodyStr)

	// set custom headers
	request.SetHeader(map[string]string{
		"X-TC-TraceId": "ffe0c072-8a5d-4e17-8887-a8a60252abca",
	})

	// 设置action所需的请求数据
	err := request.SetActionParameters(body)
	if err != nil {
		return
	}

	//创建common response
	response := tchttp.NewCommonResponse()

	//发送请求
	err = client.Send(request, response)
	if err != nil {
		fmt.Printf("fail to invoke api: %v \n", err)
	}

	// 获取响应结果
	fmt.Println(string(response.GetBody()))
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

借助于官方封装好的 client,就可以直接拼装接口,完成调用了。

这里贴一个自己封装的,将 MySQL 实例设置为只读的方法,以供参考:

// SetCdbReadOnly 设置cdb实例只读 0:只读 1:读写
func SetCdbReadOnly(credential *common.Credential, instanceid string, readonly int) (*tchttp.CommonResponse, error) {
	cpf := profile.NewClientProfile()
	cpf.HttpProfile.Endpoint = "cdb.tencentcloudapi.com"
	client := common.NewCommonClient(credential, regions.Shanghai, cpf).WithLogger(log.Default())

	request := tchttp.NewCommonRequest("cdb", "2017-03-20", "ModifyDBInstanceReadOnlyStatus")
	body := map[string]interface{}{
		"InstanceId": instanceid,
		"ReadOnly":   readonly,
	}
	err := request.SetActionParameters(body)
	if err != nil {
		return nil, err
	}
	response := tchttp.NewCommonResponse()
	err = client.Send(request, response)
	if err != nil {
		return nil, err
	}

	return response, nil
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

如此一来,要想自定义封装一些尚未集成到 SDK 的方法,就变得比较简单了。

# 如何快速找到接口

很多时候,一些相对偏门的接口,可能第一步困难的是如何找到这个接口相关的文档说明,这里也记录一下我个人的一些心得。

方法一:

通过自己的渠道,直接找到腾讯云官方的人员,咨询对应的接口,这是最高效也是最可靠的方式。

方法二: