diff --git a/src/routes/userRoutes.js b/src/routes/userRoutes.js index 094926bf..13e5626e 100644 --- a/src/routes/userRoutes.js +++ b/src/routes/userRoutes.js @@ -5,12 +5,7 @@ const userService = require('../services/userService') const apiKeyService = require('../services/apiKeyService') const logger = require('../utils/logger') const config = require('../../config/config') -const { - authenticateUser, - authenticateUserOrAdmin, - requireAdmin, - requireRole -} = require('../middleware/auth') +const { authenticateUser, authenticateUserOrAdmin, requireAdmin } = require('../middleware/auth') // 🔐 用户登录端点 router.post('/login', async (req, res) => { @@ -253,7 +248,6 @@ router.post('/api-keys', authenticateUser, async (req, res) => { } }) - // 🗑️ 删除API Key router.delete('/api-keys/:keyId', authenticateUser, async (req, res) => { try { @@ -313,7 +307,7 @@ router.get('/usage-stats', authenticateUser, async (req, res) => { } // 获取使用统计 - const stats = await apiKeyService.getUsageStats(apiKeyIds, { period, model }) + const stats = await apiKeyService.getAggregatedUsageStats(apiKeyIds, { period, model }) res.json({ success: true, @@ -584,7 +578,7 @@ router.get('/:userId/usage-stats', authenticateUserOrAdmin, requireAdmin, async } // 获取使用统计 - const stats = await apiKeyService.getUsageStats(apiKeyIds, { period, model }) + const stats = await apiKeyService.getAggregatedUsageStats(apiKeyIds, { period, model }) res.json({ success: true, diff --git a/src/services/apiKeyService.js b/src/services/apiKeyService.js index d1b14b1d..6a042ab0 100644 --- a/src/services/apiKeyService.js +++ b/src/services/apiKeyService.js @@ -536,7 +536,7 @@ class ApiKeyService { createdAt: key.createdAt, lastUsedAt: key.lastUsedAt, expiresAt: key.expiresAt, - usage: usage, + usage, dailyCost, totalCost: costStats.total, dailyCostLimit: parseFloat(key.dailyCostLimit || 0), @@ -628,8 +628,8 @@ class ApiKeyService { } } - // 🗑️ 删除API Key - async deleteApiKey(keyId) { + // 🗑️ 硬删除API Key (完全移除) + async hardDeleteApiKey(keyId) { try { const keyData = await redis.getApiKey(keyId) if (!keyData) { @@ -669,14 +669,14 @@ class ApiKeyService { } } - // 📊 获取使用统计(支持多个API Key) - async getUsageStats(keyIds, options = {}) { + // 📊 获取聚合使用统计(支持多个API Key) + async getAggregatedUsageStats(keyIds, options = {}) { try { if (!Array.isArray(keyIds)) { keyIds = [keyIds] } - const { period = 'week', model } = options + const { period: _period = 'week', model: _model } = options const stats = { totalRequests: 0, totalInputTokens: 0, diff --git a/web/admin-spa/src/views/ApiKeysView.vue b/web/admin-spa/src/views/ApiKeysView.vue index e8285841..c8b7e24b 100644 --- a/web/admin-spa/src/views/ApiKeysView.vue +++ b/web/admin-spa/src/views/ApiKeysView.vue @@ -6,418 +6,987 @@

API Keys 管理

管理和监控您的 API 密钥

-
- -
- -
-
- -
- -
-
-
- - - {{ selectedTagCount }} - -
-
- - -
-
-
- - - -
-
- - + +
+ +
+ + + +
+
+ +
+ +
+
+ +
+ + +
+
+
+ + + {{ selectedTagCount }} + +
+
+ + +
+
+
+ + + +
+
+ + + +
+ +
- - -
-
-
-
-

正在加载 API Keys...

-
+
+
+

正在加载 API Keys...

+
-
-
- -
-

暂无 API Keys

-

点击上方按钮创建您的第一个 API Key

-
+
+
+ +
+

暂无 API Keys

+

点击上方按钮创建您的第一个 API Key

+
- - - - -
-
- -
-
-
- +
-
-

