Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
0d5a8a95
Commit
0d5a8a95
authored
Dec 27, 2025
by
shaw
Browse files
fix: 修复claude token刷新失效的问题
parent
80cce858
Changes
2
Show whitespace changes
Inline
Side-by-side
backend/internal/service/token_refresher.go
View file @
0d5a8a95
...
...
@@ -43,18 +43,23 @@ func (r *ClaudeTokenRefresher) CanRefresh(account *Account) bool {
// NeedsRefresh 检查token是否需要刷新
// 基于 expires_at 字段判断是否在刷新窗口内
func
(
r
*
ClaudeTokenRefresher
)
NeedsRefresh
(
account
*
Account
,
refreshWindow
time
.
Duration
)
bool
{
expiresAtStr
:=
account
.
GetCredential
(
"expires_at"
)
if
expiresAtStr
==
""
{
return
false
}
var
expiresAt
int64
expiresAt
,
err
:=
strconv
.
ParseInt
(
expiresAtStr
,
10
,
64
)
// 方式1: 通过 GetCredential 获取(处理字符串和部分数字类型)
if
s
:=
account
.
GetCredential
(
"expires_at"
);
s
!=
""
{
v
,
err
:=
strconv
.
ParseInt
(
s
,
10
,
64
)
if
err
!=
nil
{
return
false
}
expiresAt
=
v
}
else
if
v
,
ok
:=
account
.
Credentials
[
"expires_at"
]
.
(
float64
);
ok
{
// 方式2: 直接获取 float64(处理某些 JSON 解码器将数字解析为 float64 的情况)
expiresAt
=
int64
(
v
)
}
else
{
return
false
}
expiryTime
:=
time
.
Unix
(
expiresAt
,
0
)
return
time
.
Until
(
expiryTime
)
<
refreshWindow
return
time
.
Until
(
time
.
Unix
(
expiresAt
,
0
))
<
refreshWindow
}
// Refresh 执行token刷新
...
...
backend/internal/service/token_refresher_test.go
0 → 100644
View file @
0d5a8a95
//go:build unit
package
service
import
(
"strconv"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func
TestClaudeTokenRefresher_NeedsRefresh
(
t
*
testing
.
T
)
{
refresher
:=
&
ClaudeTokenRefresher
{}
refreshWindow
:=
30
*
time
.
Minute
tests
:=
[]
struct
{
name
string
credentials
map
[
string
]
any
wantRefresh
bool
}{
{
name
:
"expires_at as string - expired"
,
credentials
:
map
[
string
]
any
{
"expires_at"
:
"1000"
,
// 1970-01-01 00:16:40 UTC, 已过期
},
wantRefresh
:
true
,
},
{
name
:
"expires_at as float64 - expired"
,
credentials
:
map
[
string
]
any
{
"expires_at"
:
float64
(
1000
),
// 数字类型,已过期
},
wantRefresh
:
true
,
},
{
name
:
"expires_at as string - far future"
,
credentials
:
map
[
string
]
any
{
"expires_at"
:
"9999999999"
,
// 远未来
},
wantRefresh
:
false
,
},
{
name
:
"expires_at as float64 - far future"
,
credentials
:
map
[
string
]
any
{
"expires_at"
:
float64
(
9999999999
),
// 远未来,数字类型
},
wantRefresh
:
false
,
},
{
name
:
"expires_at missing"
,
credentials
:
map
[
string
]
any
{},
wantRefresh
:
false
,
},
{
name
:
"expires_at is nil"
,
credentials
:
map
[
string
]
any
{
"expires_at"
:
nil
,
},
wantRefresh
:
false
,
},
{
name
:
"expires_at is invalid string"
,
credentials
:
map
[
string
]
any
{
"expires_at"
:
"invalid"
,
},
wantRefresh
:
false
,
},
{
name
:
"credentials is nil"
,
credentials
:
nil
,
wantRefresh
:
false
,
},
}
for
_
,
tt
:=
range
tests
{
t
.
Run
(
tt
.
name
,
func
(
t
*
testing
.
T
)
{
account
:=
&
Account
{
Platform
:
PlatformAnthropic
,
Type
:
AccountTypeOAuth
,
Credentials
:
tt
.
credentials
,
}
got
:=
refresher
.
NeedsRefresh
(
account
,
refreshWindow
)
require
.
Equal
(
t
,
tt
.
wantRefresh
,
got
)
})
}
}
func
TestClaudeTokenRefresher_NeedsRefresh_WithinWindow
(
t
*
testing
.
T
)
{
refresher
:=
&
ClaudeTokenRefresher
{}
refreshWindow
:=
30
*
time
.
Minute
// 设置一个在刷新窗口内的时间(当前时间 + 15分钟)
expiresAt
:=
time
.
Now
()
.
Add
(
15
*
time
.
Minute
)
.
Unix
()
tests
:=
[]
struct
{
name
string
credentials
map
[
string
]
any
}{
{
name
:
"string type - within refresh window"
,
credentials
:
map
[
string
]
any
{
"expires_at"
:
strconv
.
FormatInt
(
expiresAt
,
10
),
},
},
{
name
:
"float64 type - within refresh window"
,
credentials
:
map
[
string
]
any
{
"expires_at"
:
float64
(
expiresAt
),
},
},
}
for
_
,
tt
:=
range
tests
{
t
.
Run
(
tt
.
name
,
func
(
t
*
testing
.
T
)
{
account
:=
&
Account
{
Platform
:
PlatformAnthropic
,
Type
:
AccountTypeOAuth
,
Credentials
:
tt
.
credentials
,
}
got
:=
refresher
.
NeedsRefresh
(
account
,
refreshWindow
)
require
.
True
(
t
,
got
,
"should need refresh when within window"
)
})
}
}
func
TestClaudeTokenRefresher_NeedsRefresh_OutsideWindow
(
t
*
testing
.
T
)
{
refresher
:=
&
ClaudeTokenRefresher
{}
refreshWindow
:=
30
*
time
.
Minute
// 设置一个在刷新窗口外的时间(当前时间 + 1小时)
expiresAt
:=
time
.
Now
()
.
Add
(
1
*
time
.
Hour
)
.
Unix
()
tests
:=
[]
struct
{
name
string
credentials
map
[
string
]
any
}{
{
name
:
"string type - outside refresh window"
,
credentials
:
map
[
string
]
any
{
"expires_at"
:
strconv
.
FormatInt
(
expiresAt
,
10
),
},
},
{
name
:
"float64 type - outside refresh window"
,
credentials
:
map
[
string
]
any
{
"expires_at"
:
float64
(
expiresAt
),
},
},
}
for
_
,
tt
:=
range
tests
{
t
.
Run
(
tt
.
name
,
func
(
t
*
testing
.
T
)
{
account
:=
&
Account
{
Platform
:
PlatformAnthropic
,
Type
:
AccountTypeOAuth
,
Credentials
:
tt
.
credentials
,
}
got
:=
refresher
.
NeedsRefresh
(
account
,
refreshWindow
)
require
.
False
(
t
,
got
,
"should not need refresh when outside window"
)
})
}
}
func
TestClaudeTokenRefresher_CanRefresh
(
t
*
testing
.
T
)
{
refresher
:=
&
ClaudeTokenRefresher
{}
tests
:=
[]
struct
{
name
string
platform
string
accType
string
want
bool
}{
{
name
:
"anthropic oauth - can refresh"
,
platform
:
PlatformAnthropic
,
accType
:
AccountTypeOAuth
,
want
:
true
,
},
{
name
:
"anthropic api-key - cannot refresh"
,
platform
:
PlatformAnthropic
,
accType
:
AccountTypeApiKey
,
want
:
false
,
},
{
name
:
"openai oauth - cannot refresh"
,
platform
:
PlatformOpenAI
,
accType
:
AccountTypeOAuth
,
want
:
false
,
},
{
name
:
"gemini oauth - cannot refresh"
,
platform
:
PlatformGemini
,
accType
:
AccountTypeOAuth
,
want
:
false
,
},
}
for
_
,
tt
:=
range
tests
{
t
.
Run
(
tt
.
name
,
func
(
t
*
testing
.
T
)
{
account
:=
&
Account
{
Platform
:
tt
.
platform
,
Type
:
tt
.
accType
,
}
got
:=
refresher
.
CanRefresh
(
account
)
require
.
Equal
(
t
,
tt
.
want
,
got
)
})
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment