Merge branch 'Wei-Shaw:dev' into dev

This commit is contained in:
sususu98
2025-09-10 14:40:46 +08:00
committed by GitHub
12 changed files with 805 additions and 775 deletions

View File

@@ -123,7 +123,7 @@ router.get('/api-keys/:keyId/cost-debug', authenticateAdmin, async (req, res) =>
// 获取所有API Keys
router.get('/api-keys', authenticateAdmin, async (req, res) => {
try {
const { timeRange = 'all' } = req.query // all, 7days, monthly
const { timeRange = 'all', startDate, endDate } = req.query // all, 7days, monthly, custom
const apiKeys = await apiKeyService.getAllApiKeys()
// 获取用户服务来补充owner信息
@@ -133,7 +133,32 @@ router.get('/api-keys', authenticateAdmin, async (req, res) => {
const now = new Date()
const searchPatterns = []
if (timeRange === 'today') {
if (timeRange === 'custom' && startDate && endDate) {
// 自定义日期范围
const redisClient = require('../models/redis')
const start = new Date(startDate)
const end = new Date(endDate)
// 确保日期范围有效
if (start > end) {
return res.status(400).json({ error: 'Start date must be before or equal to end date' })
}
// 限制最大范围为365天
const daysDiff = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1
if (daysDiff > 365) {
return res.status(400).json({ error: 'Date range cannot exceed 365 days' })
}
// 生成日期范围内每天的搜索模式
const currentDate = new Date(start)
while (currentDate <= end) {
const tzDate = redisClient.getDateInTimezone(currentDate)
const dateStr = `${tzDate.getUTCFullYear()}-${String(tzDate.getUTCMonth() + 1).padStart(2, '0')}-${String(tzDate.getUTCDate()).padStart(2, '0')}`
searchPatterns.push(`usage:daily:*:${dateStr}`)
currentDate.setDate(currentDate.getDate() + 1)
}
} else if (timeRange === 'today') {
// 今日 - 使用时区日期
const redisClient = require('../models/redis')
const tzDate = redisClient.getDateInTimezone(now)
@@ -234,7 +259,7 @@ router.get('/api-keys', authenticateAdmin, async (req, res) => {
apiKey.usage.total.formattedCost = CostCalculator.formatCost(totalCost)
}
} else {
// 7天本月:重新计算统计数据
// 7天本月或自定义日期范围:重新计算统计数据
const tempUsage = {
requests: 0,
tokens: 0,
@@ -275,12 +300,28 @@ router.get('/api-keys', authenticateAdmin, async (req, res) => {
const tzDate = redisClient.getDateInTimezone(now)
const tzMonth = `${tzDate.getUTCFullYear()}-${String(tzDate.getUTCMonth() + 1).padStart(2, '0')}`
const modelKeys =
timeRange === 'today'
? 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:*:${tzMonth}`)
let modelKeys = []
if (timeRange === 'custom' && startDate && endDate) {
// 自定义日期范围:获取范围内所有日期的模型统计
const start = new Date(startDate)
const end = new Date(endDate)
const currentDate = new Date(start)
while (currentDate <= end) {
const tzDateForKey = redisClient.getDateInTimezone(currentDate)
const dateStr = `${tzDateForKey.getUTCFullYear()}-${String(tzDateForKey.getUTCMonth() + 1).padStart(2, '0')}-${String(tzDateForKey.getUTCDate()).padStart(2, '0')}`
const dayKeys = await client.keys(`usage:${apiKey.id}:model:daily:*:${dateStr}`)
modelKeys = modelKeys.concat(dayKeys)
currentDate.setDate(currentDate.getDate() + 1)
}
} else {
modelKeys =
timeRange === 'today'
? 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:*:${tzMonth}`)
}
const modelStatsMap = new Map()
@@ -296,8 +337,8 @@ router.get('/api-keys', authenticateAdmin, async (req, res) => {
continue
}
}
} else if (timeRange === 'today') {
// today选项已经在查询时过滤了不需要额外处理
} else if (timeRange === 'today' || timeRange === 'custom') {
// today和custom选项已经在查询时过滤了,不需要额外处理
}
const modelMatch = key.match(
@@ -948,10 +989,8 @@ router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => {
expiresAt,
dailyCostLimit,
weeklyOpusCostLimit,
weeklyGPT5HighCostLimit,
tags,
ownerId, // 新增所有者ID字段
icon // 新增图标base64编码
ownerId // 新增所有者ID字段
} = req.body
// 只允许更新指定字段
@@ -1114,22 +1153,6 @@ router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => {
updates.weeklyOpusCostLimit = costLimit
}
// 处理 GPT-5 High 周费用限制
if (
weeklyGPT5HighCostLimit !== undefined &&
weeklyGPT5HighCostLimit !== null &&
weeklyGPT5HighCostLimit !== ''
) {
const costLimit = Number(weeklyGPT5HighCostLimit)
// 明确验证非负数0 表示禁用,负数无意义)
if (isNaN(costLimit) || costLimit < 0) {
return res
.status(400)
.json({ error: 'Weekly GPT-5 High cost limit must be a non-negative number' })
}
updates.weeklyGPT5HighCostLimit = costLimit
}
// 处理标签
if (tags !== undefined) {
if (!Array.isArray(tags)) {
@@ -1141,19 +1164,6 @@ router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => {
updates.tags = tags
}
// 处理图标
if (icon !== undefined) {
// icon 可以是空字符串(清除图标)或 base64 编码的字符串
if (icon !== '' && typeof icon !== 'string') {
return res.status(400).json({ error: 'Icon must be a string' })
}
// 简单验证 base64 格式(如果不为空)
if (icon && !icon.startsWith('data:image/')) {
return res.status(400).json({ error: 'Icon must be a valid base64 image' })
}
updates.icon = icon
}
// 处理活跃/禁用状态状态, 放在过期处理后以确保后续增加禁用key功能
if (isActive !== undefined) {
if (typeof isActive !== 'boolean') {
@@ -4328,10 +4338,10 @@ router.get('/model-stats', authenticateAdmin, async (req, res) => {
return res.status(400).json({ error: 'Start date must be before or equal to end date' })
}
// 限制最大范围为31
// 限制最大范围为365
const daysDiff = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1
if (daysDiff > 31) {
return res.status(400).json({ error: 'Date range cannot exceed 31 days' })
if (daysDiff > 365) {
return res.status(400).json({ error: 'Date range cannot exceed 365 days' })
}
// 生成日期范围内所有日期的搜索模式
@@ -4792,10 +4802,10 @@ router.get('/api-keys/:keyId/model-stats', authenticateAdmin, async (req, res) =
return res.status(400).json({ error: 'Start date must be before or equal to end date' })
}
// 限制最大范围为31
// 限制最大范围为365
const daysDiff = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) + 1
if (daysDiff > 31) {
return res.status(400).json({ error: 'Date range cannot exceed 31 days' })
if (daysDiff > 365) {
return res.status(400).json({ error: 'Date range cannot exceed 365 days' })
}
// 生成日期范围内所有日期的搜索模式