package cloud import ( "crypto/hmac" "crypto/sha1" "encoding/base64" "encoding/json" "fmt" "io" "math/rand" "net/http" "net/url" "sort" "strings" "time" ) // AliyunProvider 阿里云 ECS Provider type AliyunProvider struct { accessKey string secretKey string version string // API 版本 } func NewAliyunProvider(accessKey, secretKey string) *AliyunProvider { return &AliyunProvider{ accessKey: accessKey, secretKey: secretKey, version: "2014-05-26", } } func (a *AliyunProvider) Name() string { return "aliyun" } // percentEncode 阿里云 RPC 签名专用 URL 编码 func percentEncode(s string) string { s = url.QueryEscape(s) s = strings.ReplaceAll(s, "+", "%20") s = strings.ReplaceAll(s, "*", "%2A") s = strings.ReplaceAll(s, "%7E", "~") return s } // sign 计算阿里云 RPC v1 HMAC-SHA1 签名 func (a *AliyunProvider) sign(params map[string]string) string { // 1. 排序参数 key keys := make([]string, 0, len(params)) for k := range params { keys = append(keys, k) } sort.Strings(keys) // 2. 拼接规范化查询字符串 var pairs []string for _, k := range keys { pairs = append(pairs, percentEncode(k)+"="+percentEncode(params[k])) } canonicalized := strings.Join(pairs, "&") // 3. 构造待签名字符串 stringToSign := "GET&" + percentEncode("/") + "&" + percentEncode(canonicalized) // 4. HMAC-SHA1 mac := hmac.New(sha1.New, []byte(a.secretKey+"&")) mac.Write([]byte(stringToSign)) return base64.StdEncoding.EncodeToString(mac.Sum(nil)) } // call 调用阿里云 RPC API func (a *AliyunProvider) call(action string, extra map[string]string, endpoint string) ([]byte, error) { if endpoint == "" { endpoint = "https://ecs.aliyuncs.com/" } params := map[string]string{ "Format": "JSON", "Version": a.version, "AccessKeyId": a.accessKey, "SignatureMethod": "HMAC-SHA1", "Timestamp": time.Now().UTC().Format("2006-01-02T15:04:05Z"), "SignatureVersion": "1.0", "SignatureNonce": fmt.Sprintf("%d", rand.Int63()), "Action": action, } for k, v := range extra { params[k] = v } params["Signature"] = a.sign(params) // 构造 GET URL var pairs []string for k, v := range params { pairs = append(pairs, url.QueryEscape(k)+"="+url.QueryEscape(v)) } reqURL := endpoint + "?" + strings.Join(pairs, "&") resp, err := http.Get(reqURL) if err != nil { return nil, fmt.Errorf("请求阿里云 API 失败: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取响应失败: %w", err) } if resp.StatusCode != 200 { return nil, fmt.Errorf("阿里云 API 返回错误 (HTTP %d): %s", resp.StatusCode, string(body)) } return body, nil } // aliyunRegionsResponse DescribeRegions 响应 type aliyunRegionsResponse struct { Regions struct { Region []struct { RegionID string `json:"RegionId"` LocalName string `json:"LocalName"` RegionEndpoint string `json:"RegionEndpoint"` } `json:"Region"` } `json:"Regions"` } // ListRegions 获取阿里云所有区域 func (a *AliyunProvider) ListRegions() ([]CloudRegion, error) { body, err := a.call("DescribeRegions", nil, "") if err != nil { return nil, err } var resp aliyunRegionsResponse if err := json.Unmarshal(body, &resp); err != nil { return nil, fmt.Errorf("解析区域响应失败: %w", err) } regions := make([]CloudRegion, 0, len(resp.Regions.Region)) for _, r := range resp.Regions.Region { regions = append(regions, CloudRegion{ RegionID: r.RegionID, LocalName: r.LocalName, Endpoint: r.RegionEndpoint, Status: "active", }) } return regions, nil } // aliyunInstancesResponse DescribeInstances 响应 type aliyunInstancesResponse struct { TotalCount int `json:"TotalCount"` Instances struct { Instance []struct { InstanceID string `json:"InstanceId"` InstanceName string `json:"InstanceName"` HostName string `json:"HostName"` Status string `json:"Status"` RegionID string `json:"RegionId"` ZoneID string `json:"ZoneId"` InstanceType string `json:"InstanceType"` CPU int `json:"Cpu"` Memory int `json:"Memory"` OSType string `json:"OSType"` CreationTime string `json:"CreationTime"` VPCAttributes struct { PrivateIPAddress struct { IPAddress []string `json:"IpAddress"` } `json:"PrivateIpAddress"` } `json:"VpcAttributes"` PublicIPAddress struct { IPAddress []string `json:"IpAddress"` } `json:"PublicIpAddress"` InnerIPAddress struct { IPAddress []string `json:"IpAddress"` } `json:"InnerIpAddress"` } `json:"Instance"` } `json:"Instances"` } // ListInstances 获取指定区域的云主机实例 func (a *AliyunProvider) ListInstances(regionID string) ([]CloudInstance, error) { endpoint := fmt.Sprintf("https://ecs.%s.aliyuncs.com/", regionID) body, err := a.call("DescribeInstances", map[string]string{ "RegionId": regionID, "PageSize": "100", }, endpoint) if err != nil { return nil, err } var resp aliyunInstancesResponse if err := json.Unmarshal(body, &resp); err != nil { return nil, fmt.Errorf("解析实例响应失败: %w", err) } instances := make([]CloudInstance, 0, len(resp.Instances.Instance)) for _, inst := range resp.Instances.Instance { privateIP := "" if len(inst.VPCAttributes.PrivateIPAddress.IPAddress) > 0 { privateIP = inst.VPCAttributes.PrivateIPAddress.IPAddress[0] } publicIP := "" if len(inst.PublicIPAddress.IPAddress) > 0 { publicIP = inst.PublicIPAddress.IPAddress[0] } osType := inst.OSType if osType == "" { osType = "linux" } instances = append(instances, CloudInstance{ InstanceID: inst.InstanceID, InstanceName: inst.InstanceName, Hostname: inst.HostName, OSType: osType, Status: inst.Status, RegionID: inst.RegionID, ZoneID: inst.ZoneID, PrivateIP: privateIP, PublicIP: publicIP, InstanceType: inst.InstanceType, CPU: inst.CPU, Memory: inst.Memory, CreationTime: inst.CreationTime, }) } return instances, nil }