mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
feat: 增加每日费用限制
This commit is contained in:
@@ -18,6 +18,37 @@ const router = express.Router();
|
||||
|
||||
// 🔑 API Keys 管理
|
||||
|
||||
// 调试:获取API Key费用详情
|
||||
router.get('/api-keys/:keyId/cost-debug', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const { keyId } = req.params;
|
||||
const costStats = await redis.getCostStats(keyId);
|
||||
const dailyCost = await redis.getDailyCost(keyId);
|
||||
const today = redis.getDateStringInTimezone();
|
||||
const client = redis.getClientSafe();
|
||||
|
||||
// 获取所有相关的Redis键
|
||||
const costKeys = await client.keys(`usage:cost:*:${keyId}:*`);
|
||||
const keyValues = {};
|
||||
|
||||
for (const key of costKeys) {
|
||||
keyValues[key] = await client.get(key);
|
||||
}
|
||||
|
||||
res.json({
|
||||
keyId,
|
||||
today,
|
||||
dailyCost,
|
||||
costStats,
|
||||
redisKeys: keyValues,
|
||||
timezone: config.system.timezoneOffset || 8
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('❌ Failed to get cost debug info:', error);
|
||||
res.status(500).json({ error: 'Failed to get cost debug info', message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// 获取所有API Keys
|
||||
router.get('/api-keys', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
@@ -29,20 +60,26 @@ router.get('/api-keys', authenticateAdmin, async (req, res) => {
|
||||
let searchPatterns = [];
|
||||
|
||||
if (timeRange === 'today') {
|
||||
// 今日
|
||||
const dateStr = now.toISOString().split('T')[0];
|
||||
// 今日 - 使用时区日期
|
||||
const redis = require('../models/redis');
|
||||
const tzDate = redis.getDateInTimezone(now);
|
||||
const dateStr = `${tzDate.getFullYear()}-${String(tzDate.getMonth() + 1).padStart(2, '0')}-${String(tzDate.getDate()).padStart(2, '0')}`;
|
||||
searchPatterns.push(`usage:daily:*:${dateStr}`);
|
||||
} else if (timeRange === '7days') {
|
||||
// 最近7天
|
||||
const redis = require('../models/redis');
|
||||
for (let i = 0; i < 7; i++) {
|
||||
const date = new Date(now);
|
||||
date.setDate(date.getDate() - i);
|
||||
const dateStr = date.toISOString().split('T')[0];
|
||||
const tzDate = redis.getDateInTimezone(date);
|
||||
const dateStr = `${tzDate.getFullYear()}-${String(tzDate.getMonth() + 1).padStart(2, '0')}-${String(tzDate.getDate()).padStart(2, '0')}`;
|
||||
searchPatterns.push(`usage:daily:*:${dateStr}`);
|
||||
}
|
||||
} else if (timeRange === 'monthly') {
|
||||
// 本月
|
||||
const currentMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
||||
const redis = require('../models/redis');
|
||||
const tzDate = redis.getDateInTimezone(now);
|
||||
const currentMonth = `${tzDate.getFullYear()}-${String(tzDate.getMonth() + 1).padStart(2, '0')}`;
|
||||
searchPatterns.push(`usage:monthly:*:${currentMonth}`);
|
||||
}
|
||||
|
||||
@@ -149,11 +186,16 @@ router.get('/api-keys', authenticateAdmin, async (req, res) => {
|
||||
|
||||
// 计算指定时间范围的费用
|
||||
let totalCost = 0;
|
||||
const redis = require('../models/redis');
|
||||
const tzToday = redis.getDateStringInTimezone(now);
|
||||
const tzDate = redis.getDateInTimezone(now);
|
||||
const tzMonth = `${tzDate.getFullYear()}-${String(tzDate.getMonth() + 1).padStart(2, '0')}`;
|
||||
|
||||
const modelKeys = timeRange === 'today'
|
||||
? await client.keys(`usage:${apiKey.id}:model:daily:*:${now.toISOString().split('T')[0]}`)
|
||||
? await client.keys(`usage:${apiKey.id}:model:daily:*:${tzToday}`)
|
||||
: timeRange === '7days'
|
||||
? await client.keys(`usage:${apiKey.id}:model:daily:*:*`)
|
||||
: await client.keys(`usage:${apiKey.id}:model:monthly:*:${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`);
|
||||
: await client.keys(`usage:${apiKey.id}:model:monthly:*:${tzMonth}`);
|
||||
|
||||
const modelStatsMap = new Map();
|
||||
|
||||
@@ -277,7 +319,8 @@ router.post('/api-keys', authenticateAdmin, async (req, res) => {
|
||||
enableModelRestriction,
|
||||
restrictedModels,
|
||||
enableClientRestriction,
|
||||
allowedClients
|
||||
allowedClients,
|
||||
dailyCostLimit
|
||||
} = req.body;
|
||||
|
||||
// 输入验证
|
||||
@@ -342,7 +385,8 @@ router.post('/api-keys', authenticateAdmin, async (req, res) => {
|
||||
enableModelRestriction,
|
||||
restrictedModels,
|
||||
enableClientRestriction,
|
||||
allowedClients
|
||||
allowedClients,
|
||||
dailyCostLimit
|
||||
});
|
||||
|
||||
logger.success(`🔑 Admin created new API key: ${name}`);
|
||||
@@ -357,7 +401,7 @@ router.post('/api-keys', authenticateAdmin, async (req, res) => {
|
||||
router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const { keyId } = req.params;
|
||||
const { tokenLimit, concurrencyLimit, rateLimitWindow, rateLimitRequests, claudeAccountId, geminiAccountId, permissions, enableModelRestriction, restrictedModels, enableClientRestriction, allowedClients, expiresAt } = req.body;
|
||||
const { tokenLimit, concurrencyLimit, rateLimitWindow, rateLimitRequests, claudeAccountId, geminiAccountId, permissions, enableModelRestriction, restrictedModels, enableClientRestriction, allowedClients, expiresAt, dailyCostLimit } = req.body;
|
||||
|
||||
// 只允许更新指定字段
|
||||
const updates = {};
|
||||
@@ -453,6 +497,15 @@ router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理每日费用限制
|
||||
if (dailyCostLimit !== undefined && dailyCostLimit !== null && dailyCostLimit !== '') {
|
||||
const costLimit = Number(dailyCostLimit);
|
||||
if (isNaN(costLimit) || costLimit < 0) {
|
||||
return res.status(400).json({ error: 'Daily cost limit must be a non-negative number' });
|
||||
}
|
||||
updates.dailyCostLimit = costLimit;
|
||||
}
|
||||
|
||||
await apiKeyService.updateApiKey(keyId, updates);
|
||||
|
||||
logger.success(`📝 Admin updated API key: ${keyId}`);
|
||||
|
||||
Reference in New Issue
Block a user