diff --git a/backend/internal/handler/admin/account_handler.go b/backend/internal/handler/admin/account_handler.go index 3b6827e9..8351d1af 100644 --- a/backend/internal/handler/admin/account_handler.go +++ b/backend/internal/handler/admin/account_handler.go @@ -2,6 +2,7 @@ package admin import ( "strconv" + "strings" "github.com/Wei-Shaw/sub2api/internal/model" "github.com/Wei-Shaw/sub2api/internal/pkg/claude" @@ -375,18 +376,22 @@ func (h *AccountHandler) Refresh(c *gin.Context) { newCredentials[k] = v } - // Update token-related fields - newCredentials["access_token"] = tokenInfo.AccessToken - newCredentials["token_type"] = tokenInfo.TokenType - newCredentials["expires_in"] = tokenInfo.ExpiresIn - newCredentials["expires_at"] = tokenInfo.ExpiresAt - newCredentials["refresh_token"] = tokenInfo.RefreshToken - newCredentials["scope"] = tokenInfo.Scope - } + // Update token-related fields + newCredentials["access_token"] = tokenInfo.AccessToken + newCredentials["token_type"] = tokenInfo.TokenType + newCredentials["expires_in"] = strconv.FormatInt(tokenInfo.ExpiresIn, 10) + newCredentials["expires_at"] = strconv.FormatInt(tokenInfo.ExpiresAt, 10) + if strings.TrimSpace(tokenInfo.RefreshToken) != "" { + newCredentials["refresh_token"] = tokenInfo.RefreshToken + } + if strings.TrimSpace(tokenInfo.Scope) != "" { + newCredentials["scope"] = tokenInfo.Scope + } + } - updatedAccount, err := h.adminService.UpdateAccount(c.Request.Context(), accountID, &service.UpdateAccountInput{ - Credentials: newCredentials, - }) + updatedAccount, err := h.adminService.UpdateAccount(c.Request.Context(), accountID, &service.UpdateAccountInput{ + Credentials: newCredentials, + }) if err != nil { response.ErrorFrom(c, err) return diff --git a/backend/internal/model/account.go b/backend/internal/model/account.go index 9b09b114..8abd9049 100644 --- a/backend/internal/model/account.go +++ b/backend/internal/model/account.go @@ -4,6 +4,7 @@ import ( "database/sql/driver" "encoding/json" "errors" + "strconv" "time" "gorm.io/gorm" @@ -128,8 +129,37 @@ func (a *Account) GetCredential(key string) string { return "" } if v, ok := a.Credentials[key]; ok { - if s, ok := v.(string); ok { - return s + switch vv := v.(type) { + case string: + return vv + case json.Number: + return vv.String() + case float64: + // JSON numbers decode to float64; keep integer formatting for integer-like values. + i := int64(vv) + if vv == float64(i) { + return strconv.FormatInt(i, 10) + } + return strconv.FormatFloat(vv, 'f', -1, 64) + case float32: + f := float64(vv) + i := int64(f) + if f == float64(i) { + return strconv.FormatInt(i, 10) + } + return strconv.FormatFloat(f, 'f', -1, 64) + case int: + return strconv.FormatInt(int64(vv), 10) + case int64: + return strconv.FormatInt(vv, 10) + case int32: + return strconv.FormatInt(int64(vv), 10) + case uint: + return strconv.FormatUint(uint64(vv), 10) + case uint64: + return strconv.FormatUint(vv, 10) + case uint32: + return strconv.FormatUint(uint64(vv), 10) } } return "" @@ -291,6 +321,11 @@ func (a *Account) IsAnthropic() bool { return a.Platform == PlatformAnthropic } +// IsGemini 检查是否为 Gemini 平台账号 +func (a *Account) IsGemini() bool { + return a.Platform == PlatformGemini +} + // IsOpenAIOAuth 检查是否为 OpenAI OAuth 类型账号 func (a *Account) IsOpenAIOAuth() bool { return a.IsOpenAI() && a.Type == AccountTypeOAuth diff --git a/backend/internal/service/gemini_oauth_service.go b/backend/internal/service/gemini_oauth_service.go index 067a2455..b3dc3f09 100644 --- a/backend/internal/service/gemini_oauth_service.go +++ b/backend/internal/service/gemini_oauth_service.go @@ -139,7 +139,8 @@ func (s *GeminiOAuthService) ExchangeCode(ctx context.Context, input *GeminiExch } s.sessionStore.Delete(input.SessionID) - expiresAt := time.Now().Unix() + tokenResp.ExpiresIn + // 计算过期时间时减去 5 分钟安全时间窗口,考虑网络延迟和时钟偏差 + expiresAt := time.Now().Unix() + tokenResp.ExpiresIn - 300 projectID, _ := s.fetchProjectID(ctx, tokenResp.AccessToken, proxyURL) return &GeminiTokenInfo{ @@ -167,7 +168,8 @@ func (s *GeminiOAuthService) RefreshToken(ctx context.Context, refreshToken, pro tokenResp, err := s.oauthClient.RefreshToken(ctx, refreshToken, proxyURL) if err == nil { - expiresAt := time.Now().Unix() + tokenResp.ExpiresIn + // 计算过期时间时减去 5 分钟安全时间窗口,考虑网络延迟和时钟偏差 + expiresAt := time.Now().Unix() + tokenResp.ExpiresIn - 300 return &GeminiTokenInfo{ AccessToken: tokenResp.AccessToken, RefreshToken: tokenResp.RefreshToken,