mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
Merge pull request #825 from atoz03/feat/account-quota [skip ci]
Feat:account quota
This commit is contained in:
214
src/routes/admin/accountBalance.js
Normal file
214
src/routes/admin/accountBalance.js
Normal file
@@ -0,0 +1,214 @@
|
||||
const express = require('express')
|
||||
const { authenticateAdmin } = require('../../middleware/auth')
|
||||
const logger = require('../../utils/logger')
|
||||
const accountBalanceService = require('../../services/accountBalanceService')
|
||||
const balanceScriptService = require('../../services/balanceScriptService')
|
||||
const { isBalanceScriptEnabled } = require('../../utils/featureFlags')
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
const ensureValidPlatform = (rawPlatform) => {
|
||||
const normalized = accountBalanceService.normalizePlatform(rawPlatform)
|
||||
if (!normalized) {
|
||||
return { ok: false, status: 400, error: '缺少 platform 参数' }
|
||||
}
|
||||
|
||||
const supported = accountBalanceService.getSupportedPlatforms()
|
||||
if (!supported.includes(normalized)) {
|
||||
return { ok: false, status: 400, error: `不支持的平台: ${normalized}` }
|
||||
}
|
||||
|
||||
return { ok: true, platform: normalized }
|
||||
}
|
||||
|
||||
// 1) 获取账户余额(默认本地统计优先,可选触发 Provider)
|
||||
// GET /admin/accounts/:accountId/balance?platform=xxx&queryApi=false
|
||||
router.get('/accounts/:accountId/balance', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const { accountId } = req.params
|
||||
const { platform, queryApi } = req.query
|
||||
|
||||
const valid = ensureValidPlatform(platform)
|
||||
if (!valid.ok) {
|
||||
return res.status(valid.status).json({ success: false, error: valid.error })
|
||||
}
|
||||
|
||||
const balance = await accountBalanceService.getAccountBalance(accountId, valid.platform, {
|
||||
queryApi
|
||||
})
|
||||
|
||||
if (!balance) {
|
||||
return res.status(404).json({ success: false, error: 'Account not found' })
|
||||
}
|
||||
|
||||
return res.json(balance)
|
||||
} catch (error) {
|
||||
logger.error('获取账户余额失败', error)
|
||||
return res.status(500).json({ success: false, error: error.message })
|
||||
}
|
||||
})
|
||||
|
||||
// 2) 强制刷新账户余额(强制触发查询:优先脚本;Provider 仅为降级)
|
||||
// POST /admin/accounts/:accountId/balance/refresh
|
||||
// Body: { platform: 'xxx' }
|
||||
router.post('/accounts/:accountId/balance/refresh', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const { accountId } = req.params
|
||||
const { platform } = req.body || {}
|
||||
|
||||
const valid = ensureValidPlatform(platform)
|
||||
if (!valid.ok) {
|
||||
return res.status(valid.status).json({ success: false, error: valid.error })
|
||||
}
|
||||
|
||||
logger.info(`手动刷新余额: ${valid.platform}:${accountId}`)
|
||||
|
||||
const balance = await accountBalanceService.refreshAccountBalance(accountId, valid.platform)
|
||||
if (!balance) {
|
||||
return res.status(404).json({ success: false, error: 'Account not found' })
|
||||
}
|
||||
|
||||
return res.json(balance)
|
||||
} catch (error) {
|
||||
logger.error('刷新账户余额失败', error)
|
||||
return res.status(500).json({ success: false, error: error.message })
|
||||
}
|
||||
})
|
||||
|
||||
// 3) 批量获取平台所有账户余额
|
||||
// GET /admin/accounts/balance/platform/:platform?queryApi=false
|
||||
router.get('/accounts/balance/platform/:platform', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const { platform } = req.params
|
||||
const { queryApi } = req.query
|
||||
|
||||
const valid = ensureValidPlatform(platform)
|
||||
if (!valid.ok) {
|
||||
return res.status(valid.status).json({ success: false, error: valid.error })
|
||||
}
|
||||
|
||||
const balances = await accountBalanceService.getAllAccountsBalance(valid.platform, { queryApi })
|
||||
|
||||
return res.json({ success: true, data: balances })
|
||||
} catch (error) {
|
||||
logger.error('批量获取余额失败', error)
|
||||
return res.status(500).json({ success: false, error: error.message })
|
||||
}
|
||||
})
|
||||
|
||||
// 4) 获取余额汇总(Dashboard 用)
|
||||
// GET /admin/accounts/balance/summary
|
||||
router.get('/accounts/balance/summary', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const summary = await accountBalanceService.getBalanceSummary()
|
||||
return res.json({ success: true, data: summary })
|
||||
} catch (error) {
|
||||
logger.error('获取余额汇总失败', error)
|
||||
return res.status(500).json({ success: false, error: error.message })
|
||||
}
|
||||
})
|
||||
|
||||
// 5) 清除缓存
|
||||
// DELETE /admin/accounts/:accountId/balance/cache?platform=xxx
|
||||
router.delete('/accounts/:accountId/balance/cache', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const { accountId } = req.params
|
||||
const { platform } = req.query
|
||||
|
||||
const valid = ensureValidPlatform(platform)
|
||||
if (!valid.ok) {
|
||||
return res.status(valid.status).json({ success: false, error: valid.error })
|
||||
}
|
||||
|
||||
await accountBalanceService.clearCache(accountId, valid.platform)
|
||||
|
||||
return res.json({ success: true, message: '缓存已清除' })
|
||||
} catch (error) {
|
||||
logger.error('清除缓存失败', error)
|
||||
return res.status(500).json({ success: false, error: error.message })
|
||||
}
|
||||
})
|
||||
|
||||
// 6) 获取/保存/测试余额脚本配置(单账户)
|
||||
router.get('/accounts/:accountId/balance/script', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const { accountId } = req.params
|
||||
const { platform } = req.query
|
||||
|
||||
const valid = ensureValidPlatform(platform)
|
||||
if (!valid.ok) {
|
||||
return res.status(valid.status).json({ success: false, error: valid.error })
|
||||
}
|
||||
|
||||
const config = await accountBalanceService.redis.getBalanceScriptConfig(
|
||||
valid.platform,
|
||||
accountId
|
||||
)
|
||||
return res.json({ success: true, data: config || null })
|
||||
} catch (error) {
|
||||
logger.error('获取余额脚本配置失败', error)
|
||||
return res.status(500).json({ success: false, error: error.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.put('/accounts/:accountId/balance/script', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const { accountId } = req.params
|
||||
const { platform } = req.query
|
||||
const valid = ensureValidPlatform(platform)
|
||||
if (!valid.ok) {
|
||||
return res.status(valid.status).json({ success: false, error: valid.error })
|
||||
}
|
||||
|
||||
const payload = req.body || {}
|
||||
await accountBalanceService.redis.setBalanceScriptConfig(valid.platform, accountId, payload)
|
||||
return res.json({ success: true, data: payload })
|
||||
} catch (error) {
|
||||
logger.error('保存余额脚本配置失败', error)
|
||||
return res.status(500).json({ success: false, error: error.message })
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/accounts/:accountId/balance/script/test', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const { accountId } = req.params
|
||||
const { platform } = req.query
|
||||
const valid = ensureValidPlatform(platform)
|
||||
if (!valid.ok) {
|
||||
return res.status(valid.status).json({ success: false, error: valid.error })
|
||||
}
|
||||
|
||||
if (!isBalanceScriptEnabled()) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: '余额脚本功能已禁用(可通过 BALANCE_SCRIPT_ENABLED=true 启用)'
|
||||
})
|
||||
}
|
||||
|
||||
const payload = req.body || {}
|
||||
const { scriptBody } = payload
|
||||
if (!scriptBody) {
|
||||
return res.status(400).json({ success: false, error: '脚本内容不能为空' })
|
||||
}
|
||||
|
||||
const result = await balanceScriptService.execute({
|
||||
scriptBody,
|
||||
timeoutSeconds: payload.timeoutSeconds || 10,
|
||||
variables: {
|
||||
baseUrl: payload.baseUrl || '',
|
||||
apiKey: payload.apiKey || '',
|
||||
token: payload.token || '',
|
||||
accountId,
|
||||
platform: valid.platform,
|
||||
extra: payload.extra || ''
|
||||
}
|
||||
})
|
||||
|
||||
return res.json({ success: true, data: result })
|
||||
} catch (error) {
|
||||
logger.error('测试余额脚本失败', error)
|
||||
return res.status(400).json({ success: false, error: error.message })
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
41
src/routes/admin/balanceScripts.js
Normal file
41
src/routes/admin/balanceScripts.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const express = require('express')
|
||||
const { authenticateAdmin } = require('../../middleware/auth')
|
||||
const balanceScriptService = require('../../services/balanceScriptService')
|
||||
const router = express.Router()
|
||||
|
||||
// 获取全部脚本配置列表
|
||||
router.get('/balance-scripts', authenticateAdmin, (req, res) => {
|
||||
const items = balanceScriptService.listConfigs()
|
||||
return res.json({ success: true, data: items })
|
||||
})
|
||||
|
||||
// 获取单个脚本配置
|
||||
router.get('/balance-scripts/:name', authenticateAdmin, (req, res) => {
|
||||
const { name } = req.params
|
||||
const config = balanceScriptService.getConfig(name || 'default')
|
||||
return res.json({ success: true, data: config })
|
||||
})
|
||||
|
||||
// 保存脚本配置
|
||||
router.put('/balance-scripts/:name', authenticateAdmin, (req, res) => {
|
||||
try {
|
||||
const { name } = req.params
|
||||
const saved = balanceScriptService.saveConfig(name || 'default', req.body || {})
|
||||
return res.json({ success: true, data: saved })
|
||||
} catch (error) {
|
||||
return res.status(400).json({ success: false, error: error.message })
|
||||
}
|
||||
})
|
||||
|
||||
// 测试脚本(不落库)
|
||||
router.post('/balance-scripts/:name/test', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const { name } = req.params
|
||||
const result = await balanceScriptService.testScript(name || 'default', req.body || {})
|
||||
return res.json({ success: true, data: result })
|
||||
} catch (error) {
|
||||
return res.status(400).json({ success: false, error: error.message })
|
||||
}
|
||||
})
|
||||
|
||||
module.exports = router
|
||||
@@ -21,6 +21,7 @@ const openaiResponsesAccountsRoutes = require('./openaiResponsesAccounts')
|
||||
const droidAccountsRoutes = require('./droidAccounts')
|
||||
const dashboardRoutes = require('./dashboard')
|
||||
const usageStatsRoutes = require('./usageStats')
|
||||
const accountBalanceRoutes = require('./accountBalance')
|
||||
const systemRoutes = require('./system')
|
||||
const concurrencyRoutes = require('./concurrency')
|
||||
const claudeRelayConfigRoutes = require('./claudeRelayConfig')
|
||||
@@ -37,6 +38,7 @@ router.use('/', openaiResponsesAccountsRoutes)
|
||||
router.use('/', droidAccountsRoutes)
|
||||
router.use('/', dashboardRoutes)
|
||||
router.use('/', usageStatsRoutes)
|
||||
router.use('/', accountBalanceRoutes)
|
||||
router.use('/', systemRoutes)
|
||||
router.use('/', concurrencyRoutes)
|
||||
router.use('/', claudeRelayConfigRoutes)
|
||||
|
||||
Reference in New Issue
Block a user