- {{ key.name }} -

-

- {{ key.id }} -

+ + +
+ + {{ tag }} +
-
- -
- {{ key.isActive ? '活跃' : '已停用' }} - -
- -
- -
- - - Claude - - - {{ getClaudeBindingInfo(key) }} - -
- -
- - - Gemini - - - {{ getGeminiBindingInfo(key) }} - -
- -
- - - OpenAI - - - {{ getOpenAIBindingInfo(key) }} - -
- -
- - 使用共享池 -
-
- - -
- -
-
- 今日使用 + +
-
-
-
-

- {{ formatNumber(key.usage?.daily?.requests || 0) }} 次 -

-

请求

-
-
-

- ${{ (key.dailyCost || 0).toFixed(4) }} -

-

费用

-
-
-
- 最后使用 - {{ - formatLastUsed(key.lastUsedAt) - }} -
-
- - -
-
- 每日费用限额 - - ${{ (key.dailyCost || 0).toFixed(2) }} / ${{ key.dailyCostLimit.toFixed(2) }} - -
-
-
-
-
- - - -
- - -
-
- 创建时间 - {{ formatDate(key.createdAt) }} -
-
- 过期时间 -
- - {{ key.expiresAt ? formatDate(key.expiresAt) : '永不过期' }} - + + +
- -
- - {{ tag }} - -
- - -
- - - - - -
-
-
- - -
-
- - 共 {{ sortedApiKeys.length }} 条记录 - -
- 每页显示 - - -
-
- -
- - +
+ + 共 {{ sortedApiKeys.length }} 条记录 + +
+ 每页显示 + + +
+
- -
- - - +
+ + - - + +
+ + + - - - + + + + + + +
+ + + +
+
+
+ + +
+
+
+

正在加载已删除的 API Keys...

- - +
+
+ +
+

暂无已删除的 API Keys

+

已删除的 API Keys 会出现在这里

+
+ + +
+ + + + + + + + + + + + + + + + + + + + + +
+ 名称 + + 创建者 + + 创建时间 + + 删除者 + + 删除时间 + + 使用统计 +
+
+
+ +
+
+
+ {{ key.name }} +
+
+ {{ key.id }} +
+
+
+
+
+ + + 管理员 + + + + {{ key.userUsername }} + + + + 未知 + +
+
+ {{ formatDate(key.createdAt) }} + +
+ + + {{ key.deletedBy }} + + + + {{ key.deletedBy }} + + + + {{ key.deletedBy }} + +
+
+ {{ formatDate(key.deletedAt) }} + +
+
+ 请求 + + {{ formatNumber(key.usage?.total?.requests || 0) }}次 + +
+
+ 费用 + + ${{ (key.usage?.total?.cost || 0).toFixed(4) }} + +
+
+ 最后使用 + + {{ formatLastUsed(key.lastUsedAt) }} + +
+
从未使用
+
+
+
+ + + + + + + + + + + + + + + + +
- - - - - - - - - - - - - - - - -
@@ -1154,6 +1367,11 @@ const clientsStore = useClientsStore() const apiKeys = ref([]) const apiKeysLoading = ref(false) const apiKeyStatsTimeRange = ref('today') + +// Tab management +const activeTab = ref('active') +const deletedApiKeys = ref([]) +const deletedApiKeysLoading = ref(false) const apiKeysSortBy = ref('') const apiKeysSortOrder = ref('asc') const expandedApiKeys = ref({}) @@ -1376,6 +1594,22 @@ const loadApiKeys = async () => { } } +// 加载已删除的API Keys +const loadDeletedApiKeys = async () => { + activeTab.value = 'deleted' + deletedApiKeysLoading.value = true + try { + const data = await apiClient.get('/admin/api-keys/deleted') + if (data.success) { + deletedApiKeys.value = data.apiKeys || [] + } + } catch (error) { + showToast('加载已删除的 API Keys 失败', 'error') + } finally { + deletedApiKeysLoading.value = false + } +} + // 排序API Keys const sortApiKeys = (field) => { if (apiKeysSortBy.value === field) {