File size: 7,923 Bytes
69b897d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
#!/usr/bin/env node
/**
* 数据迁移脚本:修复历史使用统计数据
*
* 功能:
* 1. 统一 totalTokens 和 allTokens 字段
* 2. 确保 allTokens 包含所有类型的 tokens
* 3. 修复历史数据的不一致性
*
* 使用方法:
* node scripts/fix-usage-stats.js [--dry-run]
*/
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 模式 - 不会实际修改数据')
}
// 连接到 Redis
await redis.connect()
logger.success('✅ 已连接到 Redis')
const client = redis.getClientSafe()
// 统计信息
const stats = {
totalKeys: 0,
fixedTotalKeys: 0,
fixedDailyKeys: 0,
fixedMonthlyKeys: 0,
fixedModelKeys: 0,
errors: 0
}
// 1. 修复 API Key 级别的总统计
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
// 计算正确的 allTokens
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++
}
}
// 2. 修复每日统计数据
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++
}
}
// 3. 修复每月统计数据
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++
}
}
// 4. 修复模型级别的统计数据
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++
}
}
}
// 5. 验证修复结果
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)
})
|