mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 12:57:57 +08:00
refactor(platform-seam): gatewayplatform 接缝——Provider/Registry + 两处 Forward 分发替换
经双视角评审裁决的收窄版 Phase-3 接缝(零行为变更):
- internal/gatewayplatform:Provider{Platform,Forward} 两方法 v1 接口 +
ForwardRequest{Parsed,Body,IsStickySession,SessionGroupID,SessionKey} +
构造期注册 Registry(重复 panic,运行期并发只读)
- 3 个单语句直返 adapter(anthropic/antigravity/gemini,错误零包裹——
BetaBlockedError/PromptTooLong 的 errors.As 链由 T4 特征化守护)
- Messages 端点两处平台分发改 registry 查找;Type!=APIKey 条件保留调用点
- OpenAI(独立 handler)、v1beta 端点、内核 internal/plugin 零触碰
门禁:T1-T5 特征化全绿、安全网 45 包、bench allocs 全部持平(266/329/93/134)。
This commit is contained in:
@@ -251,11 +251,12 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
||||
runtime := plugin.ProvideModuleRuntime(host, pluginConfig)
|
||||
moduleHandler := admin.NewModuleHandler(runtime)
|
||||
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, adminAnnouncementHandler, dataManagementHandler, backupHandler, oAuthHandler, openAIOAuthHandler, geminiOAuthHandler, antigravityOAuthHandler, proxyHandler, adminRedeemHandler, promoHandler, settingHandler, opsHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler, userAttributeHandler, errorPassthroughHandler, tlsFingerprintProfileHandler, adminAPIKeyHandler, scheduledTestHandler, channelHandler, channelMonitorHandler, channelMonitorRequestTemplateHandler, contentModerationHandler, paymentHandler, affiliateHandler, complianceHandler, moduleHandler)
|
||||
gatewayplatformRegistry := handler.ProvideGatewayPlatformRegistry(gatewayService, antigravityGatewayService)
|
||||
usageRecordWorkerPool := service.NewUsageRecordWorkerPool(configConfig)
|
||||
chain := handler.ProvideGatewayHookChain(contentModerationService)
|
||||
userMsgQueueCache := repository.NewUserMsgQueueCache(redisClient)
|
||||
userMessageQueueService := service.ProvideUserMessageQueueService(userMsgQueueCache, rpmCache, configConfig)
|
||||
gatewayHandler := handler.NewGatewayHandler(gatewayService, geminiMessagesCompatService, antigravityGatewayService, userService, concurrencyService, billingCacheService, usageService, apiKeyService, usageRecordWorkerPool, errorPassthroughService, chain, userMessageQueueService, configConfig, settingService)
|
||||
gatewayHandler := handler.NewGatewayHandler(gatewayService, geminiMessagesCompatService, antigravityGatewayService, gatewayplatformRegistry, userService, concurrencyService, billingCacheService, usageService, apiKeyService, usageRecordWorkerPool, errorPassthroughService, chain, userMessageQueueService, configConfig, settingService)
|
||||
openAIGatewayHandler := handler.NewOpenAIGatewayHandler(openAIGatewayService, concurrencyService, billingCacheService, apiKeyService, usageRecordWorkerPool, errorPassthroughService, contentModerationService, chain, configConfig)
|
||||
handlerSettingHandler := handler.ProvideSettingHandler(settingService, buildInfo, notificationEmailService)
|
||||
totpHandler := handler.NewTotpHandler(totpService)
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package gatewayplatform
|
||||
|
||||
// 本文件的三个 adapter 仅包装现有 service 的 Forward 入口(实现零移动)。
|
||||
//
|
||||
// 错误透传铁律(SEAM-DESIGN 裁决 / T4 特征化锁定):底层返回的 error 一律
|
||||
// 原样 return,禁止任何包裹——handler 对 *service.BetaBlockedError /
|
||||
// *service.PromptTooLongError / *service.UpstreamFailoverError 的 errors.As
|
||||
// 断言链依赖错误原值。
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// geminiForwardAction 是 /v1/messages gemini 分支传给 ForwardGemini 的 action
|
||||
// 常量(SEAM-DESIGN 裁决:硬编码 "generateContent" + 测试锁定;该参数仅决定
|
||||
// 客户端响应形态合法性,上游恒为 streamGenerateContent)。
|
||||
const geminiForwardAction = "generateContent"
|
||||
|
||||
// AnthropicProvider 包装 GatewayService.Forward(Claude 协议主路径;
|
||||
// :794 条件不命中侧,含 antigravity 平台的 APIKey 账号)。
|
||||
type AnthropicProvider struct {
|
||||
gateway *service.GatewayService
|
||||
}
|
||||
|
||||
// NewAnthropicProvider 构造 anthropic 平台 Provider。
|
||||
func NewAnthropicProvider(gateway *service.GatewayService) *AnthropicProvider {
|
||||
return &AnthropicProvider{gateway: gateway}
|
||||
}
|
||||
|
||||
// Platform 返回 service.PlatformAnthropic。
|
||||
func (p *AnthropicProvider) Platform() string { return service.PlatformAnthropic }
|
||||
|
||||
// Forward 转发到 GatewayService.Forward;error 原样透传。
|
||||
func (p *AnthropicProvider) Forward(ctx context.Context, c *gin.Context, account *service.Account, req *ForwardRequest) (*service.ForwardResult, error) {
|
||||
return p.gateway.Forward(ctx, c, account, req.Parsed)
|
||||
}
|
||||
|
||||
// AntigravityProvider 包装 AntigravityGatewayService.Forward(Claude 协议 →
|
||||
// antigravity 上游;:794 条件命中侧:Platform == Antigravity && Type != APIKey)。
|
||||
type AntigravityProvider struct {
|
||||
antigravity *service.AntigravityGatewayService
|
||||
}
|
||||
|
||||
// NewAntigravityProvider 构造 antigravity 平台 Provider。
|
||||
func NewAntigravityProvider(antigravity *service.AntigravityGatewayService) *AntigravityProvider {
|
||||
return &AntigravityProvider{antigravity: antigravity}
|
||||
}
|
||||
|
||||
// Platform 返回 service.PlatformAntigravity。
|
||||
func (p *AntigravityProvider) Platform() string { return service.PlatformAntigravity }
|
||||
|
||||
// Forward 转发到 AntigravityGatewayService.Forward;error 原样透传。
|
||||
func (p *AntigravityProvider) Forward(ctx context.Context, c *gin.Context, account *service.Account, req *ForwardRequest) (*service.ForwardResult, error) {
|
||||
return p.antigravity.Forward(ctx, c, account, req.Body, req.IsStickySession)
|
||||
}
|
||||
|
||||
// GeminiProvider 包装 AntigravityGatewayService.ForwardGemini(gemini 平台
|
||||
// 分组下的 antigravity 账号,:444 命中侧;gemini 平台的非 antigravity 账号
|
||||
// 走 GeminiMessagesCompatService,映射逻辑保留在调用点)。
|
||||
type GeminiProvider struct {
|
||||
antigravity *service.AntigravityGatewayService
|
||||
}
|
||||
|
||||
// NewGeminiProvider 构造 gemini 平台 Provider。
|
||||
func NewGeminiProvider(antigravity *service.AntigravityGatewayService) *GeminiProvider {
|
||||
return &GeminiProvider{antigravity: antigravity}
|
||||
}
|
||||
|
||||
// Platform 返回 service.PlatformGemini。
|
||||
func (p *GeminiProvider) Platform() string { return service.PlatformGemini }
|
||||
|
||||
// Forward 转发到 AntigravityGatewayService.ForwardGemini;error 原样透传。
|
||||
// model/stream 取自 req.Parsed(与原调用点 reqModel/reqStream 同源),
|
||||
// 粘性会话维度经 WithForwardGeminiSession 透传。
|
||||
func (p *GeminiProvider) Forward(ctx context.Context, c *gin.Context, account *service.Account, req *ForwardRequest) (*service.ForwardResult, error) {
|
||||
return p.antigravity.ForwardGemini(
|
||||
ctx,
|
||||
c,
|
||||
account,
|
||||
req.Parsed.Model,
|
||||
geminiForwardAction,
|
||||
req.Parsed.Stream,
|
||||
req.Body,
|
||||
req.IsStickySession,
|
||||
service.WithForwardGeminiSession(req.SessionGroupID, req.SessionKey),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Package gatewayplatform 定义 /v1/messages 平台 Forward 分发的 Provider 接缝
|
||||
// (Phase-3 SEAM-DESIGN v2 裁决记录)。
|
||||
//
|
||||
// 设计要点:
|
||||
// - Provider v1 接口仅 Platform/Forward 两方法(DefaultModels / FallbackModel /
|
||||
// InvalidateToken 等经评审裁决不进接口);
|
||||
// - adapter 实现零移动:仅包装现有 service 的 Forward 入口;底层 error 一律
|
||||
// 原样透传(handler 对 BetaBlockedError / PromptTooLongError 等的
|
||||
// errors.As 断言链依赖错误原值,禁止任何包裹);
|
||||
// - Registry 构造期注册、运行期并发只读;平台 → Provider 的映射逻辑
|
||||
// (如 :794 的 Type != APIKey 条件)保留在调用点,registry 只做查找;
|
||||
// - OpenAI 走独立 OpenAIGatewayHandler 与独立计费管线,不进本注册表;
|
||||
// 禁止统一跨平台返回类型(OpenAIForwardResult 载荷计费字段会丢失)。
|
||||
package gatewayplatform
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ForwardRequest 是平台 Forward 的统一入参面。字段集为两处分发点现有入参的
|
||||
// 并集 {Parsed, Body, IsStickySession, SessionGroupID, SessionKey},不为未接入
|
||||
// 平台虚构维度;各调用点保留自己的入参表达式,按各 Provider 的消费面填充。
|
||||
type ForwardRequest struct {
|
||||
// Parsed 为已解析的网关请求(anthropic / gemini Provider 消费;
|
||||
// gemini Provider 读取 Parsed.Model 与 Parsed.Stream)。
|
||||
Parsed *service.ParsedRequest
|
||||
// Body 为转发请求体原文(antigravity / gemini Provider 消费)。
|
||||
Body []byte
|
||||
// IsStickySession 表示请求已命中粘性会话绑定(antigravity / gemini
|
||||
// Provider 消费)。
|
||||
IsStickySession bool
|
||||
// SessionGroupID 与 SessionKey 为粘性会话维度(仅 gemini Provider 消费,
|
||||
// 经 WithForwardGeminiSession 透传到模型限流切换时的粘性绑定清除)。
|
||||
SessionGroupID int64
|
||||
SessionKey string
|
||||
}
|
||||
|
||||
// Provider 是单一平台的 Forward 入口。
|
||||
//
|
||||
// 契约:
|
||||
// - Platform 返回 service.Platform* 现有常量,作为 Registry 的查找键;
|
||||
// - Forward 将请求转发到对应平台 service,error 必须原样透传(禁止包裹)。
|
||||
type Provider interface {
|
||||
// Platform 返回该 Provider 服务的平台标识(service.Platform* 常量)。
|
||||
Platform() string
|
||||
// Forward 执行平台转发。
|
||||
Forward(ctx context.Context, c *gin.Context, account *service.Account, req *ForwardRequest) (*service.ForwardResult, error)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package gatewayplatform
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Registry 是 platform → Provider 的只读查找表:构造期一次性注册,
|
||||
// 构造完成后并发只读(无锁)。
|
||||
type Registry struct {
|
||||
providers map[string]Provider
|
||||
}
|
||||
|
||||
// NewRegistry 构造注册表。providers 中出现 nil Provider、空 Platform() 或
|
||||
// 重复平台时 panic——装配错误必须在启动期(Wire 装配)暴露,而非请求期。
|
||||
func NewRegistry(providers ...Provider) *Registry {
|
||||
m := make(map[string]Provider, len(providers))
|
||||
for _, p := range providers {
|
||||
if p == nil {
|
||||
panic("gatewayplatform: nil provider")
|
||||
}
|
||||
platform := p.Platform()
|
||||
if platform == "" {
|
||||
panic("gatewayplatform: provider with empty platform")
|
||||
}
|
||||
if _, dup := m[platform]; dup {
|
||||
panic(fmt.Sprintf("gatewayplatform: duplicate provider for platform %q", platform))
|
||||
}
|
||||
m[platform] = p
|
||||
}
|
||||
return &Registry{providers: m}
|
||||
}
|
||||
|
||||
// Get 返回平台对应的 Provider;未注册(或 nil Registry)返回 nil。
|
||||
// 调用点须保证所查平台已在装配期注册(Wire 注册全集,miss 属编程错误)。
|
||||
func (r *Registry) Get(platform string) Provider {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
return r.providers[platform]
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package gatewayplatform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// stubProvider 是注册表测试用的最小 Provider 实现。
|
||||
type stubProvider struct {
|
||||
platform string
|
||||
}
|
||||
|
||||
func (s *stubProvider) Platform() string { return s.platform }
|
||||
|
||||
func (s *stubProvider) Forward(_ context.Context, _ *gin.Context, _ *service.Account, _ *ForwardRequest) (*service.ForwardResult, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestRegistry_GetReturnsRegisteredProvider(t *testing.T) {
|
||||
anthropic := &stubProvider{platform: service.PlatformAnthropic}
|
||||
antigravity := &stubProvider{platform: service.PlatformAntigravity}
|
||||
gemini := &stubProvider{platform: service.PlatformGemini}
|
||||
|
||||
registry := NewRegistry(anthropic, antigravity, gemini)
|
||||
|
||||
require.Same(t, Provider(anthropic), registry.Get(service.PlatformAnthropic))
|
||||
require.Same(t, Provider(antigravity), registry.Get(service.PlatformAntigravity))
|
||||
require.Same(t, Provider(gemini), registry.Get(service.PlatformGemini))
|
||||
}
|
||||
|
||||
func TestRegistry_GetUnregisteredPlatformReturnsNil(t *testing.T) {
|
||||
registry := NewRegistry(&stubProvider{platform: service.PlatformAnthropic})
|
||||
|
||||
require.Nil(t, registry.Get(service.PlatformOpenAI), "未注册平台必须返回 nil")
|
||||
require.Nil(t, registry.Get(""), "空平台必须返回 nil")
|
||||
|
||||
var nilRegistry *Registry
|
||||
require.Nil(t, nilRegistry.Get(service.PlatformAnthropic), "nil Registry 必须返回 nil 而非 panic")
|
||||
}
|
||||
|
||||
func TestNewRegistry_DuplicatePlatformPanics(t *testing.T) {
|
||||
require.PanicsWithValue(t,
|
||||
`gatewayplatform: duplicate provider for platform "anthropic"`,
|
||||
func() {
|
||||
NewRegistry(
|
||||
&stubProvider{platform: service.PlatformAnthropic},
|
||||
&stubProvider{platform: service.PlatformAnthropic},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewRegistry_NilProviderPanics(t *testing.T) {
|
||||
require.PanicsWithValue(t, "gatewayplatform: nil provider", func() {
|
||||
NewRegistry(&stubProvider{platform: service.PlatformAnthropic}, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewRegistry_EmptyPlatformPanics(t *testing.T) {
|
||||
require.PanicsWithValue(t, "gatewayplatform: provider with empty platform", func() {
|
||||
NewRegistry(&stubProvider{platform: ""})
|
||||
})
|
||||
}
|
||||
|
||||
// TestRegistry_ConcurrentGet 验证构造完成后的注册表可被并发只读访问
|
||||
// (配合 -race 检测数据竞争)。
|
||||
func TestRegistry_ConcurrentGet(t *testing.T) {
|
||||
registry := NewRegistry(
|
||||
&stubProvider{platform: service.PlatformAnthropic},
|
||||
&stubProvider{platform: service.PlatformAntigravity},
|
||||
&stubProvider{platform: service.PlatformGemini},
|
||||
)
|
||||
|
||||
platforms := []string{
|
||||
service.PlatformAnthropic,
|
||||
service.PlatformAntigravity,
|
||||
service.PlatformGemini,
|
||||
service.PlatformOpenAI, // miss 路径同样并发安全
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 32; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 200; j++ {
|
||||
platform := platforms[(i+j)%len(platforms)]
|
||||
got := registry.Get(platform)
|
||||
if platform == service.PlatformOpenAI {
|
||||
require.Nil(t, got)
|
||||
} else {
|
||||
require.NotNil(t, got)
|
||||
require.Equal(t, platform, got.Platform())
|
||||
}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// TestAdapters_PlatformIdentity 锁定三个 adapter 的注册键 = service.Platform* 常量
|
||||
// (Registry 查找键与 :444/:794 调用点的查找表达式一致)。
|
||||
func TestAdapters_PlatformIdentity(t *testing.T) {
|
||||
require.Equal(t, service.PlatformAnthropic, NewAnthropicProvider(nil).Platform())
|
||||
require.Equal(t, service.PlatformAntigravity, NewAntigravityProvider(nil).Platform())
|
||||
require.Equal(t, service.PlatformGemini, NewGeminiProvider(nil).Platform())
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
"github.com/Wei-Shaw/sub2api/internal/domain"
|
||||
"github.com/Wei-Shaw/sub2api/internal/gatewayhook"
|
||||
"github.com/Wei-Shaw/sub2api/internal/gatewayplatform"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/antigravity"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/claude"
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
|
||||
@@ -42,6 +43,7 @@ type GatewayHandler struct {
|
||||
gatewayService *service.GatewayService
|
||||
geminiCompatService *service.GeminiMessagesCompatService
|
||||
antigravityGatewayService *service.AntigravityGatewayService
|
||||
platformRegistry *gatewayplatform.Registry
|
||||
userService *service.UserService
|
||||
billingCacheService *service.BillingCacheService
|
||||
usageService *service.UsageService
|
||||
@@ -62,6 +64,7 @@ func NewGatewayHandler(
|
||||
gatewayService *service.GatewayService,
|
||||
geminiCompatService *service.GeminiMessagesCompatService,
|
||||
antigravityGatewayService *service.AntigravityGatewayService,
|
||||
platformRegistry *gatewayplatform.Registry,
|
||||
userService *service.UserService,
|
||||
concurrencyService *service.ConcurrencyService,
|
||||
billingCacheService *service.BillingCacheService,
|
||||
@@ -97,6 +100,7 @@ func NewGatewayHandler(
|
||||
gatewayService: gatewayService,
|
||||
geminiCompatService: geminiCompatService,
|
||||
antigravityGatewayService: antigravityGatewayService,
|
||||
platformRegistry: platformRegistry,
|
||||
userService: userService,
|
||||
billingCacheService: billingCacheService,
|
||||
usageService: usageService,
|
||||
@@ -442,16 +446,20 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
|
||||
// 记录 Forward 前已写入字节数,Forward 后若增加则说明 SSE 内容已发,禁止 failover
|
||||
writerSizeBeforeForward := c.Writer.Size()
|
||||
if account.Platform == service.PlatformAntigravity {
|
||||
result, err = h.antigravityGatewayService.ForwardGemini(
|
||||
// gemini provider → AntigravityGatewayService.ForwardGemini:
|
||||
// model/stream 经 Parsed 透传(与原 reqModel/reqStream 同源),
|
||||
// action 由 adapter 硬编码 "generateContent"。
|
||||
result, err = h.platformRegistry.Get(service.PlatformGemini).Forward(
|
||||
requestCtx,
|
||||
c,
|
||||
account,
|
||||
reqModel,
|
||||
"generateContent",
|
||||
reqStream,
|
||||
body,
|
||||
hasBoundSession,
|
||||
service.WithForwardGeminiSession(derefGroupID(apiKey.GroupID), sessionKey),
|
||||
&gatewayplatform.ForwardRequest{
|
||||
Parsed: parsedReq,
|
||||
Body: body,
|
||||
IsStickySession: hasBoundSession,
|
||||
SessionGroupID: derefGroupID(apiKey.GroupID),
|
||||
SessionKey: sessionKey,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
result, err = h.geminiCompatService.Forward(requestCtx, c, account, body)
|
||||
@@ -791,10 +799,17 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
|
||||
}
|
||||
// 记录 Forward 前已写入字节数,Forward 后若增加则说明 SSE 内容已发,禁止 failover
|
||||
writerSizeBeforeForward := c.Writer.Size()
|
||||
// 平台 → Provider 的映射逻辑保留在调用点:antigravity 平台的
|
||||
// APIKey 账号走 anthropic provider(Claude 协议直连上游)。
|
||||
forwardReq := &gatewayplatform.ForwardRequest{
|
||||
Parsed: attemptParsedReq,
|
||||
Body: attemptBody,
|
||||
IsStickySession: hasBoundSession,
|
||||
}
|
||||
if account.Platform == service.PlatformAntigravity && account.Type != service.AccountTypeAPIKey {
|
||||
result, err = h.antigravityGatewayService.Forward(requestCtx, c, account, attemptBody, hasBoundSession)
|
||||
result, err = h.platformRegistry.Get(service.PlatformAntigravity).Forward(requestCtx, c, account, forwardReq)
|
||||
} else {
|
||||
result, err = h.gatewayService.Forward(requestCtx, c, account, attemptParsedReq)
|
||||
result, err = h.platformRegistry.Get(service.PlatformAnthropic).Forward(requestCtx, c, account, forwardReq)
|
||||
}
|
||||
|
||||
// 兜底释放串行锁(正常情况已通过回调提前释放)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/Wei-Shaw/sub2api/internal/gatewayplatform"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
)
|
||||
|
||||
// ProvideGatewayPlatformRegistry 装配 /v1/messages 平台 Forward 分发注册表
|
||||
// (Phase-3 SEAM-DESIGN 裁决:anthropic / antigravity / gemini 三 adapter;
|
||||
// OpenAI 走独立 OpenAIGatewayHandler 与独立计费管线,不进本注册表)。
|
||||
func ProvideGatewayPlatformRegistry(
|
||||
gatewayService *service.GatewayService,
|
||||
antigravityGatewayService *service.AntigravityGatewayService,
|
||||
) *gatewayplatform.Registry {
|
||||
return gatewayplatform.NewRegistry(
|
||||
gatewayplatform.NewAnthropicProvider(gatewayService),
|
||||
gatewayplatform.NewAntigravityProvider(antigravityGatewayService),
|
||||
gatewayplatform.NewGeminiProvider(antigravityGatewayService),
|
||||
)
|
||||
}
|
||||
@@ -282,6 +282,7 @@ func p3CharNewHandler(t *testing.T, group *service.Group, accounts []*service.Ac
|
||||
gwSvc,
|
||||
nil, // geminiCompatService(:444 非 antigravity 分支误入会 panic → 用例显式失败)
|
||||
agSvc,
|
||||
ProvideGatewayPlatformRegistry(gwSvc, agSvc), // 与生产 Wire 装配同构的平台分发注册表
|
||||
nil, // userService
|
||||
concurrencySvc,
|
||||
billingCacheSvc,
|
||||
|
||||
@@ -346,6 +346,7 @@ func schedInvNewHandler(t *testing.T, group *service.Group, accounts []*service.
|
||||
gwSvc,
|
||||
nil, // geminiCompatService
|
||||
nil, // antigravityGatewayService
|
||||
ProvideGatewayPlatformRegistry(gwSvc, nil), // anthropic 账号经 anthropic provider → gwSvc.Forward
|
||||
nil, // userService
|
||||
concurrencySvc,
|
||||
billingCacheSvc,
|
||||
@@ -406,7 +407,7 @@ func schedInvMessagesBody() []byte {
|
||||
|
||||
func TestSchedulingInvariant_FailoverSwitchLimit_DefaultValues(t *testing.T) {
|
||||
t.Run("anthropic与gemini默认上限", func(t *testing.T) {
|
||||
h := NewGatewayHandler(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
h := NewGatewayHandler(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
require.Equal(t, 10, h.maxAccountSwitches, "anthropic 默认换号上限必须为 10")
|
||||
require.Equal(t, 3, h.maxAccountSwitchesGemini, "gemini 默认换号上限必须为 3")
|
||||
})
|
||||
@@ -420,7 +421,7 @@ func TestSchedulingInvariant_FailoverSwitchLimit_DefaultValues(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
cfg.Gateway.MaxAccountSwitches = 5
|
||||
cfg.Gateway.MaxAccountSwitchesGemini = 2
|
||||
h := NewGatewayHandler(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, cfg, nil)
|
||||
h := NewGatewayHandler(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, cfg, nil)
|
||||
require.Equal(t, 5, h.maxAccountSwitches)
|
||||
require.Equal(t, 2, h.maxAccountSwitchesGemini)
|
||||
})
|
||||
@@ -546,7 +547,7 @@ func TestSchedulingInvariant_FailoverSameAccountRetry_FullChain(t *testing.T) {
|
||||
// (gemini 平台 Forward 的服务层内部 500 重试带秒级退避,完整 e2e 不可在
|
||||
// 单测时间预算内执行,故此处固化 handler 循环契约层语义。)
|
||||
func TestSchedulingInvariant_FailoverGemini_SwitchLimitLoopContract(t *testing.T) {
|
||||
h := NewGatewayHandler(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
h := NewGatewayHandler(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil)
|
||||
require.Equal(t, 3, h.maxAccountSwitchesGemini)
|
||||
|
||||
mock := &mockTempUnscheduler{}
|
||||
|
||||
@@ -150,6 +150,7 @@ var ProviderSet = wire.NewSet(
|
||||
NewAnnouncementHandler,
|
||||
NewChannelMonitorUserHandler,
|
||||
ProvideGatewayHookChain,
|
||||
ProvideGatewayPlatformRegistry,
|
||||
NewGatewayHandler,
|
||||
NewOpenAIGatewayHandler,
|
||||
NewTotpHandler,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# 完成报告: [TASK-002] gatewayplatform 接缝(Provider/Registry/3 adapter + 分发替换)
|
||||
|
||||
- **完成状态**: Success
|
||||
- **关联设计**: [SEAM-DESIGN.md v2 裁决记录](../../../.claude/plugin-refactor/phases/phase-3_platform/SEAM-DESIGN.md)
|
||||
- **完成日期**: 2026-06-12
|
||||
|
||||
## 1. 任务完成简报
|
||||
|
||||
`internal/gatewayplatform` 落地:`Provider{Platform; Forward}`(v1 仅两方法)+ `ForwardRequest{Parsed, Body, IsStickySession, SessionGroupID, SessionKey}` + 构造期注册 Registry(重复/nil panic,运行期并发只读无锁)+ 3 个单语句直返 adapter(anthropic/antigravity/gemini,action="generateContent" 常量)。`:444`/`:794` 两处分发改 registry 查找,**`Type != APIKey` 条件保留在调用点**。OpenAI、v1beta 端点的 ForwardGemini、内核全部零触碰。
|
||||
|
||||
## 2. 等价性证据(要点)
|
||||
|
||||
- :444 逐字段对照:reqModel/reqStream 与 Parsed.Model/Stream 同源且无 mutation(gemini 分支不经 :794 的克隆循环);session 两字段经 ForwardRequest 重组同名 option——T2/T3 断言;
|
||||
- :794 双分支共享 ForwardRequest(attemptBody=attemptParsedReq.Body.Bytes() 关系不变);session 维度留零值(现状不传、adapter 不消费——不虚构维度);
|
||||
- gemini 平台非 antigravity 账号的 else 分支(geminiCompatService.Forward)保持原状(不在裁决 3 adapter 内);
|
||||
- 错误透传:adapter 单语句 `return p.svc.Forward(...)`,T4 两用例(BetaBlockedError/PromptTooLong 的 errors.As 链)经新路径全绿。
|
||||
|
||||
## 3. 文件变更
|
||||
|
||||
新增 gatewayplatform 包 4 文件 + handler/gateway_platform.go(Wire provider);修改 gateway_handler.go(字段+两处替换)、handler/wire.go、wire_gen.go(wire@v0.7.0 生成);两个测试文件仅夹具加参(断言零改动)。
|
||||
|
||||
## 4. 验证(主控复跑确认)
|
||||
|
||||
T1-T4+action 契约全绿、gatewayplatform 包 -race 通过、`make test-invariants` 45 包、全量 unit 零失败、build/vet/gofmt 干净、bench compare exit 0(allocs 全部持平:266/329/93/134)。
|
||||
Reference in New Issue
Block a user