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 Key
-
+
+
+
+
+
暂无 API Keys
+
点击上方按钮创建您的第一个 API Key
+
-
-
-
-
-
- |
- 名称
-
-
- |
-
- 标签
- |
-
- 状态
-
-
- |
-
- 使用统计
-
- (费用
-
- )
-
- |
-
- 创建时间
-
-
- |
-
- 过期时间
-
-
- |
-
- 操作
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ key.name }}
-
-
- {{ key.id }}
-
-
-
-
-
-
-
- Claude
-
-
- {{ getClaudeBindingInfo(key) }}
-
-
-
-
-
-
- Gemini
-
-
- {{ getGeminiBindingInfo(key) }}
-
-
-
-
-
-
- OpenAI
-
-
- {{ getOpenAIBindingInfo(key) }}
-
-
-
-
-
- 使用共享池
-
-
-
-
- |
-
-
-
- {{ tag }}
-
- 无标签
-
- |
-
-
+
+
+
+
+ |
-
- {{ key.isActive ? '活跃' : '禁用' }}
-
-
- |
-
-
-
-
- 今日请求
- {{ formatNumber(key.usage?.daily?.requests || 0) }}次
-
-
- 今日费用
- ${{ (key.dailyCost || 0).toFixed(4) }}
-
-
- 最后使用
- {{
- formatLastUsed(key.lastUsedAt)
- }}
-
-
-
-
-
-
- 费用限额
-
- ${{ (key.dailyCost || 0).toFixed(2) }} / ${{
- key.dailyCostLimit.toFixed(2)
- }}
-
-
-
-
-
-
-
+
+
+ 标签
+ |
+
+ 状态
+
-
-
-
-
+ |
+
+ 使用统计
+
+ (费用
+
+ )
+
+ |
+
+ 创建时间
+
+
+ |
+
+ 过期时间
+
+
+ |
+
+ 操作
+ |
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ key.name }}
+
+
+ {{ key.id }}
+
+
+
+
+
+
+
+ Claude
+
+
+ {{ getClaudeBindingInfo(key) }}
+
+
+
+
+
+
+ Gemini
+
+
+ {{ getGeminiBindingInfo(key) }}
+
+
+
+
+
+
+ OpenAI
+
+
+ {{ getOpenAIBindingInfo(key) }}
+
+
+
+
+
+ 使用共享池
+
+
+
+
+ |
+
+
+
+ {{ tag }}
+
+ 无标签
+
+ |
+
+
-
- 查看详细统计
-
+
+ {{ key.isActive ? '活跃' : '禁用' }}
+
+ |
+
+
+
+
+
+ 今日请求
+ {{ formatNumber(key.usage?.daily?.requests || 0) }}次
+
+
+ 今日费用
+ ${{ (key.dailyCost || 0).toFixed(4) }}
+
+
+ 最后使用
+ {{
+ formatLastUsed(key.lastUsedAt)
+ }}
+
+
+
+
+
+
+ 费用限额
+
+ ${{ (key.dailyCost || 0).toFixed(2) }} / ${{
+ key.dailyCostLimit.toFixed(2)
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+ {{ new Date(key.createdAt).toLocaleDateString() }}
+ |
+
+
+
+
+
+ 已过期
+
+
+
+ {{ formatExpireDate(key.expiresAt) }}
+
+
+ {{ formatExpireDate(key.expiresAt) }}
+
+
+
+
+ 永不过期
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+ 模型使用分布
+
+
+
+ {{ apiKeyModelStats[key.id].length }} 个模型
+
+
+
+
+
+
+
+
+
+
+ onApiKeyCustomDateRangeChange(key.id, value)
+ "
+ />
+
+
+
+
+
+
+
+
+ 暂无模型使用数据
+
+
+
+ 尝试调整时间范围或点击刷新重新加载数据
+
+
+
+
+
+
+ {{
+ stat.model
+ }}
+ {{ stat.requests }} 次请求
+
+
+
+
+
+
+
+ 总Token:
+
+ {{
+ formatTokenCount(stat.allTokens)
+ }}
+
+
+
+
+ 费用:
+
+ {{
+ calculateModelCost(stat)
+ }}
+
+
+
+
+
+ 输入:
+
+ {{
+ formatTokenCount(stat.inputTokens)
+ }}
+
+
+
+
+ 输出:
+
+ {{
+ formatTokenCount(stat.outputTokens)
+ }}
+
+
+
+
+ 缓存创建:
+
+ {{
+ formatTokenCount(stat.cacheCreateTokens)
+ }}
+
+
+
+
+ 缓存读取:
+
+ {{
+ formatTokenCount(stat.cacheReadTokens)
+ }}
+
+
+
+
+
+
+
+
+ {{
+ calculateApiKeyModelPercentage(
+ stat.allTokens,
+ apiKeyModelStats[key.id]
+ )
+ }}%
+
+
+
+
+
+
+
+
+
+
+ 总计统计
+
+
+
+ 总请求:
+ {{
+ apiKeyModelStats[key.id].reduce(
+ (sum, stat) => sum + stat.requests,
+ 0
+ )
+ }}
+
+
+ 总Token:
+ {{
+ formatTokenCount(
+ apiKeyModelStats[key.id].reduce(
+ (sum, stat) => sum + stat.allTokens,
+ 0
+ )
+ )
+ }}
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ key.name }}
+
+
+ {{ key.id }}
+
+
+
+
+
+ {{ key.isActive ? '活跃' : '已停用' }}
+
+
+
+
+
+
+
+
+
+ Claude
+
+
+ {{ getClaudeBindingInfo(key) }}
+
+
+
+
+
+
+ Gemini
+
+
+ {{ getGeminiBindingInfo(key) }}
+
+
+
+
+
+
+ OpenAI
+
+
+ {{ getOpenAIBindingInfo(key) }}
+
+
+
+
+
+ 使用共享池
+
+
+
+
+
+
+
+
+ 今日使用
+
+
+
+
+
+ {{ formatNumber(key.usage?.daily?.requests || 0) }} 次
+
+ 请求
+
+
+
+ ${{ (key.dailyCost || 0).toFixed(4) }}
+
+ 费用
- |
-
- {{ new Date(key.createdAt).toLocaleDateString() }}
- |
-
-
-
-
-
- 已过期
-
-
-
- {{ formatExpireDate(key.expiresAt) }}
-
-
- {{ formatExpireDate(key.expiresAt) }}
-
+
+ 最后使用
+ {{
+ formatLastUsed(key.lastUsedAt)
+ }}
+
+
+
+
+
+
+ 每日费用限额
+
+ ${{ (key.dailyCost || 0).toFixed(2) }} / ${{ key.dailyCostLimit.toFixed(2) }}
-
-
- 永不过期
+
+
+
+
+
+
+
+
+
+
+
+ 创建时间
+ {{ formatDate(key.createdAt) }}
+
+
+ 过期时间
+
+
+ {{ key.expiresAt ? formatDate(key.expiresAt) : '永不过期' }}
- |
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
- 模型使用分布
-
-
-
- {{ apiKeyModelStats[key.id].length }} 个模型
-
-
-
-
-
-
-
-
-
-
- onApiKeyCustomDateRangeChange(key.id, value)
- "
- />
-
-
-
-
-
-
-
-
- 暂无模型使用数据
-
-
- 尝试调整时间范围或点击刷新重新加载数据
-
-
-
-
-
- {{
- stat.model
- }}
- {{ stat.requests }} 次请求
-
-
-
-
-
-
-
- 总Token:
-
- {{
- formatTokenCount(stat.allTokens)
- }}
-
-
-
-
- 费用:
-
- {{
- calculateModelCost(stat)
- }}
-
-
-
-
-
- 输入:
-
- {{
- formatTokenCount(stat.inputTokens)
- }}
-
-
-
-
- 输出:
-
- {{
- formatTokenCount(stat.outputTokens)
- }}
-
-
-
-
- 缓存创建:
-
- {{
- formatTokenCount(stat.cacheCreateTokens)
- }}
-
-
-
-
- 缓存读取:
-
- {{
- formatTokenCount(stat.cacheReadTokens)
- }}
-
-
-
-
-
-
-
-
- {{
- calculateApiKeyModelPercentage(
- stat.allTokens,
- apiKeyModelStats[key.id]
- )
- }}%
-
-
-
-
-
-
-
-
-
-
- 总计统计
-
-
-
- 总请求:
- {{
- apiKeyModelStats[key.id].reduce((sum, stat) => sum + stat.requests, 0)
- }}
-
-
- 总Token:
- {{
- formatTokenCount(
- apiKeyModelStats[key.id].reduce(
- (sum, stat) => sum + stat.allTokens,
- 0
- )
- )
- }}
-
-
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ 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 }} 条记录
-
-
- 每页显示
-
- 条
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -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) {