@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"oci-portal/internal/model"
|
||||
@@ -32,14 +33,23 @@ type OverviewCostDay struct {
|
||||
Amount float64 `json:"amount"`
|
||||
}
|
||||
|
||||
// OverviewCost 是近 7 日成本快照聚合;金额跨配置直接相加,
|
||||
// Currency 取快照中出现的第一个币种(混合币种时以此为准展示)。
|
||||
// OverviewCost 是近 7 日成本快照聚合;同币种金额跨配置相加,跨币种拆 Series。
|
||||
type OverviewCost struct {
|
||||
HasActiveTask bool `json:"hasActiveTask"`
|
||||
CoveredConfigs int `json:"coveredConfigs"`
|
||||
Currency string `json:"currency"`
|
||||
Total float64 `json:"total"`
|
||||
Days []OverviewCostDay `json:"days"`
|
||||
// Series 按币种拆分的序列(合计降序);顶层 Currency/Total/Days 恒为首个
|
||||
// (主)币种,多币种租户的其余币种只出现在 Series,不与主币种相加。
|
||||
Series []OverviewCostSeries `json:"series"`
|
||||
}
|
||||
|
||||
// OverviewCostSeries 是单一币种的成本序列;跨币种金额不可直接相加。
|
||||
type OverviewCostSeries struct {
|
||||
Currency string `json:"currency"`
|
||||
Total float64 `json:"total"`
|
||||
Days []OverviewCostDay `json:"days"`
|
||||
}
|
||||
|
||||
// OverviewTasks 是后台任务数量统计。
|
||||
@@ -150,29 +160,53 @@ func (s *OciConfigService) overviewCost(ctx context.Context, out *Overview) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
// aggregateCostDays 把逐配置逐日的快照聚合为跨租户的每日序列(snaps 须按 day 升序)。
|
||||
// aggregateCostDays 把逐配置逐日的快照按币种聚合(snaps 须按 day 升序)。
|
||||
// 不同币种金额不可直接相加:按币种拆序列,顶层 Currency/Total/Days 取合计最大的
|
||||
// 主币种,其余币种只出现在 Series 里。
|
||||
func aggregateCostDays(cost *OverviewCost, snaps []model.CostSnapshot) {
|
||||
byDay := map[string]float64{}
|
||||
perCur := map[string]map[string]float64{}
|
||||
dayOrder := map[string][]string{}
|
||||
covered := map[uint]bool{}
|
||||
var order []string
|
||||
for _, snap := range snaps {
|
||||
byDay := perCur[snap.Currency]
|
||||
if byDay == nil {
|
||||
byDay = map[string]float64{}
|
||||
perCur[snap.Currency] = byDay
|
||||
}
|
||||
if _, ok := byDay[snap.Day]; !ok {
|
||||
order = append(order, snap.Day)
|
||||
dayOrder[snap.Currency] = append(dayOrder[snap.Currency], snap.Day)
|
||||
}
|
||||
byDay[snap.Day] += snap.Amount
|
||||
covered[snap.OciConfigID] = true
|
||||
if cost.Currency == "" {
|
||||
cost.Currency = snap.Currency
|
||||
}
|
||||
}
|
||||
cost.CoveredConfigs = len(covered)
|
||||
cost.Days = make([]OverviewCostDay, 0, len(order))
|
||||
for _, day := range order {
|
||||
cost.Days = append(cost.Days, OverviewCostDay{Day: day, Amount: byDay[day]})
|
||||
cost.Total += byDay[day]
|
||||
cost.Days = []OverviewCostDay{}
|
||||
cost.Series = costSeries(perCur, dayOrder)
|
||||
if len(cost.Series) > 0 {
|
||||
cost.Currency, cost.Total, cost.Days = cost.Series[0].Currency, cost.Series[0].Total, cost.Series[0].Days
|
||||
}
|
||||
}
|
||||
|
||||
// costSeries 组装各币种序列并按合计降序(相同合计按币种名,保证稳定输出)。
|
||||
func costSeries(perCur map[string]map[string]float64, dayOrder map[string][]string) []OverviewCostSeries {
|
||||
series := make([]OverviewCostSeries, 0, len(perCur))
|
||||
for currency, byDay := range perCur {
|
||||
item := OverviewCostSeries{Currency: currency, Days: make([]OverviewCostDay, 0, len(byDay))}
|
||||
for _, day := range dayOrder[currency] {
|
||||
item.Days = append(item.Days, OverviewCostDay{Day: day, Amount: byDay[day]})
|
||||
item.Total += byDay[day]
|
||||
}
|
||||
series = append(series, item)
|
||||
}
|
||||
sort.Slice(series, func(i, j int) bool {
|
||||
if series[i].Total != series[j].Total {
|
||||
return series[i].Total > series[j].Total
|
||||
}
|
||||
return series[i].Currency < series[j].Currency
|
||||
})
|
||||
return series
|
||||
}
|
||||
|
||||
func (s *OciConfigService) overviewTasks(ctx context.Context, out *Overview) error {
|
||||
var tasks []model.Task
|
||||
if err := s.db.WithContext(ctx).Find(&tasks).Error; err != nil {
|
||||
|
||||
Reference in New Issue
Block a user