feat: 实现 GlobalModel 别名匹配系统

主要更改:
- GlobalModel 支持 model_aliases 配置,允许使用正则表达式定义别名规则
- Provider Key 的 allowed_models 现在可以通过别名规则匹配 GlobalModel
- 新增 ModelAliasesTab 组件用于管理模型别名配置
- Provider 详情页新增别名映射预览功能,展示 Key 白名单与 GlobalModel 别名的匹配关系
- 路由预览 API 返回 Key 的 allowed_models 信息

安全特性:
- 使用 regex 库的原生超时保护(100ms)防止 ReDoS 攻击
- 别名规则数量限制(50 条/模型)和长度限制(200 字符)
- 别名映射预览 API 添加超时保护和结果截断

其他改进:
- GlobalModel 更新/删除时使用行级锁防止并发竞态
- 缓存失效逻辑优化,支持异步清理和正则缓存清空
- 路由 Tab 布局重构,使用 flexbox 替代绝对定位
This commit is contained in:
fawney19
2026-01-13 16:04:15 +08:00
parent 9fea71a70c
commit 85decd7487
21 changed files with 3845 additions and 2308 deletions

View File

@@ -104,6 +104,19 @@
<span class="hidden sm:inline">链路控制</span>
<span class="sm:hidden">链路</span>
</button>
<button
type="button"
class="flex-1 px-2 sm:px-4 py-2 text-xs sm:text-sm font-medium rounded-md transition-all duration-200"
:class="[
detailTab === 'aliases'
? 'bg-primary text-primary-foreground shadow-sm'
: 'text-muted-foreground hover:text-foreground hover:bg-background/50'
]"
@click="detailTab = 'aliases'"
>
<span class="hidden sm:inline">模型映射</span>
<span class="sm:hidden">映射</span>
</button>
</div>
<!-- Tab 内容 -->
@@ -419,6 +432,17 @@
@delete-provider="handleDeleteProviderFromRouting"
/>
</div>
<!-- Tab 3: 模型映射 -->
<div v-show="detailTab === 'aliases'">
<ModelAliasesTab
v-if="model"
:global-model-id="model.id"
:model-name="model.name"
:aliases="model.config?.model_aliases || []"
@update="handleAliasesUpdate"
/>
</div>
</div>
</Card>
</div>
@@ -456,6 +480,7 @@ import TableRow from '@/components/ui/table-row.vue'
import TableHead from '@/components/ui/table-head.vue'
import TableCell from '@/components/ui/table-cell.vue'
import RoutingTab from './RoutingTab.vue'
import ModelAliasesTab from './ModelAliasesTab.vue'
// 使用外部类型定义
import type { GlobalModelResponse } from '@/api/global-models'
@@ -518,6 +543,13 @@ function refreshRoutingData() {
routingTabRef.value?.loadRoutingData?.()
}
// 处理模型别名更新
function handleAliasesUpdate(_aliases: string[]) {
// 别名已在 ModelAliasesTab 内部保存到服务器
// 刷新路由数据以反映可能的候选变化
refreshRoutingData()
}
// 暴露刷新方法给父组件
defineExpose({
refreshRoutingData