|
|
#!/usr/bin/env node |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
require('dotenv').config() |
|
|
const redis = require('../src/models/redis') |
|
|
const logger = require('../src/utils/logger') |
|
|
|
|
|
|
|
|
const args = process.argv.slice(2) |
|
|
const isDryRun = args.includes('--dry-run') |
|
|
|
|
|
async function fixUsageStats() { |
|
|
try { |
|
|
logger.info('🔧 开始修复使用统计数据...') |
|
|
if (isDryRun) { |
|
|
logger.info('📝 DRY RUN 模式 - 不会实际修改数据') |
|
|
} |
|
|
|
|
|
|
|
|
await redis.connect() |
|
|
logger.success('✅ 已连接到 Redis') |
|
|
|
|
|
const client = redis.getClientSafe() |
|
|
|
|
|
|
|
|
const stats = { |
|
|
totalKeys: 0, |
|
|
fixedTotalKeys: 0, |
|
|
fixedDailyKeys: 0, |
|
|
fixedMonthlyKeys: 0, |
|
|
fixedModelKeys: 0, |
|
|
errors: 0 |
|
|
} |
|
|
|
|
|
|
|
|
logger.info('\n📊 修复 API Key 总统计数据...') |
|
|
const apiKeyPattern = 'apikey:*' |
|
|
const apiKeys = await client.keys(apiKeyPattern) |
|
|
stats.totalKeys = apiKeys.length |
|
|
|
|
|
for (const apiKeyKey of apiKeys) { |
|
|
const keyId = apiKeyKey.replace('apikey:', '') |
|
|
const usageKey = `usage:${keyId}` |
|
|
|
|
|
try { |
|
|
const usageData = await client.hgetall(usageKey) |
|
|
if (usageData && Object.keys(usageData).length > 0) { |
|
|
const inputTokens = parseInt(usageData.totalInputTokens) || 0 |
|
|
const outputTokens = parseInt(usageData.totalOutputTokens) || 0 |
|
|
const cacheCreateTokens = parseInt(usageData.totalCacheCreateTokens) || 0 |
|
|
const cacheReadTokens = parseInt(usageData.totalCacheReadTokens) || 0 |
|
|
const currentAllTokens = parseInt(usageData.totalAllTokens) || 0 |
|
|
|
|
|
|
|
|
const correctAllTokens = inputTokens + outputTokens + cacheCreateTokens + cacheReadTokens |
|
|
|
|
|
if (currentAllTokens !== correctAllTokens && correctAllTokens > 0) { |
|
|
logger.info(` 修复 ${keyId}: ${currentAllTokens} -> ${correctAllTokens}`) |
|
|
|
|
|
if (!isDryRun) { |
|
|
await client.hset(usageKey, 'totalAllTokens', correctAllTokens) |
|
|
} |
|
|
stats.fixedTotalKeys++ |
|
|
} |
|
|
} |
|
|
} catch (error) { |
|
|
logger.error(` 错误处理 ${keyId}: ${error.message}`) |
|
|
stats.errors++ |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
logger.info('\n📅 修复每日统计数据...') |
|
|
const dailyPattern = 'usage:daily:*' |
|
|
const dailyKeys = await client.keys(dailyPattern) |
|
|
|
|
|
for (const dailyKey of dailyKeys) { |
|
|
try { |
|
|
const data = await client.hgetall(dailyKey) |
|
|
if (data && Object.keys(data).length > 0) { |
|
|
const inputTokens = parseInt(data.inputTokens) || 0 |
|
|
const outputTokens = parseInt(data.outputTokens) || 0 |
|
|
const cacheCreateTokens = parseInt(data.cacheCreateTokens) || 0 |
|
|
const cacheReadTokens = parseInt(data.cacheReadTokens) || 0 |
|
|
const currentAllTokens = parseInt(data.allTokens) || 0 |
|
|
|
|
|
const correctAllTokens = inputTokens + outputTokens + cacheCreateTokens + cacheReadTokens |
|
|
|
|
|
if (currentAllTokens !== correctAllTokens && correctAllTokens > 0) { |
|
|
if (!isDryRun) { |
|
|
await client.hset(dailyKey, 'allTokens', correctAllTokens) |
|
|
} |
|
|
stats.fixedDailyKeys++ |
|
|
} |
|
|
} |
|
|
} catch (error) { |
|
|
logger.error(` 错误处理 ${dailyKey}: ${error.message}`) |
|
|
stats.errors++ |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
logger.info('\n📆 修复每月统计数据...') |
|
|
const monthlyPattern = 'usage:monthly:*' |
|
|
const monthlyKeys = await client.keys(monthlyPattern) |
|
|
|
|
|
for (const monthlyKey of monthlyKeys) { |
|
|
try { |
|
|
const data = await client.hgetall(monthlyKey) |
|
|
if (data && Object.keys(data).length > 0) { |
|
|
const inputTokens = parseInt(data.inputTokens) || 0 |
|
|
const outputTokens = parseInt(data.outputTokens) || 0 |
|
|
const cacheCreateTokens = parseInt(data.cacheCreateTokens) || 0 |
|
|
const cacheReadTokens = parseInt(data.cacheReadTokens) || 0 |
|
|
const currentAllTokens = parseInt(data.allTokens) || 0 |
|
|
|
|
|
const correctAllTokens = inputTokens + outputTokens + cacheCreateTokens + cacheReadTokens |
|
|
|
|
|
if (currentAllTokens !== correctAllTokens && correctAllTokens > 0) { |
|
|
if (!isDryRun) { |
|
|
await client.hset(monthlyKey, 'allTokens', correctAllTokens) |
|
|
} |
|
|
stats.fixedMonthlyKeys++ |
|
|
} |
|
|
} |
|
|
} catch (error) { |
|
|
logger.error(` 错误处理 ${monthlyKey}: ${error.message}`) |
|
|
stats.errors++ |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
logger.info('\n🤖 修复模型级别统计数据...') |
|
|
const modelPatterns = [ |
|
|
'usage:model:daily:*', |
|
|
'usage:model:monthly:*', |
|
|
'usage:*:model:daily:*', |
|
|
'usage:*:model:monthly:*' |
|
|
] |
|
|
|
|
|
for (const pattern of modelPatterns) { |
|
|
const modelKeys = await client.keys(pattern) |
|
|
|
|
|
for (const modelKey of modelKeys) { |
|
|
try { |
|
|
const data = await client.hgetall(modelKey) |
|
|
if (data && Object.keys(data).length > 0) { |
|
|
const inputTokens = parseInt(data.inputTokens) || 0 |
|
|
const outputTokens = parseInt(data.outputTokens) || 0 |
|
|
const cacheCreateTokens = parseInt(data.cacheCreateTokens) || 0 |
|
|
const cacheReadTokens = parseInt(data.cacheReadTokens) || 0 |
|
|
const currentAllTokens = parseInt(data.allTokens) || 0 |
|
|
|
|
|
const correctAllTokens = |
|
|
inputTokens + outputTokens + cacheCreateTokens + cacheReadTokens |
|
|
|
|
|
if (currentAllTokens !== correctAllTokens && correctAllTokens > 0) { |
|
|
if (!isDryRun) { |
|
|
await client.hset(modelKey, 'allTokens', correctAllTokens) |
|
|
} |
|
|
stats.fixedModelKeys++ |
|
|
} |
|
|
} |
|
|
} catch (error) { |
|
|
logger.error(` 错误处理 ${modelKey}: ${error.message}`) |
|
|
stats.errors++ |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (!isDryRun) { |
|
|
logger.info('\n✅ 验证修复结果...') |
|
|
|
|
|
|
|
|
const sampleSize = Math.min(5, apiKeys.length) |
|
|
for (let i = 0; i < sampleSize; i++) { |
|
|
const randomIndex = Math.floor(Math.random() * apiKeys.length) |
|
|
const keyId = apiKeys[randomIndex].replace('apikey:', '') |
|
|
const usage = await redis.getUsageStats(keyId) |
|
|
|
|
|
logger.info(` 样本 ${keyId}:`) |
|
|
logger.info(` Total tokens: ${usage.total.tokens}`) |
|
|
logger.info(` All tokens: ${usage.total.allTokens}`) |
|
|
logger.info(` 一致性: ${usage.total.tokens === usage.total.allTokens ? '✅' : '❌'}`) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
logger.info('\n📊 修复统计:') |
|
|
logger.info(` 总 API Keys: ${stats.totalKeys}`) |
|
|
logger.info(` 修复的总统计: ${stats.fixedTotalKeys}`) |
|
|
logger.info(` 修复的日统计: ${stats.fixedDailyKeys}`) |
|
|
logger.info(` 修复的月统计: ${stats.fixedMonthlyKeys}`) |
|
|
logger.info(` 修复的模型统计: ${stats.fixedModelKeys}`) |
|
|
logger.info(` 错误数: ${stats.errors}`) |
|
|
|
|
|
if (isDryRun) { |
|
|
logger.info('\n💡 这是 DRY RUN - 没有实际修改数据') |
|
|
logger.info(' 运行不带 --dry-run 参数来实际执行修复') |
|
|
} else { |
|
|
logger.success('\n✅ 数据修复完成!') |
|
|
} |
|
|
} catch (error) { |
|
|
logger.error('❌ 修复过程出错:', error) |
|
|
process.exit(1) |
|
|
} finally { |
|
|
await redis.disconnect() |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
fixUsageStats().catch((error) => { |
|
|
logger.error('❌ 未处理的错误:', error) |
|
|
process.exit(1) |
|
|
}) |
|
|
|