feat(gateway): 系统设置控制未分组 Key 调度 — Handler 层中间件拦截

新增系统设置 allow_ungrouped_key_scheduling(默认关闭),
未分组的 API Key 在网关请求时直接返回 403,
由 RequireGroupAssignment 中间件统一拦截,
支持 Anthropic / Google 两种错误格式响应。

全栈实现:常量 → 结构体 → 解析/更新/初始化 → DTO → 管理接口 →
中间件 → 路由注册 → 前端设置界面 + i18n。
This commit is contained in:
QTom
2026-03-03 19:56:27 +08:00
parent ccf6a921c7
commit 0c7cbe3566
13 changed files with 150 additions and 7 deletions

View File

@@ -737,6 +737,34 @@
</div>
</div>
<!-- Gateway Scheduling Settings -->
<div class="card">
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">
{{ t('admin.settings.scheduling.title') }}
</h2>
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
{{ t('admin.settings.scheduling.description') }}
</p>
</div>
<div class="p-6">
<div class="flex items-center justify-between">
<div>
<label class="text-sm font-medium text-gray-700 dark:text-gray-300">
{{ t('admin.settings.scheduling.allowUngroupedKey') }}
</label>
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">
{{ t('admin.settings.scheduling.allowUngroupedKeyHint') }}
</p>
</div>
<label class="toggle">
<input v-model="form.allow_ungrouped_key_scheduling" type="checkbox" />
<span class="toggle-slider"></span>
</label>
</div>
</div>
</div>
<!-- Site Settings -->
<div class="card">
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
@@ -1438,7 +1466,9 @@ const form = reactive<SettingsForm>({
ops_query_mode_default: 'auto',
ops_metrics_interval_seconds: 60,
// Claude Code version check
min_claude_code_version: ''
min_claude_code_version: '',
// 分组隔离
allow_ungrouped_key_scheduling: false
})
const defaultSubscriptionGroupOptions = computed<DefaultSubscriptionGroupOption[]>(() =>
@@ -1623,7 +1653,8 @@ async function saveSettings() {
fallback_model_antigravity: form.fallback_model_antigravity,
enable_identity_patch: form.enable_identity_patch,
identity_patch_prompt: form.identity_patch_prompt,
min_claude_code_version: form.min_claude_code_version
min_claude_code_version: form.min_claude_code_version,
allow_ungrouped_key_scheduling: form.allow_ungrouped_key_scheduling
}
const updated = await adminAPI.settings.updateSettings(payload)
Object.assign(form, updated)