Files
sub2api/backend/internal/handler/wire.go
T
feeeei d5a802f84a feat(gemini): 支持显式上下文缓存(AI Studio / Vertex / 自建网关上游)
新增 /v1beta/cachedContents 的创建、列表、查询、更新有效期与删除接口。
参与账号:官方地址的 AI Studio API Key 账号、Vertex service account 账号,
以及开启"上游支持显式缓存"的自定义地址 API Key 账号。

- 网关自发缓存 ID,绑定上游资源名、持有账号与创建它的 API Key
  (迁移 243 gemini_cached_contents);list/get 只读本地记录
- 引用缓存的 generateContent / streamGenerateContent / countTokens
  改写为上游资源名,只发往持有缓存的账号,不参与会话粘性、不换号;
  拒绝重复键与 snake_case 写法;在持有账号上重试耗尽后,
  上游 429 / 5xx 原样透传,401 / 403 映射为 502
- 计费:创建时一次性收取缓存 token 输入费与整个有效期的存储费;
  延长按库中实际推进的时长收费,纯存储费不算一次请求;缩短与删除不退费
- 存储单价随计费模型取价,来自定价覆盖文件字段
  cache_storage_cost_per_token_per_hour,未配置的模型拒绝创建;
  存储费同时计入账号统计成本,渠道按 token 重算成本时叠加且不重复计入
- 扣费前校验:创建在上游建成后按确定金额校验余额 / 订阅额度 /
  API Key 额度并登记不封顶的在途预留,不足时删除上游缓存、只收输入费;
  延长在调用上游前按增量时长校验
- 有效期上限 gateway.gemini_cached_content_max_ttl_seconds,默认 86400,
  有效期向上取整到秒;创建请求保留数字原文、兼容 snake_case 元数据字段
- 上游调用与落库同客户端连接脱钩;2xx 不可用响应不换号并删除已建缓存
- 缓存不存在:AI Studio 的 403、Vertex 生成请求的 400 与缓存资源接口的
  404 统一识别;原生转发(含流式)不重试、不换号、不改账号状态,
  向客户端返回标准 403,不暴露上游缓存资源 ID,上游原始错误登记到 ops;
  PATCH 遇上游 404 移除本地记录并返回 403,DELETE 视为成功
- 自建网关上游:账号开关 explicit_cache_upstream,缓存接口发往账号自身
  base_url;可选 explicit_cache_upstream_max_ttl_seconds 对齐上游 TTL 上限,
  显式 TTL 超限时创建跳过该账号(全部超限返回 400),未显式时按上限创建,
  PATCH 取本站与账号上限的较小值;绑定缓存的请求在网关账号上收到
  429 / 5xx 时不重试、不限流该账号;前端创建 / 编辑账号弹窗新增开关与上限输入框
- 用量与 ops 端点统计:带缓存 ID 的路径归一化为 /v1beta/cachedContents,
  缓存操作的上游端点记为 /v1beta/cachedContents
2026-10-10 23:37:47 +08:00

299 lines
12 KiB
Go

