初始提交:OCI 面板后端(含 GenAI 网关一期)
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"oci-portal/internal/model"
|
||||
)
|
||||
|
||||
// OverviewTenants 是租户数量与类别分布。
|
||||
type OverviewTenants struct {
|
||||
Total int `json:"total"`
|
||||
Alive int `json:"alive"`
|
||||
Dead int `json:"dead"`
|
||||
ByType map[string]int `json:"byType"`
|
||||
}
|
||||
|
||||
// OverviewCheck 是测活快照聚合;实例数为各配置默认区域之和,
|
||||
// CoveredConfigs/TotalConfigs 用于前端标注覆盖度。
|
||||
type OverviewCheck struct {
|
||||
HasActiveTask bool `json:"hasActiveTask"`
|
||||
CoveredConfigs int `json:"coveredConfigs"`
|
||||
TotalConfigs int `json:"totalConfigs"`
|
||||
InstanceCount int `json:"instanceCount"`
|
||||
LastCheckedAt *time.Time `json:"lastCheckedAt"`
|
||||
}
|
||||
|
||||
// OverviewCostDay 是一天的跨租户成本合计。
|
||||
type OverviewCostDay struct {
|
||||
Day string `json:"day"`
|
||||
Amount float64 `json:"amount"`
|
||||
}
|
||||
|
||||
// OverviewCost 是近 7 日成本快照聚合;金额跨配置直接相加,
|
||||
// Currency 取快照中出现的第一个币种(混合币种时以此为准展示)。
|
||||
type OverviewCost struct {
|
||||
HasActiveTask bool `json:"hasActiveTask"`
|
||||
CoveredConfigs int `json:"coveredConfigs"`
|
||||
Currency string `json:"currency"`
|
||||
Total float64 `json:"total"`
|
||||
Days []OverviewCostDay `json:"days"`
|
||||
}
|
||||
|
||||
// OverviewTasks 是后台任务数量统计。
|
||||
type OverviewTasks struct {
|
||||
Total int `json:"total"`
|
||||
Active int `json:"active"`
|
||||
HealthCheck int `json:"healthCheck"`
|
||||
Cost int `json:"cost"`
|
||||
Snatch int `json:"snatch"`
|
||||
}
|
||||
|
||||
// Overview 是总览页聚合数据。
|
||||
type Overview struct {
|
||||
Tenants OverviewTenants `json:"tenants"`
|
||||
Check OverviewCheck `json:"check"`
|
||||
Cost OverviewCost `json:"cost"`
|
||||
Tasks OverviewTasks `json:"tasks"`
|
||||
}
|
||||
|
||||
// Overview 聚合总览数据:租户分布、测活/成本快照与任务统计,全部读本地库不发云端请求。
|
||||
func (s *OciConfigService) Overview(ctx context.Context) (*Overview, error) {
|
||||
var out Overview
|
||||
if err := s.overviewTenants(ctx, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.overviewCheck(ctx, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.overviewCost(ctx, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.overviewTasks(ctx, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
func (s *OciConfigService) overviewTenants(ctx context.Context, out *Overview) error {
|
||||
configs, err := s.List(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.Tenants.Total = len(configs)
|
||||
out.Tenants.ByType = map[string]int{}
|
||||
for _, cfg := range configs {
|
||||
out.Tenants.ByType[cfg.AccountType]++
|
||||
switch cfg.AliveStatus {
|
||||
case model.AliveStatusAlive:
|
||||
out.Tenants.Alive++
|
||||
case model.AliveStatusDead:
|
||||
out.Tenants.Dead++
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasActiveTask 判断是否存在指定类型且调度中的任务。
|
||||
func (s *OciConfigService) hasActiveTask(ctx context.Context, taskType string) (bool, error) {
|
||||
var n int64
|
||||
err := s.db.WithContext(ctx).Model(&model.Task{}).
|
||||
Where("type = ? AND status = ?", taskType, model.TaskStatusActive).Count(&n).Error
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("count %s tasks: %w", taskType, err)
|
||||
}
|
||||
return n > 0, nil
|
||||
}
|
||||
|
||||
func (s *OciConfigService) overviewCheck(ctx context.Context, out *Overview) error {
|
||||
has, err := s.hasActiveTask(ctx, model.TaskTypeHealthCheck)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.Check.HasActiveTask = has
|
||||
out.Check.TotalConfigs = out.Tenants.Total
|
||||
var snaps []model.CheckSnapshot
|
||||
err = s.db.WithContext(ctx).
|
||||
Where("oci_config_id IN (?)", s.db.Model(&model.OciConfig{}).Select("id")).
|
||||
Find(&snaps).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf("load check snapshots: %w", err)
|
||||
}
|
||||
out.Check.CoveredConfigs = len(snaps)
|
||||
for _, snap := range snaps {
|
||||
out.Check.InstanceCount += snap.InstanceCount
|
||||
if out.Check.LastCheckedAt == nil || snap.CheckedAt.After(*out.Check.LastCheckedAt) {
|
||||
t := snap.CheckedAt
|
||||
out.Check.LastCheckedAt = &t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *OciConfigService) overviewCost(ctx context.Context, out *Overview) error {
|
||||
has, err := s.hasActiveTask(ctx, model.TaskTypeCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.Cost.HasActiveTask = has
|
||||
since := time.Now().UTC().AddDate(0, 0, -7).Format("2006-01-02")
|
||||
var snaps []model.CostSnapshot
|
||||
err = s.db.WithContext(ctx).
|
||||
Where("day >= ? AND oci_config_id IN (?)", since, s.db.Model(&model.OciConfig{}).Select("id")).
|
||||
Order("day").Find(&snaps).Error
|
||||
if err != nil {
|
||||
return fmt.Errorf("load cost snapshots: %w", err)
|
||||
}
|
||||
aggregateCostDays(&out.Cost, snaps)
|
||||
return nil
|
||||
}
|
||||
|
||||
// aggregateCostDays 把逐配置逐日的快照聚合为跨租户的每日序列(snaps 须按 day 升序)。
|
||||
func aggregateCostDays(cost *OverviewCost, snaps []model.CostSnapshot) {
|
||||
byDay := map[string]float64{}
|
||||
covered := map[uint]bool{}
|
||||
var order []string
|
||||
for _, snap := range snaps {
|
||||
if _, ok := byDay[snap.Day]; !ok {
|
||||
order = append(order, 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]
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return fmt.Errorf("list tasks: %w", err)
|
||||
}
|
||||
out.Tasks.Total = len(tasks)
|
||||
for _, t := range tasks {
|
||||
if t.Status == model.TaskStatusActive {
|
||||
out.Tasks.Active++
|
||||
}
|
||||
switch t.Type {
|
||||
case model.TaskTypeHealthCheck:
|
||||
out.Tasks.HealthCheck++
|
||||
case model.TaskTypeCost:
|
||||
out.Tasks.Cost++
|
||||
case model.TaskTypeSnatch:
|
||||
out.Tasks.Snatch++
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user