| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- package service
- import (
- "context"
- "strings"
- "time"
- )
- // TokenRefresher 定义平台特定的token刷新策略接口
- // 通过此接口可以扩展支持不同平台(Anthropic/OpenAI/Gemini)
- type TokenRefresher interface {
- // CanRefresh 检查此刷新器是否能处理指定账号
- CanRefresh(account *Account) bool
- // NeedsRefresh 检查账号的token是否需要刷新
- NeedsRefresh(account *Account, refreshWindow time.Duration) bool
- // Refresh 执行token刷新,返回更新后的credentials
- // 注意:返回的map应该保留原有credentials中的所有字段,只更新token相关字段
- Refresh(ctx context.Context, account *Account) (map[string]any, error)
- }
- // ClaudeTokenRefresher 处理Anthropic/Claude OAuth token刷新
- type ClaudeTokenRefresher struct {
- oauthService *OAuthService
- }
- // NewClaudeTokenRefresher 创建Claude token刷新器
- func NewClaudeTokenRefresher(oauthService *OAuthService) *ClaudeTokenRefresher {
- return &ClaudeTokenRefresher{
- oauthService: oauthService,
- }
- }
- // CacheKey 返回用于分布式锁的缓存键
- func (r *ClaudeTokenRefresher) CacheKey(account *Account) string {
- return ClaudeTokenCacheKey(account)
- }
- // CanRefresh 检查是否能处理此账号
- // 处理 anthropic 平台的 oauth 与 setup-token 类型账号。
- // 两者的 access_token 均为短期令牌(expires_in=28800,即 8h),到期都需刷新;
- // setup-token 之前被排除会导致其 access_token 过期后请求 401。
- // 此处与手动刷新入口(account.IsOAuth())保持一致,实际是否刷新由 NeedsRefresh
- // 基于 expires_at 门控,并在分布式锁保护下执行,不会造成过度刷新。
- func (r *ClaudeTokenRefresher) CanRefresh(account *Account) bool {
- return account.Platform == PlatformAnthropic && account.IsOAuth()
- }
- // NeedsRefresh 检查token是否需要刷新
- // 基于 expires_at 字段判断是否在刷新窗口内
- func (r *ClaudeTokenRefresher) NeedsRefresh(account *Account, refreshWindow time.Duration) bool {
- expiresAt := account.GetCredentialAsTime("expires_at")
- if expiresAt == nil {
- return false
- }
- return time.Until(*expiresAt) < refreshWindow
- }
- // Refresh 执行token刷新
- // 保留原有credentials中的所有字段,只更新token相关字段
- func (r *ClaudeTokenRefresher) Refresh(ctx context.Context, account *Account) (map[string]any, error) {
- tokenInfo, err := r.oauthService.RefreshAccountToken(ctx, account)
- if err != nil {
- return nil, err
- }
- newCredentials := BuildClaudeAccountCredentials(tokenInfo)
- newCredentials = MergeCredentials(account.Credentials, newCredentials)
- return newCredentials, nil
- }
- // OpenAITokenRefresher 处理 OpenAI OAuth token刷新
- type OpenAITokenRefresher struct {
- openaiOAuthService *OpenAIOAuthService
- accountRepo AccountRepository
- }
- // NewOpenAITokenRefresher 创建 OpenAI token刷新器
- func NewOpenAITokenRefresher(openaiOAuthService *OpenAIOAuthService, accountRepo AccountRepository) *OpenAITokenRefresher {
- return &OpenAITokenRefresher{
- openaiOAuthService: openaiOAuthService,
- accountRepo: accountRepo,
- }
- }
- // CacheKey 返回用于分布式锁的缓存键
- func (r *OpenAITokenRefresher) CacheKey(account *Account) string {
- return OpenAITokenCacheKey(account)
- }
- // CanRefresh 检查是否能处理此账号
- func (r *OpenAITokenRefresher) CanRefresh(account *Account) bool {
- if account.IsCredentialShadow() {
- return false
- }
- return account.Platform == PlatformOpenAI && account.Type == AccountTypeOAuth
- }
- // NeedsRefresh 检查token是否需要刷新
- // expires_at 缺失且处于限流状态时需要刷新,防止限流期间 token 静默过期
- func (r *OpenAITokenRefresher) NeedsRefresh(account *Account, refreshWindow time.Duration) bool {
- if account.IsOpenAIPersonalAccessToken() {
- return false
- }
- if strings.TrimSpace(account.GetOpenAIRefreshToken()) == "" {
- return false
- }
- expiresAt := account.GetCredentialAsTime("expires_at")
- if expiresAt == nil {
- return account.IsRateLimited()
- }
- return time.Until(*expiresAt) < refreshWindow
- }
- // Refresh 执行token刷新
- // 保留原有credentials中的所有字段,只更新token相关字段
- func (r *OpenAITokenRefresher) Refresh(ctx context.Context, account *Account) (map[string]any, error) {
- tokenInfo, err := r.openaiOAuthService.RefreshAccountToken(ctx, account)
- if err != nil {
- return nil, err
- }
- // 使用服务提供的方法构建新凭证,并保留原有字段
- newCredentials := r.openaiOAuthService.BuildAccountCredentials(tokenInfo)
- newCredentials = MergeCredentials(account.Credentials, newCredentials)
- newCredentials = NormalizeOpenAIPersonalAccessTokenCredentials(account, tokenInfo, newCredentials)
- return newCredentials, nil
- }
|