feat: api-stats页面查询专属账号会话窗口

This commit is contained in:
shaw
2025-09-28 14:36:38 +08:00
parent 90dce32cfc
commit b123cc35c1
4 changed files with 598 additions and 1 deletions

View File

@@ -3,6 +3,8 @@ const redis = require('../models/redis')
const logger = require('../utils/logger')
const apiKeyService = require('../services/apiKeyService')
const CostCalculator = require('../utils/costCalculator')
const claudeAccountService = require('../services/claudeAccountService')
const openaiAccountService = require('../services/openaiAccountService')
const router = express.Router()
@@ -335,6 +337,50 @@ router.post('/api/user-stats', async (req, res) => {
logger.warn(`Failed to get current usage for key ${keyId}:`, error)
}
const boundAccountDetails = {}
const accountDetailTasks = []
if (fullKeyData.claudeAccountId) {
accountDetailTasks.push(
(async () => {
try {
const overview = await claudeAccountService.getAccountOverview(
fullKeyData.claudeAccountId
)
if (overview && overview.accountType === 'dedicated') {
boundAccountDetails.claude = overview
}
} catch (error) {
logger.warn(`⚠️ Failed to load Claude account overview for key ${keyId}:`, error)
}
})()
)
}
if (fullKeyData.openaiAccountId) {
accountDetailTasks.push(
(async () => {
try {
const overview = await openaiAccountService.getAccountOverview(
fullKeyData.openaiAccountId
)
if (overview && overview.accountType === 'dedicated') {
boundAccountDetails.openai = overview
}
} catch (error) {
logger.warn(`⚠️ Failed to load OpenAI account overview for key ${keyId}:`, error)
}
})()
)
}
if (accountDetailTasks.length > 0) {
await Promise.allSettled(accountDetailTasks)
}
// 构建响应数据只返回该API Key自己的信息确保不泄露其他信息
const responseData = {
id: keyId,
@@ -399,7 +445,12 @@ router.post('/api/user-stats', async (req, res) => {
geminiAccountId:
fullKeyData.geminiAccountId && fullKeyData.geminiAccountId !== ''
? fullKeyData.geminiAccountId
: null
: null,
openaiAccountId:
fullKeyData.openaiAccountId && fullKeyData.openaiAccountId !== ''
? fullKeyData.openaiAccountId
: null,
details: Object.keys(boundAccountDetails).length > 0 ? boundAccountDetails : null
},
// 模型和客户端限制信息

View File

@@ -518,6 +518,60 @@ class ClaudeAccountService {
}
}
// 📋 获取单个账号的概要信息(用于前端展示会话窗口等状态)
async getAccountOverview(accountId) {
try {
const accountData = await redis.getClaudeAccount(accountId)
if (!accountData || Object.keys(accountData).length === 0) {
return null
}
const [sessionWindowInfo, rateLimitInfo] = await Promise.all([
this.getSessionWindowInfo(accountId),
this.getAccountRateLimitInfo(accountId)
])
const sessionWindow = sessionWindowInfo || {
hasActiveWindow: false,
windowStart: null,
windowEnd: null,
progress: 0,
remainingTime: null,
lastRequestTime: accountData.lastRequestTime || null,
sessionWindowStatus: accountData.sessionWindowStatus || null
}
const rateLimitStatus = rateLimitInfo
? {
isRateLimited: !!rateLimitInfo.isRateLimited,
rateLimitedAt: rateLimitInfo.rateLimitedAt || null,
minutesRemaining: rateLimitInfo.minutesRemaining || 0,
rateLimitEndAt: rateLimitInfo.rateLimitEndAt || null
}
: {
isRateLimited: false,
rateLimitedAt: null,
minutesRemaining: 0,
rateLimitEndAt: null
}
return {
id: accountData.id,
name: accountData.name,
accountType: accountData.accountType || 'shared',
platform: accountData.platform || 'claude',
isActive: accountData.isActive === 'true',
schedulable: accountData.schedulable !== 'false',
sessionWindow,
rateLimitStatus
}
} catch (error) {
logger.error(`❌ Failed to build Claude account overview for ${accountId}:`, error)
return null
}
}
// 📝 更新Claude账户
async updateAccount(accountId, updates) {
try {

View File

@@ -808,6 +808,48 @@ async function getAllAccounts() {
return accounts
}
// 获取单个账户的概要信息(用于外部展示基本状态)
async function getAccountOverview(accountId) {
const client = redisClient.getClientSafe()
const accountData = await client.hgetall(`${OPENAI_ACCOUNT_KEY_PREFIX}${accountId}`)
if (!accountData || Object.keys(accountData).length === 0) {
return null
}
const codexUsage = buildCodexUsageSnapshot(accountData)
const rateLimitInfo = await getAccountRateLimitInfo(accountId)
if (accountData.proxy) {
try {
accountData.proxy = JSON.parse(accountData.proxy)
} catch (error) {
accountData.proxy = null
}
}
const scopes =
accountData.scopes && accountData.scopes.trim() ? accountData.scopes.split(' ') : []
return {
id: accountData.id,
name: accountData.name,
accountType: accountData.accountType || 'shared',
platform: accountData.platform || 'openai',
isActive: accountData.isActive === 'true',
schedulable: accountData.schedulable !== 'false',
rateLimitStatus: rateLimitInfo || {
status: 'normal',
isRateLimited: false,
rateLimitedAt: null,
rateLimitResetAt: null,
minutesRemaining: 0
},
codexUsage,
scopes
}
}
// 选择可用账户(支持专属和共享账户)
async function selectAvailableAccount(apiKeyId, sessionHash = null) {
// 首先检查是否有粘性会话
@@ -1175,6 +1217,7 @@ async function updateCodexUsageSnapshot(accountId, usageSnapshot) {
module.exports = {
createAccount,
getAccount,
getAccountOverview,
updateAccount,
deleteAccount,
getAllAccounts,