feat(group-filter): 分组账号过滤控制 — require_oauth_only + require_privacy_set

为 OpenAI/Antigravity/Anthropic/Gemini 分组新增两个布尔控制字段:
- require_oauth_only: 创建/更新账号绑定分组时拒绝 apikey 类型加入
- require_privacy_set: 调度选号时跳过 privacy 未成功设置的账号并标记 error

后端:Ent schema 新增字段 + 迁移、Group CRUD 全链路透传、
      gateway_service 与 openai_account_scheduler 两套调度路径过滤
前端:创建/编辑表单 toggle 开关(OpenAI/Antigravity/Anthropic/Gemini 平台可见)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
QTom
2026-03-27 18:02:48 +08:00
parent 318aa5e0d3
commit aeed2eb9ad
26 changed files with 708 additions and 6 deletions

View File

@@ -399,6 +399,8 @@ export interface Group {
fallback_group_id_on_invalid_request: number | null
// OpenAI Messages 调度开关(用户侧需要此字段判断是否展示 Claude Code 教程)
allow_messages_dispatch?: boolean
require_oauth_only: boolean
require_privacy_set: boolean
created_at: string
updated_at: string
}
@@ -510,6 +512,8 @@ export interface CreateGroupRequest {
mcp_xml_inject?: boolean
simulate_claude_max_enabled?: boolean
supported_model_scopes?: string[]
require_oauth_only?: boolean
require_privacy_set?: boolean
// 从指定分组复制账号
copy_accounts_from_group_ids?: number[]
}
@@ -539,6 +543,8 @@ export interface UpdateGroupRequest {
mcp_xml_inject?: boolean
simulate_claude_max_enabled?: boolean
supported_model_scopes?: string[]
require_oauth_only?: boolean
require_privacy_set?: boolean
copy_accounts_from_group_ids?: number[]
}

View File

