mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 04:50:52 +08:00
The provider reconciliation logic did not correctly handle aliased provider configurations (e.g., using YAML anchors). When a provider config was aliased, the check for configuration equality would pass, causing the system to reuse the existing provider instance without rebuilding it, even if the underlying configuration had changed. This change introduces a check to detect if the old and new provider configurations point to the same object in memory. If they are aliased, the provider is now always rebuilt to ensure it reflects the latest configuration. The optimization to reuse an existing provider based on deep equality is now only applied to non-aliased providers.
240 lines
6.0 KiB
Go
240 lines
6.0 KiB
Go
package access
|
|
|
|
import (
|
|
"reflect"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
|
)
|
|
|
|
// ReconcileProviders builds the desired provider list by reusing existing providers when possible
|
|
// and creating or removing providers only when their configuration changed. It returns the final
|
|
// ordered provider slice along with the identifiers of providers that were added, updated, or
|
|
// removed compared to the previous configuration.
|
|
func ReconcileProviders(oldCfg, newCfg *config.Config, existing []Provider) (result []Provider, added, updated, removed []string, err error) {
|
|
if newCfg == nil {
|
|
return nil, nil, nil, nil, nil
|
|
}
|
|
|
|
existingMap := make(map[string]Provider, len(existing))
|
|
for _, provider := range existing {
|
|
if provider == nil {
|
|
continue
|
|
}
|
|
existingMap[provider.Identifier()] = provider
|
|
}
|
|
|
|
oldCfgMap := accessProviderMap(oldCfg)
|
|
newEntries := collectProviderEntries(newCfg)
|
|
|
|
result = make([]Provider, 0, len(newEntries))
|
|
finalIDs := make(map[string]struct{}, len(newEntries))
|
|
|
|
for _, providerCfg := range newEntries {
|
|
key := providerIdentifier(providerCfg)
|
|
if key == "" {
|
|
continue
|
|
}
|
|
|
|
if oldCfgProvider, ok := oldCfgMap[key]; ok {
|
|
isAliased := oldCfgProvider == providerCfg
|
|
if !isAliased && providerConfigEqual(oldCfgProvider, providerCfg) {
|
|
if existingProvider, okExisting := existingMap[key]; okExisting {
|
|
result = append(result, existingProvider)
|
|
finalIDs[key] = struct{}{}
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
provider, buildErr := buildProvider(providerCfg, newCfg)
|
|
if buildErr != nil {
|
|
return nil, nil, nil, nil, buildErr
|
|
}
|
|
if _, ok := oldCfgMap[key]; ok {
|
|
if _, existed := existingMap[key]; existed {
|
|
updated = append(updated, key)
|
|
} else {
|
|
added = append(added, key)
|
|
}
|
|
} else {
|
|
added = append(added, key)
|
|
}
|
|
result = append(result, provider)
|
|
finalIDs[key] = struct{}{}
|
|
}
|
|
|
|
if len(result) == 0 && len(newCfg.APIKeys) > 0 {
|
|
config.SyncInlineAPIKeys(newCfg, newCfg.APIKeys)
|
|
if providerCfg := newCfg.ConfigAPIKeyProvider(); providerCfg != nil {
|
|
key := providerIdentifier(providerCfg)
|
|
if key != "" {
|
|
if oldCfgProvider, ok := oldCfgMap[key]; ok {
|
|
isAliased := oldCfgProvider == providerCfg
|
|
if !isAliased && providerConfigEqual(oldCfgProvider, providerCfg) {
|
|
if existingProvider, okExisting := existingMap[key]; okExisting {
|
|
result = append(result, existingProvider)
|
|
} else {
|
|
provider, buildErr := buildProvider(providerCfg, newCfg)
|
|
if buildErr != nil {
|
|
return nil, nil, nil, nil, buildErr
|
|
}
|
|
if _, existed := existingMap[key]; existed {
|
|
updated = append(updated, key)
|
|
} else {
|
|
added = append(added, key)
|
|
}
|
|
result = append(result, provider)
|
|
}
|
|
} else {
|
|
provider, buildErr := buildProvider(providerCfg, newCfg)
|
|
if buildErr != nil {
|
|
return nil, nil, nil, nil, buildErr
|
|
}
|
|
if _, existed := existingMap[key]; existed {
|
|
updated = append(updated, key)
|
|
} else {
|
|
added = append(added, key)
|
|
}
|
|
result = append(result, provider)
|
|
}
|
|
} else {
|
|
provider, buildErr := buildProvider(providerCfg, newCfg)
|
|
if buildErr != nil {
|
|
return nil, nil, nil, nil, buildErr
|
|
}
|
|
added = append(added, key)
|
|
result = append(result, provider)
|
|
}
|
|
finalIDs[key] = struct{}{}
|
|
}
|
|
}
|
|
}
|
|
|
|
removedSet := make(map[string]struct{})
|
|
for id := range existingMap {
|
|
if _, ok := finalIDs[id]; !ok {
|
|
removedSet[id] = struct{}{}
|
|
}
|
|
}
|
|
|
|
removed = make([]string, 0, len(removedSet))
|
|
for id := range removedSet {
|
|
removed = append(removed, id)
|
|
}
|
|
|
|
sort.Strings(added)
|
|
sort.Strings(updated)
|
|
sort.Strings(removed)
|
|
|
|
return result, added, updated, removed, nil
|
|
}
|
|
|
|
func accessProviderMap(cfg *config.Config) map[string]*config.AccessProvider {
|
|
result := make(map[string]*config.AccessProvider)
|
|
if cfg == nil {
|
|
return result
|
|
}
|
|
for i := range cfg.Access.Providers {
|
|
providerCfg := &cfg.Access.Providers[i]
|
|
if providerCfg.Type == "" {
|
|
continue
|
|
}
|
|
key := providerIdentifier(providerCfg)
|
|
if key == "" {
|
|
continue
|
|
}
|
|
result[key] = providerCfg
|
|
}
|
|
if len(result) == 0 && len(cfg.APIKeys) > 0 {
|
|
if provider := cfg.ConfigAPIKeyProvider(); provider != nil {
|
|
if key := providerIdentifier(provider); key != "" {
|
|
result[key] = provider
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func collectProviderEntries(cfg *config.Config) []*config.AccessProvider {
|
|
entries := make([]*config.AccessProvider, 0, len(cfg.Access.Providers))
|
|
if cfg == nil {
|
|
return entries
|
|
}
|
|
for i := range cfg.Access.Providers {
|
|
providerCfg := &cfg.Access.Providers[i]
|
|
if providerCfg.Type == "" {
|
|
continue
|
|
}
|
|
if key := providerIdentifier(providerCfg); key != "" {
|
|
entries = append(entries, providerCfg)
|
|
}
|
|
}
|
|
return entries
|
|
}
|
|
|
|
func providerIdentifier(provider *config.AccessProvider) string {
|
|
if provider == nil {
|
|
return ""
|
|
}
|
|
if name := strings.TrimSpace(provider.Name); name != "" {
|
|
return name
|
|
}
|
|
typ := strings.TrimSpace(provider.Type)
|
|
if typ == "" {
|
|
return ""
|
|
}
|
|
if strings.EqualFold(typ, config.AccessProviderTypeConfigAPIKey) {
|
|
return config.DefaultAccessProviderName
|
|
}
|
|
return typ
|
|
}
|
|
|
|
func providerConfigEqual(a, b *config.AccessProvider) bool {
|
|
if a == nil || b == nil {
|
|
return a == nil && b == nil
|
|
}
|
|
if !strings.EqualFold(strings.TrimSpace(a.Type), strings.TrimSpace(b.Type)) {
|
|
return false
|
|
}
|
|
if strings.TrimSpace(a.SDK) != strings.TrimSpace(b.SDK) {
|
|
return false
|
|
}
|
|
if !stringSetEqual(a.APIKeys, b.APIKeys) {
|
|
return false
|
|
}
|
|
if len(a.Config) != len(b.Config) {
|
|
return false
|
|
}
|
|
if len(a.Config) > 0 && !reflect.DeepEqual(a.Config, b.Config) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
func stringSetEqual(a, b []string) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
if len(a) == 0 {
|
|
return true
|
|
}
|
|
seen := make(map[string]int, len(a))
|
|
for _, val := range a {
|
|
seen[val]++
|
|
}
|
|
for _, val := range b {
|
|
count := seen[val]
|
|
if count == 0 {
|
|
return false
|
|
}
|
|
if count == 1 {
|
|
delete(seen, val)
|
|
} else {
|
|
seen[val] = count - 1
|
|
}
|
|
}
|
|
return len(seen) == 0
|
|
}
|