diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go index d3c5adc7dc..18f5fc4ef6 100644 --- a/backend/cmd/server/wire_gen.go +++ b/backend/cmd/server/wire_gen.go @@ -177,7 +177,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { soraGenerationService := service.NewSoraGenerationService(soraGenerationRepository, soraStorageRouter, soraQuotaService) settingHandler := admin.NewSettingHandler(settingService, emailService, turnstileService, opsService, soraS3Storage) soraGDriveOAuthService := service.NewSoraGDriveOAuthService(settingService) - gdriveOAuthHandler := admin.NewGDriveOAuthHandler(settingService, soraGDriveOAuthService) + gdriveOAuthHandler := admin.NewGDriveOAuthHandler(settingService, soraGDriveOAuthService, soraGDriveStorage) opsHandler := admin.NewOpsHandler(opsService) updateCache := repository.NewUpdateCache(redisClient) gitHubReleaseClient := repository.ProvideGitHubReleaseClient(configConfig) diff --git a/backend/internal/handler/admin/gdrive_oauth_handler.go b/backend/internal/handler/admin/gdrive_oauth_handler.go index bea604bccf..bdf549066c 100644 --- a/backend/internal/handler/admin/gdrive_oauth_handler.go +++ b/backend/internal/handler/admin/gdrive_oauth_handler.go @@ -13,13 +13,15 @@ import ( type GDriveOAuthHandler struct { settingService *service.SettingService gdriveOAuth *service.SoraGDriveOAuthService + gdriveStorage *service.SoraGDriveStorage } // NewGDriveOAuthHandler 创建 GDrive OAuth Handler。 -func NewGDriveOAuthHandler(settingService *service.SettingService, gdriveOAuth *service.SoraGDriveOAuthService) *GDriveOAuthHandler { +func NewGDriveOAuthHandler(settingService *service.SettingService, gdriveOAuth *service.SoraGDriveOAuthService, gdriveStorage *service.SoraGDriveStorage) *GDriveOAuthHandler { return &GDriveOAuthHandler{ settingService: settingService, gdriveOAuth: gdriveOAuth, + gdriveStorage: gdriveStorage, } } @@ -127,3 +129,20 @@ func (h *GDriveOAuthHandler) OAuthCallback(c *gin.Context) { "message": "OAuth 授权成功", }) } + +// TestGDriveStorage 测试 GDrive 存储的完整上传→下载→删除流程。 +// POST /api/v1/admin/settings/sora-storage/gdrive-test +func (h *GDriveOAuthHandler) TestGDriveStorage(c *gin.Context) { + if h.gdriveStorage == nil { + response.Error(c, http.StatusInternalServerError, "GDrive storage not initialized") + return + } + + result, err := h.gdriveStorage.TestFullCycle(c.Request.Context()) + if err != nil { + response.Error(c, http.StatusBadRequest, "GDrive 测试失败: "+err.Error()) + return + } + + response.Success(c, result) +} diff --git a/backend/internal/server/routes/admin.go b/backend/internal/server/routes/admin.go index b3267cb660..a00cb5849d 100644 --- a/backend/internal/server/routes/admin.go +++ b/backend/internal/server/routes/admin.go @@ -413,6 +413,7 @@ func registerSettingsRoutes(admin *gin.RouterGroup, h *handler.Handlers) { // Google Drive OAuth adminSettings.POST("/sora-storage/gdrive-oauth/start", h.Admin.GDriveOAuth.StartOAuth) adminSettings.POST("/sora-storage/gdrive-oauth/callback", h.Admin.GDriveOAuth.OAuthCallback) + adminSettings.POST("/sora-storage/gdrive-test", h.Admin.GDriveOAuth.TestGDriveStorage) } } diff --git a/backend/internal/service/sora_gdrive_storage.go b/backend/internal/service/sora_gdrive_storage.go index b43f34b2d9..5b3c974211 100644 --- a/backend/internal/service/sora_gdrive_storage.go +++ b/backend/internal/service/sora_gdrive_storage.go @@ -188,6 +188,66 @@ func (s *SoraGDriveStorage) TestConnection(ctx context.Context) error { return nil } +// TestFullCycle 执行完整的上传→获取链接→删除测试。 +func (s *SoraGDriveStorage) TestFullCycle(ctx context.Context) (map[string]any, error) { + srv, cfg, err := s.getService(ctx) + if err != nil { + return nil, fmt.Errorf("init client: %w", err) + } + + result := map[string]any{} + + // 1. 测试 API 连接 + about, err := srv.About.Get().Fields("storageQuota").Context(ctx).Do() + if err != nil { + return nil, fmt.Errorf("API connection failed: %w", err) + } + if about.StorageQuota != nil { + result["quota_limit_bytes"] = about.StorageQuota.Limit + result["quota_used_bytes"] = about.StorageQuota.Usage + } + + // 2. 上传测试文件 + testContent := "sub2api GDrive test file - " + time.Now().Format(time.RFC3339) + fileMeta := &drive.File{ + Name: "sub2api_test_" + uuid.NewString()[:8] + ".txt", + MimeType: "text/plain", + } + if cfg.FolderID != "" { + fileMeta.Parents = []string{cfg.FolderID} + } + uploaded, err := srv.Files.Create(fileMeta). + Media(strings.NewReader(testContent)). + Fields("id,name,size,webViewLink"). + Context(ctx).Do() + if err != nil { + return nil, fmt.Errorf("upload test file failed: %w", err) + } + result["uploaded_file_id"] = uploaded.Id + result["uploaded_file_name"] = uploaded.Name + result["uploaded_file_size"] = uploaded.Size + result["web_view_link"] = uploaded.WebViewLink + + // 3. 获取访问链接 + accessURL, err := s.GetAccessURL(ctx, uploaded.Id) + if err != nil { + // 即使获取链接失败,仍尝试清理 + _ = srv.Files.Delete(uploaded.Id).Context(ctx).Do() + return nil, fmt.Errorf("get access URL failed: %w", err) + } + result["access_url"] = accessURL + + // 4. 删除测试文件 + if err := srv.Files.Delete(uploaded.Id).Context(ctx).Do(); err != nil { + result["delete_warning"] = fmt.Sprintf("delete failed (manual cleanup needed): %v", err) + } else { + result["deleted"] = true + } + + result["status"] = "ok" + return result, nil +} + // IsHealthy 返回 Google Drive 健康状态(带短缓存)。 func (s *SoraGDriveStorage) IsHealthy(ctx context.Context) bool { if s == nil {