Skip to content

Fix key selection errors and enhance limit mode with resets - #14

Merged
LoSealL merged 5 commits into
developfrom
dev
Sep 10, 2026
Merged

LoSealL merged 5 commits into
developfrom
dev

Conversation

@LoSealL

@LoSealL LoSealL commented Sep 10, 2026

Copy link
Copy Markdown

No description provided.

- ManageMultiKeys 各 key 变更操作后回算渠道状态:全部密钥禁用且无冷却时
  渠道自动禁用并退出 abilities 选择池,重新启用任一密钥时自动恢复
- 分发器不再吞掉 SetupContextForSelectedChannel 的错误:选中渠道暂无可用
  密钥时按优先级逐级降级重选,全部不可用时返回 503 与明确错误信息
- LimitPattern 新增 reset_cycle(daily / weekly:N / monthly:N),冷却优先级:
  错误信息捕获的重置时间 > 周期边界 > 默认分钟数;当月无 N 号时顺延到下月 1 号
- 新增阿里云 token-plan 额度耗尽预设(无重置时间,按每月 1 号冷却)
- 前端校验不再强制 (?P<reset>...) 捕获组,日期布局行仅在含捕获组时显示,
  新增重置周期下拉与日号输入
Expand all / Export Now / 日志导出任务提示 / OAuth / Resize column / Top
均为代码中已使用但 locale 文件缺失的 key
- 手动「测试连接」失败接入 processChannelError,与正常转发、定时巡检一致:
  先按多 Key 限额模式给命中密钥设置冷却,未命中且满足自动禁用条件时禁用该密钥
- GetRootUser 与 CheckNotificationLimit 增加空依赖防护,避免后台通知协程
  在 DB/Redis 未初始化时 panic
Copilot AI lite review requested due to automatic review settings September 10, 2026 11:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The distributor fallback selection and root-user lookup have edge cases that can still fail or terminate early, and the new fallback tests can be flaky with SQLite :memory: without a single-connection pool.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR improves multi-key channel resiliency and “limit pattern” cooldown behavior by adding reset-cycle support (daily/weekly/monthly) and by making distributor/key-management flows handle “no available key” scenarios more safely.

Changes:

  • Add reset_cycle support for multi-key limit patterns (frontend editor + backend cooldown computation + tests).
  • Improve multi-key channel status syncing and distributor fallback when selected channels have no usable keys.
  • Align manual “test connection” error handling with relay behavior (cooldown/auto-disable) and add i18n strings.
