| |
| |
| package storage |
|
|
| import ( |
| "context" |
| "log" |
| "maps" |
| "strings" |
| "sync" |
| "time" |
|
|
| modelpkg "ccLoad/internal/model" |
| "ccLoad/internal/util" |
| ) |
|
|
| |
| |
| type ChannelCache struct { |
| store Store |
| channelsByModel map[string][]*modelpkg.Config |
| channelsByModelAndProtocol map[string]map[string][]*modelpkg.Config |
| channelsByType map[string][]*modelpkg.Config |
| channelsByExposedProtocol map[string][]*modelpkg.Config |
| allChannels []*modelpkg.Config |
| lastUpdate time.Time |
| mutex sync.RWMutex |
| refreshMutex sync.Mutex |
| ttl time.Duration |
|
|
| |
| apiKeysByChannelID map[int64][]*modelpkg.APIKey |
| cooldownCache struct { |
| channels map[int64]time.Time |
| keys map[int64]map[int]time.Time |
| channelLastUpdate time.Time |
| keyLastUpdate time.Time |
| ttl time.Duration |
| } |
| } |
|
|
| |
| func NewChannelCache(store Store, ttl time.Duration) *ChannelCache { |
| return &ChannelCache{ |
| store: store, |
| channelsByModel: make(map[string][]*modelpkg.Config), |
| channelsByModelAndProtocol: make(map[string]map[string][]*modelpkg.Config), |
| channelsByType: make(map[string][]*modelpkg.Config), |
| channelsByExposedProtocol: make(map[string][]*modelpkg.Config), |
| allChannels: make([]*modelpkg.Config, 0), |
| ttl: ttl, |
|
|
| |
| apiKeysByChannelID: make(map[int64][]*modelpkg.APIKey), |
| cooldownCache: struct { |
| channels map[int64]time.Time |
| keys map[int64]map[int]time.Time |
| channelLastUpdate time.Time |
| keyLastUpdate time.Time |
| ttl time.Duration |
| }{ |
| channels: make(map[int64]time.Time), |
| keys: make(map[int64]map[int]time.Time), |
| ttl: 30 * time.Second, |
| }, |
| } |
| } |
|
|
| |
| |
| func deepCopyConfigs(src []*modelpkg.Config) []*modelpkg.Config { |
| if src == nil { |
| return nil |
| } |
|
|
| result := make([]*modelpkg.Config, len(src)) |
| for i, cfg := range src { |
| result[i] = cfg.Clone() |
| } |
| return result |
| } |
|
|
| |
| |
| func (c *ChannelCache) GetEnabledChannelsByModel(ctx context.Context, model string) ([]*modelpkg.Config, error) { |
| if err := c.refreshIfNeeded(ctx); err != nil { |
| |
| return c.store.GetEnabledChannelsByModel(ctx, model) |
| } |
|
|
| c.mutex.RLock() |
| defer c.mutex.RUnlock() |
|
|
| if model == "*" { |
| |
| return deepCopyConfigs(c.allChannels), nil |
| } |
|
|
| |
| channels, exists := c.channelsByModel[model] |
| if !exists { |
| return []*modelpkg.Config{}, nil |
| } |
|
|
| return deepCopyConfigs(channels), nil |
| } |
|
|
| |
| |
| func (c *ChannelCache) GetEnabledChannelsByType(ctx context.Context, channelType string) ([]*modelpkg.Config, error) { |
| normalizedType := util.NormalizeChannelType(channelType) |
| if err := c.refreshIfNeeded(ctx); err != nil { |
| |
| return c.store.GetEnabledChannelsByType(ctx, normalizedType) |
| } |
|
|
| c.mutex.RLock() |
| defer c.mutex.RUnlock() |
|
|
| channels, exists := c.channelsByType[normalizedType] |
| if !exists { |
| return []*modelpkg.Config{}, nil |
| } |
|
|
| |
| return deepCopyConfigs(channels), nil |
| } |
|
|
| |
| func (c *ChannelCache) GetEnabledChannelsByExposedProtocol(ctx context.Context, protocol string) ([]*modelpkg.Config, error) { |
| protocol = normalizeProtocol(protocol) |
| if protocol == "" { |
| return []*modelpkg.Config{}, nil |
| } |
| if err := c.refreshIfNeeded(ctx); err != nil { |
| return c.store.GetEnabledChannelsByExposedProtocol(ctx, protocol) |
| } |
|
|
| c.mutex.RLock() |
| defer c.mutex.RUnlock() |
|
|
| channels, exists := c.channelsByExposedProtocol[protocol] |
| if !exists { |
| return []*modelpkg.Config{}, nil |
| } |
|
|
| return deepCopyConfigs(channels), nil |
| } |
|
|
| |
| func (c *ChannelCache) GetEnabledChannelsByModelAndProtocol(ctx context.Context, modelName string, protocol string) ([]*modelpkg.Config, error) { |
| protocol = normalizeProtocol(protocol) |
| if protocol == "" { |
| return c.GetEnabledChannelsByModel(ctx, modelName) |
| } |
| if err := c.refreshIfNeeded(ctx); err != nil { |
| channels, err := c.store.GetEnabledChannelsByModelAndProtocol(ctx, modelName, protocol) |
| if err != nil { |
| return nil, err |
| } |
| return deepCopyConfigs(channels), nil |
| } |
|
|
| c.mutex.RLock() |
| defer c.mutex.RUnlock() |
|
|
| if modelName == "*" { |
| channels, exists := c.channelsByExposedProtocol[protocol] |
| if !exists { |
| return []*modelpkg.Config{}, nil |
| } |
| return deepCopyConfigs(channels), nil |
| } |
|
|
| byProtocol, exists := c.channelsByModelAndProtocol[modelName] |
| if !exists { |
| return []*modelpkg.Config{}, nil |
| } |
|
|
| channels, exists := byProtocol[protocol] |
| if !exists { |
| return []*modelpkg.Config{}, nil |
| } |
| return deepCopyConfigs(channels), nil |
| } |
|
|
| func normalizeProtocol(protocol string) string { |
| return strings.ToLower(strings.TrimSpace(protocol)) |
| } |
|
|
| |
| |
| func (c *ChannelCache) GetConfig(ctx context.Context, channelID int64) (*modelpkg.Config, error) { |
| return c.store.GetConfig(ctx, channelID) |
| } |
|
|
| |
| |
| |
| func (c *ChannelCache) refreshIfNeeded(ctx context.Context) error { |
| c.mutex.RLock() |
| needsRefresh := time.Since(c.lastUpdate) > c.ttl |
| c.mutex.RUnlock() |
|
|
| if !needsRefresh { |
| return nil |
| } |
|
|
| |
| c.refreshMutex.Lock() |
| defer c.refreshMutex.Unlock() |
|
|
| |
| c.mutex.RLock() |
| stale := time.Since(c.lastUpdate) > c.ttl |
| c.mutex.RUnlock() |
| if !stale { |
| return nil |
| } |
|
|
| return c.refreshCache(ctx) |
| } |
|
|
| |
| |
| |
| |
| func (c *ChannelCache) refreshCache(ctx context.Context) error { |
| start := time.Now() |
|
|
| allChannels, err := c.store.GetEnabledChannelsByModel(ctx, "*") |
| if err != nil { |
| return err |
| } |
|
|
| |
| byModel := make(map[string][]*modelpkg.Config) |
| byModelAndProtocol := make(map[string]map[string][]*modelpkg.Config) |
| byType := make(map[string][]*modelpkg.Config) |
| byExposedProtocol := make(map[string][]*modelpkg.Config) |
|
|
| for _, channel := range allChannels { |
| channelType := channel.GetChannelType() |
| byType[channelType] = append(byType[channelType], channel) |
| protocols := channel.SupportedProtocols() |
| for _, protocol := range protocols { |
| byExposedProtocol[protocol] = append(byExposedProtocol[protocol], channel) |
| } |
|
|
| |
| for _, model := range channel.GetModels() { |
| byModel[model] = append(byModel[model], channel) |
| if _, exists := byModelAndProtocol[model]; !exists { |
| byModelAndProtocol[model] = make(map[string][]*modelpkg.Config) |
| } |
| for _, protocol := range protocols { |
| byModelAndProtocol[model][protocol] = append(byModelAndProtocol[model][protocol], channel) |
| } |
| } |
| } |
|
|
| |
| c.mutex.Lock() |
| c.allChannels = allChannels |
| c.channelsByModel = byModel |
| c.channelsByModelAndProtocol = byModelAndProtocol |
| c.channelsByType = byType |
| c.channelsByExposedProtocol = byExposedProtocol |
| c.lastUpdate = time.Now() |
| c.mutex.Unlock() |
|
|
| refreshDuration := time.Since(start) |
| if refreshDuration > 5*time.Second { |
| log.Printf("[WARN] 缓存刷新过慢: %v, 渠道数: %d, 模型数: %d, 类型数: %d", |
| refreshDuration, len(allChannels), len(byModel), len(byType)) |
| } |
|
|
| return nil |
| } |
|
|
| |
| func (c *ChannelCache) InvalidateCache() { |
| c.mutex.Lock() |
| defer c.mutex.Unlock() |
|
|
| c.lastUpdate = time.Time{} |
| } |
|
|
| |
| func (c *ChannelCache) GetAPIKeys(ctx context.Context, channelID int64) ([]*modelpkg.APIKey, error) { |
| |
| c.mutex.RLock() |
| if keys, exists := c.apiKeysByChannelID[channelID]; exists { |
| c.mutex.RUnlock() |
| |
| result := make([]*modelpkg.APIKey, len(keys)) |
| for i, key := range keys { |
| keyCopy := *key |
| result[i] = &keyCopy |
| } |
| return result, nil |
| } |
| c.mutex.RUnlock() |
|
|
| |
| keys, err := c.store.GetAPIKeys(ctx, channelID) |
| if err != nil { |
| return nil, err |
| } |
|
|
| |
| c.mutex.Lock() |
| c.apiKeysByChannelID[channelID] = keys |
| c.mutex.Unlock() |
|
|
| result := make([]*modelpkg.APIKey, len(keys)) |
| for i, key := range keys { |
| keyCopy := *key |
| result[i] = &keyCopy |
| } |
| return result, nil |
| } |
|
|
| |
| func (c *ChannelCache) GetAllChannelCooldowns(ctx context.Context) (map[int64]time.Time, error) { |
| |
| c.mutex.RLock() |
| if time.Since(c.cooldownCache.channelLastUpdate) <= c.cooldownCache.ttl { |
| |
| result := make(map[int64]time.Time, len(c.cooldownCache.channels)) |
| maps.Copy(result, c.cooldownCache.channels) |
| c.mutex.RUnlock() |
| return result, nil |
| } |
| c.mutex.RUnlock() |
|
|
| |
| cooldowns, err := c.store.GetAllChannelCooldowns(ctx) |
| if err != nil { |
| return nil, err |
| } |
|
|
| |
| c.mutex.Lock() |
| c.cooldownCache.channels = cooldowns |
| c.cooldownCache.channelLastUpdate = time.Now() |
| c.mutex.Unlock() |
|
|
| result := make(map[int64]time.Time, len(cooldowns)) |
| maps.Copy(result, cooldowns) |
| return result, nil |
| } |
|
|
| |
| func (c *ChannelCache) GetAllKeyCooldowns(ctx context.Context) (map[int64]map[int]time.Time, error) { |
| |
| c.mutex.RLock() |
| if time.Since(c.cooldownCache.keyLastUpdate) <= c.cooldownCache.ttl { |
| |
| result := make(map[int64]map[int]time.Time) |
| for k, v := range c.cooldownCache.keys { |
| keyMap := make(map[int]time.Time) |
| maps.Copy(keyMap, v) |
| result[k] = keyMap |
| } |
| c.mutex.RUnlock() |
| return result, nil |
| } |
| c.mutex.RUnlock() |
|
|
| |
| cooldowns, err := c.store.GetAllKeyCooldowns(ctx) |
| if err != nil { |
| return nil, err |
| } |
|
|
| |
| c.mutex.Lock() |
| c.cooldownCache.keys = cooldowns |
| c.cooldownCache.keyLastUpdate = time.Now() |
| c.mutex.Unlock() |
|
|
| result := make(map[int64]map[int]time.Time, len(cooldowns)) |
| for k, v := range cooldowns { |
| keyMap := make(map[int]time.Time, len(v)) |
| maps.Copy(keyMap, v) |
| result[k] = keyMap |
| } |
| return result, nil |
| } |
|
|
| |
| func (c *ChannelCache) InvalidateAPIKeysCache(channelID int64) { |
| c.mutex.Lock() |
| defer c.mutex.Unlock() |
| delete(c.apiKeysByChannelID, channelID) |
| } |
|
|
| |
| func (c *ChannelCache) InvalidateAllAPIKeysCache() { |
| c.mutex.Lock() |
| defer c.mutex.Unlock() |
| c.apiKeysByChannelID = make(map[int64][]*modelpkg.APIKey) |
| } |
|
|
| |
| func (c *ChannelCache) InvalidateCooldownCache() { |
| c.mutex.Lock() |
| defer c.mutex.Unlock() |
| c.cooldownCache.channelLastUpdate = time.Time{} |
| c.cooldownCache.keyLastUpdate = time.Time{} |
| } |
|
|