package service import ( "context" "fmt" "gorm.io/gorm" "oci-portal/internal/model" "oci-portal/internal/oci" ) // syncScopeCaches 按配置开关同步区域 / 区间缓存:开启则整组刷新,关闭则清空。 // 随测活(导入 / 修改 / 测活按钮 / 定时任务)触发;OCI 调用失败不影响测活主流程。 func (s *OciConfigService) syncScopeCaches(ctx context.Context, cfg *model.OciConfig, cred oci.Credentials) { if cfg.MultiRegion { if subs, err := s.client.ListRegionSubscriptions(ctx, cred); err == nil { _ = s.saveRegionCache(ctx, cfg.ID, subs) } } else { s.db.WithContext(ctx).Where("oci_config_id = ?", cfg.ID).Delete(&model.RegionCache{}) } if cfg.MultiCompartment { if comps, err := s.client.ListCompartments(ctx, cred); err == nil { _ = s.saveCompartmentCache(ctx, cfg.ID, comps) } } else { s.db.WithContext(ctx).Where("oci_config_id = ?", cfg.ID).Delete(&model.CompartmentCache{}) } } // saveRegionCache 整组覆盖写入订阅区域缓存。 func (s *OciConfigService) saveRegionCache(ctx context.Context, cfgID uint, subs []oci.RegionSubscription) error { rows := make([]model.RegionCache, 0, len(subs)) for _, sub := range subs { rows = append(rows, model.RegionCache{ OciConfigID: cfgID, Key: sub.Key, Name: sub.Name, Status: sub.Status, IsHomeRegion: sub.IsHomeRegion, }) } return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Where("oci_config_id = ?", cfgID).Delete(&model.RegionCache{}).Error; err != nil { return err } if len(rows) == 0 { return nil } return tx.Create(&rows).Error }) } // saveCompartmentCache 整组覆盖写入 compartment 缓存。 func (s *OciConfigService) saveCompartmentCache(ctx context.Context, cfgID uint, comps []oci.Compartment) error { rows := make([]model.CompartmentCache, 0, len(comps)) for _, c := range comps { rows = append(rows, model.CompartmentCache{ OciConfigID: cfgID, OCID: c.ID, Name: c.Name, State: c.LifecycleState, }) } return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Where("oci_config_id = ?", cfgID).Delete(&model.CompartmentCache{}).Error; err != nil { return err } if len(rows) == 0 { return nil } return tx.Create(&rows).Error }) } // CachedRegions 返回筛选器用的区域列表:未开启多区域支持时锁定为配置默认区域; // 缓存缺失或存在非 READY 状态(订阅进行中)时实时刷新,直到全部 READY。 func (s *OciConfigService) CachedRegions(ctx context.Context, id uint) ([]RegionSubscriptionView, error) { cfg, err := s.Get(ctx, id) if err != nil { return nil, err } if !cfg.MultiRegion { return defaultOnlyRegion(cfg), nil } var rows []model.RegionCache if err := s.db.WithContext(ctx).Where("oci_config_id = ?", id).Find(&rows).Error; err != nil { return nil, fmt.Errorf("load region cache: %w", err) } if len(rows) == 0 || hasPendingRegion(rows) { if fresh, err := s.refreshRegionCache(ctx, cfg); err == nil { return fresh, nil } // 实时刷新失败时退回现有缓存,避免筛选器整体不可用 } return regionCacheViews(rows), nil } // refreshRegionCache 实时拉取订阅区域并覆盖缓存。 func (s *OciConfigService) refreshRegionCache(ctx context.Context, cfg *model.OciConfig) ([]RegionSubscriptionView, error) { cred, err := s.credentialsOf(cfg) if err != nil { return nil, err } subs, err := s.client.ListRegionSubscriptions(ctx, cred) if err != nil { return nil, err } if err := s.saveRegionCache(ctx, cfg.ID, subs); err != nil { return nil, err } return enrichRegionAlias(subs), nil } // reconcileRegionCache 把 SDK 实时数据与缓存比较,有差异时覆盖缓存。 func (s *OciConfigService) reconcileRegionCache(ctx context.Context, cfgID uint, subs []oci.RegionSubscription) { var rows []model.RegionCache if err := s.db.WithContext(ctx).Where("oci_config_id = ?", cfgID).Find(&rows).Error; err != nil { return } if regionCacheDiffers(rows, subs) { _ = s.saveRegionCache(ctx, cfgID, subs) } } func regionCacheDiffers(rows []model.RegionCache, subs []oci.RegionSubscription) bool { if len(rows) != len(subs) { return true } byKey := make(map[string]model.RegionCache, len(rows)) for _, r := range rows { byKey[r.Key] = r } for _, sub := range subs { r, ok := byKey[sub.Key] if !ok || r.Status != sub.Status || r.Name != sub.Name || r.IsHomeRegion != sub.IsHomeRegion { return true } } return false } func hasPendingRegion(rows []model.RegionCache) bool { for _, r := range rows { if r.Status != regionStatusReady { return true } } return false } // regionStatusReady 是 OCI 区域订阅完成态。 const regionStatusReady = "READY" // defaultOnlyRegion 为未开启多区域支持的配置合成单元素列表(配置默认区域)。 func defaultOnlyRegion(cfg *model.OciConfig) []RegionSubscriptionView { sub := oci.RegionSubscription{ Key: cfg.HomeRegionKey, Name: cfg.Region, Status: regionStatusReady, } if info, ok := oci.RegionByKey(cfg.HomeRegionKey); ok { sub.IsHomeRegion = info.Name == cfg.Region } return enrichRegionAlias([]oci.RegionSubscription{sub}) } func regionCacheViews(rows []model.RegionCache) []RegionSubscriptionView { subs := make([]oci.RegionSubscription, 0, len(rows)) for _, r := range rows { subs = append(subs, oci.RegionSubscription{ Key: r.Key, Name: r.Name, Status: r.Status, IsHomeRegion: r.IsHomeRegion, }) } // 与实时接口一致:主区域置顶,其余按 alias 字典序 oci.SortRegionSubscriptions(subs) return enrichRegionAlias(subs) } // CachedCompartments 返回筛选器用的区间列表:未开启多区间支持返回空(前端锁定根); // 已开启但缓存为空时实时拉取一次入库。 func (s *OciConfigService) CachedCompartments(ctx context.Context, id uint) ([]oci.Compartment, error) { cfg, err := s.Get(ctx, id) if err != nil { return nil, err } if !cfg.MultiCompartment { return []oci.Compartment{}, nil } var rows []model.CompartmentCache if err := s.db.WithContext(ctx).Where("oci_config_id = ?", id). Order("name").Find(&rows).Error; err != nil { return nil, fmt.Errorf("load compartment cache: %w", err) } if len(rows) == 0 { return s.refreshCompartmentCache(ctx, cfg) } comps := make([]oci.Compartment, 0, len(rows)) for _, r := range rows { comps = append(comps, oci.Compartment{ID: r.OCID, Name: r.Name, LifecycleState: r.State}) } return comps, nil } // refreshCompartmentCache 实时拉取 compartment 并覆盖缓存。 func (s *OciConfigService) refreshCompartmentCache(ctx context.Context, cfg *model.OciConfig) ([]oci.Compartment, error) { cred, err := s.credentialsOf(cfg) if err != nil { return nil, err } comps, err := s.client.ListCompartments(ctx, cred) if err != nil { return nil, err } if err := s.saveCompartmentCache(ctx, cfg.ID, comps); err != nil { return nil, err } return comps, nil }