feat: 支持固定类型 Provider OAuth 授权

- 新增 provider_type 字段区分自定义/预置 Provider 类型(claude_code/codex/gemini_cli/antigravity)
- 实现完整 OAuth 2.0 授权流程:start(生成授权 URL + PKCE)、complete(换取 token)、refresh
- 前端 KeyFormDialog 添加 OAuth 授权 UI,支持开始授权、粘贴回调 URL、完成授权、强制刷新
- 请求时自动检测 token 过期并刷新(120s 预留窗口 + Redis 分布式锁防并发)
- 固定类型 Provider 自动创建预置端点并锁定 base_url/custom_path
- 数据库迁移:添加 providers.provider_type,扩展 api_key 列为 TEXT
- 可选依赖 tls-client 用于 Claude token 请求的 TLS 指纹伪装
This commit is contained in:
AAEE86
2026-02-04 10:24:25 +08:00
parent f6dac1c38a
commit e4fdc65e52
25 changed files with 1818 additions and 36 deletions
@@ -27,13 +27,29 @@
/>
</div>
<div class="space-y-1.5">
<Label for="website">主站链接</Label>
<Input
id="website"
v-model="form.website"
placeholder="https://..."
type="url"
/>
<Label>提供商类型</Label>
<Select
v-model="form.provider_type"
v-model:open="providerTypeSelectOpen"
:disabled="isEditMode"
>
<SelectTrigger>
<SelectValue placeholder="请选择" />
</SelectTrigger>
<SelectContent>
<SelectItem value="custom">自定义</SelectItem>
<SelectItem value="claude_code">ClaudeCode</SelectItem>
<SelectItem value="codex">Codex</SelectItem>
<SelectItem value="gemini_cli">GeminiCli</SelectItem>
<SelectItem value="antigravity">Antigravity</SelectItem>
</SelectContent>
</Select>
<p
v-if="!isEditMode && form.provider_type !== 'custom'"
class="text-xs text-muted-foreground"
>
固定类型 Provider 将自动创建并锁定端点(base_url/custom_path 不可修改)。
</p>
</div>
</div>
@@ -45,6 +61,15 @@
placeholder="提供商描述(可选)"
/>
</div>
<div class="space-y-1.5">
<Label for="website">主站链接</Label>
<Input
id="website"
v-model="form.website"
placeholder="https://example.com(可选)"
/>
</div>
</div>
<!-- 计费与限流 / 请求配置 -->
@@ -297,6 +322,7 @@ const emit = defineEmits<{
const { success, error: showError } = useToast()
const loading = ref(false)
const providerTypeSelectOpen = ref(false)
const billingTypeSelectOpen = ref(false)
// 内部状态
@@ -305,6 +331,7 @@ const internalOpen = computed(() => props.modelValue)
// 表单数据
const form = ref({
name: '',
provider_type: 'custom' as 'custom' | 'claude_code' | 'codex' | 'gemini_cli' | 'antigravity',
description: '',
website: '',
// 计费配置
@@ -335,6 +362,7 @@ const form = ref({
function resetForm() {
form.value = {
name: '',
provider_type: 'custom',
description: '',
website: '',
billing_type: 'pay_as_you_go',
@@ -367,6 +395,7 @@ function loadProviderData() {
const proxy = props.provider.proxy
form.value = {
name: props.provider.name,
provider_type: props.provider.provider_type || 'custom',
description: props.provider.description || '',
website: props.provider.website || '',
billing_type: (props.provider.billing_type as 'monthly_quota' | 'pay_as_you_go' | 'free_tier') || 'pay_as_you_go',
@@ -430,6 +459,7 @@ const handleSubmit = async () => {
const payload = {
name: form.value.name,
provider_type: form.value.provider_type,
description: form.value.description || undefined,
website: form.value.website || undefined,
billing_type: form.value.billing_type,