Merge pull request #996 from yptse123/feat/per-key-weekly-reset-time [skip ci]

feat: add per-API-key weekly cost reset day/hour configuration
This commit is contained in:
Wesley Liddick
2026-02-24 08:53:47 +08:00
committed by GitHub
10 changed files with 612 additions and 104 deletions

View File

@@ -161,7 +161,9 @@ class ApiKeyService {
activationUnit = 'days', // 新增:激活时间单位 'hours' 或 'days'
expirationMode = 'fixed', // 新增:过期模式 'fixed'(固定时间) 或 'activation'(首次使用后激活)
icon = '', // 新增图标base64编码
serviceRates = {} // API Key 级别服务倍率覆盖
serviceRates = {}, // API Key 级别服务倍率覆盖
weeklyResetDay = 1, // 周费用重置日 (1=周一 ... 7=周日)
weeklyResetHour = 0 // 周费用重置时 (0-23)
} = options
// 生成简单的API Key (64字符十六进制)
@@ -211,7 +213,9 @@ class ApiKeyService {
userId: options.userId || '',
userUsername: options.userUsername || '',
icon: icon || '', // 新增图标base64编码
serviceRates: JSON.stringify(serviceRates || {}) // API Key 级别服务倍率
serviceRates: JSON.stringify(serviceRates || {}), // API Key 级别服务倍率
weeklyResetDay: String(weeklyResetDay || 1), // 周费用重置日 (1-7)
weeklyResetHour: String(weeklyResetHour || 0) // 周费用重置时 (0-23)
}
// 保存API Key数据并建立哈希映射
@@ -373,8 +377,12 @@ class ApiKeyService {
costQueries.push(redis.getCostStats(keyData.id).then((v) => ({ totalCost: v?.total || 0 })))
}
if (weeklyOpusCostLimit > 0) {
const resetDay = parseInt(keyData.weeklyResetDay || 1)
const resetHour = parseInt(keyData.weeklyResetHour || 0)
costQueries.push(
redis.getWeeklyOpusCost(keyData.id).then((v) => ({ weeklyOpusCost: v || 0 }))
redis
.getWeeklyOpusCost(keyData.id, resetDay, resetHour)
.then((v) => ({ weeklyOpusCost: v || 0 }))
)
}
@@ -449,6 +457,8 @@ class ApiKeyService {
dailyCost: costData.dailyCost || 0,
totalCost: costData.totalCost || 0,
weeklyOpusCost: costData.weeklyOpusCost || 0,
weeklyResetDay: parseInt(keyData.weeklyResetDay || 1),
weeklyResetHour: parseInt(keyData.weeklyResetHour || 0),
tags,
serviceRates
}
@@ -577,7 +587,12 @@ class ApiKeyService {
weeklyOpusCostLimit: parseFloat(keyData.weeklyOpusCostLimit || 0),
dailyCost: dailyCost || 0,
totalCost: costStats?.total || 0,
weeklyOpusCost: (await redis.getWeeklyOpusCost(keyData.id)) || 0,
weeklyOpusCost:
(await redis.getWeeklyOpusCost(
keyData.id,
parseInt(keyData.weeklyResetDay || 1),
parseInt(keyData.weeklyResetHour || 0)
)) || 0,
tags,
usage
}
@@ -783,7 +798,12 @@ class ApiKeyService {
key.totalCostLimit = parseFloat(key.totalCostLimit || 0)
key.weeklyOpusCostLimit = parseFloat(key.weeklyOpusCostLimit || 0)
key.dailyCost = (await redis.getDailyCost(key.id)) || 0
key.weeklyOpusCost = (await redis.getWeeklyOpusCost(key.id)) || 0
key.weeklyOpusCost =
(await redis.getWeeklyOpusCost(
key.id,
parseInt(key.weeklyResetDay || 1),
parseInt(key.weeklyResetHour || 0)
)) || 0
key.activationDays = parseInt(key.activationDays || 0)
key.activationUnit = key.activationUnit || 'days'
key.expirationMode = key.expirationMode || 'fixed'
@@ -1215,7 +1235,9 @@ class ApiKeyService {
'userId', // 新增用户ID所有者变更
'userUsername', // 新增:用户名(所有者变更)
'createdBy', // 新增:创建者(所有者变更)
'serviceRates' // API Key 级别服务倍率
'serviceRates', // API Key 级别服务倍率
'weeklyResetDay', // 周费用重置日 (1-7)
'weeklyResetHour' // 周费用重置时 (0-23)
]
const updatedData = { ...keyData }
@@ -1645,7 +1667,7 @@ class ApiKeyService {
}
}
// 📊 记录 Opus 模型费用(仅限 claude 和 claude-console 账户)
// 📊 记录 Opus 模型费用(仅限 claude 和 claude-console 账户,支持自定义重置周期
// ratedCost: 倍率后的成本(用于限额校验)
// realCost: 真实成本(用于对账),如果不传则等于 ratedCost
async recordOpusCost(keyId, ratedCost, realCost, model, accountType) {
@@ -1662,8 +1684,13 @@ class ApiKeyService {
return // 不是 claude 账户,直接返回
}
// 获取 key 的重置配置
const keyData = await redis.getApiKey(keyId)
const resetDay = parseInt(keyData?.weeklyResetDay || 1)
const resetHour = parseInt(keyData?.weeklyResetHour || 0)
// 记录 Opus 周费用(倍率成本和真实成本)
await redis.incrementWeeklyOpusCost(keyId, ratedCost, realCost)
await redis.incrementWeeklyOpusCost(keyId, ratedCost, realCost, resetDay, resetHour)
logger.database(
`💰 Recorded Opus weekly cost for ${keyId}: rated=$${ratedCost.toFixed(6)}, real=$${realCost.toFixed(6)}, model: ${model}`
)