fix: improve code quality and add type safety for Key updates

- Replace f-string logging with lazy formatting in keys.py (lines 256, 265)
- Add EndpointAPIKeyUpdate type interface for frontend type safety
- Use typed EndpointAPIKeyUpdate instead of any in KeyFormDialog.vue
This commit is contained in:
fawney19
2025-12-23 00:11:10 +08:00
parent 1d5c378343
commit d7384e69d9
6 changed files with 53 additions and 13 deletions

View File

@@ -110,6 +110,24 @@ export interface EndpointAPIKey {
request_results_window?: Array<{ ts: number; ok: boolean }> // 请求结果滑动窗口 request_results_window?: Array<{ ts: number; ok: boolean }> // 请求结果滑动窗口
} }
export interface EndpointAPIKeyUpdate {
name?: string
api_key?: string // 仅在需要更新时提供
rate_multiplier?: number
internal_priority?: number
global_priority?: number | null
max_concurrent?: number | null // null 表示切换为自适应模式
rate_limit?: number
daily_limit?: number
monthly_limit?: number
allowed_models?: string[] | null
capabilities?: Record<string, boolean> | null
cache_ttl_minutes?: number
max_probe_interval_minutes?: number
note?: string
is_active?: boolean
}
export interface EndpointHealthDetail { export interface EndpointHealthDetail {
api_format: string api_format: string
health_score: number health_score: number

View File

@@ -260,6 +260,7 @@ import {
updateEndpointKey, updateEndpointKey,
getAllCapabilities, getAllCapabilities,
type EndpointAPIKey, type EndpointAPIKey,
type EndpointAPIKeyUpdate,
type ProviderEndpoint, type ProviderEndpoint,
type CapabilityDefinition type CapabilityDefinition
} from '@/api/endpoints' } from '@/api/endpoints'
@@ -386,10 +387,11 @@ function loadKeyData() {
api_key: '', api_key: '',
rate_multiplier: props.editingKey.rate_multiplier || 1.0, rate_multiplier: props.editingKey.rate_multiplier || 1.0,
internal_priority: props.editingKey.internal_priority ?? 50, internal_priority: props.editingKey.internal_priority ?? 50,
max_concurrent: props.editingKey.max_concurrent || undefined, // 保留原始的 null/undefined 状态null 表示自适应模式
rate_limit: props.editingKey.rate_limit || undefined, max_concurrent: props.editingKey.max_concurrent ?? undefined,
daily_limit: props.editingKey.daily_limit || undefined, rate_limit: props.editingKey.rate_limit ?? undefined,
monthly_limit: props.editingKey.monthly_limit || undefined, daily_limit: props.editingKey.daily_limit ?? undefined,
monthly_limit: props.editingKey.monthly_limit ?? undefined,
cache_ttl_minutes: props.editingKey.cache_ttl_minutes ?? 5, cache_ttl_minutes: props.editingKey.cache_ttl_minutes ?? 5,
max_probe_interval_minutes: props.editingKey.max_probe_interval_minutes ?? 32, max_probe_interval_minutes: props.editingKey.max_probe_interval_minutes ?? 32,
note: props.editingKey.note || '', note: props.editingKey.note || '',
@@ -439,12 +441,17 @@ async function handleSave() {
saving.value = true saving.value = true
try { try {
if (props.editingKey) { if (props.editingKey) {
// 更新 // 更新模式
const updateData: any = { // 注意max_concurrent 需要显式发送 null 来切换到自适应模式
// undefined 会在 JSON 中被忽略,所以用 null 表示"清空/自适应"
const updateData: EndpointAPIKeyUpdate = {
name: form.value.name, name: form.value.name,
rate_multiplier: form.value.rate_multiplier, rate_multiplier: form.value.rate_multiplier,
internal_priority: form.value.internal_priority, internal_priority: form.value.internal_priority,
max_concurrent: form.value.max_concurrent, // 显式使用 null 表示自适应模式,这样后端能区分"未提供"和"设置为 null"
// 注意:只有 max_concurrent 需要这种处理,因为它有"自适应模式"的概念
// 其他限制字段rate_limit 等)不支持"清空"操作undefined 会被 JSON 忽略即不更新
max_concurrent: form.value.max_concurrent === undefined ? null : form.value.max_concurrent,
rate_limit: form.value.rate_limit, rate_limit: form.value.rate_limit,
daily_limit: form.value.daily_limit, daily_limit: form.value.daily_limit,
monthly_limit: form.value.monthly_limit, monthly_limit: form.value.monthly_limit,

View File

@@ -483,9 +483,9 @@
<span <span
v-if="key.max_concurrent || key.is_adaptive" v-if="key.max_concurrent || key.is_adaptive"
class="text-muted-foreground" class="text-muted-foreground"
:title="key.is_adaptive ? `自适应并发限制(学习值: ${key.learned_max_concurrent ?? '未学习'}` : '固定并发限制'" :title="key.is_adaptive ? `自适应并发限制(学习值: ${key.learned_max_concurrent ?? '未学习'}` : `固定并发限制: ${key.max_concurrent}`"
> >
{{ key.is_adaptive ? '自适应' : '固定' }}并发: {{ key.learned_max_concurrent || key.max_concurrent || 3 }} {{ key.is_adaptive ? '自适应' : '固定' }}并发: {{ key.is_adaptive ? (key.learned_max_concurrent ?? '学习中') : key.max_concurrent }}
</span> </span>
</div> </div>
</div> </div>

View File

@@ -246,6 +246,15 @@ class AdminUpdateEndpointKeyAdapter(AdminApiAdapter):
if "api_key" in update_data: if "api_key" in update_data:
update_data["api_key"] = crypto_service.encrypt(update_data["api_key"]) update_data["api_key"] = crypto_service.encrypt(update_data["api_key"])
# 特殊处理 max_concurrent需要区分"未提供"和"显式设置为 null"
# 当 max_concurrent 被显式设置时(在 model_fields_set 中),即使值为 None 也应该更新
if "max_concurrent" in self.key_data.model_fields_set:
update_data["max_concurrent"] = self.key_data.max_concurrent
# 切换到自适应模式时,清空学习到的并发限制,让系统重新学习
if self.key_data.max_concurrent is None:
update_data["learned_max_concurrent"] = None
logger.info("Key %s 切换为自适应并发模式", self.key_id)
for field, value in update_data.items(): for field, value in update_data.items():
setattr(key, field, value) setattr(key, field, value)
key.updated_at = datetime.now(timezone.utc) key.updated_at = datetime.now(timezone.utc)
@@ -253,7 +262,7 @@ class AdminUpdateEndpointKeyAdapter(AdminApiAdapter):
db.commit() db.commit()
db.refresh(key) db.refresh(key)
logger.info(f"[OK] 更新 Key: ID={self.key_id}, Updates={list(update_data.keys())}") logger.info("[OK] 更新 Key: ID=%s, Updates=%s", self.key_id, list(update_data.keys()))
try: try:
decrypted_key = crypto_service.decrypt(key.api_key) decrypted_key = crypto_service.decrypt(key.api_key)

View File

@@ -77,7 +77,10 @@ class ConcurrencyDefaults:
MAX_CONCURRENT_LIMIT = 200 MAX_CONCURRENT_LIMIT = 200
# 最小并发限制下限 # 最小并发限制下限
MIN_CONCURRENT_LIMIT = 1 # 设置为 3 而不是 1因为预留机制10%预留给缓存用户)会导致
# 当 learned_max_concurrent=1 时新用户实际可用槽位为 0永远无法命中
# 注意:当 limit < 10 时,预留机制实际不生效(预留槽位 = 0这是可接受的
MIN_CONCURRENT_LIMIT = 3
# === 探测性扩容参数 === # === 探测性扩容参数 ===
# 探测性扩容间隔(分钟)- 长时间无 429 且有流量时尝试扩容 # 探测性扩容间隔(分钟)- 长时间无 429 且有流量时尝试扩容

View File

@@ -226,8 +226,11 @@ class EndpointAPIKeyUpdate(BaseModel):
global_priority: Optional[int] = Field( global_priority: Optional[int] = Field(
default=None, description="全局 Key 优先级(全局 Key 优先模式,数字越小越优先)" default=None, description="全局 Key 优先级(全局 Key 优先模式,数字越小越优先)"
) )
# 注意:max_concurrent=None 表示不更新,要切换为自适应模式请使用专用 API # max_concurrent: 使用特殊标记区分"未提供"和"设置为 null自适应模式"
max_concurrent: Optional[int] = Field(default=None, ge=1, description="最大并发数") # - 不提供字段:不更新
# - 提供 null切换为自适应模式
# - 提供数字:设置固定并发限制
max_concurrent: Optional[int] = Field(default=None, ge=1, description="最大并发数null=自适应模式)")
rate_limit: Optional[int] = Field(default=None, ge=1, description="速率限制") rate_limit: Optional[int] = Field(default=None, ge=1, description="速率限制")
daily_limit: Optional[int] = Field(default=None, ge=1, description="每日限制") daily_limit: Optional[int] = Field(default=None, ge=1, description="每日限制")
monthly_limit: Optional[int] = Field(default=None, ge=1, description="每月限制") monthly_limit: Optional[int] = Field(default=None, ge=1, description="每月限制")