From ee23e67c85dfa82ff26ec8e79ba8dcb48aa766b8 Mon Sep 17 00:00:00 2001 From: QTom Date: Fri, 27 Mar 2026 13:54:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(privacy):=20=E5=88=9B=E5=BB=BA/=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E5=88=9B=E5=BB=BA=20OpenAI=20OAuth=20=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E6=97=B6=E5=BC=82=E6=AD=A5=E8=AE=BE=E7=BD=AE=E9=9A=90?= =?UTF-8?q?=E7=A7=81=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 参照 Antigravity 的模式,单个创建时同步调用 ForceOpenAIPrivacy, 批量创建时收集 OpenAI OAuth 账号后异步 goroutine 设置,避免阻塞请求。 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../internal/handler/admin/account_handler.go | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/backend/internal/handler/admin/account_handler.go b/backend/internal/handler/admin/account_handler.go index 9eaf0bfd..ce5cffe4 100644 --- a/backend/internal/handler/admin/account_handler.go +++ b/backend/internal/handler/admin/account_handler.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "log" + "log/slog" "net/http" "strconv" "strings" @@ -536,6 +537,10 @@ func (h *AccountHandler) Create(c *gin.Context) { if execErr != nil { return nil, execErr } + // Antigravity OAuth: 新账号直接设置隐私 + h.adminService.ForceAntigravityPrivacy(ctx, account) + // OpenAI OAuth: 新账号直接设置隐私 + h.adminService.ForceOpenAIPrivacy(ctx, account) return h.buildAccountResponseWithRuntime(ctx, account), nil }) if err != nil { @@ -782,6 +787,8 @@ func (h *AccountHandler) refreshSingleAccount(ctx context.Context, account *serv if account.IsOpenAI() { tokenInfo, err := h.openaiOAuthService.RefreshAccountToken(ctx, account) if err != nil { + // 刷新失败但 access_token 可能仍有效,尝试设置隐私 + h.adminService.EnsureOpenAIPrivacy(ctx, account) return nil, "", err } @@ -883,6 +890,8 @@ func (h *AccountHandler) refreshSingleAccount(ctx context.Context, account *serv // OpenAI OAuth: 刷新成功后检查并设置 privacy_mode h.adminService.EnsureOpenAIPrivacy(ctx, updatedAccount) + // Antigravity OAuth: 刷新成功后检查并设置 privacy_mode + h.adminService.EnsureAntigravityPrivacy(ctx, updatedAccount) return updatedAccount, "", nil } @@ -1154,6 +1163,9 @@ func (h *AccountHandler) BatchCreate(c *gin.Context) { success := 0 failed := 0 results := make([]gin.H, 0, len(req.Accounts)) + // 收集需要异步设置隐私的 OAuth 账号 + var antigravityPrivacyAccounts []*service.Account + var openaiPrivacyAccounts []*service.Account for _, item := range req.Accounts { if item.RateMultiplier != nil && *item.RateMultiplier < 0 { @@ -1196,6 +1208,15 @@ func (h *AccountHandler) BatchCreate(c *gin.Context) { }) continue } + // 收集需要异步设置隐私的 OAuth 账号 + if account.Type == service.AccountTypeOAuth { + switch account.Platform { + case service.PlatformAntigravity: + antigravityPrivacyAccounts = append(antigravityPrivacyAccounts, account) + case service.PlatformOpenAI: + openaiPrivacyAccounts = append(openaiPrivacyAccounts, account) + } + } success++ results = append(results, gin.H{ "name": item.Name, @@ -1204,6 +1225,37 @@ func (h *AccountHandler) BatchCreate(c *gin.Context) { }) } + // 异步设置隐私,避免批量创建时阻塞请求 + adminSvc := h.adminService + if len(antigravityPrivacyAccounts) > 0 { + accounts := antigravityPrivacyAccounts + go func() { + defer func() { + if r := recover(); r != nil { + slog.Error("batch_create_antigravity_privacy_panic", "recover", r) + } + }() + bgCtx := context.Background() + for _, acc := range accounts { + adminSvc.ForceAntigravityPrivacy(bgCtx, acc) + } + }() + } + if len(openaiPrivacyAccounts) > 0 { + accounts := openaiPrivacyAccounts + go func() { + defer func() { + if r := recover(); r != nil { + slog.Error("batch_create_openai_privacy_panic", "recover", r) + } + }() + bgCtx := context.Background() + for _, acc := range accounts { + adminSvc.ForceOpenAIPrivacy(bgCtx, acc) + } + }() + } + return gin.H{ "success": success, "failed": failed, @@ -1869,6 +1921,51 @@ func (h *AccountHandler) GetAvailableModels(c *gin.Context) { response.Success(c, models) } +// SetPrivacy handles setting privacy for a single OpenAI/Antigravity OAuth account +// POST /api/v1/admin/accounts/:id/set-privacy +func (h *AccountHandler) SetPrivacy(c *gin.Context) { + accountID, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + response.BadRequest(c, "Invalid account ID") + return + } + account, err := h.adminService.GetAccount(c.Request.Context(), accountID) + if err != nil { + response.NotFound(c, "Account not found") + return + } + if account.Type != service.AccountTypeOAuth { + response.BadRequest(c, "Only OAuth accounts support privacy setting") + return + } + var mode string + switch account.Platform { + case service.PlatformOpenAI: + mode = h.adminService.ForceOpenAIPrivacy(c.Request.Context(), account) + case service.PlatformAntigravity: + mode = h.adminService.ForceAntigravityPrivacy(c.Request.Context(), account) + default: + response.BadRequest(c, "Only OpenAI and Antigravity OAuth accounts support privacy setting") + return + } + if mode == "" { + response.BadRequest(c, "Cannot set privacy: missing access_token") + return + } + // 从 DB 重新读取以确保返回最新状态 + updated, err := h.adminService.GetAccount(c.Request.Context(), accountID) + if err != nil { + // 隐私已设置成功但读取失败,回退到内存更新 + if account.Extra == nil { + account.Extra = make(map[string]any) + } + account.Extra["privacy_mode"] = mode + response.Success(c, h.buildAccountResponseWithRuntime(c.Request.Context(), account)) + return + } + response.Success(c, h.buildAccountResponseWithRuntime(c.Request.Context(), updated)) +} + // RefreshTier handles refreshing Google One tier for a single account // POST /api/v1/admin/accounts/:id/refresh-tier func (h *AccountHandler) RefreshTier(c *gin.Context) {