package handler
import (
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/handler/admin"
"github.com/Wei-Shaw/sub2api/internal/securityaudit"
"github.com/Wei-Shaw/sub2api/internal/service"
"github.com/google/wire"
)
// ProvideAdminHandlers creates the AdminHandlers struct
func ProvideAdminHandlers(
dashboardHandler *admin.DashboardHandler,
userHandler *admin.UserHandler,
groupHandler *admin.GroupHandler,
accountHandler *admin.AccountHandler,
announcementHandler *admin.AnnouncementHandler,
dataManagementHandler *admin.DataManagementHandler,
backupHandler *admin.BackupHandler,
oauthHandler *admin.OAuthHandler,
openaiOAuthHandler *admin.OpenAIOAuthHandler,
geminiOAuthHandler *admin.GeminiOAuthHandler,
antigravityOAuthHandler *admin.AntigravityOAuthHandler,
grokOAuthHandler *admin.GrokOAuthHandler,
cnProviderHandler *admin.CNProviderHandler,
proxyHandler *admin.ProxyHandler,
redeemHandler *admin.RedeemHandler,
promoHandler *admin.PromoHandler,
settingHandler *admin.SettingHandler,
opsHandler *admin.OpsHandler,
systemHandler *admin.SystemHandler,
subscriptionHandler *admin.SubscriptionHandler,
usageHandler *admin.UsageHandler,
userAttributeHandler *admin.UserAttributeHandler,
errorPassthroughHandler *admin.ErrorPassthroughHandler,
tlsFingerprintProfileHandler *admin.TLSFingerprintProfileHandler,
pluginHandler *admin.PluginHandler,
apiKeyHandler *admin.AdminAPIKeyHandler,
scheduledTestHandler *admin.ScheduledTestHandler,
channelHandler *admin.ChannelHandler,
channelMonitorHandler *admin.ChannelMonitorHandler,
channelMonitorTemplateHandler *admin.ChannelMonitorRequestTemplateHandler,
contentModerationHandler *admin.ContentModerationHandler,
promptAuditHandler *securityaudit.PromptAdminHandler,
paymentHandler *admin.PaymentHandler,
affiliateHandler *admin.AffiliateHandler,
complianceHandler *admin.ComplianceHandler,
auditLogHandler *admin.AuditLogHandler,
upstreamBillingProbe *service.UpstreamBillingProbeService,
ollamaCloudUsage *service.OllamaCloudUsageService,
opencodeGoUsage *service.OpenCodeGoUsageService,
claudeResetCredits *service.ClaudeResetCreditService,
) *AdminHandlers {
accountHandler.SetUpstreamBillingProbeService(upstreamBillingProbe)
accountHandler.SetOllamaCloudUsageService(ollamaCloudUsage)
accountHandler.SetOpenCodeGoUsageService(opencodeGoUsage)
accountHandler.SetClaudeResetCreditService(claudeResetCredits)
return &AdminHandlers{
Dashboard: dashboardHandler,
User: userHandler,
Group: groupHandler,
Account: accountHandler,
Announcement: announcementHandler,
DataManagement: dataManagementHandler,
Backup: backupHandler,
OAuth: oauthHandler,
OpenAIOAuth: openaiOAuthHandler,
GeminiOAuth: geminiOAuthHandler,
AntigravityOAuth: antigravityOAuthHandler,
GrokOAuth: grokOAuthHandler,
CNProvider: cnProviderHandler,
Proxy: proxyHandler,
Redeem: redeemHandler,
Promo: promoHandler,
Setting: settingHandler,
Ops: opsHandler,
System: systemHandler,
Subscription: subscriptionHandler,
Usage: usageHandler,
UserAttribute: userAttributeHandler,
ErrorPassthrough: errorPassthroughHandler,
TLSFingerprintProfile: tlsFingerprintProfileHandler,
Plugin: pluginHandler,
APIKey: apiKeyHandler,
ScheduledTest: scheduledTestHandler,
Channel: channelHandler,
ChannelMonitor: channelMonitorHandler,
ChannelMonitorTemplate: channelMonitorTemplateHandler,
ContentModeration: contentModerationHandler,
PromptAudit: promptAuditHandler,
Payment: paymentHandler,
Affiliate: affiliateHandler,
Compliance: complianceHandler,
AuditLog: auditLogHandler,
}
}
func ProvideGatewayHandler(
gatewayService *service.GatewayService,
openAIGatewayService *service.OpenAIGatewayService,
geminiCompatService *service.GeminiMessagesCompatService,
antigravityGatewayService *service.AntigravityGatewayService,
userService *service.UserService,
concurrencyService *service.ConcurrencyService,
billingCacheService *service.BillingCacheService,
usageService *service.UsageService,
apiKeyService *service.APIKeyService,
usageRecordWorkerPool *service.UsageRecordWorkerPool,
errorPassthroughService *service.ErrorPassthroughService,
contentModerationService *service.ContentModerationService,
userMsgQueueService *service.UserMessageQueueService,
cfg *config.Config,
settingService *service.SettingService,
coordinator *securityaudit.Coordinator,
geminiCachedContentService *service.GeminiCachedContentService,
) *GatewayHandler {
h := NewGatewayHandler(gatewayService, openAIGatewayService, geminiCompatService, antigravityGatewayService,
userService, concurrencyService, billingCacheService, usageService, apiKeyService, usageRecordWorkerPool,
errorPassthroughService, contentModerationService, userMsgQueueService, cfg, settingService)
h.securityAuditCoordinator = coordinator
h.geminiCachedContentService = geminiCachedContentService
return h
}
func ProvideOpenAIGatewayHandler(
gatewayService *service.OpenAIGatewayService,
pluginManager *service.PluginManager,
concurrencyService *service.ConcurrencyService,
billingCacheService *service.BillingCacheService,
apiKeyService *service.APIKeyService,
usageRecordWorkerPool *service.UsageRecordWorkerPool,
errorPassthroughService *service.ErrorPassthroughService,
contentModerationService *service.ContentModerationService,
opsService *service.OpsService,
grokQuotaService *service.GrokQuotaService,
cfg *config.Config,
coordinator *securityaudit.Coordinator,
compositeResolver *service.CompositeRouteResolver,
) *OpenAIGatewayHandler {
gatewayService.SetPluginManager(pluginManager)
h := NewOpenAIGatewayHandler(gatewayService, concurrencyService, billingCacheService, apiKeyService,
usageRecordWorkerPool, errorPassthroughService, contentModerationService, opsService, cfg)
h.compositeResolver = compositeResolver
h.securityAuditCoordinator = coordinator
h.grokMediaEligibilityProber = grokQuotaService
return h
}
func ProvideBatchImageHandler(
batchService *service.BatchImagePublicService,
download *service.BatchImageDownloadService,
cleanup *service.BatchImageCleanupService,
openAI *OpenAIGatewayHandler,
) *BatchImageHandler {
h := NewBatchImageHandler(batchService, download, cleanup)
h.openAI = openAI
return h
}
// ProvideSystemHandler creates admin.SystemHandler with UpdateService
func ProvideSystemHandler(updateService *service.UpdateService, lockService *service.SystemOperationLockService) *admin.SystemHandler {
return admin.NewSystemHandler(updateService, lockService)
}
// ProvideSettingHandler creates SettingHandler with version from BuildInfo
func ProvideSettingHandler(settingService *service.SettingService, buildInfo BuildInfo, notificationEmailService *service.NotificationEmailService) *SettingHandler {
h := NewSettingHandler(settingService, buildInfo.Version)
h.SetNotificationEmailService(notificationEmailService)
return h
}
// ProvideAdminSettingHandler creates admin.SettingHandler with notification template APIs.
func ProvideAdminSettingHandler(settingService *service.SettingService, emailService *service.EmailService, turnstileService *service.TurnstileService, aliyunCaptchaService *service.AliyunCaptchaService, opsService *service.OpsService, paymentConfigService *service.PaymentConfigService, paymentService *service.PaymentService, userAttributeService *service.UserAttributeService, notificationEmailService *service.NotificationEmailService, totpService *service.TotpService, userService *service.UserService) *admin.SettingHandler {
h := admin.NewSettingHandler(settingService, emailService, turnstileService, opsService, paymentConfigService, paymentService, userAttributeService)
h.SetNotificationEmailService(notificationEmailService)
h.SetAliyunCaptchaService(aliyunCaptchaService)
h.SetStepUpDeps(totpService, userService)
return h
}
// ProvideHandlers creates the Handlers struct
func ProvideHandlers(
authHandler *AuthHandler,
userHandler *UserHandler,
apiKeyHandler *APIKeyHandler,
usageHandler *UsageHandler,
redeemHandler *RedeemHandler,
subscriptionHandler *SubscriptionHandler,
announcementHandler *AnnouncementHandler,
channelMonitorUserHandler *ChannelMonitorUserHandler,
channelMonitorV2Handler *ChannelMonitorV2Handler,
adminHandlers *AdminHandlers,
gatewayHandler *GatewayHandler,
openaiGatewayHandler *OpenAIGatewayHandler,
settingHandler *SettingHandler,
totpHandler *TotpHandler,
passkeyHandler *PasskeyHandler,
paymentHandler *PaymentHandler,
paymentWebhookHandler *PaymentWebhookHandler,
availableChannelHandler *AvailableChannelHandler,
modelPlazaHandler *ModelPlazaHandler,
asyncImageHandler *AsyncImageHandler,
batchImageHandler *BatchImageHandler,
_ *service.IdempotencyCoordinator,
_ *service.IdempotencyCleanupService,
_ *service.OpenAIQuotaAutoResetService,
) *Handlers {
return &Handlers{
Auth: authHandler,
User: userHandler,
APIKey: apiKeyHandler,
Usage: usageHandler,
Redeem: redeemHandler,
Subscription: subscriptionHandler,
Announcement: announcementHandler,
ChannelMonitor: channelMonitorUserHandler,
ChannelMonitorV2: channelMonitorV2Handler,
Admin: adminHandlers,
Gateway: gatewayHandler,
OpenAIGateway: openaiGatewayHandler,
Setting: settingHandler,
Totp: totpHandler,
Passkey: passkeyHandler,
Payment: paymentHandler,
PaymentWebhook: paymentWebhookHandler,
AvailableChannel: availableChannelHandler,
ModelPlaza: modelPlazaHandler,
AsyncImage: asyncImageHandler,
BatchImage: batchImageHandler,
}
}
// ProviderSet is the Wire provider set for all handlers
var ProviderSet = wire.NewSet(
// Top-level handlers
NewAuthHandler,
NewUserHandler,
NewAPIKeyHandler,
NewUsageHandler,
NewRedeemHandler,
NewSubscriptionHandler,
NewAnnouncementHandler,
NewChannelMonitorUserHandler,
NewChannelMonitorV2Handler,
ProvideGatewayHandler,
ProvideOpenAIGatewayHandler,
NewTotpHandler,
NewPasskeyHandler,
ProvideSettingHandler,
NewPaymentHandler,
NewPaymentWebhookHandler,
NewAvailableChannelHandler,
NewModelPlazaHandler,
NewAsyncImageHandler,
ProvideBatchImageHandler,
// Admin handlers
admin.NewDashboardHandler,
admin.NewUserHandler,
admin.NewGroupHandlerWithConfig,
admin.ProvideAccountHandler,
admin.NewAnnouncementHandler,
admin.NewDataManagementHandler,
admin.NewBackupHandler,
admin.NewOAuthHandler,
admin.NewOpenAIOAuthHandler,
admin.NewGeminiOAuthHandler,
admin.NewAntigravityOAuthHandler,
admin.NewGrokOAuthHandler,
admin.NewCNProviderHandler,
admin.NewProxyHandler,
admin.NewRedeemHandler,
admin.NewPromoHandler,
ProvideAdminSettingHandler,
admin.NewOpsHandler,
ProvideSystemHandler,
admin.NewSubscriptionHandler,
admin.NewUsageHandler,
admin.NewUserAttributeHandler,
admin.NewErrorPassthroughHandler,
admin.NewTLSFingerprintProfileHandler,
admin.NewPluginHandler,
admin.NewAdminAPIKeyHandler,
admin.NewScheduledTestHandler,
admin.NewChannelHandler,
admin.NewChannelMonitorHandler,
admin.NewChannelMonitorRequestTemplateHandler,
admin.NewContentModerationHandler,
admin.NewPaymentHandler,
admin.NewAffiliateHandler,
admin.NewComplianceHandler,
admin.NewAuditLogHandler,
// AdminHandlers and Handlers constructors
ProvideAdminHandlers,
ProvideHandlers,
)