mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: api-stats页面查询专属账号会话窗口
This commit is contained in:
@@ -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
|
||||
},
|
||||
|
||||
// 模型和客户端限制信息
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user