File summaries
File Description
web/src/i18n/locales/zh.json Add new UI strings for reset-cycle and limit-pattern editor updates.
web/src/i18n/locales/zh-TW.json Add new UI strings for reset-cycle and limit-pattern editor updates.
web/src/i18n/locales/vi.json Add new UI strings for reset-cycle and limit-pattern editor updates.
web/src/i18n/locales/ru.json Add new UI strings for reset-cycle and limit-pattern editor updates.
web/src/i18n/locales/ja.json Add new UI strings for reset-cycle and limit-pattern editor updates.
web/src/i18n/locales/fr.json Add new UI strings for reset-cycle and limit-pattern editor updates.
web/src/i18n/locales/en.json Add new UI strings for reset-cycle and limit-pattern editor updates.
web/src/features/channels/types.ts Extend LimitPattern schema with optional reset_cycle; relax date_layout to allow empty.
web/src/features/channels/lib/limit-pattern-utils.ts Add reset-cycle parsing/formatting helpers and a new preset that relies on cycle-based reset.
web/src/features/channels/lib/limit-pattern-utils.test.ts Expand unit tests to cover reset-cycle helpers and preset behavior.
web/src/features/channels/lib/channel-form.ts Update channel form schema to allow empty date_layout and include optional reset_cycle.
web/src/features/channels/components/limit-patterns-editor.tsx Update UI to support reset cycles and optional reset capture group patterns.
service/notify-limit.go Avoid Redis limiter usage when Redis client isn’t initialized.
service/channel.go Add nextCycleResetTime and use ResetCycle as a cooldown strategy when no reset timestamp is captured.
service/channel_limit_test.go Add tests for cycle reset behavior and precedence rules.
model/user.go Guard GetRootUser when DB is nil to avoid background-task panics.
model/channel.go Add ResetCycle to LimitPattern; add SyncMultiKeyChannelStatus and unify the “all keys disabled” reason.
model/channel_status_test.go Add tests for SyncMultiKeyChannelStatus behavior and DB/abilities persistence.
middleware/distributor.go Add distributor re-selection logic when a selected channel has no available key; add new i18n error path.
middleware/distributor_fallback_test.go New tests covering distributor fallback selection behavior.
i18n/locales/zh-TW.yaml Add backend i18n message for “channel has no available key”.
i18n/locales/zh-CN.yaml Add backend i18n message for “channel has no available key”.
i18n/locales/en.yaml Add backend i18n message for “channel has no available key”.
i18n/keys.go Add new backend i18n key constant.
controller/channel.go Ensure multi-key management operations re-sync channel status after key state changes.
controller/channel-test.go Preserve upstream status codes in manual tests; route failures through unified error processing.
controller/channel_test_internal_test.go Add integration-style tests for manual test error handling (cooldown/auto-disable).
Review details
  • Files reviewed: 27/27 changed files
  • Comments generated: 4
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread model/user.go
Comment on lines 1336 to 1343
func GetRootUser() (user *User) {
// 异步通知等后台任务可能在 DB 尚未初始化或已被回收后调用,此时视为无 root 用户
if DB == nil {
return &User{}
}
DB.Where("role = ?", common.RoleRootUser).First(&user)
return user
}
Comment thread middleware/distributor.go
Comment on lines +200 to +219
func selectChannelWithAvailableKey(c *gin.Context, failedChannelId int, modelName string) *model.Channel {
usingGroup := common.GetContextKeyString(c, constant.ContextKeyUsingGroup)
tried := map[int]bool{failedChannelId: true}
for retry := 1; ; retry++ {
next, _, err := service.CacheGetRandomSatisfiedChannel(&service.RetryParam{
Ctx: c,
ModelName: modelName,
TokenGroup: usingGroup,
RequestPath: c.Request.URL.Path,
Retry: common.GetPointer(retry),
})
if err != nil || next == nil || tried[next.Id] {
return nil
}
tried[next.Id] = true
if SetupContextForSelectedChannel(c, next, modelName) == nil {
return next
}
}
}
Comment thread middleware/distributor_fallback_test.go
Comment on lines 145 to 150
<Input
value={pattern.regex}
placeholder={t('Regex with (?P<reset>...) group')}
placeholder={t('Regex with optional (?P<reset>...) group')}
className='font-mono text-sm'
onChange={(e) => updatePattern(index, { regex: e.target.value })}
aria-invalid={!validation.valid}
- 降级重选不再因随机重复渠道而提前放弃:同层内继续重试,仅当尝试次数
  明显超过已见渠道数仍无新渠道时终止(PR #14 评论 2)
- 降级重选测试的 SQLite :memory: fixture 限制单连接,避免多连接各自独立
  内存库导致的偶发失败(PR #14 评论 3)
- 限额模式编辑器在正则含 (?P<reset>) 捕获组而时间格式为空时给出警告
  (后端会忽略捕获的时间戳,回退周期/分钟数)(PR #14 评论 4)
- 评论 1(GetRootUser 在无 root 用户时返回 nil)经实测不成立:GORM 在
  record-not-found 时分配零值结构体,ToBaseUser 不会 panic

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new distributor fallback helper can skip same-priority candidates due to retry-tier advancement on repeats, which can incorrectly fall through to lower priority even when a same-tier channel has an available key.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 27/27 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread middleware/distributor.go
Comment on lines +205 to +209
for retry := 1; ; retry++ {
attempts++
next, _, err := service.CacheGetRandomSatisfiedChannel(&service.RetryParam{
Ctx: c,
ModelName: modelName,
Comment on lines +26 to +50
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
if err != nil {
panic("failed to open test db: " + err.Error())
}
// :memory: 下每个连接是独立库,单连接保证所有读写落在同一份数据上
sqlDB, err := db.DB()
if err != nil {
panic("failed to get sql.DB: " + err.Error())
}
sqlDB.SetMaxOpenConns(1)
model.DB = db
common.SetDatabaseTypes(common.DatabaseTypeSQLite, common.DatabaseTypeSQLite)
if err := db.AutoMigrate(&model.Channel{}, &model.Ability{}); err != nil {
panic("failed to migrate test db: " + err.Error())
}
}
require.NoError(t, model.DB.Exec("DELETE FROM abilities").Error)
require.NoError(t, model.DB.Exec("DELETE FROM channels").Error)

memoryCacheEnabled := common.MemoryCacheEnabled
common.MemoryCacheEnabled = true
t.Cleanup(func() {
common.MemoryCacheEnabled = memoryCacheEnabled
model.InitChannelCache()
})
@LoSealL
LoSealL merged commit 5da243a into develop Sep 10, 2026
3 checks passed
@LoSealL
LoSealL deleted the dev branch September 10, 2026 14:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants