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
fc5b9c82
Unverified
Commit
fc5b9c82
authored
Mar 02, 2026
by
Wesley Liddick
Committed by
GitHub
Mar 02, 2026
Browse files
Merge pull request #705 from DaydreamCoding/feat/fingerprint-ttl-lazy-renewal
feat(identity): 指纹缓存 TTL 懒续期机制
parents
f490f445
d869ac95
Changes
2
Hide whitespace changes
Inline
Side-by-side
backend/internal/repository/identity_cache.go
View file @
fc5b9c82
...
@@ -12,7 +12,7 @@ import (
...
@@ -12,7 +12,7 @@ import (
const
(
const
(
fingerprintKeyPrefix
=
"fingerprint:"
fingerprintKeyPrefix
=
"fingerprint:"
fingerprintTTL
=
24
*
time
.
Hour
fingerprintTTL
=
7
*
24
*
time
.
Hour
// 7天,配合每24小时懒续期可保持活跃账号永不过期
maskedSessionKeyPrefix
=
"masked_session:"
maskedSessionKeyPrefix
=
"masked_session:"
maskedSessionTTL
=
15
*
time
.
Minute
maskedSessionTTL
=
15
*
time
.
Minute
)
)
...
...
backend/internal/service/identity_service.go
View file @
fc5b9c82
...
@@ -46,6 +46,7 @@ type Fingerprint struct {
...
@@ -46,6 +46,7 @@ type Fingerprint struct {
StainlessArch
string
StainlessArch
string
StainlessRuntime
string
StainlessRuntime
string
StainlessRuntimeVersion
string
StainlessRuntimeVersion
string
UpdatedAt
int64
`json:",omitempty"`
// Unix timestamp,用于判断是否需要续期TTL
}
}
// IdentityCache defines cache operations for identity service
// IdentityCache defines cache operations for identity service
...
@@ -78,14 +79,26 @@ func (s *IdentityService) GetOrCreateFingerprint(ctx context.Context, accountID
...
@@ -78,14 +79,26 @@ func (s *IdentityService) GetOrCreateFingerprint(ctx context.Context, accountID
// 尝试从缓存获取指纹
// 尝试从缓存获取指纹
cached
,
err
:=
s
.
cache
.
GetFingerprint
(
ctx
,
accountID
)
cached
,
err
:=
s
.
cache
.
GetFingerprint
(
ctx
,
accountID
)
if
err
==
nil
&&
cached
!=
nil
{
if
err
==
nil
&&
cached
!=
nil
{
needWrite
:=
false
// 检查客户端的user-agent是否是更新版本
// 检查客户端的user-agent是否是更新版本
clientUA
:=
headers
.
Get
(
"User-Agent"
)
clientUA
:=
headers
.
Get
(
"User-Agent"
)
if
clientUA
!=
""
&&
isNewerVersion
(
clientUA
,
cached
.
UserAgent
)
{
if
clientUA
!=
""
&&
isNewerVersion
(
clientUA
,
cached
.
UserAgent
)
{
// 更新user-agent
// 版本升级:merge 语义 — 仅更新请求中实际携带的字段,保留缓存值
cached
.
UserAgent
=
clientUA
// 避免缺失的头被硬编码默认值覆盖(如新 CLI 版本 + 旧 SDK 默认值的不一致)
// 保存更新后的指纹
mergeHeadersIntoFingerprint
(
cached
,
headers
)
_
=
s
.
cache
.
SetFingerprint
(
ctx
,
accountID
,
cached
)
needWrite
=
true
logger
.
LegacyPrintf
(
"service.identity"
,
"Updated fingerprint user-agent for account %d: %s"
,
accountID
,
clientUA
)
logger
.
LegacyPrintf
(
"service.identity"
,
"Updated fingerprint for account %d: %s (merge update)"
,
accountID
,
clientUA
)
}
else
if
time
.
Since
(
time
.
Unix
(
cached
.
UpdatedAt
,
0
))
>
24
*
time
.
Hour
{
// 距上次写入超过24小时,续期TTL
needWrite
=
true
}
if
needWrite
{
cached
.
UpdatedAt
=
time
.
Now
()
.
Unix
()
if
err
:=
s
.
cache
.
SetFingerprint
(
ctx
,
accountID
,
cached
);
err
!=
nil
{
logger
.
LegacyPrintf
(
"service.identity"
,
"Warning: failed to refresh fingerprint for account %d: %v"
,
accountID
,
err
)
}
}
}
return
cached
,
nil
return
cached
,
nil
}
}
...
@@ -95,8 +108,9 @@ func (s *IdentityService) GetOrCreateFingerprint(ctx context.Context, accountID
...
@@ -95,8 +108,9 @@ func (s *IdentityService) GetOrCreateFingerprint(ctx context.Context, accountID
// 生成随机ClientID
// 生成随机ClientID
fp
.
ClientID
=
generateClientID
()
fp
.
ClientID
=
generateClientID
()
fp
.
UpdatedAt
=
time
.
Now
()
.
Unix
()
// 保存到缓存(
永不过
期)
// 保存到缓存(
7天TTL,每24小时自动续
期)
if
err
:=
s
.
cache
.
SetFingerprint
(
ctx
,
accountID
,
fp
);
err
!=
nil
{
if
err
:=
s
.
cache
.
SetFingerprint
(
ctx
,
accountID
,
fp
);
err
!=
nil
{
logger
.
LegacyPrintf
(
"service.identity"
,
"Warning: failed to cache fingerprint for account %d: %v"
,
accountID
,
err
)
logger
.
LegacyPrintf
(
"service.identity"
,
"Warning: failed to cache fingerprint for account %d: %v"
,
accountID
,
err
)
}
}
...
@@ -127,6 +141,31 @@ func (s *IdentityService) createFingerprintFromHeaders(headers http.Header) *Fin
...
@@ -127,6 +141,31 @@ func (s *IdentityService) createFingerprintFromHeaders(headers http.Header) *Fin
return
fp
return
fp
}
}
// mergeHeadersIntoFingerprint 将请求头中实际存在的字段合并到现有指纹中(用于版本升级场景)
// 关键语义:请求中有的字段 → 用新值覆盖;缺失的头 → 保留缓存中的已有值
// 与 createFingerprintFromHeaders 的区别:后者用于首次创建,缺失头回退到 defaultFingerprint;
// 本函数用于升级更新,缺失头保留缓存值,避免将已知的真实值退化为硬编码默认值
func
mergeHeadersIntoFingerprint
(
fp
*
Fingerprint
,
headers
http
.
Header
)
{
// User-Agent:版本升级的触发条件,一定存在
if
ua
:=
headers
.
Get
(
"User-Agent"
);
ua
!=
""
{
fp
.
UserAgent
=
ua
}
// X-Stainless-* 头:仅在请求中实际携带时才更新,否则保留缓存值
mergeHeader
(
headers
,
"X-Stainless-Lang"
,
&
fp
.
StainlessLang
)
mergeHeader
(
headers
,
"X-Stainless-Package-Version"
,
&
fp
.
StainlessPackageVersion
)
mergeHeader
(
headers
,
"X-Stainless-OS"
,
&
fp
.
StainlessOS
)
mergeHeader
(
headers
,
"X-Stainless-Arch"
,
&
fp
.
StainlessArch
)
mergeHeader
(
headers
,
"X-Stainless-Runtime"
,
&
fp
.
StainlessRuntime
)
mergeHeader
(
headers
,
"X-Stainless-Runtime-Version"
,
&
fp
.
StainlessRuntimeVersion
)
}
// mergeHeader 如果请求头中存在该字段则更新目标值,否则保留原值
func
mergeHeader
(
headers
http
.
Header
,
key
string
,
target
*
string
)
{
if
v
:=
headers
.
Get
(
key
);
v
!=
""
{
*
target
=
v
}
}
// getHeaderOrDefault 获取header值,如果不存在则返回默认值
// getHeaderOrDefault 获取header值,如果不存在则返回默认值
func
getHeaderOrDefault
(
headers
http
.
Header
,
key
,
defaultValue
string
)
string
{
func
getHeaderOrDefault
(
headers
http
.
Header
,
key
,
defaultValue
string
)
string
{
if
v
:=
headers
.
Get
(
key
);
v
!=
""
{
if
v
:=
headers
.
Get
(
key
);
v
!=
""
{
...
@@ -371,8 +410,25 @@ func parseUserAgentVersion(ua string) (major, minor, patch int, ok bool) {
...
@@ -371,8 +410,25 @@ func parseUserAgentVersion(ua string) (major, minor, patch int, ok bool) {
return
major
,
minor
,
patch
,
true
return
major
,
minor
,
patch
,
true
}
}
// extractProduct 提取 User-Agent 中 "/" 前的产品名
// 例如:claude-cli/2.1.22 (external, cli) -> "claude-cli"
func
extractProduct
(
ua
string
)
string
{
if
idx
:=
strings
.
Index
(
ua
,
"/"
);
idx
>
0
{
return
strings
.
ToLower
(
ua
[
:
idx
])
}
return
""
}
// isNewerVersion 比较版本号,判断newUA是否比cachedUA更新
// isNewerVersion 比较版本号,判断newUA是否比cachedUA更新
// 要求产品名一致(防止浏览器 UA 如 Mozilla/5.0 误判为更新版本)
func
isNewerVersion
(
newUA
,
cachedUA
string
)
bool
{
func
isNewerVersion
(
newUA
,
cachedUA
string
)
bool
{
// 校验产品名一致性
newProduct
:=
extractProduct
(
newUA
)
cachedProduct
:=
extractProduct
(
cachedUA
)
if
newProduct
==
""
||
cachedProduct
==
""
||
newProduct
!=
cachedProduct
{
return
false
}
newMajor
,
newMinor
,
newPatch
,
newOk
:=
parseUserAgentVersion
(
newUA
)
newMajor
,
newMinor
,
newPatch
,
newOk
:=
parseUserAgentVersion
(
newUA
)
cachedMajor
,
cachedMinor
,
cachedPatch
,
cachedOk
:=
parseUserAgentVersion
(
cachedUA
)
cachedMajor
,
cachedMinor
,
cachedPatch
,
cachedOk
:=
parseUserAgentVersion
(
cachedUA
)
...
...
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