合并所有新功能到Wei-Shaw仓库(排除ApiStatsView.vue)

 新增功能:
- GPT-5 High推理级别费用追踪和限制
- API Key图标上传功能
- 优化的进度条显示组件
- 暗黑模式UI兼容
- 完整的前后端集成

🔥 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
DuanNaiSheQu
2025-09-10 13:38:27 +08:00
parent ca79e08c81
commit af3d688e98
12 changed files with 775 additions and 805 deletions

View File

@@ -733,6 +733,62 @@ class RedisClient {
logger.debug(`💰 Opus cost incremented successfully, new weekly total: $${results[0][1]}`)
}
// 💰 获取本周 GPT-5 High 费用
async getWeeklyGPT5HighCost(keyId) {
try {
if (!keyId) {
logger.warn('getWeeklyGPT5HighCost: keyId is required')
return 0
}
const currentWeek = getWeekStringInTimezone()
const costKey = `usage:gpt5-high:weekly:${keyId}:${currentWeek}`
const cost = await this.client.get(costKey)
const result = parseFloat(cost || 0)
logger.debug(
`💰 Getting weekly GPT-5 High cost for ${keyId}, week: ${currentWeek}, result: $${result.toFixed(4)}`
)
return result
} catch (error) {
logger.error('Error getting weekly GPT-5 High cost:', error)
return 0 // 发生错误时返回0不影响正常流程
}
}
// 💰 增加本周 GPT-5 High 费用
async incrementWeeklyGPT5HighCost(keyId, amount) {
try {
if (!keyId || !amount || amount <= 0) {
logger.warn('incrementWeeklyGPT5HighCost: invalid parameters', { keyId, amount })
return
}
const currentWeek = getWeekStringInTimezone()
const weeklyKey = `usage:gpt5-high:weekly:${keyId}:${currentWeek}`
const totalKey = `usage:gpt5-high:total:${keyId}`
logger.debug(
`💰 Incrementing weekly GPT-5 High cost for ${keyId}, week: ${currentWeek}, amount: $${amount.toFixed(4)}`
)
// 使用 pipeline 批量执行,提高性能和一致性
const pipeline = this.client.pipeline()
pipeline.incrbyfloat(weeklyKey, amount)
pipeline.incrbyfloat(totalKey, amount)
pipeline.expire(weeklyKey, 60 * 60 * 24 * 14) // 2周后过期
await pipeline.exec()
logger.debug(
`💰 Weekly GPT-5 High cost updated successfully for ${keyId}`
)
} catch (error) {
logger.error('Error incrementing weekly GPT-5 High cost:', error)
// 不抛出错误,避免影响主流程
}
}
// 💰 计算账户的每日费用(基于模型使用)
async getAccountDailyCost(accountId) {
const CostCalculator = require('../utils/costCalculator')
@@ -1356,12 +1412,9 @@ class RedisClient {
}
// 🔗 会话sticky映射管理
async setSessionAccountMapping(sessionHash, accountId, ttl = null) {
const appConfig = require('../../config/config')
// 从配置读取TTL小时转换为秒默认1小时
const defaultTTL = ttl !== null ? ttl : (appConfig.session?.stickyTtlHours || 1) * 60 * 60
async setSessionAccountMapping(sessionHash, accountId, ttl = 3600) {
const key = `sticky_session:${sessionHash}`
await this.client.set(key, accountId, 'EX', defaultTTL)
await this.client.set(key, accountId, 'EX', ttl)
}
async getSessionAccountMapping(sessionHash) {
@@ -1369,57 +1422,6 @@ class RedisClient {
return await this.client.get(key)
}
// 🚀 智能会话TTL续期剩余时间少于阈值时自动续期
async extendSessionAccountMappingTTL(sessionHash) {
const appConfig = require('../../config/config')
const key = `sticky_session:${sessionHash}`
// 📊 从配置获取参数
const ttlHours = appConfig.session?.stickyTtlHours || 1 // 小时默认1小时
const thresholdMinutes = appConfig.session?.renewalThresholdMinutes || 0 // 分钟默认0不续期
// 如果阈值为0不执行续期
if (thresholdMinutes === 0) {
return true
}
const fullTTL = ttlHours * 60 * 60 // 转换为秒
const renewalThreshold = thresholdMinutes * 60 // 转换为秒
try {
// 获取当前剩余TTL
const remainingTTL = await this.client.ttl(key)
// 键不存在或已过期
if (remainingTTL === -2) {
return false
}
// 键存在但没有TTL永不过期不需要处理
if (remainingTTL === -1) {
return true
}
// 🎯 智能续期策略:仅在剩余时间少于阈值时才续期
if (remainingTTL < renewalThreshold) {
await this.client.expire(key, fullTTL)
logger.debug(
`🔄 Renewed sticky session TTL: ${sessionHash} (was ${Math.round(remainingTTL / 60)}min, renewed to ${ttlHours}h)`
)
return true
}
// 剩余时间充足,无需续期
logger.debug(
`✅ Sticky session TTL sufficient: ${sessionHash} (remaining ${Math.round(remainingTTL / 60)}min)`
)
return true
} catch (error) {
logger.error('❌ Failed to extend session TTL:', error)
return false
}
}
async deleteSessionAccountMapping(sessionHash) {
const key = `sticky_session:${sessionHash}`
return await this.client.del(key)
@@ -1707,4 +1709,4 @@ redisClient.getDateStringInTimezone = getDateStringInTimezone
redisClient.getHourInTimezone = getHourInTimezone
redisClient.getWeekStringInTimezone = getWeekStringInTimezone
module.exports = redisClient
module.exports = redisClient