mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-07 18:22:28 +08:00
refactor: API Key 过期时间改用日期选择器,rate_limit 支持无限制
- 前端:将过期时间设置从"天数输入"改为"日期选择器",更直观 - 后端:新增 expires_at 字段(ISO 日期格式),兼容旧版 expire_days - rate_limit 字段现在支持 null 表示无限制,移除默认值 100 - 解析逻辑:过期时间设为当天 UTC 23:59:59.999999
This commit is contained in:
@@ -42,7 +42,7 @@ export interface UserApiKeyExport {
|
||||
allowed_endpoints?: string[] | null
|
||||
allowed_api_formats?: string[] | null
|
||||
allowed_models?: string[] | null
|
||||
rate_limit?: number
|
||||
rate_limit?: number | null // null = 无限制
|
||||
concurrent_limit?: number | null
|
||||
force_capabilities?: any
|
||||
is_active: boolean
|
||||
@@ -220,7 +220,7 @@ export interface AdminApiKey {
|
||||
total_requests?: number
|
||||
total_tokens?: number
|
||||
total_cost_usd?: number
|
||||
rate_limit?: number
|
||||
rate_limit?: number | null // null = 无限制
|
||||
allowed_providers?: string[] | null // 允许的提供商列表
|
||||
allowed_api_formats?: string[] | null // 允许的 API 格式列表
|
||||
allowed_models?: string[] | null // 允许的模型列表
|
||||
@@ -236,8 +236,8 @@ export interface CreateStandaloneApiKeyRequest {
|
||||
allowed_providers?: string[] | null
|
||||
allowed_api_formats?: string[] | null
|
||||
allowed_models?: string[] | null
|
||||
rate_limit?: number
|
||||
expire_days?: number | null // null = 永不过期
|
||||
rate_limit?: number | null // null = 无限制
|
||||
expires_at?: string | null // ISO 日期字符串,如 "2025-12-31",null = 永不过期
|
||||
initial_balance_usd: number // 初始余额,必须设置
|
||||
auto_delete_on_expiry?: boolean // 过期后是否自动删除
|
||||
}
|
||||
|
||||
@@ -79,45 +79,45 @@
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label
|
||||
for="form-expire-days"
|
||||
for="form-expires-at"
|
||||
class="text-sm font-medium"
|
||||
>有效期设置</Label>
|
||||
<div class="flex items-center gap-2">
|
||||
<Input
|
||||
id="form-expire-days"
|
||||
:model-value="form.expire_days ?? ''"
|
||||
type="number"
|
||||
min="1"
|
||||
max="3650"
|
||||
placeholder="天数"
|
||||
:class="form.never_expire ? 'flex-1 h-9 opacity-50' : 'flex-1 h-9'"
|
||||
:disabled="form.never_expire"
|
||||
@update:model-value="(v) => form.expire_days = parseNumberInput(v, { min: 1, max: 3650 })"
|
||||
/>
|
||||
<label class="flex items-center gap-1.5 border rounded-md px-2 py-1.5 bg-muted/50 cursor-pointer text-xs whitespace-nowrap">
|
||||
<input
|
||||
v-model="form.never_expire"
|
||||
type="checkbox"
|
||||
class="h-3.5 w-3.5 rounded border-gray-300 cursor-pointer"
|
||||
@change="onNeverExpireChange"
|
||||
<div class="relative flex-1">
|
||||
<Input
|
||||
id="form-expires-at"
|
||||
:model-value="form.expires_at || ''"
|
||||
type="date"
|
||||
:min="minExpiryDate"
|
||||
class="h-9 pr-8"
|
||||
:placeholder="form.expires_at ? '' : '永不过期'"
|
||||
@update:model-value="(v) => form.expires_at = v || undefined"
|
||||
/>
|
||||
<button
|
||||
v-if="form.expires_at"
|
||||
type="button"
|
||||
class="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground"
|
||||
title="清空(永不过期)"
|
||||
@click="clearExpiryDate"
|
||||
>
|
||||
永不过期
|
||||
</label>
|
||||
<X class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<label
|
||||
class="flex items-center gap-1.5 border rounded-md px-2 py-1.5 bg-muted/50 cursor-pointer text-xs whitespace-nowrap"
|
||||
:class="form.never_expire ? 'opacity-50' : ''"
|
||||
:class="!form.expires_at ? 'opacity-50 cursor-not-allowed' : ''"
|
||||
>
|
||||
<input
|
||||
v-model="form.auto_delete_on_expiry"
|
||||
type="checkbox"
|
||||
class="h-3.5 w-3.5 rounded border-gray-300 cursor-pointer"
|
||||
:disabled="form.never_expire"
|
||||
:disabled="!form.expires_at"
|
||||
>
|
||||
到期删除
|
||||
</label>
|
||||
</div>
|
||||
<p class="text-xs text-muted-foreground">
|
||||
不勾选"到期删除"则仅禁用
|
||||
{{ form.expires_at ? '到期后' + (form.auto_delete_on_expiry ? '自动删除' : '仅禁用') + '(当天 UTC 23:59 失效)' : '留空表示永不过期' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -280,7 +280,7 @@ import {
|
||||
Input,
|
||||
Label,
|
||||
} from '@/components/ui'
|
||||
import { Plus, SquarePen, Key, Shield, ChevronDown } from 'lucide-vue-next'
|
||||
import { Plus, SquarePen, Key, Shield, ChevronDown, X } from 'lucide-vue-next'
|
||||
import { useFormDialog } from '@/composables/useFormDialog'
|
||||
import { ModelMultiSelect } from '@/components/common'
|
||||
import { getProvidersSummary } from '@/api/endpoints/providers'
|
||||
@@ -294,8 +294,7 @@ export interface StandaloneKeyFormData {
|
||||
id?: string
|
||||
name: string
|
||||
initial_balance_usd?: number
|
||||
expire_days?: number
|
||||
never_expire: boolean
|
||||
expires_at?: string // ISO 日期字符串,如 "2025-12-31",undefined = 永不过期
|
||||
rate_limit?: number
|
||||
auto_delete_on_expiry: boolean
|
||||
allowed_providers: string[]
|
||||
@@ -329,8 +328,7 @@ const allApiFormats = ref<string[]>([])
|
||||
const form = ref<StandaloneKeyFormData>({
|
||||
name: '',
|
||||
initial_balance_usd: 10,
|
||||
expire_days: undefined,
|
||||
never_expire: true,
|
||||
expires_at: undefined,
|
||||
rate_limit: undefined,
|
||||
auto_delete_on_expiry: false,
|
||||
allowed_providers: [],
|
||||
@@ -338,12 +336,18 @@ const form = ref<StandaloneKeyFormData>({
|
||||
allowed_models: []
|
||||
})
|
||||
|
||||
// 计算最小可选日期(明天)
|
||||
const minExpiryDate = computed(() => {
|
||||
const tomorrow = new Date()
|
||||
tomorrow.setDate(tomorrow.getDate() + 1)
|
||||
return tomorrow.toISOString().split('T')[0]
|
||||
})
|
||||
|
||||
function resetForm() {
|
||||
form.value = {
|
||||
name: '',
|
||||
initial_balance_usd: 10,
|
||||
expire_days: undefined,
|
||||
never_expire: true,
|
||||
expires_at: undefined,
|
||||
rate_limit: undefined,
|
||||
auto_delete_on_expiry: false,
|
||||
allowed_providers: [],
|
||||
@@ -360,8 +364,7 @@ function loadKeyData() {
|
||||
id: props.apiKey.id,
|
||||
name: props.apiKey.name || '',
|
||||
initial_balance_usd: props.apiKey.initial_balance_usd,
|
||||
expire_days: props.apiKey.expire_days,
|
||||
never_expire: props.apiKey.never_expire,
|
||||
expires_at: props.apiKey.expires_at,
|
||||
rate_limit: props.apiKey.rate_limit,
|
||||
auto_delete_on_expiry: props.apiKey.auto_delete_on_expiry,
|
||||
allowed_providers: props.apiKey.allowed_providers || [],
|
||||
@@ -406,12 +409,10 @@ function toggleSelection(field: 'allowed_providers' | 'allowed_api_formats' | 'a
|
||||
}
|
||||
}
|
||||
|
||||
// 永不过期切换
|
||||
function onNeverExpireChange() {
|
||||
if (form.value.never_expire) {
|
||||
form.value.expire_days = undefined
|
||||
form.value.auto_delete_on_expiry = false
|
||||
}
|
||||
// 清空过期日期(同时清空到期删除选项)
|
||||
function clearExpiryDate() {
|
||||
form.value.expires_at = undefined
|
||||
form.value.auto_delete_on_expiry = false
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
|
||||
@@ -850,28 +850,20 @@ async function deleteApiKey(apiKey: AdminApiKey) {
|
||||
}
|
||||
|
||||
function editApiKey(apiKey: AdminApiKey) {
|
||||
// 计算过期天数
|
||||
let expireDays: number | undefined = undefined
|
||||
let neverExpire = true
|
||||
// 解析过期日期为 YYYY-MM-DD 格式
|
||||
// 保留原始日期,不做时间过滤(避免编辑当天过期的 Key 时意外清空)
|
||||
let expiresAt: string | undefined = undefined
|
||||
|
||||
if (apiKey.expires_at) {
|
||||
const expiresDate = new Date(apiKey.expires_at)
|
||||
const now = new Date()
|
||||
const diffMs = expiresDate.getTime() - now.getTime()
|
||||
const diffDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24))
|
||||
|
||||
if (diffDays > 0) {
|
||||
expireDays = diffDays
|
||||
neverExpire = false
|
||||
}
|
||||
expiresAt = expiresDate.toISOString().split('T')[0]
|
||||
}
|
||||
|
||||
editingKeyData.value = {
|
||||
id: apiKey.id,
|
||||
name: apiKey.name || '',
|
||||
expire_days: expireDays,
|
||||
never_expire: neverExpire,
|
||||
rate_limit: apiKey.rate_limit || 100,
|
||||
expires_at: expiresAt,
|
||||
rate_limit: apiKey.rate_limit ?? undefined,
|
||||
auto_delete_on_expiry: apiKey.auto_delete_on_expiry || false,
|
||||
allowed_providers: apiKey.allowed_providers || [],
|
||||
allowed_api_formats: apiKey.allowed_api_formats || [],
|
||||
@@ -1033,14 +1025,25 @@ function closeKeyFormDialog() {
|
||||
|
||||
// 统一处理表单提交
|
||||
async function handleKeyFormSubmit(data: StandaloneKeyFormData) {
|
||||
// 验证过期日期(如果设置了,必须晚于今天)
|
||||
if (data.expires_at) {
|
||||
const selectedDate = new Date(data.expires_at)
|
||||
const today = new Date()
|
||||
today.setHours(0, 0, 0, 0)
|
||||
if (selectedDate <= today) {
|
||||
error('过期日期必须晚于今天')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
keyFormDialogRef.value?.setSaving(true)
|
||||
try {
|
||||
if (data.id) {
|
||||
// 更新
|
||||
const updateData: Partial<CreateStandaloneApiKeyRequest> = {
|
||||
name: data.name || undefined,
|
||||
rate_limit: data.rate_limit,
|
||||
expire_days: data.never_expire ? null : (data.expire_days || null),
|
||||
rate_limit: data.rate_limit ?? null, // undefined = 无限制,显式传 null
|
||||
expires_at: data.expires_at || null, // undefined/空 = 永不过期
|
||||
auto_delete_on_expiry: data.auto_delete_on_expiry,
|
||||
// 空数组表示清除限制(允许全部),后端会将空数组存为 NULL
|
||||
allowed_providers: data.allowed_providers,
|
||||
@@ -1058,8 +1061,8 @@ async function handleKeyFormSubmit(data: StandaloneKeyFormData) {
|
||||
const createData: CreateStandaloneApiKeyRequest = {
|
||||
name: data.name || undefined,
|
||||
initial_balance_usd: data.initial_balance_usd,
|
||||
rate_limit: data.rate_limit,
|
||||
expire_days: data.never_expire ? null : (data.expire_days || null),
|
||||
rate_limit: data.rate_limit ?? null, // undefined = 无限制,显式传 null
|
||||
expires_at: data.expires_at || null, // undefined/空 = 永不过期
|
||||
auto_delete_on_expiry: data.auto_delete_on_expiry,
|
||||
// 空数组表示不设置限制(允许全部),后端会将空数组存为 NULL
|
||||
allowed_providers: data.allowed_providers,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
独立余额Key:不关联用户配额,有独立余额限制,用于给非注册用户使用。
|
||||
"""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Request
|
||||
@@ -11,14 +11,50 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from src.api.base.admin_adapter import AdminApiAdapter
|
||||
from src.api.base.pipeline import ApiRequestPipeline
|
||||
from src.core.exceptions import NotFoundException
|
||||
from src.core.exceptions import InvalidRequestException, NotFoundException
|
||||
from src.core.logger import logger
|
||||
from src.database import get_db
|
||||
from src.models.api import CreateApiKeyRequest
|
||||
from src.models.database import ApiKey, User
|
||||
from src.models.database import ApiKey
|
||||
from src.services.user.apikey import ApiKeyService
|
||||
|
||||
|
||||
def parse_expiry_date(date_str: Optional[str]) -> Optional[datetime]:
|
||||
"""解析过期日期字符串为 datetime 对象(UTC 时区)。
|
||||
|
||||
Args:
|
||||
date_str: 日期字符串,支持 "YYYY-MM-DD" 或 ISO 格式
|
||||
|
||||
Returns:
|
||||
datetime 对象(当天 23:59:59.999999 UTC),或 None 如果输入为空
|
||||
|
||||
Raises:
|
||||
BadRequestException: 日期格式无效
|
||||
"""
|
||||
if not date_str or not date_str.strip():
|
||||
return None
|
||||
|
||||
date_str = date_str.strip()
|
||||
|
||||
# 尝试 YYYY-MM-DD 格式
|
||||
try:
|
||||
parsed_date = datetime.strptime(date_str, "%Y-%m-%d")
|
||||
# 设置为当天结束时间 (23:59:59.999999 UTC)
|
||||
return parsed_date.replace(
|
||||
hour=23, minute=59, second=59, microsecond=999999, tzinfo=timezone.utc
|
||||
)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# 尝试完整 ISO 格式
|
||||
try:
|
||||
return datetime.fromisoformat(date_str.replace("Z", "+00:00"))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
raise InvalidRequestException(f"无效的日期格式: {date_str},请使用 YYYY-MM-DD 格式")
|
||||
|
||||
|
||||
router = APIRouter(prefix="/api/admin/api-keys", tags=["Admin - API Keys (Standalone)"])
|
||||
pipeline = ApiRequestPipeline()
|
||||
|
||||
@@ -215,6 +251,9 @@ class AdminCreateStandaloneKeyAdapter(AdminApiAdapter):
|
||||
# 独立Key需要关联到管理员用户(从context获取)
|
||||
admin_user_id = context.user.id
|
||||
|
||||
# 解析过期时间(优先使用 expires_at,其次使用 expire_days)
|
||||
expires_at_dt = parse_expiry_date(self.key_data.expires_at)
|
||||
|
||||
# 创建独立Key
|
||||
api_key, plain_key = ApiKeyService.create_api_key(
|
||||
db=db,
|
||||
@@ -224,7 +263,8 @@ class AdminCreateStandaloneKeyAdapter(AdminApiAdapter):
|
||||
allowed_api_formats=self.key_data.allowed_api_formats,
|
||||
allowed_models=self.key_data.allowed_models,
|
||||
rate_limit=self.key_data.rate_limit, # None 表示不限制
|
||||
expire_days=self.key_data.expire_days,
|
||||
expire_days=self.key_data.expire_days, # 兼容旧版
|
||||
expires_at=expires_at_dt, # 优先使用
|
||||
initial_balance_usd=self.key_data.initial_balance_usd,
|
||||
is_standalone=True, # 标记为独立Key
|
||||
auto_delete_on_expiry=self.key_data.auto_delete_on_expiry,
|
||||
@@ -270,7 +310,8 @@ class AdminUpdateApiKeyAdapter(AdminApiAdapter):
|
||||
update_data = {}
|
||||
if self.key_data.name is not None:
|
||||
update_data["name"] = self.key_data.name
|
||||
if self.key_data.rate_limit is not None:
|
||||
# rate_limit: 显式传递时更新(包括 null 表示无限制)
|
||||
if "rate_limit" in self.key_data.model_fields_set:
|
||||
update_data["rate_limit"] = self.key_data.rate_limit
|
||||
if (
|
||||
hasattr(self.key_data, "auto_delete_on_expiry")
|
||||
@@ -287,19 +328,21 @@ class AdminUpdateApiKeyAdapter(AdminApiAdapter):
|
||||
update_data["allowed_models"] = self.key_data.allowed_models
|
||||
|
||||
# 处理过期时间
|
||||
if self.key_data.expire_days is not None:
|
||||
if self.key_data.expire_days > 0:
|
||||
from datetime import timedelta
|
||||
|
||||
# 优先使用 expires_at(如果显式传递且有值)
|
||||
if self.key_data.expires_at and self.key_data.expires_at.strip():
|
||||
update_data["expires_at"] = parse_expiry_date(self.key_data.expires_at)
|
||||
elif "expires_at" in self.key_data.model_fields_set:
|
||||
# expires_at 明确传递为 null 或空字符串,设为永不过期
|
||||
update_data["expires_at"] = None
|
||||
# 兼容旧版 expire_days
|
||||
elif "expire_days" in self.key_data.model_fields_set:
|
||||
if self.key_data.expire_days is not None and self.key_data.expire_days > 0:
|
||||
update_data["expires_at"] = datetime.now(timezone.utc) + timedelta(
|
||||
days=self.key_data.expire_days
|
||||
)
|
||||
else:
|
||||
# expire_days = 0 或负数表示永不过期
|
||||
# expire_days = None/0/负数 表示永不过期
|
||||
update_data["expires_at"] = None
|
||||
elif hasattr(self.key_data, "expire_days") and self.key_data.expire_days is None:
|
||||
# 明确传递 None,设为永不过期
|
||||
update_data["expires_at"] = None
|
||||
|
||||
# 使用 ApiKeyService 更新
|
||||
updated_key = ApiKeyService.update_api_key(db, self.key_id, **update_data)
|
||||
|
||||
@@ -1133,7 +1133,7 @@ class AdminImportUsersAdapter(AdminApiAdapter):
|
||||
allowed_endpoints=key_data.get("allowed_endpoints"),
|
||||
allowed_api_formats=key_data.get("allowed_api_formats"),
|
||||
allowed_models=key_data.get("allowed_models"),
|
||||
rate_limit=key_data.get("rate_limit", 100),
|
||||
rate_limit=key_data.get("rate_limit"), # None = 无限制
|
||||
concurrent_limit=key_data.get("concurrent_limit", 5),
|
||||
force_capabilities=key_data.get("force_capabilities"),
|
||||
is_active=key_data.get("is_active", True),
|
||||
|
||||
@@ -431,7 +431,7 @@ class AdminCreateUserKeyAdapter(AdminApiAdapter):
|
||||
name=key_data.name,
|
||||
allowed_providers=key_data.allowed_providers,
|
||||
allowed_models=key_data.allowed_models,
|
||||
rate_limit=key_data.rate_limit or 100,
|
||||
rate_limit=key_data.rate_limit, # None = 无限制
|
||||
expire_days=key_data.expire_days,
|
||||
initial_balance_usd=None, # 普通Key不设置余额限制
|
||||
is_standalone=False, # 不是独立Key
|
||||
|
||||
@@ -309,8 +309,9 @@ class CreateApiKeyRequest(BaseModel):
|
||||
allowed_endpoints: Optional[List[str]] = None # 允许使用的端点 ID 列表
|
||||
allowed_api_formats: Optional[List[str]] = None # 允许使用的 API 格式列表
|
||||
allowed_models: Optional[List[str]] = None # 允许使用的模型名称列表
|
||||
rate_limit: Optional[int] = 100
|
||||
expire_days: Optional[int] = None # None = 永不过期,数字 = 多少天后过期
|
||||
rate_limit: Optional[int] = None # None = 无限制
|
||||
expire_days: Optional[int] = None # None = 永不过期,数字 = 多少天后过期(兼容旧版)
|
||||
expires_at: Optional[str] = None # ISO 日期字符串,如 "2025-12-31",优先于 expire_days
|
||||
initial_balance_usd: Optional[float] = Field(
|
||||
None, description="初始余额(USD),仅用于独立Key,None = 无限制"
|
||||
)
|
||||
|
||||
@@ -150,7 +150,7 @@ class ApiKey(Base):
|
||||
allowed_endpoints = Column(JSON, nullable=True) # 允许使用的端点 ID 列表
|
||||
allowed_api_formats = Column(JSON, nullable=True) # 允许使用的 API 格式列表
|
||||
allowed_models = Column(JSON, nullable=True) # 允许使用的模型名称列表
|
||||
rate_limit = Column(Integer, default=100) # 每分钟请求限制
|
||||
rate_limit = Column(Integer, default=None, nullable=True) # 每分钟请求限制,None = 无限制
|
||||
concurrent_limit = Column(Integer, default=5, nullable=True) # 并发请求限制
|
||||
|
||||
# Key 能力配置
|
||||
|
||||
@@ -25,9 +25,10 @@ class ApiKeyService:
|
||||
allowed_providers: Optional[List[str]] = None,
|
||||
allowed_api_formats: Optional[List[str]] = None,
|
||||
allowed_models: Optional[List[str]] = None,
|
||||
rate_limit: int = 100,
|
||||
rate_limit: Optional[int] = None,
|
||||
concurrent_limit: int = 5,
|
||||
expire_days: Optional[int] = None,
|
||||
expires_at: Optional[datetime] = None, # 直接传入过期时间,优先于 expire_days
|
||||
initial_balance_usd: Optional[float] = None,
|
||||
is_standalone: bool = False,
|
||||
auto_delete_on_expiry: bool = False,
|
||||
@@ -44,6 +45,7 @@ class ApiKeyService:
|
||||
rate_limit: 速率限制
|
||||
concurrent_limit: 并发限制
|
||||
expire_days: 过期天数,None = 永不过期
|
||||
expires_at: 直接指定过期时间,优先于 expire_days
|
||||
initial_balance_usd: 初始余额(USD),仅用于独立Key,None = 无限制
|
||||
is_standalone: 是否为独立余额Key(仅管理员可创建)
|
||||
auto_delete_on_expiry: 过期后是否自动删除(True=物理删除,False=仅禁用)
|
||||
@@ -54,10 +56,10 @@ class ApiKeyService:
|
||||
key_hash = ApiKey.hash_key(key)
|
||||
key_encrypted = crypto_service.encrypt(key) # 加密存储密钥
|
||||
|
||||
# 计算过期时间
|
||||
expires_at = None
|
||||
if expire_days:
|
||||
expires_at = datetime.now(timezone.utc) + timedelta(days=expire_days)
|
||||
# 计算过期时间:优先使用 expires_at,其次使用 expire_days
|
||||
final_expires_at = expires_at
|
||||
if final_expires_at is None and expire_days:
|
||||
final_expires_at = datetime.now(timezone.utc) + timedelta(days=expire_days)
|
||||
|
||||
# 空数组转为 None(表示不限制)
|
||||
api_key = ApiKey(
|
||||
@@ -70,7 +72,7 @@ class ApiKeyService:
|
||||
allowed_models=allowed_models or None,
|
||||
rate_limit=rate_limit,
|
||||
concurrent_limit=concurrent_limit,
|
||||
expires_at=expires_at,
|
||||
expires_at=final_expires_at,
|
||||
balance_used_usd=0.0,
|
||||
current_balance_usd=initial_balance_usd, # 直接使用初始余额,None = 无限制
|
||||
is_standalone=is_standalone,
|
||||
@@ -145,6 +147,9 @@ class ApiKeyService:
|
||||
# 允许显式设置为空数组/None 的字段(空数组会转为 None,表示"全部")
|
||||
nullable_list_fields = {"allowed_providers", "allowed_api_formats", "allowed_models"}
|
||||
|
||||
# 允许显式设置为 None 的字段(如 expires_at=None 表示永不过期,rate_limit=None 表示无限制)
|
||||
nullable_fields = {"expires_at", "rate_limit"}
|
||||
|
||||
for field, value in kwargs.items():
|
||||
if field not in updatable_fields:
|
||||
continue
|
||||
@@ -153,6 +158,9 @@ class ApiKeyService:
|
||||
if value is not None:
|
||||
# 空数组转为 None(表示允许全部)
|
||||
setattr(api_key, field, value if value else None)
|
||||
elif field in nullable_fields:
|
||||
# 这些字段允许显式设置为 None
|
||||
setattr(api_key, field, value)
|
||||
elif value is not None:
|
||||
setattr(api_key, field, value)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user