@@ -44,26 +44,27 @@ type SettingRepository interface {
4444 Delete (ctx context.Context , key string ) error
4545}
4646
47- // cachedMinVersion 缓存最低 Claude Code 版本号(进程内缓存,60s TTL)
48- type cachedMinVersion struct {
49- value string // 空字符串 = 不检查
47+ // cachedVersionBounds 缓存 Claude Code 版本号上下限(进程内缓存,60s TTL)
48+ type cachedVersionBounds struct {
49+ min string // 空字符串 = 不检查
50+ max string // 空字符串 = 不检查
5051 expiresAt int64 // unix nano
5152}
5253
53- // minVersionCache 最低版本号进程内缓存
54- var minVersionCache atomic.Value // *cachedMinVersion
54+ // versionBoundsCache 版本号上下限进程内缓存
55+ var versionBoundsCache atomic.Value // *cachedVersionBounds
5556
56- // minVersionSF 防止缓存过期时 thundering herd
57- var minVersionSF singleflight.Group
57+ // versionBoundsSF 防止缓存过期时 thundering herd
58+ var versionBoundsSF singleflight.Group
5859
59- // minVersionCacheTTL 缓存有效期
60- const minVersionCacheTTL = 60 * time .Second
60+ // versionBoundsCacheTTL 缓存有效期
61+ const versionBoundsCacheTTL = 60 * time .Second
6162
62- // minVersionErrorTTL DB 错误时的短缓存,快速重试
63- const minVersionErrorTTL = 5 * time .Second
63+ // versionBoundsErrorTTL DB 错误时的短缓存,快速重试
64+ const versionBoundsErrorTTL = 5 * time .Second
6465
65- // minVersionDBTimeout singleflight 内 DB 查询超时,独立于请求 context
66- const minVersionDBTimeout = 5 * time .Second
66+ // versionBoundsDBTimeout singleflight 内 DB 查询超时,独立于请求 context
67+ const versionBoundsDBTimeout = 5 * time .Second
6768
6869// cachedBackendMode Backend Mode cache (in-process, 60s TTL)
6970type cachedBackendMode struct {
@@ -484,6 +485,7 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet
484485
485486 // Claude Code version check
486487 updates [SettingKeyMinClaudeCodeVersion ] = settings .MinClaudeCodeVersion
488+ updates [SettingKeyMaxClaudeCodeVersion ] = settings .MaxClaudeCodeVersion
487489
488490 // 分组隔离
489491 updates [SettingKeyAllowUngroupedKeyScheduling ] = strconv .FormatBool (settings .AllowUngroupedKeyScheduling )
@@ -494,10 +496,11 @@ func (s *SettingService) UpdateSettings(ctx context.Context, settings *SystemSet
494496 err = s .settingRepo .SetMultiple (ctx , updates )
495497 if err == nil {
496498 // 先使 inflight singleflight 失效,再刷新缓存,缩小旧值覆盖新值的竞态窗口
497- minVersionSF .Forget ("min_version" )
498- minVersionCache .Store (& cachedMinVersion {
499- value : settings .MinClaudeCodeVersion ,
500- expiresAt : time .Now ().Add (minVersionCacheTTL ).UnixNano (),
499+ versionBoundsSF .Forget ("version_bounds" )
500+ versionBoundsCache .Store (& cachedVersionBounds {
501+ min : settings .MinClaudeCodeVersion ,
502+ max : settings .MaxClaudeCodeVersion ,
503+ expiresAt : time .Now ().Add (versionBoundsCacheTTL ).UnixNano (),
501504 })
502505 backendModeSF .Forget ("backend_mode" )
503506 backendModeCache .Store (& cachedBackendMode {
@@ -760,6 +763,7 @@ func (s *SettingService) InitializeDefaultSettings(ctx context.Context) error {
760763
761764 // Claude Code version check (default: empty = disabled)
762765 SettingKeyMinClaudeCodeVersion : "" ,
766+ SettingKeyMaxClaudeCodeVersion : "" ,
763767
764768 // 分组隔离(默认不允许未分组 Key 调度)
765769 SettingKeyAllowUngroupedKeyScheduling : "false" ,
@@ -895,6 +899,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
895899
896900 // Claude Code version check
897901 result .MinClaudeCodeVersion = settings [SettingKeyMinClaudeCodeVersion ]
902+ result .MaxClaudeCodeVersion = settings [SettingKeyMaxClaudeCodeVersion ]
898903
899904 // 分组隔离
900905 result .AllowUngroupedKeyScheduling = settings [SettingKeyAllowUngroupedKeyScheduling ] == "true"
@@ -1281,51 +1286,61 @@ func (s *SettingService) IsUngroupedKeySchedulingAllowed(ctx context.Context) bo
12811286 return value == "true"
12821287}
12831288
1284- // GetMinClaudeCodeVersion 获取最低 Claude Code 版本号要求
1289+ // GetClaudeCodeVersionBounds 获取 Claude Code 版本号上下限要求
12851290// 使用进程内 atomic.Value 缓存,60 秒 TTL,热路径零锁开销
12861291// singleflight 防止缓存过期时 thundering herd
1287- // 返回空字符串表示不做版本检查
1288- func (s * SettingService ) GetMinClaudeCodeVersion (ctx context.Context ) string {
1289- if cached , ok := minVersionCache .Load ().(* cachedMinVersion ); ok {
1292+ // 返回空字符串表示不做对应方向的版本检查
1293+ func (s * SettingService ) GetClaudeCodeVersionBounds (ctx context.Context ) ( min , max string ) {
1294+ if cached , ok := versionBoundsCache .Load ().(* cachedVersionBounds ); ok {
12901295 if time .Now ().UnixNano () < cached .expiresAt {
1291- return cached .value
1296+ return cached .min , cached . max
12921297 }
12931298 }
12941299 // singleflight: 同一时刻只有一个 goroutine 查询 DB,其余复用结果
1295- result , err , _ := minVersionSF .Do ("min_version" , func () (any , error ) {
1300+ type bounds struct { min , max string }
1301+ result , err , _ := versionBoundsSF .Do ("version_bounds" , func () (any , error ) {
12961302 // 二次检查,避免排队的 goroutine 重复查询
1297- if cached , ok := minVersionCache .Load ().(* cachedMinVersion ); ok {
1303+ if cached , ok := versionBoundsCache .Load ().(* cachedVersionBounds ); ok {
12981304 if time .Now ().UnixNano () < cached .expiresAt {
1299- return cached .value , nil
1305+ return bounds { cached .min , cached . max } , nil
13001306 }
13011307 }
13021308 // 使用独立 context:断开请求取消链,避免客户端断连导致空值被长期缓存
1303- dbCtx , cancel := context .WithTimeout (context .WithoutCancel (ctx ), minVersionDBTimeout )
1309+ dbCtx , cancel := context .WithTimeout (context .WithoutCancel (ctx ), versionBoundsDBTimeout )
13041310 defer cancel ()
1305- value , err := s .settingRepo .GetValue (dbCtx , SettingKeyMinClaudeCodeVersion )
1311+ values , err := s .settingRepo .GetMultiple (dbCtx , []string {
1312+ SettingKeyMinClaudeCodeVersion ,
1313+ SettingKeyMaxClaudeCodeVersion ,
1314+ })
13061315 if err != nil {
13071316 // fail-open: DB 错误时不阻塞请求,但记录日志并使用短 TTL 快速重试
1308- slog .Warn ("failed to get min claude code version setting, skipping version check" , "error" , err )
1309- minVersionCache .Store (& cachedMinVersion {
1310- value : "" ,
1311- expiresAt : time .Now ().Add (minVersionErrorTTL ).UnixNano (),
1317+ slog .Warn ("failed to get claude code version bounds setting, skipping version check" , "error" , err )
1318+ versionBoundsCache .Store (& cachedVersionBounds {
1319+ min : "" ,
1320+ max : "" ,
1321+ expiresAt : time .Now ().Add (versionBoundsErrorTTL ).UnixNano (),
13121322 })
1313- return "" , nil
1323+ return bounds { "" , "" } , nil
13141324 }
1315- minVersionCache .Store (& cachedMinVersion {
1316- value : value ,
1317- expiresAt : time .Now ().Add (minVersionCacheTTL ).UnixNano (),
1325+ b := bounds {
1326+ min : values [SettingKeyMinClaudeCodeVersion ],
1327+ max : values [SettingKeyMaxClaudeCodeVersion ],
1328+ }
1329+ versionBoundsCache .Store (& cachedVersionBounds {
1330+ min : b .min ,
1331+ max : b .max ,
1332+ expiresAt : time .Now ().Add (versionBoundsCacheTTL ).UnixNano (),
13181333 })
1319- return value , nil
1334+ return b , nil
13201335 })
13211336 if err != nil {
1322- return ""
1337+ return "" , ""
13231338 }
1324- ver , ok := result .(string )
1339+ b , ok := result .(bounds )
13251340 if ! ok {
1326- return ""
1341+ return "" , ""
13271342 }
1328- return ver
1343+ return b . min , b . max
13291344}
13301345
13311346// GetRectifierSettings 获取请求整流器配置
0 commit comments