github.com/SupenBysz/gf-admin-community@v0.7.4/internal/logic/sdk_tencent/sdk_tencent.go (about)

     1  package sdk_tencent
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/hmac"
     7  	"crypto/sha256"
     8  	"encoding/hex"
     9  	"fmt"
    10  	"github.com/SupenBysz/gf-admin-community/sys_model"
    11  	"github.com/SupenBysz/gf-admin-community/sys_model/sys_dao"
    12  	"github.com/SupenBysz/gf-admin-community/sys_service"
    13  	"github.com/gogf/gf/v2/container/garray"
    14  	"github.com/gogf/gf/v2/database/gdb"
    15  	"github.com/gogf/gf/v2/encoding/gjson"
    16  	"github.com/gogf/gf/v2/errors/gerror"
    17  	"github.com/gogf/gf/v2/frame/g"
    18  	"github.com/gogf/gf/v2/os/gtime"
    19  	"github.com/gogf/gf/v2/util/gconv"
    20  	"github.com/kysion/base-library/utility/json"
    21  	"github.com/kysion/base-library/utility/kconv"
    22  	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
    23  	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
    24  	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
    25  	faceid "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/faceid/v20180301"
    26  	"log"
    27  	"net/http"
    28  	"strconv"
    29  	"strings"
    30  	"time"
    31  )
    32  
    33  // 腾讯云服务平台
    34  
    35  type sSdkTencent struct {
    36  	TencentSdkConfTokenList []*sys_model.TencentSdkConfToken
    37  	sysConfigName           string
    38  	conf                    gdb.CacheOption
    39  }
    40  
    41  // New SdkBaidu 系统配置逻辑实现
    42  func New() sys_service.ISdkTencent {
    43  	return &sSdkTencent{
    44  		TencentSdkConfTokenList: make([]*sys_model.TencentSdkConfToken, 0),
    45  		sysConfigName:           "tencent_sdk_conf",
    46  		conf: gdb.CacheOption{
    47  			Duration: time.Hour,
    48  			Force:    false,
    49  		},
    50  	}
    51  }
    52  
    53  func init() {
    54  	sys_service.RegisterSdkTencent(New())
    55  }
    56  
    57  // fetchTencentSdkConfToken 根据 identifier 获取腾讯云API Token  (API获取方式)
    58  func (s *sSdkTencent) fetchTencentSdkConfToken(ctx context.Context, identifier string) (tokenInfo *sys_model.TencentSdkConfToken, err error) {
    59  
    60  	info, err := s.GetTencentSdkConf(ctx, identifier)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	client := g.Client()
    65  
    66  	// URL 请求的服务器URL
    67  	var host = "https://rkp.tencentcloudapi.com"
    68  
    69  	// 请求头
    70  	header := make(map[string]string)
    71  
    72  	header["X-TC-Action"] = "GetToken"
    73  	header["Content-type"] = "application/json"
    74  	header["X-TC-Region"] = ""
    75  	header["X-TC-Timestamp"] = gtime.Now().TimestampStr()
    76  	header["X-TC-Version"] = info.Version
    77  	// header["Authorization"] = ""
    78  	header["X-TC-Language"] = "zh-CN"
    79  
    80  	client.Header(header)
    81  
    82  	// 请求数据,
    83  	param := g.Map{
    84  		// 业务ID
    85  		"BusinessId": gconv.Int64(info.AppID),
    86  		// 业务子场景
    87  		"Scene": 0,
    88  		// 业务侧账号体系下的用户ID (不是必填)
    89  		"BusinessUserId": info.AESKey,
    90  		// 用户侧的IP (不是必填)
    91  		"AppClientIp": info.AppID,
    92  		// 过期时间 (不是必填)
    93  		"ExpireTime": info.APIKey,
    94  	}
    95  
    96  	response, err := client.Post(ctx, host, param)
    97  	if err != nil {
    98  		return nil, sys_service.SysLogs().ErrorSimple(ctx, err, "获取腾讯云API Token 失败", sys_dao.SysConfig.Table()+":"+s.sysConfigName)
    99  	}
   100  
   101  	// 接受返回数据,json解析
   102  	newTokenInfo := sys_model.TencentAccessToken{}
   103  
   104  	err = gjson.DecodeTo(response.ReadAllString(), &newTokenInfo)
   105  	if nil != err {
   106  		return nil, sys_service.SysLogs().ErrorSimple(ctx, err, "获取腾讯云API Token 失败", sys_dao.SysConfig.Table()+":"+s.sysConfigName)
   107  	}
   108  
   109  	var result *sys_model.TencentSdkConfToken = nil
   110  	newItems := garray.New()
   111  	for _, item := range s.TencentSdkConfTokenList {
   112  		if item.Identifier == identifier {
   113  			result = &sys_model.TencentSdkConfToken{
   114  				TencentSdkConf:     *info,
   115  				TencentAccessToken: newTokenInfo,
   116  			}
   117  			newItems.Append(*result)
   118  			continue
   119  		}
   120  
   121  		newItems.Append(*result)
   122  	}
   123  
   124  	if result == nil {
   125  		result = &sys_model.TencentSdkConfToken{
   126  			TencentSdkConf:     *info,
   127  			TencentAccessToken: newTokenInfo,
   128  		}
   129  		newItems.Append(*result)
   130  	}
   131  
   132  	// 返回我们需要的token信息
   133  	return result, nil
   134  }
   135  
   136  // GetTencentSdkConfList 获取腾讯云SDK应用配置列表
   137  func (s *sSdkTencent) GetTencentSdkConfList(ctx context.Context) ([]*sys_model.TencentSdkConf, error) {
   138  	items := make([]*sys_model.TencentSdkConf, 0)
   139  	config, err := sys_service.SysConfig().GetByName(ctx, s.sysConfigName)
   140  	if err != nil {
   141  		return items, sys_service.SysLogs().ErrorSimple(ctx, gerror.New("腾讯云SDK配置信息获取失败"), "", sys_dao.SysConfig.Table()+":"+s.sysConfigName)
   142  	}
   143  
   144  	if config.Value == "" {
   145  		return items, nil
   146  	}
   147  
   148  	_ = gjson.DecodeTo(config.Value, &items)
   149  
   150  	return items, nil
   151  }
   152  
   153  // GetTencentSdkConf 根据identifier标识获取SDK配置信息
   154  func (s *sSdkTencent) GetTencentSdkConf(ctx context.Context, identifier string) (tokenInfo *sys_model.TencentSdkConf, err error) {
   155  	items, err := s.GetTencentSdkConfList(ctx)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	// 循环所有配置,筛选出符合条件的配置
   161  	for _, conf := range items {
   162  		if conf.Identifier == identifier {
   163  			return conf, nil
   164  		}
   165  	}
   166  
   167  	return nil, sys_service.SysLogs().ErrorSimple(ctx, err, "根据 identifier 查询腾讯云SDK应用配置信息失败", sys_dao.SysConfig.Table()+":"+s.sysConfigName)
   168  }
   169  
   170  // SaveTencentSdkConf 保存SDK应用配信息, isCreate判断是更新还是新建
   171  func (s *sSdkTencent) SaveTencentSdkConf(ctx context.Context, info *sys_model.TencentSdkConf, isCreate bool) (*sys_model.TencentSdkConf, error) {
   172  	oldItems, _ := s.GetTencentSdkConfList(ctx)
   173  
   174  	isHas := false
   175  	newItems := make([]*sys_model.TencentSdkConf, 0)
   176  	for _, conf := range oldItems {
   177  		if conf.Identifier == info.Identifier { // 如果标识符相等,说明已经存在, 将最新的追加到新的容器中
   178  			isHas = true
   179  			newItems = append(newItems, info)
   180  			continue
   181  		}
   182  
   183  		newItems = append(newItems, conf) // 将旧的Item追加到新的容器中
   184  	}
   185  
   186  	if !isHas { // 不存在
   187  		if isCreate { // 创建 --- 追加info (原有的 + 最新的Info)
   188  			newItems = append(newItems, info)
   189  		} else { // 更新 --- 不存在此配置,那么就提示错误
   190  			return nil, sys_service.SysLogs().ErrorSimple(ctx, gerror.New("腾讯云SDK配置信息保存失败,标识符错误"), "", sys_dao.SysConfig.Table()+":"+s.sysConfigName)
   191  		}
   192  	}
   193  
   194  	// 序列化后进行保存至数据库
   195  	jsonString := gjson.MustEncodeString(newItems)
   196  	_, err := sys_service.SysConfig().SaveConfig(ctx, &sys_model.SysConfig{
   197  		Name:  s.sysConfigName,
   198  		Value: jsonString,
   199  	})
   200  	if err != nil {
   201  		return nil, sys_service.SysLogs().ErrorSimple(ctx, err, "腾讯云SDK配置信息保存失败", sys_dao.SysConfig.Table()+":"+s.sysConfigName)
   202  	}
   203  
   204  	// 同步腾讯云SDK应用配置缓存列表
   205  	s.syncTencentSdkConfList(ctx)
   206  
   207  	return info, nil
   208  }
   209  
   210  // syncTencentSdkConfList 同步腾讯云SDK应用配置信息列表缓存  (代码中要是用到了s.TencentSdkConfList缓存变量的话,一定需要在CUD操作后调用此方法更新缓存变量)
   211  func (s *sSdkTencent) syncTencentSdkConfList(ctx context.Context) error {
   212  	items, err := s.GetTencentSdkConfList(ctx)
   213  	if err != nil {
   214  		return err
   215  	}
   216  
   217  	newTokenItems := make([]*sys_model.TencentSdkConfToken, 0)
   218  	for _, conf := range items {
   219  		for _, tokenInfo := range s.TencentSdkConfTokenList { // tokenList
   220  			if tokenInfo.Identifier == conf.Identifier {
   221  				newTokenItems = append(newTokenItems, tokenInfo)
   222  			}
   223  		}
   224  	}
   225  
   226  	s.TencentSdkConfTokenList = newTokenItems
   227  
   228  	return nil
   229  }
   230  
   231  // DeleteTencentSdkConf 删除腾讯云SDK应用配置信息
   232  func (s *sSdkTencent) DeleteTencentSdkConf(ctx context.Context, identifier string) (bool, error) {
   233  	items, err := s.GetTencentSdkConfList(ctx)
   234  
   235  	isHas := false
   236  	newItems := garray.New(false)
   237  	for _, conf := range items {
   238  		if conf.Identifier == identifier {
   239  			isHas = true
   240  			continue
   241  		}
   242  		newItems.Append(conf)
   243  	}
   244  
   245  	if !isHas {
   246  		return false, sys_service.SysLogs().ErrorSimple(ctx, err, "要删除的腾讯云SDK配置信息不存在", sys_dao.SysConfig.Table()+":"+s.sysConfigName)
   247  	}
   248  
   249  	jsonString := gjson.MustEncodeString(newItems)
   250  	_, err = sys_service.SysConfig().SaveConfig(ctx, &sys_model.SysConfig{
   251  		Name:  s.sysConfigName,
   252  		Value: jsonString,
   253  	})
   254  	if err != nil {
   255  		return false, sys_service.SysLogs().ErrorSimple(ctx, err, "腾讯云SDK配置信息删除失败", sys_dao.SysConfig.Table()+":"+s.sysConfigName)
   256  	}
   257  
   258  	// 同步Token列表
   259  	s.syncTencentSdkConfList(ctx)
   260  
   261  	return true, nil
   262  }
   263  
   264  // 腾讯云服务的具体应用实例
   265  
   266  // LivenessRecognition 人脸核身(SDK接入模式)
   267  func (s *sSdkTencent) LivenessRecognition(ctx context.Context, idCard, name, livenessType string) {
   268  	// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密
   269  	// 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议采用更安全的方式来使用密钥,请参见:https://cloud.tencent.com/document/product/1278/85305
   270  	// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
   271  
   272  	config, err := s.GetTencentSdkConf(ctx, "tencent_config")
   273  
   274  	credential := common.NewCredential(
   275  		config.AESKey,
   276  		config.SecretKey,
   277  	)
   278  	// 实例化一个client选项,可选的,没有特殊需求可以跳过
   279  	cpf := profile.NewClientProfile()
   280  	cpf.HttpProfile.Endpoint = "faceid.tencentcloudapi.com"
   281  	// 实例化要请求产品的client对象,clientProfile是可选的
   282  	client, _ := faceid.NewClient(credential, "", cpf)
   283  
   284  	// 实例化一个请求对象,每个接口都会对应一个request对象
   285  	request := faceid.NewLivenessRecognitionRequest()
   286  	request.IdCard = common.StringPtr(idCard)
   287  	request.Name = common.StringPtr(name)
   288  	request.LivenessType = common.StringPtr(livenessType)
   289  
   290  	// 返回的resp是一个LivenessRecognitionResponse的实例,与请求对象对应
   291  	response, err := client.LivenessRecognition(request)
   292  	if _, ok := err.(*errors.TencentCloudSDKError); ok {
   293  		fmt.Printf("An API error has returned: %s", err)
   294  		return
   295  	}
   296  	if err != nil {
   297  		panic(err)
   298  	}
   299  	// 输出json格式的字符串回包
   300  	fmt.Printf("%s", response.ToJsonString())
   301  
   302  }
   303  
   304  // DetectAuth 腾讯云-实名核身鉴权
   305  func (s *sSdkTencent) DetectAuth(ctx context.Context, idCard, name, returnUrl string) (*sys_model.DetectAuthRes, error) {
   306  	// 实例化一个认证对象,入参需要传入腾讯云账户 SecretId 和 SecretKey,此处还需注意密钥对的保密
   307  	// 代码泄露可能会导致 SecretId 和 SecretKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考,建议采用更安全的方式来使用密钥,请参见:https://cloud.tencent.com/document/product/1278/85305
   308  	// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
   309  	config, err := s.GetTencentSdkConf(ctx, "tencent_config")
   310  
   311  	credential := common.NewCredential(
   312  		config.AESKey,
   313  		config.SecretKey,
   314  	)
   315  	// 实例化一个client选项,可选的,没有特殊需求可以跳过
   316  	cpf := profile.NewClientProfile()
   317  	cpf.HttpProfile.Endpoint = "faceid.tencentcloudapi.com"
   318  	// 实例化要请求产品的client对象,clientProfile是可选的
   319  	client, _ := faceid.NewClient(credential, "", cpf)
   320  
   321  	// 实例化一个请求对象,每个接口都会对应一个request对象
   322  	request := faceid.NewDetectAuthRequest()
   323  
   324  	request.RuleId = common.StringPtr("1") // TODO 现在先硬编码写死了,后续RuleId应该做数据库管理
   325  	request.IdCard = common.StringPtr(idCard)
   326  	request.Name = common.StringPtr(name)
   327  	request.RedirectUrl = common.StringPtr(returnUrl)
   328  
   329  	// 返回的resp是一个DetectAuthResponse的实例,与请求对象对应
   330  	response, err := client.DetectAuth(request)
   331  	if _, ok := err.(*errors.TencentCloudSDKError); ok {
   332  		fmt.Printf("An API error has returned: %s", err)
   333  		return nil, sys_service.SysLogs().ErrorSimple(ctx, err, "An API error has returned: "+err.Error(), s.sysConfigName)
   334  	}
   335  	if err != nil {
   336  		return nil, sys_service.SysLogs().ErrorSimple(ctx, err, "腾讯云-实名核身鉴权请求失败", s.sysConfigName)
   337  	}
   338  	// 输出json格式的字符串回包
   339  	fmt.Printf("%s", response.ToJsonString())
   340  
   341  	res := sys_model.DetectAuthRes{}
   342  	if response.Response != nil {
   343  		_ = gjson.DecodeTo(response.Response, &res)
   344  
   345  	}
   346  
   347  	return &res, err
   348  
   349  	/*
   350  		响应示例:
   351  			{"Response":{"Url":"https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx1f7125112b74db52\u0026redirect_uri=https%3A%2F%2Fopen.faceid.qq.com%2Fv1%2Fapi%2FgetCode%3FbizRedirect%3Dhttps%253A%252F%252Ffaceid.qq.com%252Fapi%252Fauth%252FgetOpenidAndSaveToken%253Ftoken%253D43DCB8C3-D330-429C-AF46-DB73BA9EE794\u0026response_type=code\u0026scope=snsapi_base\u0026state=\u0026component_appid=wx9802ee81e68d6dee#wechat_redirect","BizToken":"43DCB8C3-D330-429C-AF46-DB73BA9EE794","RequestId":"95be8b7f-f6f8-4735-895f-ee1c3d1f4ab9"}}
   352  	*/
   353  }
   354  
   355  // GetDetectAuthResult 获取腾讯云-实名核身鉴权结果
   356  func (s *sSdkTencent) GetDetectAuthResult(ctx context.Context, bizToken string, ruleId ...string) (*sys_model.GetDetectAuthResultRes, error) {
   357  	config, err := s.GetTencentSdkConf(ctx, "tencent_config")
   358  	credential := common.NewCredential(
   359  		config.AESKey,
   360  		config.SecretKey,
   361  	)
   362  	// 实例化一个client选项,可选的,没有特殊需求可以跳过
   363  	cpf := profile.NewClientProfile()
   364  	cpf.HttpProfile.Endpoint = "faceid.tencentcloudapi.com"
   365  	// 实例化要请求产品的client对象,clientProfile是可选的
   366  	client, _ := faceid.NewClient(credential, "", cpf)
   367  
   368  	// 实例化一个请求对象,每个接口都会对应一个request对象
   369  	request := faceid.NewGetDetectInfoRequest()
   370  
   371  	request.BizToken = common.StringPtr(bizToken)
   372  	id := "1"
   373  	request.RuleId = common.StringPtr(id) // TODO 现在先硬编码写死了,后续RuleId应该做数据库管理
   374  	//request.RuleId = common.StringPtr(ruleId)
   375  
   376  	// 返回的resp是一个GetDetectInfoResponse的实例,与请求对象对应
   377  	response, err := client.GetDetectInfo(request)
   378  	if _, ok := err.(*errors.TencentCloudSDKError); ok {
   379  		fmt.Printf("An API error has returned: %s", err)
   380  		return nil, sys_service.SysLogs().ErrorSimple(ctx, err, "An API error has returned: "+err.Error(), s.sysConfigName)
   381  	}
   382  	if err != nil {
   383  		panic(err)
   384  	}
   385  	// 输出json格式的字符串回包
   386  	//fmt.Printf("%s", response.ToJsonString())
   387  
   388  	var tempInfo struct {
   389  		Response struct {
   390  			RequestId *string `json:"RequestId,omitnil,omitempty" name:"RequestId" `
   391  
   392  			DetectInfo *string `json:"DetectInfo,omitnil,omitempty" name:"DetectInfo" `
   393  		} `json:"Response"`
   394  	}
   395  
   396  	res := sys_model.GetDetectAuthResultRes{}
   397  	if response.Response != nil {
   398  		//gjson.DecodeTo(response.Response.DetectInfo, &res)
   399  
   400  		//jsonString := response.ToJsonString()
   401  		//gjson.DecodeTo(jsonString, &res)
   402  
   403  		t := kconv.Struct(response.ToJsonString(), &tempInfo)
   404  		jsonStr, _ := json.UnescapeJSON(*t.Response.DetectInfo)
   405  		gjson.DecodeTo(jsonStr, &res)
   406  		res.RequestId = response.Response.RequestId
   407  	}
   408  
   409  	return &res, err
   410  
   411  	/*
   412  		 响应示例:
   413  			{
   414  		  "Text": {
   415  		    "ErrCode": 0,
   416  		    "ErrMsg": "成功",
   417  		    "IdCard": "****",
   418  		    "Name": "林菲菲",
   419  		    "OcrNation": null,
   420  		    "OcrAddress": null,
   421  		    "OcrBirth": null,
   422  		    "OcrAuthority": null,
   423  		    "OcrValidDate": null,
   424  		    "OcrName": "",
   425  		    "OcrIdCard": "",
   426  		    "OcrGender": null,
   427  		    "LiveStatus": 0,
   428  		    "LiveMsg": "成功",
   429  		    "Comparestatus": 0,
   430  		    "Comparemsg": "成功",
   431  		    "Sim": "96.42",
   432  		    "Location": null,
   433  		    "Extra": "",
   434  		    "Detail": {
   435  		      "LivenessData": [
   436  		        {
   437  		          "ErrCode": 0,
   438  		          "ErrMsg": "成功",
   439  		          "ReqTime": "1715329563289",
   440  		          "IdCard": "*****",
   441  		          "Name": "林菲菲"
   442  		        }
   443  		      ]
   444  		    }
   445  		  },
   446  		  "IdCardData": {
   447  		    "OcrFront": null,
   448  		    "OcrBack": null
   449  		  },
   450  		  "BestFrame": {
   451  		    "BestFrame": ""
   452  		  }
   453  		}
   454  	*/
   455  }
   456  
   457  // GetDetectAuthPlusResponse 获取腾讯云-实名核身鉴权增强版结果 (v3.0接口)
   458  func (s *sSdkTencent) GetDetectAuthPlusResponse(ctx context.Context, bizToken, ruleId string) (*sys_model.GetDetectAuthPlusResponseRes, error) {
   459  	config, err := s.GetTencentSdkConf(ctx, "tencent_config")
   460  
   461  	credential := common.NewCredential(
   462  		config.AESKey,
   463  		config.SecretKey,
   464  	)
   465  	// 实例化一个client选项,可选的,没有特殊需求可以跳过
   466  	cpf := profile.NewClientProfile()
   467  	cpf.HttpProfile.Endpoint = "faceid.tencentcloudapi.com"
   468  	// 实例化要请求产品的client对象,clientProfile是可选的
   469  	client, _ := faceid.NewClient(credential, "", cpf)
   470  
   471  	// 实例化一个请求对象,每个接口都会对应一个request对象
   472  	request := faceid.NewGetDetectInfoEnhancedRequest()
   473  
   474  	request.BizToken = common.StringPtr(bizToken)
   475  	request.RuleId = common.StringPtr(ruleId)
   476  
   477  	// 返回的resp是一个GetDetectInfoEnhancedResponse的实例,与请求对象对应
   478  	response, err := client.GetDetectInfoEnhanced(request)
   479  	if _, ok := err.(*errors.TencentCloudSDKError); ok {
   480  		fmt.Printf("An API error has returned: %s", err)
   481  		return nil, sys_service.SysLogs().ErrorSimple(ctx, err, "An API error has returned: "+err.Error(), s.sysConfigName)
   482  	}
   483  	if err != nil {
   484  		return nil, sys_service.SysLogs().ErrorSimple(ctx, err, "腾讯云-获取实名核身鉴权增强版结果请求失败", s.sysConfigName)
   485  	}
   486  
   487  	// 输出json格式的字符串回包
   488  	fmt.Printf("%s", response.ToJsonString())
   489  
   490  	res := sys_model.GetDetectAuthPlusResponseRes{}
   491  	if response.Response != nil {
   492  		_ = gjson.DecodeTo(response.Response, &res)
   493  	}
   494  
   495  	return &res, err
   496  
   497  	/*
   498  	 响应示例:
   499  
   500  	*/
   501  }
   502  
   503  // LivenessRecognition_Http 人脸核身(HTTP接入模式)
   504  func (s *sSdkTencent) LivenessRecognition_Http(ctx context.Context, action, secretId, secretKey string) { // LivenessRecognition 活体核验
   505  	service := "faceid"
   506  	version := "2018-03-01"
   507  	//action := "LivenessRecognition"
   508  	region := ""
   509  	token := ""
   510  	host := "faceid.tencentcloudapi.com"
   511  	algorithm := "TC3-HMAC-SHA256"
   512  	var timestamp = time.Now().Unix()
   513  
   514  	// ************* 步骤 1:拼接规范请求串 *************
   515  	httpRequestMethod := "POST"
   516  	canonicalURI := "/"
   517  	canonicalQueryString := ""
   518  	contentType := "application/json; charset=utf-8"
   519  	canonicalHeaders := fmt.Sprintf("content-type:%s\nhost:%s\nx-tc-action:%s\n",
   520  		contentType, host, strings.ToLower(action))
   521  	signedHeaders := "content-type;host;x-tc-action"
   522  	payload := "{}"
   523  	hashedRequestPayload := sha256hex(payload)
   524  	canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s",
   525  		httpRequestMethod,
   526  		canonicalURI,
   527  		canonicalQueryString,
   528  		canonicalHeaders,
   529  		signedHeaders,
   530  		hashedRequestPayload)
   531  	log.Println(canonicalRequest)
   532  
   533  	// ************* 步骤 2:拼接待签名字符串 *************
   534  	date := time.Unix(timestamp, 0).UTC().Format("2006-01-02")
   535  	credentialScope := fmt.Sprintf("%s/%s/tc3_request", date, service)
   536  	hashedCanonicalRequest := sha256hex(canonicalRequest)
   537  	string2sign := fmt.Sprintf("%s\n%d\n%s\n%s",
   538  		algorithm,
   539  		timestamp,
   540  		credentialScope,
   541  		hashedCanonicalRequest)
   542  	log.Println(string2sign)
   543  
   544  	// ************* 步骤 3:计算签名 *************
   545  	secretDate := hmacsha256(date, "TC3"+secretKey)
   546  	secretService := hmacsha256(service, secretDate)
   547  	secretSigning := hmacsha256("tc3_request", secretService)
   548  	signature := hex.EncodeToString([]byte(hmacsha256(string2sign, secretSigning)))
   549  	log.Println(signature)
   550  
   551  	// ************* 步骤 4:拼接 Authorization *************
   552  	authorization := fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s",
   553  		algorithm,
   554  		secretId,
   555  		credentialScope,
   556  		signedHeaders,
   557  		signature)
   558  	log.Println(authorization)
   559  
   560  	// ************* 步骤 5:构造并发起请求 *************
   561  	url := "https://" + host
   562  	httpRequest, _ := http.NewRequest("POST", url, strings.NewReader(payload))
   563  	httpRequest.Header = map[string][]string{
   564  		"Host":           {host},
   565  		"X-TC-Action":    {action},
   566  		"X-TC-Version":   {version},
   567  		"X-TC-Timestamp": {strconv.FormatInt(timestamp, 10)},
   568  		"Content-Type":   {contentType},
   569  		"Authorization":  {authorization},
   570  	}
   571  	if region != "" {
   572  		httpRequest.Header["X-TC-Region"] = []string{region}
   573  	}
   574  	if token != "" {
   575  		httpRequest.Header["X-TC-Token"] = []string{token}
   576  	}
   577  	httpClient := http.Client{}
   578  	resp, err := httpClient.Do(httpRequest)
   579  	if err != nil {
   580  		log.Println(err)
   581  		return
   582  	}
   583  	defer resp.Body.Close()
   584  	body := &bytes.Buffer{}
   585  	_, err = body.ReadFrom(resp.Body)
   586  	if err != nil {
   587  		log.Println(err)
   588  		return
   589  	}
   590  	log.Println(body.String())
   591  }
   592  
   593  func sha256hex(s string) string {
   594  	b := sha256.Sum256([]byte(s))
   595  	return hex.EncodeToString(b[:])
   596  }
   597  
   598  func hmacsha256(s, key string) string {
   599  	hashed := hmac.New(sha256.New, []byte(key))
   600  	hashed.Write([]byte(s))
   601  	return string(hashed.Sum(nil))
   602  }