mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-15 23:22:59 +08:00
fix(composite): route exact account model aliases
Composite model catalogs expose exact account model_mapping aliases, but request routing only considered explicit routes or model-name detection. Unprefixed aliases were selectable but unroutable. Resolve unique exact account ownership before the detector, keep explicit routes authoritative, and fail closed when an alias is claimed across platforms.
This commit is contained in:
@@ -23,8 +23,19 @@ const (
|
||||
|
||||
CompositeRouteSourceExplicit = "route"
|
||||
CompositeRouteSourceDetector = "detector"
|
||||
CompositeRouteSourceAccount = "account_model"
|
||||
)
|
||||
|
||||
// CompositeModelOwnership identifies the concrete provider that exposes a
|
||||
// public model through an account-level exact mapping.
|
||||
type CompositeModelOwnership struct {
|
||||
TargetPlatform string
|
||||
Matched bool
|
||||
Ambiguous bool
|
||||
}
|
||||
|
||||
type CompositeModelOwnershipResolver func(context.Context, int64, string) (CompositeModelOwnership, error)
|
||||
|
||||
var (
|
||||
ErrCompositeRouteNotFound = infraerrors.NotFound("COMPOSITE_ROUTE_NOT_FOUND", "composite route not found")
|
||||
ErrCompositeRouteExists = infraerrors.Conflict("COMPOSITE_ROUTE_EXISTS", "composite route already exists")
|
||||
|
||||
@@ -8,6 +8,150 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type compositeOwnershipAccountRepo struct {
|
||||
AccountRepository
|
||||
accounts []Account
|
||||
}
|
||||
|
||||
func (r *compositeOwnershipAccountRepo) ListSchedulableByGroupID(context.Context, int64) ([]Account, error) {
|
||||
return r.accounts, nil
|
||||
}
|
||||
|
||||
// Scenario: 唯一平台的精确别名可路由
|
||||
func TestResolveCompositeModelOwnershipKeepsProviderAccountsIsolated(t *testing.T) {
|
||||
groupID := int64(7)
|
||||
repo := &compositeOwnershipAccountRepo{
|
||||
accounts: []Account{
|
||||
{
|
||||
ID: 1,
|
||||
Platform: PlatformOpenAI,
|
||||
Credentials: map[string]any{
|
||||
"model_mapping": map[string]any{"gpt-public": "gpt-5"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Platform: PlatformDeepseek,
|
||||
Credentials: map[string]any{
|
||||
"model_mapping": map[string]any{"reasoning-alias": "deepseek-v4-pro"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
svc := &GatewayService{accountRepo: repo}
|
||||
|
||||
deepSeekOwnership, err := svc.resolveCompositeModelOwnership(context.Background(), groupID, "reasoning-alias")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CompositeModelOwnership{TargetPlatform: PlatformDeepseek, Matched: true}, deepSeekOwnership)
|
||||
|
||||
openAIOwnership, err := svc.resolveCompositeModelOwnership(context.Background(), groupID, "gpt-public")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CompositeModelOwnership{TargetPlatform: PlatformOpenAI, Matched: true}, openAIOwnership)
|
||||
}
|
||||
|
||||
// Scenario: 通配符和空映射不声明所有权
|
||||
func TestResolveCompositeModelOwnershipRequiresNonEmptyExactMappings(t *testing.T) {
|
||||
groupID := int64(7)
|
||||
repo := &compositeOwnershipAccountRepo{
|
||||
accounts: []Account{
|
||||
{
|
||||
ID: 1,
|
||||
Platform: PlatformOpenAI,
|
||||
Credentials: map[string]any{
|
||||
"model_mapping": map[string]any{"*": "gpt-5", "gpt-*": "gpt-5", "empty-alias": ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Platform: PlatformGrok,
|
||||
Credentials: map[string]any{
|
||||
"model_mapping": map[string]any{"grok-public": "grok-4"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
svc := &GatewayService{accountRepo: repo}
|
||||
|
||||
for _, model := range []string{"gpt-5", "empty-alias", "unknown-alias"} {
|
||||
ownership, err := svc.resolveCompositeModelOwnership(context.Background(), groupID, model)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CompositeModelOwnership{}, ownership, "model=%s", model)
|
||||
}
|
||||
|
||||
ownership, err := svc.resolveCompositeModelOwnership(context.Background(), groupID, "grok-public")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CompositeModelOwnership{TargetPlatform: PlatformGrok, Matched: true}, ownership)
|
||||
}
|
||||
|
||||
func TestResolveCompositeModelOwnershipAllowsSamePlatformAndRejectsCrossPlatformAliases(t *testing.T) {
|
||||
groupID := int64(7)
|
||||
repo := &compositeOwnershipAccountRepo{
|
||||
accounts: []Account{
|
||||
{ID: 1, Platform: PlatformOpenAI, Credentials: map[string]any{"model_mapping": map[string]any{"shared-openai": "gpt-5", "ambiguous": "gpt-5"}}},
|
||||
{ID: 2, Platform: PlatformOpenAI, Credentials: map[string]any{"model_mapping": map[string]any{"shared-openai": "gpt-5.1"}}},
|
||||
{ID: 3, Platform: PlatformDeepseek, Credentials: map[string]any{"model_mapping": map[string]any{"ambiguous": "deepseek-v4-pro"}}},
|
||||
},
|
||||
}
|
||||
svc := &GatewayService{accountRepo: repo}
|
||||
|
||||
samePlatform, err := svc.resolveCompositeModelOwnership(context.Background(), groupID, "shared-openai")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CompositeModelOwnership{TargetPlatform: PlatformOpenAI, Matched: true}, samePlatform)
|
||||
|
||||
ambiguous, err := svc.resolveCompositeModelOwnership(context.Background(), groupID, "ambiguous")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CompositeModelOwnership{Ambiguous: true}, ambiguous)
|
||||
}
|
||||
|
||||
func TestNewGatewayServiceWiresCompositeModelOwnershipResolver(t *testing.T) {
|
||||
groupID := int64(7)
|
||||
repo := &compositeOwnershipAccountRepo{
|
||||
accounts: []Account{{
|
||||
ID: 1,
|
||||
Platform: PlatformDeepseek,
|
||||
Credentials: map[string]any{"model_mapping": map[string]any{"reasoning-alias": "deepseek-v4-pro"}},
|
||||
}},
|
||||
}
|
||||
resolver := NewCompositeRouteResolver(nil)
|
||||
svc := NewGatewayService(
|
||||
repo,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
resolver,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
require.Same(t, resolver, svc.compositeResolver)
|
||||
|
||||
decision, err := resolver.Resolve(context.Background(), groupID, "reasoning-alias", CompositeRouteEndpointResponses)
|
||||
require.NoError(t, err)
|
||||
require.True(t, decision.Matched)
|
||||
require.Equal(t, CompositeRouteSourceAccount, decision.Source)
|
||||
require.Equal(t, PlatformDeepseek, decision.TargetPlatform)
|
||||
}
|
||||
|
||||
func TestDetectModelPlatform(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
||||
@@ -8,13 +8,20 @@ import (
|
||||
)
|
||||
|
||||
type CompositeRouteResolver struct {
|
||||
repo CompositeModelRouteRepository
|
||||
repo CompositeModelRouteRepository
|
||||
modelOwnershipResolver CompositeModelOwnershipResolver
|
||||
}
|
||||
|
||||
func NewCompositeRouteResolver(repo CompositeModelRouteRepository) *CompositeRouteResolver {
|
||||
return &CompositeRouteResolver{repo: repo}
|
||||
}
|
||||
|
||||
func (r *CompositeRouteResolver) SetModelOwnershipResolver(resolver CompositeModelOwnershipResolver) {
|
||||
if r != nil {
|
||||
r.modelOwnershipResolver = resolver
|
||||
}
|
||||
}
|
||||
|
||||
func (r *CompositeRouteResolver) Resolve(ctx context.Context, groupID int64, model, endpoint string) (CompositeRouteDecision, error) {
|
||||
model = strings.TrimSpace(model)
|
||||
endpoint = normalizeCompositeRouteEndpoint(endpoint)
|
||||
@@ -51,6 +58,35 @@ func (r *CompositeRouteResolver) Resolve(ctx context.Context, groupID int64, mod
|
||||
}
|
||||
}
|
||||
|
||||
if r != nil && r.modelOwnershipResolver != nil && groupID > 0 {
|
||||
ownership, err := r.modelOwnershipResolver(ctx, groupID, model)
|
||||
if err != nil {
|
||||
// A recognizable model can still use the existing detector when the
|
||||
// account catalog is temporarily unavailable. Unknown aliases cannot.
|
||||
if _, detectable := DetectModelPlatform(model); !detectable {
|
||||
return decision, fmt.Errorf("resolve account model ownership: %w", err)
|
||||
}
|
||||
} else if ownership.Ambiguous {
|
||||
decision.Reason = "model is exposed by multiple provider platforms"
|
||||
return decision, nil
|
||||
} else if ownership.Matched {
|
||||
platform := strings.TrimSpace(ownership.TargetPlatform)
|
||||
if !isConcreteRequestPlatform(platform) {
|
||||
decision.Reason = "account model ownership has no concrete target platform"
|
||||
return decision, nil
|
||||
}
|
||||
return CompositeRouteDecision{
|
||||
Matched: true,
|
||||
Source: CompositeRouteSourceAccount,
|
||||
GroupID: groupID,
|
||||
PublicModel: model,
|
||||
TargetPlatform: platform,
|
||||
UpstreamModel: model,
|
||||
Endpoint: endpoint,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
if platform, ok := DetectModelPlatform(model); ok {
|
||||
return CompositeRouteDecision{
|
||||
Matched: true,
|
||||
|
||||
@@ -2,6 +2,7 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -69,6 +70,98 @@ func TestCompositeRouteResolverExplicitExactRouteRewritesModel(t *testing.T) {
|
||||
require.Equal(t, int64(10), decision.Route.ID)
|
||||
}
|
||||
|
||||
// Scenario: 唯一平台的精确别名可路由
|
||||
func TestCompositeRouteResolverUsesAccountModelOwnershipForUnprefixedAlias(t *testing.T) {
|
||||
resolver := NewCompositeRouteResolver(nil)
|
||||
resolver.SetModelOwnershipResolver(func(_ context.Context, groupID int64, model string) (CompositeModelOwnership, error) {
|
||||
require.Equal(t, int64(7), groupID)
|
||||
require.Equal(t, "reasoning-alias", model)
|
||||
return CompositeModelOwnership{TargetPlatform: PlatformDeepseek, Matched: true}, nil
|
||||
})
|
||||
|
||||
decision, err := resolver.Resolve(context.Background(), 7, "reasoning-alias", CompositeRouteEndpointChatCompletions)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.True(t, decision.Matched)
|
||||
require.Equal(t, CompositeRouteSourceAccount, decision.Source)
|
||||
require.Equal(t, PlatformDeepseek, decision.TargetPlatform)
|
||||
require.Equal(t, "reasoning-alias", decision.UpstreamModel)
|
||||
}
|
||||
|
||||
func TestCompositeRouteResolverAccountOwnershipOverridesBuiltInDetector(t *testing.T) {
|
||||
resolver := NewCompositeRouteResolver(nil)
|
||||
resolver.SetModelOwnershipResolver(func(context.Context, int64, string) (CompositeModelOwnership, error) {
|
||||
return CompositeModelOwnership{TargetPlatform: PlatformDeepseek, Matched: true}, nil
|
||||
})
|
||||
|
||||
decision, err := resolver.Resolve(context.Background(), 7, "gpt-5", CompositeRouteEndpointResponses)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.True(t, decision.Matched)
|
||||
require.Equal(t, CompositeRouteSourceAccount, decision.Source)
|
||||
require.Equal(t, PlatformDeepseek, decision.TargetPlatform)
|
||||
}
|
||||
|
||||
// Scenario: 显式路由保持最高优先级
|
||||
func TestCompositeRouteResolverExplicitRouteBeatsAccountOwnership(t *testing.T) {
|
||||
resolver := NewCompositeRouteResolver(compositeRouteRepoStub{
|
||||
routes: []CompositeModelRoute{{
|
||||
ID: 10,
|
||||
GroupID: 7,
|
||||
PublicModel: "reasoning-alias",
|
||||
MatchType: CompositeRouteMatchExact,
|
||||
TargetPlatform: PlatformOpenAI,
|
||||
UpstreamModel: "gpt-5",
|
||||
Endpoint: CompositeRouteEndpointAny,
|
||||
Enabled: true,
|
||||
}},
|
||||
})
|
||||
resolver.SetModelOwnershipResolver(func(context.Context, int64, string) (CompositeModelOwnership, error) {
|
||||
return CompositeModelOwnership{TargetPlatform: PlatformDeepseek, Matched: true}, nil
|
||||
})
|
||||
|
||||
decision, err := resolver.Resolve(context.Background(), 7, "reasoning-alias", CompositeRouteEndpointChatCompletions)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.True(t, decision.Matched)
|
||||
require.Equal(t, CompositeRouteSourceExplicit, decision.Source)
|
||||
require.Equal(t, PlatformOpenAI, decision.TargetPlatform)
|
||||
require.Equal(t, "gpt-5", decision.UpstreamModel)
|
||||
}
|
||||
|
||||
// Scenario: 跨平台同名别名不被猜测
|
||||
func TestCompositeRouteResolverDoesNotGuessAmbiguousAccountOwnership(t *testing.T) {
|
||||
resolver := NewCompositeRouteResolver(nil)
|
||||
resolver.SetModelOwnershipResolver(func(context.Context, int64, string) (CompositeModelOwnership, error) {
|
||||
return CompositeModelOwnership{Ambiguous: true}, nil
|
||||
})
|
||||
|
||||
decision, err := resolver.Resolve(context.Background(), 7, "shared-alias", CompositeRouteEndpointChatCompletions)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.False(t, decision.Matched)
|
||||
require.Empty(t, decision.TargetPlatform)
|
||||
require.Equal(t, "model is exposed by multiple provider platforms", decision.Reason)
|
||||
}
|
||||
|
||||
func TestCompositeRouteResolverOwnershipLookupErrorFallsBackOnlyForDetectableModels(t *testing.T) {
|
||||
lookupErr := errors.New("account catalog unavailable")
|
||||
resolver := NewCompositeRouteResolver(nil)
|
||||
resolver.SetModelOwnershipResolver(func(context.Context, int64, string) (CompositeModelOwnership, error) {
|
||||
return CompositeModelOwnership{}, lookupErr
|
||||
})
|
||||
|
||||
detected, err := resolver.Resolve(context.Background(), 7, "gpt-5", CompositeRouteEndpointResponses)
|
||||
require.NoError(t, err)
|
||||
require.True(t, detected.Matched)
|
||||
require.Equal(t, CompositeRouteSourceDetector, detected.Source)
|
||||
require.Equal(t, PlatformOpenAI, detected.TargetPlatform)
|
||||
|
||||
unknown, err := resolver.Resolve(context.Background(), 7, "company-model", CompositeRouteEndpointResponses)
|
||||
require.ErrorIs(t, err, lookupErr)
|
||||
require.False(t, unknown.Matched)
|
||||
}
|
||||
|
||||
func TestCompositeRouteResolverPrefersEndpointSpecificLongestPrefix(t *testing.T) {
|
||||
resolver := NewCompositeRouteResolver(compositeRouteRepoStub{
|
||||
routes: []CompositeModelRoute{
|
||||
|
||||
@@ -564,6 +564,46 @@ func TestGetAvailableModels_UsesShortCacheAndSupportsInvalidation(t *testing.T)
|
||||
require.Equal(t, int64(2), store)
|
||||
}
|
||||
|
||||
// Scenario: 账号模型变更会失效所属平台缓存
|
||||
func TestResolveCompositeModelOwnershipUsesModelsCacheInvalidation(t *testing.T) {
|
||||
groupID := int64(9)
|
||||
repo := &modelsListAccountRepoStub{
|
||||
byGroup: map[int64][]Account{
|
||||
groupID: {{
|
||||
ID: 1,
|
||||
Platform: PlatformDeepseek,
|
||||
Credentials: map[string]any{"model_mapping": map[string]any{"company-model": "deepseek-v4-pro"}},
|
||||
}},
|
||||
},
|
||||
}
|
||||
svc := &GatewayService{
|
||||
accountRepo: repo,
|
||||
modelsListCache: gocache.New(time.Minute, time.Minute),
|
||||
modelsListCacheTTL: time.Minute,
|
||||
}
|
||||
|
||||
first, err := svc.resolveCompositeModelOwnership(context.Background(), groupID, "company-model")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CompositeModelOwnership{TargetPlatform: PlatformDeepseek, Matched: true}, first)
|
||||
require.Equal(t, int64(1), repo.listByGroupCalls.Load())
|
||||
|
||||
repo.byGroup[groupID] = []Account{{
|
||||
ID: 2,
|
||||
Platform: PlatformOpenAI,
|
||||
Credentials: map[string]any{"model_mapping": map[string]any{"company-model": "gpt-5"}},
|
||||
}}
|
||||
cached, err := svc.resolveCompositeModelOwnership(context.Background(), groupID, "company-model")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, first, cached)
|
||||
require.Equal(t, int64(1), repo.listByGroupCalls.Load())
|
||||
|
||||
svc.InvalidateAvailableModelsCache(&groupID, PlatformDeepseek)
|
||||
refreshed, err := svc.resolveCompositeModelOwnership(context.Background(), groupID, "company-model")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, CompositeModelOwnership{TargetPlatform: PlatformOpenAI, Matched: true}, refreshed)
|
||||
require.Equal(t, int64(2), repo.listByGroupCalls.Load())
|
||||
}
|
||||
|
||||
func TestGetAvailableModels_ErrorAndGlobalListBranches(t *testing.T) {
|
||||
resetGatewayHotpathStatsForTest()
|
||||
|
||||
|
||||
@@ -71,8 +71,9 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
cacheTTLTarget5m = "5m"
|
||||
cacheTTLTarget1h = "1h"
|
||||
cacheTTLTarget5m = "5m"
|
||||
cacheTTLTarget1h = "1h"
|
||||
compositeModelOwnershipCachePrefix = "composite-owner|"
|
||||
)
|
||||
|
||||
// ForceCacheBillingContextKey 强制缓存计费上下文键
|
||||
@@ -528,6 +529,10 @@ func modelsListCacheKey(groupID *int64, platform string) string {
|
||||
return fmt.Sprintf("%d|%s", derefGroupID(groupID), strings.TrimSpace(platform))
|
||||
}
|
||||
|
||||
func compositeModelOwnershipCacheKey(groupID int64, model string) string {
|
||||
return fmt.Sprintf("%s%d|%s", compositeModelOwnershipCachePrefix, groupID, strings.TrimSpace(model))
|
||||
}
|
||||
|
||||
func prefetchedStickyGroupIDFromContext(ctx context.Context) (int64, bool) {
|
||||
return PrefetchedStickyGroupIDFromContext(ctx)
|
||||
}
|
||||
@@ -853,6 +858,9 @@ func NewGatewayService(
|
||||
balanceNotifyService: balanceNotifyService,
|
||||
userPlatformQuotaRepo: userPlatformQuotaRepo,
|
||||
}
|
||||
if compositeResolver != nil {
|
||||
compositeResolver.SetModelOwnershipResolver(svc.resolveCompositeModelOwnership)
|
||||
}
|
||||
svc.userGroupRateResolver = newUserGroupRateResolver(
|
||||
userGroupRateRepo,
|
||||
svc.userGroupRateCache,
|
||||
@@ -1442,6 +1450,59 @@ func (s *GatewayService) GetAvailableModels(ctx context.Context, groupID *int64,
|
||||
return cloneStringSlice(models)
|
||||
}
|
||||
|
||||
func (s *GatewayService) resolveCompositeModelOwnership(ctx context.Context, groupID int64, model string) (CompositeModelOwnership, error) {
|
||||
model = strings.TrimSpace(model)
|
||||
if s == nil || s.accountRepo == nil || groupID <= 0 || model == "" {
|
||||
return CompositeModelOwnership{}, nil
|
||||
}
|
||||
|
||||
cacheKey := compositeModelOwnershipCacheKey(groupID, model)
|
||||
if s.modelsListCache != nil {
|
||||
if cached, found := s.modelsListCache.Get(cacheKey); found {
|
||||
if ownership, ok := cached.(CompositeModelOwnership); ok {
|
||||
return ownership, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
accounts, err := s.accountRepo.ListSchedulableByGroupID(ctx, groupID)
|
||||
if err != nil {
|
||||
return CompositeModelOwnership{}, err
|
||||
}
|
||||
|
||||
platforms := make(map[string]struct{})
|
||||
for _, account := range accounts {
|
||||
platform := strings.TrimSpace(account.Platform)
|
||||
if !isConcreteRequestPlatform(platform) || !explicitModelMappingClaims(account, model) {
|
||||
continue
|
||||
}
|
||||
platforms[platform] = struct{}{}
|
||||
}
|
||||
|
||||
ownership := CompositeModelOwnership{}
|
||||
if len(platforms) == 1 {
|
||||
for platform := range platforms {
|
||||
ownership.TargetPlatform = platform
|
||||
}
|
||||
ownership.Matched = true
|
||||
} else if len(platforms) > 1 {
|
||||
ownership.Ambiguous = true
|
||||
}
|
||||
|
||||
if s.modelsListCache != nil {
|
||||
s.modelsListCache.Set(cacheKey, ownership, s.modelsListCacheTTL)
|
||||
}
|
||||
return ownership, nil
|
||||
}
|
||||
|
||||
func explicitModelMappingClaims(account Account, model string) bool {
|
||||
if account.Credentials == nil || model == "" {
|
||||
return false
|
||||
}
|
||||
mapped, ok := stringMappingFromRaw(account.Credentials["model_mapping"])[model]
|
||||
return ok && strings.TrimSpace(mapped) != ""
|
||||
}
|
||||
|
||||
// GetSchedulablePlatforms returns the concrete platforms that currently have
|
||||
// schedulable accounts in the target group.
|
||||
func (s *GatewayService) GetSchedulablePlatforms(ctx context.Context, groupID *int64) map[string]struct{} {
|
||||
@@ -1474,6 +1535,7 @@ func (s *GatewayService) InvalidateAvailableModelsCache(groupID *int64, platform
|
||||
if s == nil || s.modelsListCache == nil {
|
||||
return
|
||||
}
|
||||
s.invalidateCompositeModelOwnershipCache(groupID)
|
||||
|
||||
normalizedPlatform := strings.TrimSpace(platform)
|
||||
// 完整匹配时精准失效;否则按维度批量失效。
|
||||
@@ -1502,6 +1564,26 @@ func (s *GatewayService) InvalidateAvailableModelsCache(groupID *int64, platform
|
||||
}
|
||||
}
|
||||
|
||||
func (s *GatewayService) invalidateCompositeModelOwnershipCache(groupID *int64) {
|
||||
for key := range s.modelsListCache.Items() {
|
||||
if !strings.HasPrefix(key, compositeModelOwnershipCachePrefix) {
|
||||
continue
|
||||
}
|
||||
if groupID == nil {
|
||||
s.modelsListCache.Delete(key)
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(strings.TrimPrefix(key, compositeModelOwnershipCachePrefix), "|", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
cachedGroupID, err := strconv.ParseInt(parts[0], 10, 64)
|
||||
if err == nil && cachedGroupID == *groupID {
|
||||
s.modelsListCache.Delete(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const debugGatewayBodyDefaultFilename = "gateway_debug.log"
|
||||
|
||||
// initDebugGatewayBodyFile 初始化网关调试日志文件。
|
||||
|
||||
Reference in New Issue
Block a user