初始提交:OCI 面板后端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:04 +08:00
commit b9a3e97e84
168 changed files with 31794 additions and 0 deletions
+196
View File
@@ -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
}