package oci import ( "context" "slices" "strings" "time" "oci-portal/internal/cache" ) // 读缓存 TTL 分级:过渡态资源短、准静态长(调研①方案 A)。 // 实例列表 10s:前端 5s 自动刷新隔次命中,OCI 调用减半,状态延迟可控。 const ( cacheTTLInstances = 10 * time.Second cacheTTLVolatile = 30 * time.Second // 卷 / 挂载 / 网卡 cacheTTLNetwork = 2 * time.Minute // VCN / 子网 / 安全列表 cacheTTLStatic = 10 * time.Minute // 镜像 / 可用域 ) // CachedClient 用进程内 TTL 缓存包装真实客户端,削减 OCI 读放大; // 写操作直通并失效同租户全部读缓存(粗粒度换正确性,重建仅一次回源)。 // 未覆写的方法经嵌入接口直通。 type CachedClient struct { Client store *cache.Cache } // NewCachedClient 包装 inner;上限 4096 键(单键几 KB,整体几 MB 级)。 func NewCachedClient(inner Client) *CachedClient { return &CachedClient{Client: inner, store: cache.New(4096)} } // ckey 组缓存键;租户 OCID 在最前,写失效按前缀一锅端。 func ckey(cred Credentials, parts ...string) string { return cred.TenancyOCID + "|" + strings.Join(parts, "|") } // bust 写操作成功后失效该租户全部读缓存。 func (c *CachedClient) bust(err error, cred Credentials) { if err == nil { c.store.DeletePrefix(cred.TenancyOCID + "|") } } // cachedList 缓存切片结果;命中返回浅拷贝,防调用方排序共享底层数组。 func cachedList[T any](c *CachedClient, key string, ttl time.Duration, fn func() ([]T, error)) ([]T, error) { v, err := cache.Do(c.store, key, ttl, fn) if err != nil { return nil, err } return slices.Clone(v), nil } // ---- 读:按 TTL 分级缓存 ---- func (c *CachedClient) ListInstances(ctx context.Context, cred Credentials, region string) ([]Instance, error) { return cachedList(c, ckey(cred, "instances", region), cacheTTLInstances, func() ([]Instance, error) { return c.Client.ListInstances(ctx, cred, region) }) } func (c *CachedClient) ListImages(ctx context.Context, cred Credentials, q ImagesQuery) ([]Image, error) { return cachedList(c, ckey(cred, "images", q.Region, q.OperatingSystem, q.Shape), cacheTTLStatic, func() ([]Image, error) { return c.Client.ListImages(ctx, cred, q) }) } func (c *CachedClient) ListVCNs(ctx context.Context, cred Credentials, region string) ([]VCN, error) { return cachedList(c, ckey(cred, "vcns", region), cacheTTLNetwork, func() ([]VCN, error) { return c.Client.ListVCNs(ctx, cred, region) }) } func (c *CachedClient) ListSubnets(ctx context.Context, cred Credentials, region, vcnID string) ([]Subnet, error) { return cachedList(c, ckey(cred, "subnets", region, vcnID), cacheTTLNetwork, func() ([]Subnet, error) { return c.Client.ListSubnets(ctx, cred, region, vcnID) }) } func (c *CachedClient) ListSecurityLists(ctx context.Context, cred Credentials, region, vcnID string) ([]SecurityList, error) { return cachedList(c, ckey(cred, "seclists", region, vcnID), cacheTTLNetwork, func() ([]SecurityList, error) { return c.Client.ListSecurityLists(ctx, cred, region, vcnID) }) } func (c *CachedClient) ListAvailabilityDomains(ctx context.Context, cred Credentials, region string) ([]string, error) { return cachedList(c, ckey(cred, "ads", region), cacheTTLStatic, func() ([]string, error) { return c.Client.ListAvailabilityDomains(ctx, cred, region) }) } func (c *CachedClient) ListBootVolumes(ctx context.Context, cred Credentials, region, availabilityDomain string) ([]BootVolume, error) { return cachedList(c, ckey(cred, "bootvols", region, availabilityDomain), cacheTTLVolatile, func() ([]BootVolume, error) { return c.Client.ListBootVolumes(ctx, cred, region, availabilityDomain) }) } func (c *CachedClient) ListBlockVolumes(ctx context.Context, cred Credentials, region, availabilityDomain string) ([]BlockVolume, error) { return cachedList(c, ckey(cred, "blockvols", region, availabilityDomain), cacheTTLVolatile, func() ([]BlockVolume, error) { return c.Client.ListBlockVolumes(ctx, cred, region, availabilityDomain) }) } func (c *CachedClient) ListInstanceVnics(ctx context.Context, cred Credentials, region, instanceID string) ([]Vnic, error) { return cachedList(c, ckey(cred, "vnics", region, instanceID), cacheTTLVolatile, func() ([]Vnic, error) { return c.Client.ListInstanceVnics(ctx, cred, region, instanceID) }) } func (c *CachedClient) ListBootVolumeAttachments(ctx context.Context, cred Credentials, region, instanceID string) ([]BootVolumeAttachment, error) { return cachedList(c, ckey(cred, "bvattach", region, instanceID), cacheTTLVolatile, func() ([]BootVolumeAttachment, error) { return c.Client.ListBootVolumeAttachments(ctx, cred, region, instanceID) }) } func (c *CachedClient) ListVolumeAttachments(ctx context.Context, cred Credentials, region, instanceID string) ([]VolumeAttachment, error) { return cachedList(c, ckey(cred, "volattach", region, instanceID), cacheTTLVolatile, func() ([]VolumeAttachment, error) { return c.Client.ListVolumeAttachments(ctx, cred, region, instanceID) }) } // ---- 写:直通,成功后按租户失效 ---- func (c *CachedClient) LaunchInstance(ctx context.Context, cred Credentials, in CreateInstanceInput) (Instance, error) { out, err := c.Client.LaunchInstance(ctx, cred, in) c.bust(err, cred) return out, err } func (c *CachedClient) UpdateInstance(ctx context.Context, cred Credentials, instanceID string, in UpdateInstanceInput) (Instance, error) { out, err := c.Client.UpdateInstance(ctx, cred, instanceID, in) c.bust(err, cred) return out, err } func (c *CachedClient) TerminateInstance(ctx context.Context, cred Credentials, region, instanceID string, preserveBootVolume bool) error { err := c.Client.TerminateInstance(ctx, cred, region, instanceID, preserveBootVolume) c.bust(err, cred) return err } func (c *CachedClient) InstanceAction(ctx context.Context, cred Credentials, region, instanceID, action string) (Instance, error) { out, err := c.Client.InstanceAction(ctx, cred, region, instanceID, action) c.bust(err, cred) return out, err } func (c *CachedClient) ChangeInstancePublicIP(ctx context.Context, cred Credentials, region, instanceID string) (string, error) { out, err := c.Client.ChangeInstancePublicIP(ctx, cred, region, instanceID) c.bust(err, cred) return out, err } func (c *CachedClient) AddInstanceIpv6(ctx context.Context, cred Credentials, region, instanceID, address string) (string, error) { out, err := c.Client.AddInstanceIpv6(ctx, cred, region, instanceID, address) c.bust(err, cred) return out, err } func (c *CachedClient) DeleteInstanceIpv6(ctx context.Context, cred Credentials, region, instanceID, address string) error { err := c.Client.DeleteInstanceIpv6(ctx, cred, region, instanceID, address) c.bust(err, cred) return err } func (c *CachedClient) CreateVCN(ctx context.Context, cred Credentials, in CreateVCNInput) (VCN, error) { out, err := c.Client.CreateVCN(ctx, cred, in) c.bust(err, cred) return out, err } func (c *CachedClient) UpdateVCN(ctx context.Context, cred Credentials, region, vcnID, displayName string) (VCN, error) { out, err := c.Client.UpdateVCN(ctx, cred, region, vcnID, displayName) c.bust(err, cred) return out, err } func (c *CachedClient) DeleteVCN(ctx context.Context, cred Credentials, region, vcnID string) error { err := c.Client.DeleteVCN(ctx, cred, region, vcnID) c.bust(err, cred) return err } func (c *CachedClient) EnableVCNIPv6(ctx context.Context, cred Credentials, region, vcnID string) ([]IPv6Step, error) { out, err := c.Client.EnableVCNIPv6(ctx, cred, region, vcnID) c.bust(err, cred) return out, err } func (c *CachedClient) CreateSubnet(ctx context.Context, cred Credentials, in CreateSubnetInput) (Subnet, error) { out, err := c.Client.CreateSubnet(ctx, cred, in) c.bust(err, cred) return out, err } func (c *CachedClient) UpdateSubnet(ctx context.Context, cred Credentials, region, subnetID, displayName string) (Subnet, error) { out, err := c.Client.UpdateSubnet(ctx, cred, region, subnetID, displayName) c.bust(err, cred) return out, err } func (c *CachedClient) DeleteSubnet(ctx context.Context, cred Credentials, region, subnetID string) error { err := c.Client.DeleteSubnet(ctx, cred, region, subnetID) c.bust(err, cred) return err } func (c *CachedClient) CreateSecurityList(ctx context.Context, cred Credentials, in CreateSecurityListInput) (SecurityList, error) { out, err := c.Client.CreateSecurityList(ctx, cred, in) c.bust(err, cred) return out, err } func (c *CachedClient) UpdateSecurityList(ctx context.Context, cred Credentials, securityListID string, in UpdateSecurityListInput) (SecurityList, error) { out, err := c.Client.UpdateSecurityList(ctx, cred, securityListID, in) c.bust(err, cred) return out, err } func (c *CachedClient) DeleteSecurityList(ctx context.Context, cred Credentials, region, securityListID string) error { err := c.Client.DeleteSecurityList(ctx, cred, region, securityListID) c.bust(err, cred) return err } func (c *CachedClient) UpdateBootVolume(ctx context.Context, cred Credentials, bootVolumeID string, in UpdateBootVolumeInput) (BootVolume, error) { out, err := c.Client.UpdateBootVolume(ctx, cred, bootVolumeID, in) c.bust(err, cred) return out, err } func (c *CachedClient) DeleteBootVolume(ctx context.Context, cred Credentials, region, bootVolumeID string) error { err := c.Client.DeleteBootVolume(ctx, cred, region, bootVolumeID) c.bust(err, cred) return err } func (c *CachedClient) AttachBootVolume(ctx context.Context, cred Credentials, region, instanceID, bootVolumeID string) (BootVolumeAttachment, error) { out, err := c.Client.AttachBootVolume(ctx, cred, region, instanceID, bootVolumeID) c.bust(err, cred) return out, err } func (c *CachedClient) DetachBootVolume(ctx context.Context, cred Credentials, region, attachmentID string) error { err := c.Client.DetachBootVolume(ctx, cred, region, attachmentID) c.bust(err, cred) return err } func (c *CachedClient) ReplaceBootVolume(ctx context.Context, cred Credentials, region, instanceID, newBootVolumeID string) (BootVolumeAttachment, error) { out, err := c.Client.ReplaceBootVolume(ctx, cred, region, instanceID, newBootVolumeID) c.bust(err, cred) return out, err } func (c *CachedClient) AttachVolume(ctx context.Context, cred Credentials, region, instanceID, volumeID string, readOnly bool) (VolumeAttachment, error) { out, err := c.Client.AttachVolume(ctx, cred, region, instanceID, volumeID, readOnly) c.bust(err, cred) return out, err } func (c *CachedClient) DetachVolume(ctx context.Context, cred Credentials, region, attachmentID string) error { err := c.Client.DetachVolume(ctx, cred, region, attachmentID) c.bust(err, cred) return err } func (c *CachedClient) AttachVnic(ctx context.Context, cred Credentials, region, instanceID string, in AttachVnicInput) (Vnic, error) { out, err := c.Client.AttachVnic(ctx, cred, region, instanceID, in) c.bust(err, cred) return out, err } func (c *CachedClient) DetachVnic(ctx context.Context, cred Credentials, region, attachmentID string) error { err := c.Client.DetachVnic(ctx, cred, region, attachmentID) c.bust(err, cred) return err } func (c *CachedClient) AddVnicIpv6(ctx context.Context, cred Credentials, region, vnicID, address string) (string, error) { out, err := c.Client.AddVnicIpv6(ctx, cred, region, vnicID, address) c.bust(err, cred) return out, err }