feat(gateway): 添加流超时处理机制
- 添加 StreamTimeoutSettings 配置结构体和系统设置 - 实现 TimeoutCounterCache Redis 计数器用于累计超时次数 - 在 RateLimitService 添加 HandleStreamTimeout 方法 - 在 gateway_service、openai_gateway_service、antigravity_gateway_service 中调用超时处理 - 添加后端 API 端点 GET/PUT /admin/settings/stream-timeout - 添加前端配置界面到系统设置页面 - 支持配置:启用开关、超时阈值、处理方式、暂停时长、触发阈值、阈值窗口 默认配置: - 启用:true - 超时阈值:60秒 - 处理方式:临时不可调度 - 暂停时长:5分钟 - 触发阈值:3次 - 阈值窗口:10分钟
This commit is contained in:
@@ -147,6 +147,161 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stream Timeout 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.streamTimeout.title') }}
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.streamTimeout.description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-5 p-6">
|
||||
<!-- Loading State -->
|
||||
<div v-if="streamTimeoutLoading" class="flex items-center gap-2 text-gray-500">
|
||||
<div class="h-4 w-4 animate-spin rounded-full border-b-2 border-primary-600"></div>
|
||||
{{ t('common.loading') }}
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<!-- Enable Stream Timeout -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label class="font-medium text-gray-900 dark:text-white">{{
|
||||
t('admin.settings.streamTimeout.enabled')
|
||||
}}</label>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.streamTimeout.enabledHint') }}
|
||||
</p>
|
||||
</div>
|
||||
<Toggle v-model="streamTimeoutForm.enabled" />
|
||||
</div>
|
||||
|
||||
<!-- Settings - Only show when enabled -->
|
||||
<div
|
||||
v-if="streamTimeoutForm.enabled"
|
||||
class="space-y-4 border-t border-gray-100 pt-4 dark:border-dark-700"
|
||||
>
|
||||
<!-- Timeout Seconds -->
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('admin.settings.streamTimeout.timeoutSeconds') }}
|
||||
</label>
|
||||
<input
|
||||
v-model.number="streamTimeoutForm.timeout_seconds"
|
||||
type="number"
|
||||
min="30"
|
||||
max="300"
|
||||
class="input w-32"
|
||||
/>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.streamTimeout.timeoutSecondsHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Action -->
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('admin.settings.streamTimeout.action') }}
|
||||
</label>
|
||||
<select v-model="streamTimeoutForm.action" class="input w-64">
|
||||
<option value="temp_unsched">{{ t('admin.settings.streamTimeout.actionTempUnsched') }}</option>
|
||||
<option value="error">{{ t('admin.settings.streamTimeout.actionError') }}</option>
|
||||
<option value="none">{{ t('admin.settings.streamTimeout.actionNone') }}</option>
|
||||
</select>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.streamTimeout.actionHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Temp Unsched Minutes (only show when action is temp_unsched) -->
|
||||
<div v-if="streamTimeoutForm.action === 'temp_unsched'">
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('admin.settings.streamTimeout.tempUnschedMinutes') }}
|
||||
</label>
|
||||
<input
|
||||
v-model.number="streamTimeoutForm.temp_unsched_minutes"
|
||||
type="number"
|
||||
min="1"
|
||||
max="60"
|
||||
class="input w-32"
|
||||
/>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.streamTimeout.tempUnschedMinutesHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Threshold Count -->
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('admin.settings.streamTimeout.thresholdCount') }}
|
||||
</label>
|
||||
<input
|
||||
v-model.number="streamTimeoutForm.threshold_count"
|
||||
type="number"
|
||||
min="1"
|
||||
max="10"
|
||||
class="input w-32"
|
||||
/>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.streamTimeout.thresholdCountHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Threshold Window Minutes -->
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ t('admin.settings.streamTimeout.thresholdWindowMinutes') }}
|
||||
</label>
|
||||
<input
|
||||
v-model.number="streamTimeoutForm.threshold_window_minutes"
|
||||
type="number"
|
||||
min="1"
|
||||
max="60"
|
||||
class="input w-32"
|
||||
/>
|
||||
<p class="mt-1.5 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('admin.settings.streamTimeout.thresholdWindowMinutesHint') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save Button -->
|
||||
<div class="flex justify-end border-t border-gray-100 pt-4 dark:border-dark-700">
|
||||
<button
|
||||
type="button"
|
||||
@click="saveStreamTimeoutSettings"
|
||||
:disabled="streamTimeoutSaving"
|
||||
class="btn btn-primary btn-sm"
|
||||
>
|
||||
<svg
|
||||
v-if="streamTimeoutSaving"
|
||||
class="mr-1 h-4 w-4 animate-spin"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle
|
||||
class="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
stroke-width="4"
|
||||
></circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
></path>
|
||||
</svg>
|
||||
{{ streamTimeoutSaving ? t('common.saving') : t('common.save') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Registration Settings -->
|
||||
<div class="card">
|
||||
<div class="border-b border-gray-100 px-6 py-4 dark:border-dark-700">
|
||||
@@ -840,6 +995,18 @@ const adminApiKeyMasked = ref('')
|
||||
const adminApiKeyOperating = ref(false)
|
||||
const newAdminApiKey = ref('')
|
||||
|
||||
// Stream Timeout 状态
|
||||
const streamTimeoutLoading = ref(true)
|
||||
const streamTimeoutSaving = ref(false)
|
||||
const streamTimeoutForm = reactive({
|
||||
enabled: true,
|
||||
timeout_seconds: 60,
|
||||
action: 'temp_unsched' as 'temp_unsched' | 'error' | 'none',
|
||||
temp_unsched_minutes: 5,
|
||||
threshold_count: 3,
|
||||
threshold_window_minutes: 10
|
||||
})
|
||||
|
||||
type SettingsForm = SystemSettings & {
|
||||
smtp_password: string
|
||||
turnstile_secret_key: string
|
||||
@@ -1129,8 +1296,44 @@ function copyNewKey() {
|
||||
})
|
||||
}
|
||||
|
||||
// Stream Timeout 方法
|
||||
async function loadStreamTimeoutSettings() {
|
||||
streamTimeoutLoading.value = true
|
||||
try {
|
||||
const settings = await adminAPI.settings.getStreamTimeoutSettings()
|
||||
Object.assign(streamTimeoutForm, settings)
|
||||
} catch (error: any) {
|
||||
console.error('Failed to load stream timeout settings:', error)
|
||||
} finally {
|
||||
streamTimeoutLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveStreamTimeoutSettings() {
|
||||
streamTimeoutSaving.value = true
|
||||
try {
|
||||
const updated = await adminAPI.settings.updateStreamTimeoutSettings({
|
||||
enabled: streamTimeoutForm.enabled,
|
||||
timeout_seconds: streamTimeoutForm.timeout_seconds,
|
||||
action: streamTimeoutForm.action,
|
||||
temp_unsched_minutes: streamTimeoutForm.temp_unsched_minutes,
|
||||
threshold_count: streamTimeoutForm.threshold_count,
|
||||
threshold_window_minutes: streamTimeoutForm.threshold_window_minutes
|
||||
})
|
||||
Object.assign(streamTimeoutForm, updated)
|
||||
appStore.showSuccess(t('admin.settings.streamTimeout.saved'))
|
||||
} catch (error: any) {
|
||||
appStore.showError(
|
||||
t('admin.settings.streamTimeout.saveFailed') + ': ' + (error.message || t('common.unknownError'))
|
||||
)
|
||||
} finally {
|
||||
streamTimeoutSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadSettings()
|
||||
loadAdminApiKey()
|
||||
loadStreamTimeoutSettings()
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user