refactor: 统一代理配置优先级链(key>provider>系统默认)并复用 HTTP 连接池

- 引入 resolve_proxy_param / build_proxy_client_kwargs 工具函数,统一
  httpx 客户端的代理+SSL+超时配置,替换各模块中零散的 get_ssl_context() 调用
- 所有涉及上游请求的模块(provider_query, usage replay, endpoint check,
  model fetch, OAuth, Vertex Auth, Gemini Files/Video 等)改用
  resolve_effective_proxy 按 key > provider > 系统默认优先级解析代理
- 流式请求改用 HTTPClientPool.get_upstream_client 复用连接池,移除各处
  http_client.aclose() 避免关闭共享客户端
- StreamProcessor._cleanup 不再关闭池中客户端,仅清理响应上下文
- 前端 EndpointFormDialog 增加 body_rules 帮助说明 Popover
- Mock handler 补充 OAuth 字段、endpoint extras 及新增 mock 路由
This commit is contained in:
fawney19
2026-02-11 03:16:53 +08:00
parent cd0acf1e6b
commit bda03d187e
22 changed files with 749 additions and 322 deletions

View File

@@ -294,9 +294,71 @@
<div
v-if="getEndpointEditBodyRules(endpoint.id).length > 0"
class="text-xs text-muted-foreground px-2"
class="flex items-center gap-1 text-xs text-muted-foreground px-2"
>
<code class="bg-muted px-1 rounded">.</code> 嵌套字段 / <code class="bg-muted px-1 rounded">[N]</code> 数组索引;值为 JSON 格式
<span><code class="bg-muted px-1 rounded">.</code> 嵌套字段 / <code class="bg-muted px-1 rounded">[N]</code> 数组索引;值为 JSON 格式</span>
<div class="flex-1" />
<Popover
:open="bodyRuleHelpOpenEndpointId === endpoint.id"
@update:open="(v: boolean) => setBodyRuleHelpOpen(endpoint.id, v)"
>
<PopoverTrigger as-child>
<button
type="button"
class="shrink-0 h-6 w-6 inline-flex items-center justify-center rounded-md hover:bg-muted/60"
title="规则说明"
aria-label="规则说明"
>
<HelpCircle class="w-3.5 h-3.5 text-muted-foreground/60" />
</button>
</PopoverTrigger>
<PopoverContent
side="bottom"
align="end"
:side-offset="6"
class="w-80 p-3 !z-[90]"
>
<div class="text-xs space-y-2">
<div>
<div class="font-medium mb-0.5">
路径语法
</div>
<div class="text-muted-foreground">
<code>metadata.user_id</code> 嵌套字段<br>
<code>messages[0].content</code> 数组索引<br>
<code>config\.v1.key</code> 转义点号
</div>
</div>
<div>
<div class="font-medium mb-0.5">
值格式 (JSON)
</div>
<div class="text-muted-foreground">
<code>123</code> 数字 / <code>"text"</code> 字符串 / <code>true</code> 布尔<br>
<code>{"k":"v"}</code> 对象 / <code>[1,2]</code> 数组 / <code>null</code><br>
<code v-pre>{{$original}}</code> 引用原值
</div>
</div>
<div>
<div class="font-medium mb-0.5">
条件运算符
</div>
<div class="text-muted-foreground">
<code>eq</code> <code>neq</code> 等于/不等于<br>
<code>gt</code> <code>lt</code> <code>gte</code> <code>lte</code> 大小比较<br>
<code>starts_with</code> <code>ends_with</code> <code>contains</code> 字符串匹配<br>
<code>matches</code> 正则匹配<br>
<code>exists</code> <code>not_exists</code> 字段存在性<br>
<code>in</code> 在列表中(值填 <code>["a","b"]</code><br>
<code>type_is</code> 类型判断string/number/boolean/array/object/null
</div>
</div>
<div class="text-muted-foreground">
规则按顺序执行,前面的修改对后续规则可见。
</div>
</div>
</PopoverContent>
</Popover>
</div>
<!-- 请求体规则列表 - 次要色边框 -->
@@ -338,6 +400,16 @@
</SelectItem>
</SelectContent>
</Select>
<Button
variant="ghost"
size="icon"
class="h-7 w-7 shrink-0"
:class="rule.conditionEnabled ? 'text-primary' : ''"
title="条件触发"
@click="toggleBodyRuleCondition(endpoint.id, index)"
>
<Filter class="w-3 h-3" />
</Button>
<template v-if="rule.action === 'set'">
<Input
:model-value="rule.path"
@@ -452,16 +524,6 @@
:title="getRegexPatternValidationTip(rule)"
/>
</template>
<Button
variant="ghost"
size="icon"
class="h-7 w-7 shrink-0"
:class="rule.conditionEnabled ? 'text-primary' : ''"
title="条件触发"
@click="toggleBodyRuleCondition(endpoint.id, index)"
>
<Filter class="w-3 h-3" />
</Button>
<Button
variant="ghost"
size="icon"
@@ -664,8 +726,11 @@ import {
Collapsible,
CollapsibleTrigger,
CollapsibleContent,
Popover,
PopoverTrigger,
PopoverContent,
} from '@/components/ui'
import { Settings, Trash2, Check, X, Power, ChevronRight, Plus, Shuffle, RotateCcw, Radio, CheckCircle, Save, Filter } from 'lucide-vue-next'
import { Settings, Trash2, Check, X, Power, ChevronRight, Plus, Shuffle, RotateCcw, Radio, CheckCircle, Save, Filter, HelpCircle } from 'lucide-vue-next'
import { useToast } from '@/composables/useToast'
import { log } from '@/utils/logger'
import AlertDialog from '@/components/common/AlertDialog.vue'
@@ -815,6 +880,13 @@ const endpointRulesExpanded = ref<Record<string, boolean>>({})
// 请求体规则 Select 的展开状态
const bodyRuleSelectOpen = ref<Record<string, boolean>>({})
// 请求体规则说明 Popover 的展开状态
const bodyRuleHelpOpenEndpointId = ref<string | null>(null)
function setBodyRuleHelpOpen(endpointId: string, open: boolean) {
bodyRuleHelpOpenEndpointId.value = open ? endpointId : null
}
// 每个端点的编辑状态(内联编辑)
const endpointEditStates = ref<Record<string, EndpointEditState>>({})
@@ -1783,6 +1855,7 @@ onMounted(() => {
// 监听 props 变化
watch(() => props.modelValue, (open) => {
bodyRuleHelpOpenEndpointId.value = null
if (open) {
localEndpoints.value = [...(props.endpoints || [])]
// 清空编辑状态,重新从端点加载