mirror of https://gitee.com/godoos/godoos.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
347 lines
11 KiB
347 lines
11 KiB
package alibaba
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"godocms/common"
|
|
"io"
|
|
"sort"
|
|
|
|
"golang.org/x/exp/maps"
|
|
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type Request struct {
|
|
httpMethod string
|
|
canonicalUri string
|
|
host string
|
|
xAcsAction string
|
|
xAcsVersion string
|
|
headers map[string]string
|
|
body []byte
|
|
queryParam map[string]interface{}
|
|
}
|
|
|
|
func NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion string) *Request {
|
|
req := &Request{
|
|
httpMethod: httpMethod,
|
|
canonicalUri: canonicalUri,
|
|
host: host,
|
|
xAcsAction: xAcsAction,
|
|
xAcsVersion: xAcsVersion,
|
|
headers: make(map[string]string),
|
|
queryParam: make(map[string]interface{}),
|
|
}
|
|
req.headers["host"] = host
|
|
req.headers["x-acs-action"] = xAcsAction
|
|
req.headers["x-acs-version"] = xAcsVersion
|
|
req.headers["x-acs-date"] = time.Now().UTC().Format(time.RFC3339)
|
|
req.headers["x-acs-signature-nonce"] = uuid.New().String()
|
|
return req
|
|
}
|
|
|
|
// os.Getenv()表示从环境变量中获取AccessKey ID和AccessKey Secret。
|
|
var (
|
|
AccessKeyId = common.LoginConf.Phone.AliyunSms.AccessKeyId
|
|
AccessKeySecret = common.LoginConf.Phone.AliyunSms.AccessKeySecret
|
|
ALGORITHM = "ACS3-HMAC-SHA256"
|
|
)
|
|
|
|
// 签名示例,您需要根据实际情况替换main方法中的示例参数。
|
|
// ROA接口和RPC接口只有canonicalUri取值逻辑是完全不同,其余内容都是相似的。
|
|
// 通过API元数据获取请求方法(methods)、请求参数名称(name)、请求参数类型(type)、请求参数位置(in),并将参数封装到SignatureRequest中。
|
|
// 1. 请求参数在元数据中显示"in":"query",通过queryParam传参。
|
|
// 2. 请求参数在元数据中显示"in": "body",通过body传参。
|
|
// 3. 请求参数在元数据中显示"in": "formData",通过body传参。
|
|
func signExample() {
|
|
// RPC接口请求示例一:请求参数"in":"query"
|
|
httpMethod := "POST" // 请求方式,大部分RPC接口同时支持POST和GET,此处以POST为例
|
|
canonicalUri := "/" // RPC接口无资源路径,故使用正斜杠(/)作为CanonicalURI
|
|
host := "ecs.cn-hangzhou.aliyuncs.com" // 云产品服务接入点
|
|
xAcsAction := "SendSms" // API名称
|
|
xAcsVersion := "2014-05-26" // API版本号
|
|
req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
|
|
// DescribeInstanceStatus请求参数如下:
|
|
// RegionId在元数据中显示的类型是String,"in":"query",必填
|
|
req.queryParam["RegionId"] = "cn-hangzhou"
|
|
// InstanceId的在元数据中显示的类型是array,"in":"query",非必填
|
|
instanceIds := []interface{}{"i-bp10igfmnyttXXXXXXXX", "i-bp1incuofvzxXXXXXXXX", "i-bp1incuofvzxXXXXXXXX"}
|
|
req.queryParam["InstanceId"] = instanceIds
|
|
|
|
// // RPC接口请求示例二:请求参数"in":"body"
|
|
// httpMethod := "POST"
|
|
// canonicalUri := "/"
|
|
// host := "ocr-api.cn-hangzhou.aliyuncs.com"
|
|
// xAcsAction := "RecognizeGeneral"
|
|
// xAcsVersion := "2021-07-07"
|
|
// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
|
|
// // 读取文件内容
|
|
// filePath := "D:\\test.png"
|
|
// bytes, err := os.ReadFile(filePath)
|
|
// if err != nil {
|
|
// fmt.Println("Error reading file:", err)
|
|
// return
|
|
// }
|
|
// req.body = bytes
|
|
// req.headers["content-type"] = "application/octet-stream"
|
|
|
|
// // RPC接口请求示例三:请求参数"in": "formData"
|
|
// httpMethod := "POST"
|
|
// canonicalUri := "/"
|
|
// host := "mt.aliyuncs.com"
|
|
// xAcsAction := "TranslateGeneral"
|
|
// xAcsVersion := "2018-10-12"
|
|
// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
|
|
// // TranslateGeneral请求参数如下:
|
|
// // Context在元数据中显示的类型是String,"in":"query",非必填
|
|
// req.queryParam["Context"] = "早上"
|
|
// // FormatType、SourceLanguage、TargetLanguage等参数,在元数据中显示"in":"formData"
|
|
// body := make(map[string]interface{})
|
|
// body["FormatType"] = "text"
|
|
// body["SourceLanguage"] = "zh"
|
|
// body["TargetLanguage"] = "en"
|
|
// body["SourceText"] = "你好"
|
|
// body["Scene"] = "general"
|
|
// str := formDataToString(body)
|
|
// req.body = []byte(*str)
|
|
// req.headers["content-type"] = "application/x-www-form-urlencoded"
|
|
|
|
// // ROA接口POST请求
|
|
// httpMethod := "POST"
|
|
// canonicalUri := "/clusters"
|
|
// host := "cs.cn-beijing.aliyuncs.com"
|
|
// xAcsAction := "CreateCluster"
|
|
// xAcsVersion := "2015-12-15"
|
|
// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
|
|
// // 封装请求参数,请求参数在元数据中显示"in": "body",表示参数放在body中
|
|
// body := make(map[string]interface{})
|
|
// body["name"] = "testDemo"
|
|
// body["region_id"] = "cn-beijing"
|
|
// body["cluster_type"] = "ExternalKubernetes"
|
|
// body["vpcid"] = "vpc-2zeou1uod4ylaXXXXXXXX"
|
|
// body["container_cidr"] = "10.0.0.0/8"
|
|
// body["service_cidr"] = "172.16.1.0/20"
|
|
// body["security_group_id"] = "sg-2ze1a0rlgeo7XXXXXXXX"
|
|
// vswitch_ids := []interface{}{"vsw-2zei30dhfldu8XXXXXXXX"}
|
|
// body["vswitch_ids"] = vswitch_ids
|
|
// jsonBytes, err := json.Marshal(body)
|
|
// if err != nil {
|
|
// fmt.Println("Error marshaling to JSON:", err)
|
|
// return
|
|
// }
|
|
// req.body = []byte(jsonBytes)
|
|
// req.headers["content-type"] = "application/json; charset=utf-8"
|
|
|
|
// // ROA接口GET请求
|
|
// httpMethod := "GET"
|
|
// // canonicalUri如果存在path参数,需要对path参数encode,percentCode({path参数})
|
|
// canonicalUri := "/clusters/" + percentCode("c558c166928f9446dae400d106e124f66") + "/resources"
|
|
// host := "cs.cn-beijing.aliyuncs.com"
|
|
// xAcsAction := "DescribeClusterResources"
|
|
// xAcsVersion := "2015-12-15"
|
|
// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
|
|
// req.queryParam["with_addon_resources"] = "true"
|
|
|
|
// // ROA接口DELETE请求
|
|
// httpMethod := "DELETE"
|
|
// // canonicalUri如果存在path参数,需要对path参数encode,percentCode({path参数})
|
|
// canonicalUri := "/clusters/" + percentCode("c558c166928f9446dae400d106e124f66")
|
|
// host := "cs.cn-beijing.aliyuncs.com"
|
|
// xAcsAction := "DeleteCluster"
|
|
// xAcsVersion := "2015-12-15"
|
|
// req := NewRequest(httpMethod, canonicalUri, host, xAcsAction, xAcsVersion)
|
|
|
|
// 签名过程
|
|
getAuthorization(req)
|
|
// 调用API
|
|
_, error := callAPI(req)
|
|
if error != nil {
|
|
println(error.Error())
|
|
}
|
|
}
|
|
|
|
func callAPI(req *Request) ([]byte, error) {
|
|
urlStr := "https://" + req.host + req.canonicalUri
|
|
q := url.Values{}
|
|
keys := maps.Keys(req.queryParam)
|
|
sort.Strings(keys)
|
|
for _, k := range keys {
|
|
v := req.queryParam[k]
|
|
q.Set(k, fmt.Sprintf("%v", v))
|
|
}
|
|
urlStr += "?" + q.Encode()
|
|
fmt.Println(urlStr)
|
|
|
|
httpReq, err := http.NewRequest(req.httpMethod, urlStr, strings.NewReader(string(req.body)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for key, value := range req.headers {
|
|
httpReq.Header.Set(key, value)
|
|
}
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(httpReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer func(Body io.ReadCloser) {
|
|
err := Body.Close()
|
|
if err != nil {
|
|
return
|
|
}
|
|
}(resp.Body)
|
|
|
|
var respBuffer bytes.Buffer
|
|
_, err = io.Copy(&respBuffer, resp.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
respBytes := respBuffer.Bytes()
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("http status code: %d, %s", resp.StatusCode, respBytes)
|
|
}
|
|
|
|
// fmt.Println(string(respBytes))
|
|
return respBytes, nil
|
|
}
|
|
|
|
func getAuthorization(req *Request) {
|
|
// 处理queryParam中参数值为List、Map类型的参数,将参数平铺
|
|
newQueryParams := make(map[string]interface{})
|
|
processObject(newQueryParams, "", req.queryParam)
|
|
req.queryParam = newQueryParams
|
|
// 步骤 1:拼接规范请求串
|
|
canonicalQueryString := ""
|
|
keys := maps.Keys(req.queryParam)
|
|
sort.Strings(keys)
|
|
for _, k := range keys {
|
|
v := req.queryParam[k]
|
|
canonicalQueryString += percentCode(url.QueryEscape(k)) + "=" + percentCode(url.QueryEscape(fmt.Sprintf("%v", v))) + "&"
|
|
}
|
|
canonicalQueryString = strings.TrimSuffix(canonicalQueryString, "&")
|
|
// fmt.Printf("canonicalQueryString========>%s\n", canonicalQueryString)
|
|
|
|
var bodyContent []byte
|
|
if req.body == nil {
|
|
bodyContent = []byte("")
|
|
} else {
|
|
bodyContent = req.body
|
|
}
|
|
hashedRequestPayload := sha256Hex(bodyContent)
|
|
req.headers["x-acs-content-sha256"] = hashedRequestPayload
|
|
|
|
canonicalHeaders := ""
|
|
signedHeaders := ""
|
|
HeadersKeys := maps.Keys(req.headers)
|
|
sort.Strings(HeadersKeys)
|
|
for _, k := range HeadersKeys {
|
|
lowerKey := strings.ToLower(k)
|
|
if lowerKey == "host" || strings.HasPrefix(lowerKey, "x-acs-") || lowerKey == "content-type" {
|
|
canonicalHeaders += lowerKey + ":" + req.headers[k] + "\n"
|
|
signedHeaders += lowerKey + ";"
|
|
}
|
|
}
|
|
signedHeaders = strings.TrimSuffix(signedHeaders, ";")
|
|
|
|
canonicalRequest := req.httpMethod + "\n" + req.canonicalUri + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload
|
|
// fmt.Printf("canonicalRequest========>\n%s\n", canonicalRequest)
|
|
|
|
// 步骤 2:拼接待签名字符串
|
|
hashedCanonicalRequest := sha256Hex([]byte(canonicalRequest))
|
|
stringToSign := ALGORITHM + "\n" + hashedCanonicalRequest
|
|
// fmt.Printf("stringToSign========>\n%s\n", stringToSign)
|
|
|
|
// 步骤 3:计算签名
|
|
byteData, err := hmac256([]byte(AccessKeySecret), stringToSign)
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
}
|
|
signature := strings.ToLower(hex.EncodeToString(byteData))
|
|
|
|
// 步骤 4:拼接Authorization
|
|
authorization := ALGORITHM + " Credential=" + AccessKeyId + ",SignedHeaders=" + signedHeaders + ",Signature=" + signature
|
|
// fmt.Printf("authorization========>%s\n", authorization)
|
|
req.headers["Authorization"] = authorization
|
|
}
|
|
|
|
func hmac256(key []byte, toSignString string) ([]byte, error) {
|
|
// 实例化HMAC-SHA256哈希
|
|
h := hmac.New(sha256.New, key)
|
|
// 写入待签名的字符串
|
|
_, err := h.Write([]byte(toSignString))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// 计算签名并返回
|
|
return h.Sum(nil), nil
|
|
}
|
|
|
|
func sha256Hex(byteArray []byte) string {
|
|
// 实例化SHA-256哈希函数
|
|
hash := sha256.New()
|
|
// 将字符串写入哈希函数
|
|
_, _ = hash.Write(byteArray)
|
|
// 计算SHA-256哈希值并转换为小写的十六进制字符串
|
|
hexString := hex.EncodeToString(hash.Sum(nil))
|
|
|
|
return hexString
|
|
}
|
|
|
|
func percentCode(str string) string {
|
|
// 替换特定的编码字符
|
|
str = strings.ReplaceAll(str, "+", "%20")
|
|
str = strings.ReplaceAll(str, "*", "%2A")
|
|
str = strings.ReplaceAll(str, "%7E", "~")
|
|
return str
|
|
}
|
|
|
|
func formDataToString(formData map[string]interface{}) *string {
|
|
tmp := make(map[string]interface{})
|
|
processObject(tmp, "", formData)
|
|
res := ""
|
|
urlEncoder := url.Values{}
|
|
for key, value := range tmp {
|
|
v := fmt.Sprintf("%v", value)
|
|
urlEncoder.Add(key, v)
|
|
}
|
|
res = urlEncoder.Encode()
|
|
return &res
|
|
}
|
|
|
|
// processObject 递归处理对象,将复杂对象(如Map和List)展开为平面的键值对
|
|
func processObject(mapResult map[string]interface{}, key string, value interface{}) {
|
|
if value == nil {
|
|
return
|
|
}
|
|
|
|
switch v := value.(type) {
|
|
case []interface{}:
|
|
for i, item := range v {
|
|
processObject(mapResult, fmt.Sprintf("%s.%d", key, i+1), item)
|
|
}
|
|
case map[string]interface{}:
|
|
for subKey, subValue := range v {
|
|
processObject(mapResult, fmt.Sprintf("%s.%s", key, subKey), subValue)
|
|
}
|
|
default:
|
|
if strings.HasPrefix(key, ".") {
|
|
key = key[1:]
|
|
}
|
|
if b, ok := v.([]byte); ok {
|
|
mapResult[key] = string(b)
|
|
} else {
|
|
mapResult[key] = fmt.Sprintf("%v", v)
|
|
}
|
|
}
|
|
}
|
|
|