feat: Complete Wire dependency injection and HTTP API routes for Antigravity
已完成: 1. ✅ 修复 SoraMediaCleanupService 未定义问题(从 provideCleanup 中删除) 2. ✅ 创建 LanguageServerService 的 Wire 提供函数 3. ✅ 更新 ProvideRouter 签名以接收 langServerService 参数 4. ✅ 更新 SetupRouter 和 registerRoutes 函数签名 5. ✅ 创建完整的 HTTP 路由处理器 (antigravity_http.go) 6. ✅ 注册 Antigravity HTTP 路由到 v1 API 分组 7. ✅ Wire_gen.go 自动生成 LanguageServerService 的注入代码 8. ✅ 项目成功编译 三层架构已就位: 下游客户端 (HTTP) ↓ sub2api HTTP 服务层 (/api/v1/cascade/*) ↓ LanguageServerService (业务逻辑层) ↓ 官方 Anthropic API (上游) POST /api/v1/cascade/start - 启动会话 POST /api/v1/cascade/message - 发送消息(SSE 流式) POST /api/v1/cascade/cancel - 取消会话 GET /api/v1/models - 获取模型列表 GET /api/v1/health - 健康检查 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
84555dcb44
commit
b8a4372328
@ -76,7 +76,6 @@ func provideCleanup(
|
|||||||
opsCleanup *service.OpsCleanupService,
|
opsCleanup *service.OpsCleanupService,
|
||||||
opsScheduledReport *service.OpsScheduledReportService,
|
opsScheduledReport *service.OpsScheduledReportService,
|
||||||
opsSystemLogSink *service.OpsSystemLogSink,
|
opsSystemLogSink *service.OpsSystemLogSink,
|
||||||
soraMediaCleanup *service.SoraMediaCleanupService,
|
|
||||||
schedulerSnapshot *service.SchedulerSnapshotService,
|
schedulerSnapshot *service.SchedulerSnapshotService,
|
||||||
tokenRefresh *service.TokenRefreshService,
|
tokenRefresh *service.TokenRefreshService,
|
||||||
accountExpiry *service.AccountExpiryService,
|
accountExpiry *service.AccountExpiryService,
|
||||||
@ -125,12 +124,6 @@ func provideCleanup(
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}},
|
}},
|
||||||
{"SoraMediaCleanupService", func() error {
|
|
||||||
if soraMediaCleanup != nil {
|
|
||||||
soraMediaCleanup.Stop()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}},
|
|
||||||
{"OpsAlertEvaluatorService", func() error {
|
{"OpsAlertEvaluatorService", func() error {
|
||||||
if opsAlertEvaluator != nil {
|
if opsAlertEvaluator != nil {
|
||||||
opsAlertEvaluator.Stop()
|
opsAlertEvaluator.Stop()
|
||||||
|
|||||||
@ -186,6 +186,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
openAITokenProvider := service.ProvideOpenAITokenProvider(accountRepository, geminiTokenCache, openAIOAuthService, oauthRefreshAPI)
|
openAITokenProvider := service.ProvideOpenAITokenProvider(accountRepository, geminiTokenCache, openAIOAuthService, oauthRefreshAPI)
|
||||||
openAIGatewayService := service.NewOpenAIGatewayService(accountRepository, usageLogRepository, usageBillingRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, httpUpstream, deferredService, openAITokenProvider, modelPricingResolver, channelService)
|
openAIGatewayService := service.NewOpenAIGatewayService(accountRepository, usageLogRepository, usageBillingRepository, userRepository, userSubscriptionRepository, userGroupRateRepository, gatewayCache, configConfig, schedulerSnapshotService, concurrencyService, billingService, rateLimitService, billingCacheService, httpUpstream, deferredService, openAITokenProvider, modelPricingResolver, channelService)
|
||||||
geminiMessagesCompatService := service.NewGeminiMessagesCompatService(accountRepository, groupRepository, gatewayCache, schedulerSnapshotService, geminiTokenProvider, rateLimitService, httpUpstream, antigravityGatewayService, configConfig)
|
geminiMessagesCompatService := service.NewGeminiMessagesCompatService(accountRepository, groupRepository, gatewayCache, schedulerSnapshotService, geminiTokenProvider, rateLimitService, httpUpstream, antigravityGatewayService, configConfig)
|
||||||
|
langServerService := service.ProvideLanguageServerService(httpUpstream)
|
||||||
opsSystemLogSink := service.ProvideOpsSystemLogSink(opsRepository)
|
opsSystemLogSink := service.ProvideOpsSystemLogSink(opsRepository)
|
||||||
opsService := service.NewOpsService(opsRepository, settingRepository, configConfig, accountRepository, userRepository, concurrencyService, gatewayService, openAIGatewayService, geminiMessagesCompatService, antigravityGatewayService, opsSystemLogSink)
|
opsService := service.NewOpsService(opsRepository, settingRepository, configConfig, accountRepository, userRepository, concurrencyService, gatewayService, openAIGatewayService, geminiMessagesCompatService, antigravityGatewayService, opsSystemLogSink)
|
||||||
settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService, opsService)
|
settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService, opsService)
|
||||||
@ -230,7 +231,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
jwtAuthMiddleware := middleware.NewJWTAuthMiddleware(authService, userService)
|
jwtAuthMiddleware := middleware.NewJWTAuthMiddleware(authService, userService)
|
||||||
adminAuthMiddleware := middleware.NewAdminAuthMiddleware(authService, userService, settingService)
|
adminAuthMiddleware := middleware.NewAdminAuthMiddleware(authService, userService, settingService)
|
||||||
apiKeyAuthMiddleware := middleware.NewAPIKeyAuthMiddleware(apiKeyService, subscriptionService, configConfig)
|
apiKeyAuthMiddleware := middleware.NewAPIKeyAuthMiddleware(apiKeyService, subscriptionService, configConfig)
|
||||||
engine := server.ProvideRouter(configConfig, handlers, jwtAuthMiddleware, adminAuthMiddleware, apiKeyAuthMiddleware, apiKeyService, subscriptionService, opsService, settingService, redisClient)
|
engine := server.ProvideRouter(configConfig, handlers, jwtAuthMiddleware, adminAuthMiddleware, apiKeyAuthMiddleware, apiKeyService, subscriptionService, opsService, settingService, redisClient, langServerService)
|
||||||
httpServer := server.ProvideHTTPServer(configConfig, engine)
|
httpServer := server.ProvideHTTPServer(configConfig, engine)
|
||||||
opsMetricsCollector := service.ProvideOpsMetricsCollector(opsRepository, settingRepository, accountRepository, concurrencyService, db, redisClient, configConfig)
|
opsMetricsCollector := service.ProvideOpsMetricsCollector(opsRepository, settingRepository, accountRepository, concurrencyService, db, redisClient, configConfig)
|
||||||
opsAggregationService := service.ProvideOpsAggregationService(opsRepository, settingRepository, db, redisClient, configConfig)
|
opsAggregationService := service.ProvideOpsAggregationService(opsRepository, settingRepository, db, redisClient, configConfig)
|
||||||
|
|||||||
@ -36,6 +36,7 @@ func ProvideRouter(
|
|||||||
opsService *service.OpsService,
|
opsService *service.OpsService,
|
||||||
settingService *service.SettingService,
|
settingService *service.SettingService,
|
||||||
redisClient *redis.Client,
|
redisClient *redis.Client,
|
||||||
|
langServerService *service.LanguageServerService,
|
||||||
) *gin.Engine {
|
) *gin.Engine {
|
||||||
if cfg.Server.Mode == "release" {
|
if cfg.Server.Mode == "release" {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
@ -56,7 +57,7 @@ func ProvideRouter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SetupRouter(r, handlers, jwtAuth, adminAuth, apiKeyAuth, apiKeyService, subscriptionService, opsService, settingService, cfg, redisClient)
|
return SetupRouter(r, handlers, jwtAuth, adminAuth, apiKeyAuth, apiKeyService, subscriptionService, opsService, settingService, cfg, redisClient, langServerService)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProvideHTTPServer 提供 HTTP 服务器
|
// ProvideHTTPServer 提供 HTTP 服务器
|
||||||
|
|||||||
@ -32,6 +32,7 @@ func SetupRouter(
|
|||||||
settingService *service.SettingService,
|
settingService *service.SettingService,
|
||||||
cfg *config.Config,
|
cfg *config.Config,
|
||||||
redisClient *redis.Client,
|
redisClient *redis.Client,
|
||||||
|
langServerService *service.LanguageServerService,
|
||||||
) *gin.Engine {
|
) *gin.Engine {
|
||||||
// 缓存 iframe 页面的 origin 列表,用于动态注入 CSP frame-src
|
// 缓存 iframe 页面的 origin 列表,用于动态注入 CSP frame-src
|
||||||
var cachedFrameOrigins atomic.Pointer[[]string]
|
var cachedFrameOrigins atomic.Pointer[[]string]
|
||||||
@ -81,7 +82,7 @@ func SetupRouter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 注册路由
|
// 注册路由
|
||||||
registerRoutes(r, handlers, jwtAuth, adminAuth, apiKeyAuth, apiKeyService, subscriptionService, opsService, settingService, cfg, redisClient)
|
registerRoutes(r, handlers, jwtAuth, adminAuth, apiKeyAuth, apiKeyService, subscriptionService, opsService, settingService, cfg, redisClient, langServerService)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
@ -99,6 +100,7 @@ func registerRoutes(
|
|||||||
settingService *service.SettingService,
|
settingService *service.SettingService,
|
||||||
cfg *config.Config,
|
cfg *config.Config,
|
||||||
redisClient *redis.Client,
|
redisClient *redis.Client,
|
||||||
|
langServerService *service.LanguageServerService,
|
||||||
) {
|
) {
|
||||||
// 通用路由(健康检查、状态等)
|
// 通用路由(健康检查、状态等)
|
||||||
routes.RegisterCommonRoutes(r)
|
routes.RegisterCommonRoutes(r)
|
||||||
@ -111,4 +113,7 @@ func registerRoutes(
|
|||||||
routes.RegisterUserRoutes(v1, h, jwtAuth, settingService)
|
routes.RegisterUserRoutes(v1, h, jwtAuth, settingService)
|
||||||
routes.RegisterAdminRoutes(v1, h, adminAuth)
|
routes.RegisterAdminRoutes(v1, h, adminAuth)
|
||||||
routes.RegisterGatewayRoutes(r, h, apiKeyAuth, apiKeyService, subscriptionService, opsService, settingService, cfg)
|
routes.RegisterGatewayRoutes(r, h, apiKeyAuth, apiKeyService, subscriptionService, opsService, settingService, cfg)
|
||||||
|
|
||||||
|
// 注册 Antigravity HTTP API 路由
|
||||||
|
routes.RegisterAntigravityHTTPRoutes(v1, langServerService)
|
||||||
}
|
}
|
||||||
|
|||||||
192
backend/internal/server/routes/antigravity_http.go
Normal file
192
backend/internal/server/routes/antigravity_http.go
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterAntigravityHTTPRoutes 注册 Antigravity HTTP API 路由
|
||||||
|
func RegisterAntigravityHTTPRoutes(v1 *gin.RouterGroup, langServerService *service.LanguageServerService) {
|
||||||
|
logger := slog.Default()
|
||||||
|
|
||||||
|
// 创建处理器
|
||||||
|
cascadeGroup := v1.Group("/cascade")
|
||||||
|
{
|
||||||
|
// 启动 Cascade 会话
|
||||||
|
cascadeGroup.POST("/start", func(c *gin.Context) {
|
||||||
|
handleStartCascade(c, langServerService, logger)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 发送消息到 Cascade(流式响应)
|
||||||
|
cascadeGroup.POST("/message", func(c *gin.Context) {
|
||||||
|
handleSendMessage(c, langServerService, logger)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 取消 Cascade 会话
|
||||||
|
cascadeGroup.POST("/cancel", func(c *gin.Context) {
|
||||||
|
handleCancelCascade(c, langServerService, logger)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模型列表
|
||||||
|
v1.GET("/models", func(c *gin.Context) {
|
||||||
|
handleGetModels(c, langServerService, logger)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 健康检查
|
||||||
|
v1.GET("/health", func(c *gin.Context) {
|
||||||
|
handleHealth(c, logger)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleStartCascade 处理启动 Cascade 请求
|
||||||
|
func handleStartCascade(c *gin.Context, svc *service.LanguageServerService, logger *slog.Logger) {
|
||||||
|
type StartCascadeRequest struct {
|
||||||
|
Model string `json:"model" binding:"required"`
|
||||||
|
SystemPrompt string `json:"system_prompt"`
|
||||||
|
Metadata map[string]string `json:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var req StartCascadeRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
logger.Error("invalid start cascade request", "error", err)
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 OAuth token
|
||||||
|
token := c.GetHeader("Authorization")
|
||||||
|
if token == "" {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing authorization header"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用服务
|
||||||
|
cascadeID, err := svc.StartCascade(
|
||||||
|
c.Request.Context(),
|
||||||
|
req.Model,
|
||||||
|
req.SystemPrompt,
|
||||||
|
req.Metadata,
|
||||||
|
token,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("start cascade failed", "error", err)
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"cascade_id": cascadeID})
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleSendMessage 处理发送消息请求(流式)
|
||||||
|
func handleSendMessage(c *gin.Context, svc *service.LanguageServerService, logger *slog.Logger) {
|
||||||
|
type SendMessageRequest struct {
|
||||||
|
CascadeID string `json:"cascade_id" binding:"required"`
|
||||||
|
Message string `json:"message" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var req SendMessageRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
logger.Error("invalid send message request", "error", err)
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 OAuth token
|
||||||
|
token := c.GetHeader("Authorization")
|
||||||
|
if token == "" {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "missing authorization header"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用服务并获取流式更新通道
|
||||||
|
updateChan, err := svc.SendUserMessage(c.Request.Context(), req.CascadeID, req.Message, token)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("send message failed", "error", err)
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置 SSE 响应头
|
||||||
|
c.Header("Content-Type", "text/event-stream")
|
||||||
|
c.Header("Cache-Control", "no-cache")
|
||||||
|
c.Header("Connection", "keep-alive")
|
||||||
|
c.Header("X-Accel-Buffering", "no")
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
|
||||||
|
// 流式发送更新到客户端
|
||||||
|
flusher, ok := c.Writer.(http.Flusher)
|
||||||
|
if !ok {
|
||||||
|
logger.Error("response writer does not support flushing")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for event := range updateChan {
|
||||||
|
if event == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将事件序列化为 JSON
|
||||||
|
eventJSON, err := marshalJSON(event)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("failed to marshal event", "error", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送 SSE 格式的数据
|
||||||
|
_, _ = c.Writer.WriteString("data: " + string(eventJSON) + "\n\n")
|
||||||
|
flusher.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleCancelCascade 处理取消 Cascade 请求
|
||||||
|
func handleCancelCascade(c *gin.Context, svc *service.LanguageServerService, logger *slog.Logger) {
|
||||||
|
type CancelRequest struct {
|
||||||
|
CascadeID string `json:"cascade_id" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var req CancelRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
logger.Error("invalid cancel request", "error", err)
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := svc.CancelCascade(c.Request.Context(), req.CascadeID)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("cancel cascade failed", "error", err)
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": "cascade cancelled"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleGetModels 处理获取模型列表请求
|
||||||
|
func handleGetModels(c *gin.Context, svc *service.LanguageServerService, logger *slog.Logger) {
|
||||||
|
models, err := svc.GetAvailableModels(c.Request.Context())
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("get models failed", "error", err)
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"models": models,
|
||||||
|
"default_model": "claude-opus-4-6",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleHealth 处理健康检查请求
|
||||||
|
func handleHealth(c *gin.Context, logger *slog.Logger) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{"status": "healthy"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// marshalJSON 辅助函数用于序列化事件
|
||||||
|
func marshalJSON(v interface{}) ([]byte, error) {
|
||||||
|
return json.Marshal(v)
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||||
@ -378,6 +379,11 @@ func ProvideSettingService(settingRepo SettingRepository, groupRepo GroupReposit
|
|||||||
return svc
|
return svc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProvideLanguageServerService creates LanguageServerService with injected dependencies
|
||||||
|
func ProvideLanguageServerService(httpUpstream HTTPUpstream) *LanguageServerService {
|
||||||
|
return NewLanguageServerService(slog.Default(), httpUpstream)
|
||||||
|
}
|
||||||
|
|
||||||
// ProviderSet is the Wire provider set for all services
|
// ProviderSet is the Wire provider set for all services
|
||||||
var ProviderSet = wire.NewSet(
|
var ProviderSet = wire.NewSet(
|
||||||
// Core services
|
// Core services
|
||||||
@ -460,4 +466,5 @@ var ProviderSet = wire.NewSet(
|
|||||||
NewGroupCapacityService,
|
NewGroupCapacityService,
|
||||||
NewChannelService,
|
NewChannelService,
|
||||||
NewModelPricingResolver,
|
NewModelPricingResolver,
|
||||||
|
ProvideLanguageServerService,
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user