@@ -792,6 +792,61 @@
</div>
</div>
<!-- 账号过滤控制 (OpenAI/Antigravity/Anthropic/Gemini) -->
<div v-if="['openai', 'antigravity', 'anthropic', 'gemini'].includes(createForm.platform)" class="border-t border-gray-200 dark:border-dark-400 pt-4 mt-4 space-y-4">
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">账号过滤控制</h4>
<!-- require_oauth_only toggle -->
<div class="flex items-center justify-between">
<div>
<label class="text-sm text-gray-600 dark:text-gray-400">仅允许 OAuth 账号</label>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
{{ createForm.require_oauth_only ? '已启用 — 排除 API Key 类型账号' : '未启用' }}
</p>
</div>
<button
type="button"
@click="createForm.require_oauth_only = !createForm.require_oauth_only"
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:class="
createForm.require_oauth_only ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
"
>
<span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="
createForm.require_oauth_only ? 'translate-x-6' : 'translate-x-1'
"
/>
</button>
</div>
<!-- require_privacy_set toggle -->
<div class="flex items-center justify-between">
<div>
<label class="text-sm text-gray-600 dark:text-gray-400">仅允许隐私保护已设置的账号</label>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
{{ createForm.require_privacy_set ? '已启用 — Privacy 未设置的账号将被排除' : '未启用' }}
</p>
</div>
<button
type="button"
@click="createForm.require_privacy_set = !createForm.require_privacy_set"
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:class="
createForm.require_privacy_set ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
"
>
<span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="
createForm.require_privacy_set ? 'translate-x-6' : 'translate-x-1'
"
/>
</button>
</div>
</div>
<!-- 无效请求兜底 anthropic/antigravity 平台且非订阅分组 -->
<div
v-if="['anthropic', 'antigravity'].includes(createForm.platform) && createForm.subscription_type !== 'subscription'"
@@ -1527,6 +1582,61 @@
</div>
</div>
<!-- 账号过滤控制 (OpenAI/Antigravity/Anthropic/Gemini) -->
<div v-if="['openai', 'antigravity', 'anthropic', 'gemini'].includes(editForm.platform)" class="border-t border-gray-200 dark:border-dark-400 pt-4 mt-4 space-y-4">
<h4 class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-3">账号过滤控制</h4>
<!-- require_oauth_only toggle -->
<div class="flex items-center justify-between">
<div>
<label class="text-sm text-gray-600 dark:text-gray-400">仅允许 OAuth 账号</label>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
{{ editForm.require_oauth_only ? '已启用 — 排除 API Key 类型账号' : '未启用' }}
</p>
</div>
<button
type="button"
@click="editForm.require_oauth_only = !editForm.require_oauth_only"
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:class="
editForm.require_oauth_only ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
"
>
<span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="
editForm.require_oauth_only ? 'translate-x-6' : 'translate-x-1'
"
/>
</button>
</div>
<!-- require_privacy_set toggle -->
<div class="flex items-center justify-between">
<div>
<label class="text-sm text-gray-600 dark:text-gray-400">仅允许隐私保护已设置的账号</label>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
{{ editForm.require_privacy_set ? '已启用 — Privacy 未设置的账号将被排除' : '未启用' }}
</p>
</div>
<button
type="button"
@click="editForm.require_privacy_set = !editForm.require_privacy_set"
class="relative inline-flex h-6 w-12 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none"
:class="
editForm.require_privacy_set ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-600'
"
>
<span
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
:class="
editForm.require_privacy_set ? 'translate-x-6' : 'translate-x-1'
"
/>
</button>
</div>
</div>
<!-- 无效请求兜底 anthropic/antigravity 平台且非订阅分组 -->
<div
v-if="['anthropic', 'antigravity'].includes(editForm.platform) && editForm.subscription_type !== 'subscription'"
@@ -2063,6 +2173,9 @@ const createForm = reactive({
// OpenAI Messages 调度配置(仅 openai 平台使用)
allow_messages_dispatch: false,
default_mapped_model: 'gpt-5.4',
// 账号过滤控制OpenAI/Antigravity 平台)
require_oauth_only: false,
require_privacy_set: false,
// 模型路由开关
model_routing_enabled: false,
// 支持的模型系列(仅 antigravity 平台)
@@ -2307,6 +2420,9 @@ const editForm = reactive({
// OpenAI Messages 调度配置(仅 openai 平台使用)
allow_messages_dispatch: false,
default_mapped_model: '',
// 账号过滤控制OpenAI/Antigravity 平台)
require_oauth_only: false,
require_privacy_set: false,
// 模型路由开关
model_routing_enabled: false,
// 支持的模型系列(仅 antigravity 平台)
@@ -2452,6 +2568,8 @@ const closeCreateModal = () => {
createForm.fallback_group_id = null
createForm.fallback_group_id_on_invalid_request = null
createForm.allow_messages_dispatch = false
createForm.require_oauth_only = false
createForm.require_privacy_set = false
createForm.default_mapped_model = 'gpt-5.4'
createForm.supported_model_scopes = ['claude', 'gemini_text', 'gemini_image']
createForm.mcp_xml_inject = true
@@ -2539,6 +2657,8 @@ const handleEdit = async (group: AdminGroup) => {
editForm.fallback_group_id = group.fallback_group_id
editForm.fallback_group_id_on_invalid_request = group.fallback_group_id_on_invalid_request
editForm.allow_messages_dispatch = group.allow_messages_dispatch || false
editForm.require_oauth_only = group.require_oauth_only ?? false
editForm.require_privacy_set = group.require_privacy_set ?? false
editForm.default_mapped_model = group.default_mapped_model || ''
editForm.model_routing_enabled = group.model_routing_enabled || false
editForm.supported_model_scopes = group.supported_model_scopes || ['claude', 'gemini_text', 'gemini_image']
@@ -2647,6 +2767,10 @@ watch(
createForm.allow_messages_dispatch = false
createForm.default_mapped_model = ''
}
if (!['openai', 'antigravity', 'anthropic', 'gemini'].includes(newVal)) {
createForm.require_oauth_only = false
createForm.require_privacy_set = false
}
}
)