99 lines
3.3 KiB
Go
99 lines
3.3 KiB
Go
package cloud
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"math"
|
||
"net/url"
|
||
"sort"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
)
|
||
|
||
// 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{}
|
||
const pageSize = 1000
|
||
for offset := 0; ; offset += pageSize {
|
||
query := url.Values{
|
||
"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
|
||
}
|
||
var response struct {
|
||
TotalCount int `json:"total_count"`
|
||
FeeRecords []struct {
|
||
CloudServiceType string `json:"cloud_service_type"`
|
||
CloudServiceTypeName string `json:"cloud_service_type_name"`
|
||
ResourceTypeName string `json:"resource_type_name"`
|
||
Region string `json:"region"`
|
||
RegionName string `json:"region_name"`
|
||
Amount json.RawMessage `json:"amount"`
|
||
OfficialAmount json.RawMessage `json:"official_amount"`
|
||
DiscountAmount json.RawMessage `json:"discount_amount"`
|
||
} `json:"fee_records"`
|
||
}
|
||
if err = json.Unmarshal(body, &response); err != nil {
|
||
return nil, fmt.Errorf("解析华为云 BSS 响应失败: %w", err)
|
||
}
|
||
for _, item := range response.FeeRecords {
|
||
cost := huaweiBillAmount(item.Amount)
|
||
if cost == 0 {
|
||
cost = huaweiBillAmount(item.OfficialAmount) - huaweiBillAmount(item.DiscountAmount)
|
||
}
|
||
if cost == 0 {
|
||
continue
|
||
}
|
||
service := firstNonEmpty(item.CloudServiceTypeName, item.ResourceTypeName, item.CloudServiceType, "其他")
|
||
region := firstNonEmpty(item.RegionName, item.Region, "全局")
|
||
aggregation[aggregationKey{service: service, region: region}] += cost
|
||
}
|
||
if len(response.FeeRecords) < pageSize || (response.TotalCount > 0 && offset+pageSize >= response.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: "CNY", Service: key.service, Region: key.region,
|
||
Description: "云 API 自动同步(华为云 BSS)",
|
||
})
|
||
}
|
||
return records, nil
|
||
}
|
||
|
||
// 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
|
||
}
|