Files
oci-portal/internal/service/scopecache.go
T

233 lines
7.4 KiB
Go

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 := lockOciConfig(tx, cfgID); err != nil {
return err
}
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,
ParentOCID: c.ParentID, State: c.LifecycleState,
})
}
return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
if err := lockOciConfig(tx, cfgID); err != nil {
return err
}
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 || legacyCompartmentCache(rows) {
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, ParentID: r.ParentOCID, LifecycleState: r.State,
})
}
return comps, nil
}
// legacyCompartmentCache 识别未存 parentId 的旧格式缓存:每个 compartment
// 必有父(至少是租户根),整组 parent 全空即旧数据,需实时刷新一次自愈。
func legacyCompartmentCache(rows []model.CompartmentCache) bool {
for _, r := range rows {
if r.ParentOCID != "" {
return false
}
}
return len(rows) > 0
}
// 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
}