package cloud import ( "encoding/json" "fmt" "math" "net/url" "sort" "strconv" "strings" "time" ) type huaweiBillRecord struct { ServiceTypeCode string `json:"service_type_code"` ServiceTypeName string `json:"service_type_name"` CloudServiceType string `json:"cloud_service_type"` CloudServiceTypeName string `json:"cloud_service_type_name"` ResourceTypeName string `json:"resource_type_name"` RegionCode string `json:"region_code"` Region string `json:"region"` RegionName string `json:"region_name"` Amount json.RawMessage `json:"amount"` ConsumeAmount json.RawMessage `json:"consume_amount"` OfficialAmount json.RawMessage `json:"official_amount"` DiscountAmount json.RawMessage `json:"discount_amount"` } type huaweiBillPage struct { TotalCount int `json:"total_count"` Currency string `json:"currency"` Records []huaweiBillRecord `json:"records"` FeeRecords []huaweiBillRecord `json:"fee_records"` } // FetchDailyBill 通过华为云 BSS 费用流水接口拉取单日账单。 // Logan.Li func (p *HuaweiProvider) FetchDailyBill(billingDate string) ([]CloudBillingRecord, error) { if _, err := time.Parse("2006-01-02", billingDate); err != nil { return nil, fmt.Errorf("账单日期格式应为 YYYY-MM-DD") } type aggregationKey struct { service string region string } aggregation := map[aggregationKey]float64{} currency := "CNY" const pageSize = 1000 for offset := 0; ; offset += pageSize { query := url.Values{ "bill_cycle": {billingDate[:7]}, "bill_date_begin": {billingDate}, "bill_date_end": {billingDate}, "offset": {strconv.Itoa(offset)}, "limit": {strconv.Itoa(pageSize)}, } body, err := p.signedGet("https://bss.myhuaweicloud.com/v2/bills/customer-bills/fee-records?"+query.Encode(), "bss") if err != nil { return nil, err } page, err := decodeHuaweiBillPage(body) if err != nil { return nil, err } if page.Currency != "" { currency = page.Currency } records := page.Records if len(records) == 0 { records = page.FeeRecords } for _, item := range records { cost := huaweiBillRecordCost(item) if cost == 0 { continue } service := firstNonEmpty(item.ServiceTypeName, item.CloudServiceTypeName, item.ResourceTypeName, item.ServiceTypeCode, item.CloudServiceType, "其他") region := firstNonEmpty(item.RegionName, item.RegionCode, item.Region, "全局") aggregation[aggregationKey{service: service, region: region}] += cost } if len(records) < pageSize || (page.TotalCount > 0 && offset+pageSize >= page.TotalCount) { break } } keys := make([]aggregationKey, 0, len(aggregation)) for key := range aggregation { keys = append(keys, key) } sort.Slice(keys, func(i, j int) bool { if keys[i].service == keys[j].service { return keys[i].region < keys[j].region } return keys[i].service < keys[j].service }) records := make([]CloudBillingRecord, 0, len(keys)) for _, key := range keys { records = append(records, CloudBillingRecord{ BillingDate: billingDate, Provider: "huawei", Cost: math.Round(aggregation[key]*100) / 100, Currency: currency, Service: key.service, Region: key.region, Description: "云 API 自动同步(华为云 BSS)", }) } return records, nil } // decodeHuaweiBillPage 兼容华为云 records 与旧版 fee_records 响应。 // Logan.Li func decodeHuaweiBillPage(body []byte) (huaweiBillPage, error) { var page huaweiBillPage if err := json.Unmarshal(body, &page); err != nil { return page, fmt.Errorf("解析华为云 BSS 响应失败: %w", err) } return page, nil } // huaweiBillRecordCost 依次读取实际消费、流水金额和官网价减优惠。 // Logan.Li func huaweiBillRecordCost(item huaweiBillRecord) float64 { if cost := huaweiBillAmount(item.ConsumeAmount); cost != 0 { return cost } if cost := huaweiBillAmount(item.Amount); cost != 0 { return cost } return huaweiBillAmount(item.OfficialAmount) - huaweiBillAmount(item.DiscountAmount) } // huaweiBillAmount 兼容华为云金额字段的字符串和数字格式。 // Logan.Li func huaweiBillAmount(raw json.RawMessage) float64 { value := strings.Trim(strings.TrimSpace(string(raw)), "\"") amount, err := strconv.ParseFloat(value, 64) if err != nil { return 0 } return amount }