mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
feat: 给key增加总用量限制
This commit is contained in:
@@ -533,6 +533,8 @@ router.post('/api-keys', authenticateAdmin, async (req, res) => {
|
||||
enableClientRestriction,
|
||||
allowedClients,
|
||||
dailyCostLimit,
|
||||
totalUsageLimit,
|
||||
totalCostLimit,
|
||||
weeklyOpusCostLimit,
|
||||
tags,
|
||||
activationDays, // 新增:激活后有效天数
|
||||
@@ -615,6 +617,35 @@ router.post('/api-keys', authenticateAdmin, async (req, res) => {
|
||||
return res.status(400).json({ error: 'All tags must be non-empty strings' })
|
||||
}
|
||||
|
||||
if (
|
||||
totalUsageLimit !== undefined &&
|
||||
totalUsageLimit !== null &&
|
||||
totalUsageLimit !== ''
|
||||
) {
|
||||
const usageLimit = Number(totalUsageLimit)
|
||||
if (Number.isNaN(usageLimit) || usageLimit < 0) {
|
||||
return res.status(400).json({ error: 'Total usage limit must be a non-negative number' })
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
totalCostLimit !== undefined &&
|
||||
totalCostLimit !== null &&
|
||||
totalCostLimit !== '' &&
|
||||
(Number.isNaN(Number(totalCostLimit)) || Number(totalCostLimit) < 0)
|
||||
) {
|
||||
return res.status(400).json({ error: 'Total cost limit must be a non-negative number' })
|
||||
}
|
||||
|
||||
if (
|
||||
totalCostLimit !== undefined &&
|
||||
totalCostLimit !== null &&
|
||||
totalCostLimit !== '' &&
|
||||
(Number.isNaN(Number(totalCostLimit)) || Number(totalCostLimit) < 0)
|
||||
) {
|
||||
return res.status(400).json({ error: 'Total cost limit must be a non-negative number' })
|
||||
}
|
||||
|
||||
// 验证激活相关字段
|
||||
if (expirationMode && !['fixed', 'activation'].includes(expirationMode)) {
|
||||
return res
|
||||
@@ -660,6 +691,8 @@ router.post('/api-keys', authenticateAdmin, async (req, res) => {
|
||||
enableClientRestriction,
|
||||
allowedClients,
|
||||
dailyCostLimit,
|
||||
totalUsageLimit,
|
||||
totalCostLimit,
|
||||
weeklyOpusCostLimit,
|
||||
tags,
|
||||
activationDays,
|
||||
@@ -699,6 +732,8 @@ router.post('/api-keys/batch', authenticateAdmin, async (req, res) => {
|
||||
enableClientRestriction,
|
||||
allowedClients,
|
||||
dailyCostLimit,
|
||||
totalUsageLimit,
|
||||
totalCostLimit,
|
||||
weeklyOpusCostLimit,
|
||||
tags,
|
||||
activationDays,
|
||||
@@ -748,6 +783,8 @@ router.post('/api-keys/batch', authenticateAdmin, async (req, res) => {
|
||||
enableClientRestriction,
|
||||
allowedClients,
|
||||
dailyCostLimit,
|
||||
totalUsageLimit,
|
||||
totalCostLimit,
|
||||
weeklyOpusCostLimit,
|
||||
tags,
|
||||
activationDays,
|
||||
@@ -865,6 +902,12 @@ router.put('/api-keys/batch', authenticateAdmin, async (req, res) => {
|
||||
if (updates.dailyCostLimit !== undefined) {
|
||||
finalUpdates.dailyCostLimit = updates.dailyCostLimit
|
||||
}
|
||||
if (updates.totalUsageLimit !== undefined) {
|
||||
finalUpdates.totalUsageLimit = updates.totalUsageLimit
|
||||
}
|
||||
if (updates.totalCostLimit !== undefined) {
|
||||
finalUpdates.totalCostLimit = updates.totalCostLimit
|
||||
}
|
||||
if (updates.weeklyOpusCostLimit !== undefined) {
|
||||
finalUpdates.weeklyOpusCostLimit = updates.weeklyOpusCostLimit
|
||||
}
|
||||
@@ -993,6 +1036,8 @@ router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => {
|
||||
allowedClients,
|
||||
expiresAt,
|
||||
dailyCostLimit,
|
||||
totalUsageLimit,
|
||||
totalCostLimit,
|
||||
weeklyOpusCostLimit,
|
||||
tags,
|
||||
ownerId // 新增:所有者ID字段
|
||||
@@ -1142,6 +1187,22 @@ router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => {
|
||||
updates.dailyCostLimit = costLimit
|
||||
}
|
||||
|
||||
if (totalCostLimit !== undefined && totalCostLimit !== null && totalCostLimit !== '') {
|
||||
const costLimit = Number(totalCostLimit)
|
||||
if (isNaN(costLimit) || costLimit < 0) {
|
||||
return res.status(400).json({ error: 'Total cost limit must be a non-negative number' })
|
||||
}
|
||||
updates.totalCostLimit = costLimit
|
||||
}
|
||||
|
||||
if (totalUsageLimit !== undefined && totalUsageLimit !== null && totalUsageLimit !== '') {
|
||||
const usageLimit = Number(totalUsageLimit)
|
||||
if (Number.isNaN(usageLimit) || usageLimit < 0) {
|
||||
return res.status(400).json({ error: 'Total usage limit must be a non-negative number' })
|
||||
}
|
||||
updates.totalUsageLimit = usageLimit
|
||||
}
|
||||
|
||||
// 处理 Opus 周费用限制
|
||||
if (
|
||||
weeklyOpusCostLimit !== undefined &&
|
||||
|
||||
@@ -114,6 +114,7 @@ router.post('/api/user-stats', async (req, res) => {
|
||||
|
||||
// 获取当日费用统计
|
||||
const dailyCost = await redis.getDailyCost(keyId)
|
||||
const costStats = await redis.getCostStats(keyId)
|
||||
|
||||
// 处理数据格式,与 validateApiKey 返回的格式保持一致
|
||||
// 解析限制模型数据
|
||||
@@ -140,7 +141,10 @@ router.post('/api/user-stats', async (req, res) => {
|
||||
rateLimitWindow: parseInt(keyData.rateLimitWindow) || 0,
|
||||
rateLimitRequests: parseInt(keyData.rateLimitRequests) || 0,
|
||||
dailyCostLimit: parseFloat(keyData.dailyCostLimit) || 0,
|
||||
totalUsageLimit: parseFloat(keyData.totalUsageLimit) || 0,
|
||||
totalCostLimit: parseFloat(keyData.totalCostLimit) || 0,
|
||||
dailyCost: dailyCost || 0,
|
||||
totalCost: costStats.total || 0,
|
||||
enableModelRestriction: keyData.enableModelRestriction === 'true',
|
||||
restrictedModels,
|
||||
enableClientRestriction: keyData.enableClientRestriction === 'true',
|
||||
@@ -372,11 +376,14 @@ router.post('/api/user-stats', async (req, res) => {
|
||||
rateLimitRequests: fullKeyData.rateLimitRequests || 0,
|
||||
rateLimitCost: parseFloat(fullKeyData.rateLimitCost) || 0, // 新增:费用限制
|
||||
dailyCostLimit: fullKeyData.dailyCostLimit || 0,
|
||||
totalUsageLimit: fullKeyData.totalUsageLimit || 0,
|
||||
totalCostLimit: fullKeyData.totalCostLimit || 0,
|
||||
// 当前使用量
|
||||
currentWindowRequests,
|
||||
currentWindowTokens,
|
||||
currentWindowCost, // 新增:当前窗口费用
|
||||
currentDailyCost,
|
||||
currentTotalCost: totalCost,
|
||||
// 时间窗口信息
|
||||
windowStartTime,
|
||||
windowEndTime,
|
||||
|
||||
@@ -258,6 +258,9 @@ router.get('/api-keys', authenticateUser, async (req, res) => {
|
||||
usage: flatUsage,
|
||||
dailyCost: key.dailyCost,
|
||||
dailyCostLimit: key.dailyCostLimit,
|
||||
totalUsageLimit: key.totalUsageLimit,
|
||||
totalCost: key.totalCost,
|
||||
totalCostLimit: key.totalCostLimit,
|
||||
// 不返回实际的key值,只返回前缀和后几位
|
||||
keyPreview: key.key
|
||||
? `${key.key.substring(0, 8)}...${key.key.substring(key.key.length - 4)}`
|
||||
@@ -287,7 +290,15 @@ router.get('/api-keys', authenticateUser, async (req, res) => {
|
||||
// 🔑 创建新的API Key
|
||||
router.post('/api-keys', authenticateUser, async (req, res) => {
|
||||
try {
|
||||
const { name, description, tokenLimit, expiresAt, dailyCostLimit } = req.body
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
tokenLimit,
|
||||
expiresAt,
|
||||
dailyCostLimit,
|
||||
totalUsageLimit,
|
||||
totalCostLimit
|
||||
} = req.body
|
||||
|
||||
if (!name || !name.trim()) {
|
||||
return res.status(400).json({
|
||||
@@ -296,6 +307,32 @@ router.post('/api-keys', authenticateUser, async (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
totalCostLimit !== undefined &&
|
||||
totalCostLimit !== null &&
|
||||
totalCostLimit !== '' &&
|
||||
(Number.isNaN(Number(totalCostLimit)) || Number(totalCostLimit) < 0)
|
||||
) {
|
||||
return res.status(400).json({
|
||||
error: 'Invalid total cost limit',
|
||||
message: 'Total cost limit must be a non-negative number'
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
totalUsageLimit !== undefined &&
|
||||
totalUsageLimit !== null &&
|
||||
totalUsageLimit !== ''
|
||||
) {
|
||||
const usageLimit = Number(totalUsageLimit)
|
||||
if (Number.isNaN(usageLimit) || usageLimit < 0) {
|
||||
return res.status(400).json({
|
||||
error: 'Invalid total usage limit',
|
||||
message: 'Total usage limit must be a non-negative number'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 检查用户API Key数量限制
|
||||
const userApiKeys = await apiKeyService.getUserApiKeys(req.user.id)
|
||||
if (userApiKeys.length >= config.userManagement.maxApiKeysPerUser) {
|
||||
@@ -314,6 +351,8 @@ router.post('/api-keys', authenticateUser, async (req, res) => {
|
||||
tokenLimit: tokenLimit || null,
|
||||
expiresAt: expiresAt || null,
|
||||
dailyCostLimit: dailyCostLimit || null,
|
||||
totalUsageLimit: totalUsageLimit || null,
|
||||
totalCostLimit: totalCostLimit || null,
|
||||
createdBy: 'user',
|
||||
// 设置服务权限为全部服务,确保前端显示“服务权限”为“全部服务”且具备完整访问权限
|
||||
permissions: 'all'
|
||||
@@ -337,6 +376,8 @@ router.post('/api-keys', authenticateUser, async (req, res) => {
|
||||
tokenLimit: newApiKey.tokenLimit,
|
||||
expiresAt: newApiKey.expiresAt,
|
||||
dailyCostLimit: newApiKey.dailyCostLimit,
|
||||
totalUsageLimit: newApiKey.totalUsageLimit,
|
||||
totalCostLimit: newApiKey.totalCostLimit,
|
||||
createdAt: newApiKey.createdAt
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user