mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-08 18:52:28 +08:00
feat: 用户导出支持独立余额Key,新增系统版本接口
- 用户导出/导入支持独立余额 Key (standalone_keys) - API Key 导出增加 expires_at 字段 - 新增 /api/admin/system/version 接口获取版本信息 - 前端系统设置页面显示当前版本 - 移除导入对话框中多余的 bg-muted 背景样式
This commit is contained in:
@@ -13,6 +13,7 @@ export interface UsersExportData {
|
|||||||
version: string
|
version: string
|
||||||
exported_at: string
|
exported_at: string
|
||||||
users: UserExport[]
|
users: UserExport[]
|
||||||
|
standalone_keys?: StandaloneKeyExport[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserExport {
|
export interface UserExport {
|
||||||
@@ -46,11 +47,15 @@ export interface UserApiKeyExport {
|
|||||||
concurrent_limit?: number | null
|
concurrent_limit?: number | null
|
||||||
force_capabilities?: any
|
force_capabilities?: any
|
||||||
is_active: boolean
|
is_active: boolean
|
||||||
|
expires_at?: string | null
|
||||||
auto_delete_on_expiry?: boolean
|
auto_delete_on_expiry?: boolean
|
||||||
total_requests?: number
|
total_requests?: number
|
||||||
total_cost_usd?: number
|
total_cost_usd?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 独立余额 Key 导出结构(与 UserApiKeyExport 相同,但不包含 is_standalone)
|
||||||
|
export type StandaloneKeyExport = Omit<UserApiKeyExport, 'is_standalone'>
|
||||||
|
|
||||||
export interface GlobalModelExport {
|
export interface GlobalModelExport {
|
||||||
name: string
|
name: string
|
||||||
display_name: string
|
display_name: string
|
||||||
@@ -189,6 +194,7 @@ export interface UsersImportResponse {
|
|||||||
stats: {
|
stats: {
|
||||||
users: { created: number; updated: number; skipped: number }
|
users: { created: number; updated: number; skipped: number }
|
||||||
api_keys: { created: number; skipped: number }
|
api_keys: { created: number; skipped: number }
|
||||||
|
standalone_keys?: { created: number; skipped: number }
|
||||||
errors: string[]
|
errors: string[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -473,5 +479,13 @@ export const adminApi = {
|
|||||||
`/api/admin/system/email/templates/${templateType}/reset`
|
`/api/admin/system/email/templates/${templateType}/reset`
|
||||||
)
|
)
|
||||||
return response.data
|
return response.data
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取系统版本信息
|
||||||
|
async getSystemVersion(): Promise<{ version: string }> {
|
||||||
|
const response = await apiClient.get<{ version: string }>(
|
||||||
|
'/api/admin/system/version'
|
||||||
|
)
|
||||||
|
return response.data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -464,6 +464,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardSection>
|
</CardSection>
|
||||||
|
|
||||||
|
<!-- 系统版本信息 -->
|
||||||
|
<CardSection
|
||||||
|
title="系统信息"
|
||||||
|
description="当前系统版本和构建信息"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Label class="text-sm font-medium text-muted-foreground">版本:</Label>
|
||||||
|
<span
|
||||||
|
v-if="systemVersion"
|
||||||
|
class="text-sm font-mono"
|
||||||
|
>
|
||||||
|
{{ systemVersion }}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="text-sm text-muted-foreground"
|
||||||
|
>
|
||||||
|
加载中...
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardSection>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 导入配置对话框 -->
|
<!-- 导入配置对话框 -->
|
||||||
@@ -475,7 +499,7 @@
|
|||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div
|
<div
|
||||||
v-if="importPreview"
|
v-if="importPreview"
|
||||||
class="p-3 bg-muted rounded-lg text-sm"
|
class="text-sm"
|
||||||
>
|
>
|
||||||
<p class="font-medium mb-2">
|
<p class="font-medium mb-2">
|
||||||
配置预览
|
配置预览
|
||||||
@@ -557,7 +581,7 @@
|
|||||||
class="space-y-4"
|
class="space-y-4"
|
||||||
>
|
>
|
||||||
<div class="grid grid-cols-2 gap-4 text-sm">
|
<div class="grid grid-cols-2 gap-4 text-sm">
|
||||||
<div class="p-3 bg-muted rounded-lg">
|
<div>
|
||||||
<p class="font-medium">
|
<p class="font-medium">
|
||||||
全局模型
|
全局模型
|
||||||
</p>
|
</p>
|
||||||
@@ -567,7 +591,7 @@
|
|||||||
跳过: {{ importResult.stats.global_models.skipped }}
|
跳过: {{ importResult.stats.global_models.skipped }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-3 bg-muted rounded-lg">
|
<div>
|
||||||
<p class="font-medium">
|
<p class="font-medium">
|
||||||
提供商
|
提供商
|
||||||
</p>
|
</p>
|
||||||
@@ -577,7 +601,7 @@
|
|||||||
跳过: {{ importResult.stats.providers.skipped }}
|
跳过: {{ importResult.stats.providers.skipped }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-3 bg-muted rounded-lg">
|
<div>
|
||||||
<p class="font-medium">
|
<p class="font-medium">
|
||||||
端点
|
端点
|
||||||
</p>
|
</p>
|
||||||
@@ -587,7 +611,7 @@
|
|||||||
跳过: {{ importResult.stats.endpoints.skipped }}
|
跳过: {{ importResult.stats.endpoints.skipped }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-3 bg-muted rounded-lg">
|
<div>
|
||||||
<p class="font-medium">
|
<p class="font-medium">
|
||||||
API Keys
|
API Keys
|
||||||
</p>
|
</p>
|
||||||
@@ -596,7 +620,7 @@
|
|||||||
跳过: {{ importResult.stats.keys.skipped }}
|
跳过: {{ importResult.stats.keys.skipped }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-3 bg-muted rounded-lg col-span-2">
|
<div class="col-span-2">
|
||||||
<p class="font-medium">
|
<p class="font-medium">
|
||||||
模型配置
|
模型配置
|
||||||
</p>
|
</p>
|
||||||
@@ -642,7 +666,7 @@
|
|||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div
|
<div
|
||||||
v-if="importUsersPreview"
|
v-if="importUsersPreview"
|
||||||
class="p-3 bg-muted rounded-lg text-sm"
|
class="text-sm"
|
||||||
>
|
>
|
||||||
<p class="font-medium mb-2">
|
<p class="font-medium mb-2">
|
||||||
数据预览
|
数据预览
|
||||||
@@ -652,6 +676,9 @@
|
|||||||
<li>
|
<li>
|
||||||
API Keys: {{ importUsersPreview.users?.reduce((sum: number, u: any) => sum + (u.api_keys?.length || 0), 0) }} 个
|
API Keys: {{ importUsersPreview.users?.reduce((sum: number, u: any) => sum + (u.api_keys?.length || 0), 0) }} 个
|
||||||
</li>
|
</li>
|
||||||
|
<li v-if="importUsersPreview.standalone_keys?.length">
|
||||||
|
独立余额 Keys: {{ importUsersPreview.standalone_keys.length }} 个
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -720,7 +747,7 @@
|
|||||||
class="space-y-4"
|
class="space-y-4"
|
||||||
>
|
>
|
||||||
<div class="grid grid-cols-2 gap-4 text-sm">
|
<div class="grid grid-cols-2 gap-4 text-sm">
|
||||||
<div class="p-3 bg-muted rounded-lg">
|
<div>
|
||||||
<p class="font-medium">
|
<p class="font-medium">
|
||||||
用户
|
用户
|
||||||
</p>
|
</p>
|
||||||
@@ -730,7 +757,7 @@
|
|||||||
跳过: {{ importUsersResult.stats.users.skipped }}
|
跳过: {{ importUsersResult.stats.users.skipped }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-3 bg-muted rounded-lg">
|
<div>
|
||||||
<p class="font-medium">
|
<p class="font-medium">
|
||||||
API Keys
|
API Keys
|
||||||
</p>
|
</p>
|
||||||
@@ -739,6 +766,18 @@
|
|||||||
跳过: {{ importUsersResult.stats.api_keys.skipped }}
|
跳过: {{ importUsersResult.stats.api_keys.skipped }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="importUsersResult.stats.standalone_keys"
|
||||||
|
class="col-span-2"
|
||||||
|
>
|
||||||
|
<p class="font-medium">
|
||||||
|
独立余额 Keys
|
||||||
|
</p>
|
||||||
|
<p class="text-muted-foreground">
|
||||||
|
创建: {{ importUsersResult.stats.standalone_keys.created }},
|
||||||
|
跳过: {{ importUsersResult.stats.standalone_keys.skipped }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -839,6 +878,9 @@ const importUsersResult = ref<UsersImportResponse | null>(null)
|
|||||||
const usersMergeMode = ref<'skip' | 'overwrite' | 'error'>('skip')
|
const usersMergeMode = ref<'skip' | 'overwrite' | 'error'>('skip')
|
||||||
const usersMergeModeSelectOpen = ref(false)
|
const usersMergeModeSelectOpen = ref(false)
|
||||||
|
|
||||||
|
// 系统版本信息
|
||||||
|
const systemVersion = ref<string>('')
|
||||||
|
|
||||||
const systemConfig = ref<SystemConfig>({
|
const systemConfig = ref<SystemConfig>({
|
||||||
// 基础配置
|
// 基础配置
|
||||||
default_user_quota_usd: 10.0,
|
default_user_quota_usd: 10.0,
|
||||||
@@ -890,9 +932,21 @@ const sensitiveHeadersStr = computed({
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadSystemConfig()
|
await Promise.all([
|
||||||
|
loadSystemConfig(),
|
||||||
|
loadSystemVersion()
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
async function loadSystemVersion() {
|
||||||
|
try {
|
||||||
|
const data = await adminApi.getSystemVersion()
|
||||||
|
systemVersion.value = data.version
|
||||||
|
} catch (err) {
|
||||||
|
log.error('加载系统版本失败:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadSystemConfig() {
|
async function loadSystemConfig() {
|
||||||
try {
|
try {
|
||||||
const configs = [
|
const configs = [
|
||||||
@@ -1178,12 +1232,6 @@ function handleUsersFileSelect(event: Event) {
|
|||||||
const content = e.target?.result as string
|
const content = e.target?.result as string
|
||||||
const data = JSON.parse(content) as UsersExportData
|
const data = JSON.parse(content) as UsersExportData
|
||||||
|
|
||||||
// 验证版本
|
|
||||||
if (data.version !== '1.0') {
|
|
||||||
error(`不支持的配置版本: ${data.version}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
importUsersPreview.value = data
|
importUsersPreview.value = data
|
||||||
usersMergeMode.value = 'skip'
|
usersMergeMode.value = 'skip'
|
||||||
importUsersDialogOpen.value = true
|
importUsersDialogOpen.value = true
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
"""系统设置API端点。"""
|
"""系统设置API端点。"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@@ -17,6 +19,46 @@ from src.services.email.email_template import EmailTemplate
|
|||||||
from src.services.system.config import SystemConfigService
|
from src.services.system.config import SystemConfigService
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/admin/system", tags=["Admin - System"])
|
router = APIRouter(prefix="/api/admin/system", tags=["Admin - System"])
|
||||||
|
|
||||||
|
|
||||||
|
def _get_version_from_git() -> str | None:
|
||||||
|
"""从 git describe 获取版本号"""
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "describe", "--tags", "--always"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=5,
|
||||||
|
)
|
||||||
|
if result.returncode == 0:
|
||||||
|
version = result.stdout.strip()
|
||||||
|
if version.startswith("v"):
|
||||||
|
version = version[1:]
|
||||||
|
return version
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/version")
|
||||||
|
async def get_system_version():
|
||||||
|
"""获取系统版本信息"""
|
||||||
|
# 优先从 git 获取
|
||||||
|
version = _get_version_from_git()
|
||||||
|
if version:
|
||||||
|
return {"version": version}
|
||||||
|
|
||||||
|
# 回退到静态版本文件
|
||||||
|
try:
|
||||||
|
from src._version import __version__
|
||||||
|
|
||||||
|
return {"version": __version__}
|
||||||
|
except ImportError:
|
||||||
|
return {"version": "unknown"}
|
||||||
|
|
||||||
|
|
||||||
pipeline = ApiRequestPipeline()
|
pipeline = ApiRequestPipeline()
|
||||||
|
|
||||||
|
|
||||||
@@ -950,6 +992,31 @@ class AdminExportUsersAdapter(AdminApiAdapter):
|
|||||||
|
|
||||||
db = context.db
|
db = context.db
|
||||||
|
|
||||||
|
def _serialize_api_key(key: ApiKey, include_is_standalone: bool = False) -> dict:
|
||||||
|
"""序列化 API Key 为导出格式"""
|
||||||
|
data = {
|
||||||
|
"key_hash": key.key_hash,
|
||||||
|
"key_encrypted": key.key_encrypted,
|
||||||
|
"name": key.name,
|
||||||
|
"balance_used_usd": key.balance_used_usd,
|
||||||
|
"current_balance_usd": key.current_balance_usd,
|
||||||
|
"allowed_providers": key.allowed_providers,
|
||||||
|
"allowed_endpoints": key.allowed_endpoints,
|
||||||
|
"allowed_api_formats": key.allowed_api_formats,
|
||||||
|
"allowed_models": key.allowed_models,
|
||||||
|
"rate_limit": key.rate_limit,
|
||||||
|
"concurrent_limit": key.concurrent_limit,
|
||||||
|
"force_capabilities": key.force_capabilities,
|
||||||
|
"is_active": key.is_active,
|
||||||
|
"expires_at": key.expires_at.isoformat() if key.expires_at else None,
|
||||||
|
"auto_delete_on_expiry": key.auto_delete_on_expiry,
|
||||||
|
"total_requests": key.total_requests,
|
||||||
|
"total_cost_usd": key.total_cost_usd,
|
||||||
|
}
|
||||||
|
if include_is_standalone:
|
||||||
|
data["is_standalone"] = key.is_standalone
|
||||||
|
return data
|
||||||
|
|
||||||
# 导出 Users(排除管理员)
|
# 导出 Users(排除管理员)
|
||||||
users = db.query(User).filter(
|
users = db.query(User).filter(
|
||||||
User.is_deleted.is_(False),
|
User.is_deleted.is_(False),
|
||||||
@@ -957,31 +1024,12 @@ class AdminExportUsersAdapter(AdminApiAdapter):
|
|||||||
).all()
|
).all()
|
||||||
users_data = []
|
users_data = []
|
||||||
for user in users:
|
for user in users:
|
||||||
# 导出用户的 API Keys(保留加密数据)
|
# 导出用户的 API Keys(排除独立余额Key,独立Key单独导出)
|
||||||
api_keys = db.query(ApiKey).filter(ApiKey.user_id == user.id).all()
|
api_keys = db.query(ApiKey).filter(
|
||||||
api_keys_data = []
|
ApiKey.user_id == user.id,
|
||||||
for key in api_keys:
|
ApiKey.is_standalone.is_(False)
|
||||||
api_keys_data.append(
|
).all()
|
||||||
{
|
api_keys_data = [_serialize_api_key(key, include_is_standalone=True) for key in api_keys]
|
||||||
"key_hash": key.key_hash,
|
|
||||||
"key_encrypted": key.key_encrypted,
|
|
||||||
"name": key.name,
|
|
||||||
"is_standalone": key.is_standalone,
|
|
||||||
"balance_used_usd": key.balance_used_usd,
|
|
||||||
"current_balance_usd": key.current_balance_usd,
|
|
||||||
"allowed_providers": key.allowed_providers,
|
|
||||||
"allowed_endpoints": key.allowed_endpoints,
|
|
||||||
"allowed_api_formats": key.allowed_api_formats,
|
|
||||||
"allowed_models": key.allowed_models,
|
|
||||||
"rate_limit": key.rate_limit,
|
|
||||||
"concurrent_limit": key.concurrent_limit,
|
|
||||||
"force_capabilities": key.force_capabilities,
|
|
||||||
"is_active": key.is_active,
|
|
||||||
"auto_delete_on_expiry": key.auto_delete_on_expiry,
|
|
||||||
"total_requests": key.total_requests,
|
|
||||||
"total_cost_usd": key.total_cost_usd,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
users_data.append(
|
users_data.append(
|
||||||
{
|
{
|
||||||
@@ -1001,10 +1049,15 @@ class AdminExportUsersAdapter(AdminApiAdapter):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 导出独立余额 Keys(管理员创建的,不属于普通用户)
|
||||||
|
standalone_keys = db.query(ApiKey).filter(ApiKey.is_standalone.is_(True)).all()
|
||||||
|
standalone_keys_data = [_serialize_api_key(key) for key in standalone_keys]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"version": "1.0",
|
"version": "1.1",
|
||||||
"exported_at": datetime.now(timezone.utc).isoformat(),
|
"exported_at": datetime.now(timezone.utc).isoformat(),
|
||||||
"users": users_data,
|
"users": users_data,
|
||||||
|
"standalone_keys": standalone_keys_data,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1024,21 +1077,72 @@ class AdminImportUsersAdapter(AdminApiAdapter):
|
|||||||
db = context.db
|
db = context.db
|
||||||
payload = context.ensure_json_body()
|
payload = context.ensure_json_body()
|
||||||
|
|
||||||
# 验证配置版本
|
|
||||||
version = payload.get("version")
|
|
||||||
if version != "1.0":
|
|
||||||
raise InvalidRequestException(f"不支持的配置版本: {version}")
|
|
||||||
|
|
||||||
# 获取导入选项
|
# 获取导入选项
|
||||||
merge_mode = payload.get("merge_mode", "skip") # skip, overwrite, error
|
merge_mode = payload.get("merge_mode", "skip") # skip, overwrite, error
|
||||||
users_data = payload.get("users", [])
|
users_data = payload.get("users", [])
|
||||||
|
standalone_keys_data = payload.get("standalone_keys", [])
|
||||||
|
|
||||||
stats = {
|
stats = {
|
||||||
"users": {"created": 0, "updated": 0, "skipped": 0},
|
"users": {"created": 0, "updated": 0, "skipped": 0},
|
||||||
"api_keys": {"created": 0, "skipped": 0},
|
"api_keys": {"created": 0, "skipped": 0},
|
||||||
|
"standalone_keys": {"created": 0, "skipped": 0},
|
||||||
"errors": [],
|
"errors": [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _create_api_key_from_data(
|
||||||
|
key_data: dict,
|
||||||
|
owner_id: str,
|
||||||
|
is_standalone: bool = False,
|
||||||
|
) -> tuple[ApiKey | None, str]:
|
||||||
|
"""从导入数据创建 ApiKey 对象
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(ApiKey, "created"): 成功创建
|
||||||
|
(None, "skipped"): key 已存在,跳过
|
||||||
|
(None, "invalid"): 数据无效,跳过
|
||||||
|
"""
|
||||||
|
key_hash = key_data.get("key_hash", "").strip()
|
||||||
|
if not key_hash:
|
||||||
|
return None, "invalid"
|
||||||
|
|
||||||
|
# 检查是否已存在
|
||||||
|
existing = db.query(ApiKey).filter(ApiKey.key_hash == key_hash).first()
|
||||||
|
if existing:
|
||||||
|
return None, "skipped"
|
||||||
|
|
||||||
|
# 解析 expires_at
|
||||||
|
expires_at = None
|
||||||
|
if key_data.get("expires_at"):
|
||||||
|
try:
|
||||||
|
expires_at = datetime.fromisoformat(key_data["expires_at"])
|
||||||
|
except ValueError:
|
||||||
|
stats["errors"].append(
|
||||||
|
f"API Key '{key_data.get('name', key_hash[:8])}' 的 expires_at 格式无效"
|
||||||
|
)
|
||||||
|
|
||||||
|
return ApiKey(
|
||||||
|
id=str(uuid.uuid4()),
|
||||||
|
user_id=owner_id,
|
||||||
|
key_hash=key_hash,
|
||||||
|
key_encrypted=key_data.get("key_encrypted"),
|
||||||
|
name=key_data.get("name"),
|
||||||
|
is_standalone=is_standalone or key_data.get("is_standalone", False),
|
||||||
|
balance_used_usd=key_data.get("balance_used_usd", 0.0),
|
||||||
|
current_balance_usd=key_data.get("current_balance_usd"),
|
||||||
|
allowed_providers=key_data.get("allowed_providers"),
|
||||||
|
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"),
|
||||||
|
concurrent_limit=key_data.get("concurrent_limit", 5),
|
||||||
|
force_capabilities=key_data.get("force_capabilities"),
|
||||||
|
is_active=key_data.get("is_active", True),
|
||||||
|
expires_at=expires_at,
|
||||||
|
auto_delete_on_expiry=key_data.get("auto_delete_on_expiry", False),
|
||||||
|
total_requests=key_data.get("total_requests", 0),
|
||||||
|
total_cost_usd=key_data.get("total_cost_usd", 0.0),
|
||||||
|
), "created"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for user_data in users_data:
|
for user_data in users_data:
|
||||||
# 跳过管理员角色的导入(不区分大小写)
|
# 跳过管理员角色的导入(不区分大小写)
|
||||||
@@ -1109,40 +1213,31 @@ class AdminImportUsersAdapter(AdminApiAdapter):
|
|||||||
|
|
||||||
# 导入 API Keys
|
# 导入 API Keys
|
||||||
for key_data in user_data.get("api_keys", []):
|
for key_data in user_data.get("api_keys", []):
|
||||||
# 检查是否已存在相同的 key_hash
|
new_key, status = _create_api_key_from_data(key_data, user_id)
|
||||||
if key_data.get("key_hash"):
|
if new_key:
|
||||||
existing_key = (
|
db.add(new_key)
|
||||||
db.query(ApiKey)
|
stats["api_keys"]["created"] += 1
|
||||||
.filter(ApiKey.key_hash == key_data["key_hash"])
|
elif status == "skipped":
|
||||||
.first()
|
stats["api_keys"]["skipped"] += 1
|
||||||
)
|
# invalid 数据不计入统计
|
||||||
if existing_key:
|
|
||||||
stats["api_keys"]["skipped"] += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
new_key = ApiKey(
|
# 导入独立余额 Keys(需要找一个管理员用户作为 owner)
|
||||||
id=str(uuid.uuid4()),
|
if standalone_keys_data:
|
||||||
user_id=user_id,
|
# 查找一个管理员用户作为独立Key的owner
|
||||||
key_hash=key_data.get("key_hash", ""),
|
admin_user = db.query(User).filter(User.role == UserRole.ADMIN).first()
|
||||||
key_encrypted=key_data.get("key_encrypted"),
|
if not admin_user:
|
||||||
name=key_data.get("name"),
|
stats["errors"].append("无法导入独立余额Key: 系统中没有管理员用户")
|
||||||
is_standalone=key_data.get("is_standalone", False),
|
else:
|
||||||
balance_used_usd=key_data.get("balance_used_usd", 0.0),
|
for key_data in standalone_keys_data:
|
||||||
current_balance_usd=key_data.get("current_balance_usd"),
|
new_key, status = _create_api_key_from_data(
|
||||||
allowed_providers=key_data.get("allowed_providers"),
|
key_data, admin_user.id, is_standalone=True
|
||||||
allowed_endpoints=key_data.get("allowed_endpoints"),
|
)
|
||||||
allowed_api_formats=key_data.get("allowed_api_formats"),
|
if new_key:
|
||||||
allowed_models=key_data.get("allowed_models"),
|
db.add(new_key)
|
||||||
rate_limit=key_data.get("rate_limit"), # None = 无限制
|
stats["standalone_keys"]["created"] += 1
|
||||||
concurrent_limit=key_data.get("concurrent_limit", 5),
|
elif status == "skipped":
|
||||||
force_capabilities=key_data.get("force_capabilities"),
|
stats["standalone_keys"]["skipped"] += 1
|
||||||
is_active=key_data.get("is_active", True),
|
# invalid 数据不计入统计
|
||||||
auto_delete_on_expiry=key_data.get("auto_delete_on_expiry", False),
|
|
||||||
total_requests=key_data.get("total_requests", 0),
|
|
||||||
total_cost_usd=key_data.get("total_cost_usd", 0.0),
|
|
||||||
)
|
|
||||||
db.add(new_key)
|
|
||||||
stats["api_keys"]["created"] += 1
|
|
||||||
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user