mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
refactor: standardize code formatting and linting configuration
- Replace .eslintrc.js with .eslintrc.cjs for better ES module compatibility - Add .prettierrc configuration for consistent code formatting - Update package.json with new lint and format scripts - Add nodemon.json for development hot reloading configuration - Standardize code formatting across all JavaScript and Vue files - Update web admin SPA with improved linting rules and formatting - Add prettier configuration to web admin SPA 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
const crypto = require('crypto');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const config = require('../../config/config');
|
||||
const redis = require('../models/redis');
|
||||
const logger = require('../utils/logger');
|
||||
const crypto = require('crypto')
|
||||
const { v4: uuidv4 } = require('uuid')
|
||||
const config = require('../../config/config')
|
||||
const redis = require('../models/redis')
|
||||
const logger = require('../utils/logger')
|
||||
|
||||
class ApiKeyService {
|
||||
constructor() {
|
||||
this.prefix = config.security.apiKeyPrefix;
|
||||
this.prefix = config.security.apiKeyPrefix
|
||||
}
|
||||
|
||||
// 🔑 生成新的API Key
|
||||
@@ -30,13 +30,13 @@ class ApiKeyService {
|
||||
allowedClients = [],
|
||||
dailyCostLimit = 0,
|
||||
tags = []
|
||||
} = options;
|
||||
} = options
|
||||
|
||||
// 生成简单的API Key (64字符十六进制)
|
||||
const apiKey = `${this.prefix}${this._generateSecretKey()}`;
|
||||
const keyId = uuidv4();
|
||||
const hashedKey = this._hashApiKey(apiKey);
|
||||
|
||||
const apiKey = `${this.prefix}${this._generateSecretKey()}`
|
||||
const keyId = uuidv4()
|
||||
const hashedKey = this._hashApiKey(apiKey)
|
||||
|
||||
const keyData = {
|
||||
id: keyId,
|
||||
name,
|
||||
@@ -61,13 +61,13 @@ class ApiKeyService {
|
||||
lastUsedAt: '',
|
||||
expiresAt: expiresAt || '',
|
||||
createdBy: 'admin' // 可以根据需要扩展用户系统
|
||||
};
|
||||
}
|
||||
|
||||
// 保存API Key数据并建立哈希映射
|
||||
await redis.setApiKey(keyId, keyData, hashedKey);
|
||||
|
||||
logger.success(`🔑 Generated new API key: ${name} (${keyId})`);
|
||||
|
||||
await redis.setApiKey(keyId, keyData, hashedKey)
|
||||
|
||||
logger.success(`🔑 Generated new API key: ${name} (${keyId})`)
|
||||
|
||||
return {
|
||||
id: keyId,
|
||||
apiKey, // 只在创建时返回完整的key
|
||||
@@ -91,69 +91,69 @@ class ApiKeyService {
|
||||
createdAt: keyData.createdAt,
|
||||
expiresAt: keyData.expiresAt,
|
||||
createdBy: keyData.createdBy
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 🔍 验证API Key
|
||||
// 🔍 验证API Key
|
||||
async validateApiKey(apiKey) {
|
||||
try {
|
||||
if (!apiKey || !apiKey.startsWith(this.prefix)) {
|
||||
return { valid: false, error: 'Invalid API key format' };
|
||||
return { valid: false, error: 'Invalid API key format' }
|
||||
}
|
||||
|
||||
// 计算API Key的哈希值
|
||||
const hashedKey = this._hashApiKey(apiKey);
|
||||
|
||||
const hashedKey = this._hashApiKey(apiKey)
|
||||
|
||||
// 通过哈希值直接查找API Key(性能优化)
|
||||
const keyData = await redis.findApiKeyByHash(hashedKey);
|
||||
|
||||
const keyData = await redis.findApiKeyByHash(hashedKey)
|
||||
|
||||
if (!keyData) {
|
||||
return { valid: false, error: 'API key not found' };
|
||||
return { valid: false, error: 'API key not found' }
|
||||
}
|
||||
|
||||
// 检查是否激活
|
||||
if (keyData.isActive !== 'true') {
|
||||
return { valid: false, error: 'API key is disabled' };
|
||||
return { valid: false, error: 'API key is disabled' }
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if (keyData.expiresAt && new Date() > new Date(keyData.expiresAt)) {
|
||||
return { valid: false, error: 'API key has expired' };
|
||||
return { valid: false, error: 'API key has expired' }
|
||||
}
|
||||
|
||||
// 获取使用统计(供返回数据使用)
|
||||
const usage = await redis.getUsageStats(keyData.id);
|
||||
|
||||
const usage = await redis.getUsageStats(keyData.id)
|
||||
|
||||
// 获取当日费用统计
|
||||
const dailyCost = await redis.getDailyCost(keyData.id);
|
||||
const dailyCost = await redis.getDailyCost(keyData.id)
|
||||
|
||||
// 更新最后使用时间(优化:只在实际API调用时更新,而不是验证时)
|
||||
// 注意:lastUsedAt的更新已移至recordUsage方法中
|
||||
|
||||
logger.api(`🔓 API key validated successfully: ${keyData.id}`);
|
||||
logger.api(`🔓 API key validated successfully: ${keyData.id}`)
|
||||
|
||||
// 解析限制模型数据
|
||||
let restrictedModels = [];
|
||||
let restrictedModels = []
|
||||
try {
|
||||
restrictedModels = keyData.restrictedModels ? JSON.parse(keyData.restrictedModels) : [];
|
||||
restrictedModels = keyData.restrictedModels ? JSON.parse(keyData.restrictedModels) : []
|
||||
} catch (e) {
|
||||
restrictedModels = [];
|
||||
restrictedModels = []
|
||||
}
|
||||
|
||||
// 解析允许的客户端
|
||||
let allowedClients = [];
|
||||
let allowedClients = []
|
||||
try {
|
||||
allowedClients = keyData.allowedClients ? JSON.parse(keyData.allowedClients) : [];
|
||||
allowedClients = keyData.allowedClients ? JSON.parse(keyData.allowedClients) : []
|
||||
} catch (e) {
|
||||
allowedClients = [];
|
||||
allowedClients = []
|
||||
}
|
||||
|
||||
// 解析标签
|
||||
let tags = [];
|
||||
let tags = []
|
||||
try {
|
||||
tags = keyData.tags ? JSON.parse(keyData.tags) : [];
|
||||
tags = keyData.tags ? JSON.parse(keyData.tags) : []
|
||||
} catch (e) {
|
||||
tags = [];
|
||||
tags = []
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -173,248 +173,306 @@ class ApiKeyService {
|
||||
rateLimitWindow: parseInt(keyData.rateLimitWindow || 0),
|
||||
rateLimitRequests: parseInt(keyData.rateLimitRequests || 0),
|
||||
enableModelRestriction: keyData.enableModelRestriction === 'true',
|
||||
restrictedModels: restrictedModels,
|
||||
restrictedModels,
|
||||
enableClientRestriction: keyData.enableClientRestriction === 'true',
|
||||
allowedClients: allowedClients,
|
||||
allowedClients,
|
||||
dailyCostLimit: parseFloat(keyData.dailyCostLimit || 0),
|
||||
dailyCost: dailyCost || 0,
|
||||
tags: tags,
|
||||
tags,
|
||||
usage
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('❌ API key validation error:', error);
|
||||
return { valid: false, error: 'Internal validation error' };
|
||||
logger.error('❌ API key validation error:', error)
|
||||
return { valid: false, error: 'Internal validation error' }
|
||||
}
|
||||
}
|
||||
|
||||
// 📋 获取所有API Keys
|
||||
async getAllApiKeys() {
|
||||
try {
|
||||
const apiKeys = await redis.getAllApiKeys();
|
||||
const client = redis.getClientSafe();
|
||||
|
||||
const apiKeys = await redis.getAllApiKeys()
|
||||
const client = redis.getClientSafe()
|
||||
|
||||
// 为每个key添加使用统计和当前并发数
|
||||
for (const key of apiKeys) {
|
||||
key.usage = await redis.getUsageStats(key.id);
|
||||
key.tokenLimit = parseInt(key.tokenLimit);
|
||||
key.concurrencyLimit = parseInt(key.concurrencyLimit || 0);
|
||||
key.rateLimitWindow = parseInt(key.rateLimitWindow || 0);
|
||||
key.rateLimitRequests = parseInt(key.rateLimitRequests || 0);
|
||||
key.currentConcurrency = await redis.getConcurrency(key.id);
|
||||
key.isActive = key.isActive === 'true';
|
||||
key.enableModelRestriction = key.enableModelRestriction === 'true';
|
||||
key.enableClientRestriction = key.enableClientRestriction === 'true';
|
||||
key.permissions = key.permissions || 'all'; // 兼容旧数据
|
||||
key.dailyCostLimit = parseFloat(key.dailyCostLimit || 0);
|
||||
key.dailyCost = await redis.getDailyCost(key.id) || 0;
|
||||
|
||||
key.usage = await redis.getUsageStats(key.id)
|
||||
key.tokenLimit = parseInt(key.tokenLimit)
|
||||
key.concurrencyLimit = parseInt(key.concurrencyLimit || 0)
|
||||
key.rateLimitWindow = parseInt(key.rateLimitWindow || 0)
|
||||
key.rateLimitRequests = parseInt(key.rateLimitRequests || 0)
|
||||
key.currentConcurrency = await redis.getConcurrency(key.id)
|
||||
key.isActive = key.isActive === 'true'
|
||||
key.enableModelRestriction = key.enableModelRestriction === 'true'
|
||||
key.enableClientRestriction = key.enableClientRestriction === 'true'
|
||||
key.permissions = key.permissions || 'all' // 兼容旧数据
|
||||
key.dailyCostLimit = parseFloat(key.dailyCostLimit || 0)
|
||||
key.dailyCost = (await redis.getDailyCost(key.id)) || 0
|
||||
|
||||
// 获取当前时间窗口的请求次数和Token使用量
|
||||
if (key.rateLimitWindow > 0) {
|
||||
const requestCountKey = `rate_limit:requests:${key.id}`;
|
||||
const tokenCountKey = `rate_limit:tokens:${key.id}`;
|
||||
|
||||
key.currentWindowRequests = parseInt(await client.get(requestCountKey) || '0');
|
||||
key.currentWindowTokens = parseInt(await client.get(tokenCountKey) || '0');
|
||||
const requestCountKey = `rate_limit:requests:${key.id}`
|
||||
const tokenCountKey = `rate_limit:tokens:${key.id}`
|
||||
|
||||
key.currentWindowRequests = parseInt((await client.get(requestCountKey)) || '0')
|
||||
key.currentWindowTokens = parseInt((await client.get(tokenCountKey)) || '0')
|
||||
} else {
|
||||
key.currentWindowRequests = 0;
|
||||
key.currentWindowTokens = 0;
|
||||
key.currentWindowRequests = 0
|
||||
key.currentWindowTokens = 0
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
key.restrictedModels = key.restrictedModels ? JSON.parse(key.restrictedModels) : [];
|
||||
key.restrictedModels = key.restrictedModels ? JSON.parse(key.restrictedModels) : []
|
||||
} catch (e) {
|
||||
key.restrictedModels = [];
|
||||
key.restrictedModels = []
|
||||
}
|
||||
try {
|
||||
key.allowedClients = key.allowedClients ? JSON.parse(key.allowedClients) : [];
|
||||
key.allowedClients = key.allowedClients ? JSON.parse(key.allowedClients) : []
|
||||
} catch (e) {
|
||||
key.allowedClients = [];
|
||||
key.allowedClients = []
|
||||
}
|
||||
try {
|
||||
key.tags = key.tags ? JSON.parse(key.tags) : [];
|
||||
key.tags = key.tags ? JSON.parse(key.tags) : []
|
||||
} catch (e) {
|
||||
key.tags = [];
|
||||
key.tags = []
|
||||
}
|
||||
delete key.apiKey; // 不返回哈希后的key
|
||||
delete key.apiKey // 不返回哈希后的key
|
||||
}
|
||||
|
||||
return apiKeys;
|
||||
return apiKeys
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to get API keys:', error);
|
||||
throw error;
|
||||
logger.error('❌ Failed to get API keys:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 📝 更新API Key
|
||||
async updateApiKey(keyId, updates) {
|
||||
try {
|
||||
const keyData = await redis.getApiKey(keyId);
|
||||
const keyData = await redis.getApiKey(keyId)
|
||||
if (!keyData || Object.keys(keyData).length === 0) {
|
||||
throw new Error('API key not found');
|
||||
throw new Error('API key not found')
|
||||
}
|
||||
|
||||
// 允许更新的字段
|
||||
const allowedUpdates = ['name', 'description', 'tokenLimit', 'concurrencyLimit', 'rateLimitWindow', 'rateLimitRequests', 'isActive', 'claudeAccountId', 'claudeConsoleAccountId', 'geminiAccountId', 'permissions', 'expiresAt', 'enableModelRestriction', 'restrictedModels', 'enableClientRestriction', 'allowedClients', 'dailyCostLimit', 'tags'];
|
||||
const updatedData = { ...keyData };
|
||||
const allowedUpdates = [
|
||||
'name',
|
||||
'description',
|
||||
'tokenLimit',
|
||||
'concurrencyLimit',
|
||||
'rateLimitWindow',
|
||||
'rateLimitRequests',
|
||||
'isActive',
|
||||
'claudeAccountId',
|
||||
'claudeConsoleAccountId',
|
||||
'geminiAccountId',
|
||||
'permissions',
|
||||
'expiresAt',
|
||||
'enableModelRestriction',
|
||||
'restrictedModels',
|
||||
'enableClientRestriction',
|
||||
'allowedClients',
|
||||
'dailyCostLimit',
|
||||
'tags'
|
||||
]
|
||||
const updatedData = { ...keyData }
|
||||
|
||||
for (const [field, value] of Object.entries(updates)) {
|
||||
if (allowedUpdates.includes(field)) {
|
||||
if (field === 'restrictedModels' || field === 'allowedClients' || field === 'tags') {
|
||||
// 特殊处理数组字段
|
||||
updatedData[field] = JSON.stringify(value || []);
|
||||
updatedData[field] = JSON.stringify(value || [])
|
||||
} else if (field === 'enableModelRestriction' || field === 'enableClientRestriction') {
|
||||
// 布尔值转字符串
|
||||
updatedData[field] = String(value);
|
||||
updatedData[field] = String(value)
|
||||
} else {
|
||||
updatedData[field] = (value != null ? value : '').toString();
|
||||
updatedData[field] = (value !== null && value !== undefined ? value : '').toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatedData.updatedAt = new Date().toISOString();
|
||||
|
||||
updatedData.updatedAt = new Date().toISOString()
|
||||
|
||||
// 更新时不需要重新建立哈希映射,因为API Key本身没有变化
|
||||
await redis.setApiKey(keyId, updatedData);
|
||||
|
||||
logger.success(`📝 Updated API key: ${keyId}`);
|
||||
|
||||
return { success: true };
|
||||
await redis.setApiKey(keyId, updatedData)
|
||||
|
||||
logger.success(`📝 Updated API key: ${keyId}`)
|
||||
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to update API key:', error);
|
||||
throw error;
|
||||
logger.error('❌ Failed to update API key:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 🗑️ 删除API Key
|
||||
async deleteApiKey(keyId) {
|
||||
try {
|
||||
const result = await redis.deleteApiKey(keyId);
|
||||
|
||||
const result = await redis.deleteApiKey(keyId)
|
||||
|
||||
if (result === 0) {
|
||||
throw new Error('API key not found');
|
||||
throw new Error('API key not found')
|
||||
}
|
||||
|
||||
logger.success(`🗑️ Deleted API key: ${keyId}`);
|
||||
|
||||
return { success: true };
|
||||
|
||||
logger.success(`🗑️ Deleted API key: ${keyId}`)
|
||||
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to delete API key:', error);
|
||||
throw error;
|
||||
logger.error('❌ Failed to delete API key:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 📊 记录使用情况(支持缓存token和账户级别统计)
|
||||
async recordUsage(keyId, inputTokens = 0, outputTokens = 0, cacheCreateTokens = 0, cacheReadTokens = 0, model = 'unknown', accountId = null) {
|
||||
async recordUsage(
|
||||
keyId,
|
||||
inputTokens = 0,
|
||||
outputTokens = 0,
|
||||
cacheCreateTokens = 0,
|
||||
cacheReadTokens = 0,
|
||||
model = 'unknown',
|
||||
accountId = null
|
||||
) {
|
||||
try {
|
||||
const totalTokens = inputTokens + outputTokens + cacheCreateTokens + cacheReadTokens;
|
||||
|
||||
const totalTokens = inputTokens + outputTokens + cacheCreateTokens + cacheReadTokens
|
||||
|
||||
// 计算费用
|
||||
const CostCalculator = require('../utils/costCalculator');
|
||||
const costInfo = CostCalculator.calculateCost({
|
||||
input_tokens: inputTokens,
|
||||
output_tokens: outputTokens,
|
||||
cache_creation_input_tokens: cacheCreateTokens,
|
||||
cache_read_input_tokens: cacheReadTokens
|
||||
}, model);
|
||||
|
||||
const CostCalculator = require('../utils/costCalculator')
|
||||
const costInfo = CostCalculator.calculateCost(
|
||||
{
|
||||
input_tokens: inputTokens,
|
||||
output_tokens: outputTokens,
|
||||
cache_creation_input_tokens: cacheCreateTokens,
|
||||
cache_read_input_tokens: cacheReadTokens
|
||||
},
|
||||
model
|
||||
)
|
||||
|
||||
// 记录API Key级别的使用统计
|
||||
await redis.incrementTokenUsage(keyId, totalTokens, inputTokens, outputTokens, cacheCreateTokens, cacheReadTokens, model);
|
||||
|
||||
await redis.incrementTokenUsage(
|
||||
keyId,
|
||||
totalTokens,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
cacheCreateTokens,
|
||||
cacheReadTokens,
|
||||
model
|
||||
)
|
||||
|
||||
// 记录费用统计
|
||||
if (costInfo.costs.total > 0) {
|
||||
await redis.incrementDailyCost(keyId, costInfo.costs.total);
|
||||
logger.database(`💰 Recorded cost for ${keyId}: $${costInfo.costs.total.toFixed(6)}, model: ${model}`);
|
||||
await redis.incrementDailyCost(keyId, costInfo.costs.total)
|
||||
logger.database(
|
||||
`💰 Recorded cost for ${keyId}: $${costInfo.costs.total.toFixed(6)}, model: ${model}`
|
||||
)
|
||||
} else {
|
||||
logger.debug(`💰 No cost recorded for ${keyId} - zero cost for model: ${model}`);
|
||||
logger.debug(`💰 No cost recorded for ${keyId} - zero cost for model: ${model}`)
|
||||
}
|
||||
|
||||
|
||||
// 获取API Key数据以确定关联的账户
|
||||
const keyData = await redis.getApiKey(keyId);
|
||||
const keyData = await redis.getApiKey(keyId)
|
||||
if (keyData && Object.keys(keyData).length > 0) {
|
||||
// 更新最后使用时间
|
||||
keyData.lastUsedAt = new Date().toISOString();
|
||||
await redis.setApiKey(keyId, keyData);
|
||||
|
||||
keyData.lastUsedAt = new Date().toISOString()
|
||||
await redis.setApiKey(keyId, keyData)
|
||||
|
||||
// 记录账户级别的使用统计(只统计实际处理请求的账户)
|
||||
if (accountId) {
|
||||
await redis.incrementAccountUsage(accountId, totalTokens, inputTokens, outputTokens, cacheCreateTokens, cacheReadTokens, model);
|
||||
logger.database(`📊 Recorded account usage: ${accountId} - ${totalTokens} tokens (API Key: ${keyId})`);
|
||||
await redis.incrementAccountUsage(
|
||||
accountId,
|
||||
totalTokens,
|
||||
inputTokens,
|
||||
outputTokens,
|
||||
cacheCreateTokens,
|
||||
cacheReadTokens,
|
||||
model
|
||||
)
|
||||
logger.database(
|
||||
`📊 Recorded account usage: ${accountId} - ${totalTokens} tokens (API Key: ${keyId})`
|
||||
)
|
||||
} else {
|
||||
logger.debug('⚠️ No accountId provided for usage recording, skipping account-level statistics');
|
||||
logger.debug(
|
||||
'⚠️ No accountId provided for usage recording, skipping account-level statistics'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const logParts = [`Model: ${model}`, `Input: ${inputTokens}`, `Output: ${outputTokens}`];
|
||||
if (cacheCreateTokens > 0) logParts.push(`Cache Create: ${cacheCreateTokens}`);
|
||||
if (cacheReadTokens > 0) logParts.push(`Cache Read: ${cacheReadTokens}`);
|
||||
logParts.push(`Total: ${totalTokens} tokens`);
|
||||
|
||||
logger.database(`📊 Recorded usage: ${keyId} - ${logParts.join(', ')}`);
|
||||
|
||||
const logParts = [`Model: ${model}`, `Input: ${inputTokens}`, `Output: ${outputTokens}`]
|
||||
if (cacheCreateTokens > 0) {
|
||||
logParts.push(`Cache Create: ${cacheCreateTokens}`)
|
||||
}
|
||||
if (cacheReadTokens > 0) {
|
||||
logParts.push(`Cache Read: ${cacheReadTokens}`)
|
||||
}
|
||||
logParts.push(`Total: ${totalTokens} tokens`)
|
||||
|
||||
logger.database(`📊 Recorded usage: ${keyId} - ${logParts.join(', ')}`)
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to record usage:', error);
|
||||
logger.error('❌ Failed to record usage:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 🔐 生成密钥
|
||||
_generateSecretKey() {
|
||||
return crypto.randomBytes(32).toString('hex');
|
||||
return crypto.randomBytes(32).toString('hex')
|
||||
}
|
||||
|
||||
// 🔒 哈希API Key
|
||||
_hashApiKey(apiKey) {
|
||||
return crypto.createHash('sha256').update(apiKey + config.security.encryptionKey).digest('hex');
|
||||
return crypto
|
||||
.createHash('sha256')
|
||||
.update(apiKey + config.security.encryptionKey)
|
||||
.digest('hex')
|
||||
}
|
||||
|
||||
// 📈 获取使用统计
|
||||
async getUsageStats(keyId) {
|
||||
return await redis.getUsageStats(keyId);
|
||||
return await redis.getUsageStats(keyId)
|
||||
}
|
||||
|
||||
// 📊 获取账户使用统计
|
||||
async getAccountUsageStats(accountId) {
|
||||
return await redis.getAccountUsageStats(accountId);
|
||||
return await redis.getAccountUsageStats(accountId)
|
||||
}
|
||||
|
||||
// 📈 获取所有账户使用统计
|
||||
async getAllAccountsUsageStats() {
|
||||
return await redis.getAllAccountsUsageStats();
|
||||
return await redis.getAllAccountsUsageStats()
|
||||
}
|
||||
|
||||
|
||||
// 🧹 清理过期的API Keys
|
||||
async cleanupExpiredKeys() {
|
||||
try {
|
||||
const apiKeys = await redis.getAllApiKeys();
|
||||
const now = new Date();
|
||||
let cleanedCount = 0;
|
||||
const apiKeys = await redis.getAllApiKeys()
|
||||
const now = new Date()
|
||||
let cleanedCount = 0
|
||||
|
||||
for (const key of apiKeys) {
|
||||
// 检查是否已过期且仍处于激活状态
|
||||
if (key.expiresAt && new Date(key.expiresAt) < now && key.isActive === 'true') {
|
||||
// 将过期的 API Key 标记为禁用状态,而不是直接删除
|
||||
await this.updateApiKey(key.id, { isActive: false });
|
||||
logger.info(`🔒 API Key ${key.id} (${key.name}) has expired and been disabled`);
|
||||
cleanedCount++;
|
||||
await this.updateApiKey(key.id, { isActive: false })
|
||||
logger.info(`🔒 API Key ${key.id} (${key.name}) has expired and been disabled`)
|
||||
cleanedCount++
|
||||
}
|
||||
}
|
||||
|
||||
if (cleanedCount > 0) {
|
||||
logger.success(`🧹 Disabled ${cleanedCount} expired API keys`);
|
||||
logger.success(`🧹 Disabled ${cleanedCount} expired API keys`)
|
||||
}
|
||||
|
||||
return cleanedCount;
|
||||
return cleanedCount
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to cleanup expired keys:', error);
|
||||
return 0;
|
||||
logger.error('❌ Failed to cleanup expired keys:', error)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出实例和单独的方法
|
||||
const apiKeyService = new ApiKeyService();
|
||||
const apiKeyService = new ApiKeyService()
|
||||
|
||||
// 为了方便其他服务调用,导出 recordUsage 方法
|
||||
apiKeyService.recordUsageMetrics = apiKeyService.recordUsage.bind(apiKeyService);
|
||||
apiKeyService.recordUsageMetrics = apiKeyService.recordUsage.bind(apiKeyService)
|
||||
|
||||
module.exports = apiKeyService;
|
||||
module.exports = apiKeyService
|
||||
|
||||
Reference in New Issue
Block a user