feat: 优化APIKey查询

1. 查询相关接口修改为APIKey的UUID
2. 输入APIKey查询后,自动查询API的UUID并添加浏览器地址参数,后续可以直接复制链接进行查询。同时保证了APIKey的安全性
This commit is contained in:
KevinLiao
2025-07-27 23:20:15 +08:00
parent fb2faca840
commit 75b4919693
3 changed files with 325 additions and 51 deletions

View File

@@ -49,13 +49,12 @@ router.get('/style.css', (req, res) => {
serveStaticFile(req, res, 'style.css', 'text/css; charset=utf-8');
});
// 📊 用户API Key统计查询接口 - 安全的自查询接口
router.post('/api/user-stats', async (req, res) => {
// 🔑 获取 API Key 对应的 ID
router.post('/api/get-key-id', async (req, res) => {
try {
const { apiKey } = req.body;
if (!apiKey) {
logger.security(`🔒 Missing API key in user stats query from ${req.ip || 'unknown'}`);
return res.status(400).json({
error: 'API Key is required',
message: 'Please provide your API Key'
@@ -64,19 +63,18 @@ router.post('/api/user-stats', async (req, res) => {
// 基本API Key格式验证
if (typeof apiKey !== 'string' || apiKey.length < 10 || apiKey.length > 512) {
logger.security(`🔒 Invalid API key format in user stats query from ${req.ip || 'unknown'}`);
return res.status(400).json({
error: 'Invalid API key format',
message: 'API key format is invalid'
});
}
// 验证API Key(重用现有的验证逻辑)
// 验证API Key
const validation = await apiKeyService.validateApiKey(apiKey);
if (!validation.valid) {
const clientIP = req.ip || req.connection?.remoteAddress || 'unknown';
logger.security(`🔒 Invalid API key in user stats query: ${validation.error} from ${clientIP}`);
logger.security(`🔒 Invalid API key in get-key-id: ${validation.error} from ${clientIP}`);
return res.status(401).json({
error: 'Invalid API key',
message: validation.error
@@ -84,12 +82,147 @@ router.post('/api/user-stats', async (req, res) => {
}
const keyData = validation.keyData;
res.json({
success: true,
data: {
id: keyData.id
}
});
} catch (error) {
logger.error('❌ Failed to get API key ID:', error);
res.status(500).json({
error: 'Internal server error',
message: 'Failed to retrieve API key ID'
});
}
});
// 📊 用户API Key统计查询接口 - 安全的自查询接口
router.post('/api/user-stats', async (req, res) => {
try {
const { apiKey, apiId } = req.body;
let keyData;
let keyId;
if (apiId) {
// 通过 apiId 查询
if (typeof apiId !== 'string' || !apiId.match(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i)) {
return res.status(400).json({
error: 'Invalid API ID format',
message: 'API ID must be a valid UUID'
});
}
// 直接通过 ID 获取 API Key 数据
keyData = await redis.getApiKey(apiId);
if (!keyData || Object.keys(keyData).length === 0) {
logger.security(`🔒 API key not found for ID: ${apiId} from ${req.ip || 'unknown'}`);
return res.status(404).json({
error: 'API key not found',
message: 'The specified API key does not exist'
});
}
// 检查是否激活
if (keyData.isActive !== 'true') {
return res.status(403).json({
error: 'API key is disabled',
message: 'This API key has been disabled'
});
}
// 检查是否过期
if (keyData.expiresAt && new Date() > new Date(keyData.expiresAt)) {
return res.status(403).json({
error: 'API key has expired',
message: 'This API key has expired'
});
}
keyId = apiId;
// 获取使用统计
const usage = await redis.getUsageStats(keyId);
// 获取当日费用统计
const dailyCost = await redis.getDailyCost(keyId);
// 处理数据格式,与 validateApiKey 返回的格式保持一致
// 解析限制模型数据
let restrictedModels = [];
try {
restrictedModels = keyData.restrictedModels ? JSON.parse(keyData.restrictedModels) : [];
} catch (e) {
restrictedModels = [];
}
// 解析允许的客户端数据
let allowedClients = [];
try {
allowedClients = keyData.allowedClients ? JSON.parse(keyData.allowedClients) : [];
} catch (e) {
allowedClients = [];
}
// 格式化 keyData
keyData = {
...keyData,
tokenLimit: parseInt(keyData.tokenLimit) || 0,
concurrencyLimit: parseInt(keyData.concurrencyLimit) || 0,
rateLimitWindow: parseInt(keyData.rateLimitWindow) || 0,
rateLimitRequests: parseInt(keyData.rateLimitRequests) || 0,
dailyCostLimit: parseFloat(keyData.dailyCostLimit) || 0,
dailyCost: dailyCost || 0,
enableModelRestriction: keyData.enableModelRestriction === 'true',
restrictedModels: restrictedModels,
enableClientRestriction: keyData.enableClientRestriction === 'true',
allowedClients: allowedClients,
permissions: keyData.permissions || 'all',
usage: usage // 使用完整的 usage 数据,而不是只有 total
};
} else if (apiKey) {
// 通过 apiKey 查询(保持向后兼容)
if (typeof apiKey !== 'string' || apiKey.length < 10 || apiKey.length > 512) {
logger.security(`🔒 Invalid API key format in user stats query from ${req.ip || 'unknown'}`);
return res.status(400).json({
error: 'Invalid API key format',
message: 'API key format is invalid'
});
}
// 验证API Key重用现有的验证逻辑
const validation = await apiKeyService.validateApiKey(apiKey);
if (!validation.valid) {
const clientIP = req.ip || req.connection?.remoteAddress || 'unknown';
logger.security(`🔒 Invalid API key in user stats query: ${validation.error} from ${clientIP}`);
return res.status(401).json({
error: 'Invalid API key',
message: validation.error
});
}
keyData = validation.keyData;
keyId = keyData.id;
} else {
logger.security(`🔒 Missing API key or ID in user stats query from ${req.ip || 'unknown'}`);
return res.status(400).json({
error: 'API Key or ID is required',
message: 'Please provide your API Key or API ID'
});
}
// 记录合法查询
logger.api(`📊 User stats query from key: ${keyData.name} (${keyData.id}) from ${req.ip || 'unknown'}`);
logger.api(`📊 User stats query from key: ${keyData.name} (${keyId}) from ${req.ip || 'unknown'}`);
// 获取验证结果中的完整keyData包含isActive状态和cost信息
const fullKeyData = validation.keyData;
const fullKeyData = keyData;
// 计算总费用 - 使用与模型统计相同的逻辑(按模型分别计算)
let totalCost = 0;
@@ -99,7 +232,7 @@ router.post('/api/user-stats', async (req, res) => {
const client = redis.getClientSafe();
// 获取所有月度模型统计与model-stats接口相同的逻辑
const allModelKeys = await client.keys(`usage:${fullKeyData.id}:model:monthly:*:*`);
const allModelKeys = await client.keys(`usage:${keyId}:model:monthly:*:*`);
const modelUsageMap = new Map();
for (const key of allModelKeys) {
@@ -157,7 +290,7 @@ router.post('/api/user-stats', async (req, res) => {
formattedCost = CostCalculator.formatCost(totalCost);
} catch (error) {
logger.warn(`Failed to calculate detailed cost for key ${fullKeyData.id}:`, error);
logger.warn(`Failed to calculate detailed cost for key ${keyId}:`, error);
// 回退到简单计算
if (fullKeyData.usage?.total?.allTokens > 0) {
const usage = fullKeyData.usage.total;
@@ -176,7 +309,7 @@ router.post('/api/user-stats', async (req, res) => {
// 构建响应数据只返回该API Key自己的信息确保不泄露其他信息
const responseData = {
id: fullKeyData.id,
id: keyId,
name: fullKeyData.name,
description: keyData.description || '',
isActive: true, // 如果能通过validateApiKey验证说明一定是激活的
@@ -242,30 +375,71 @@ router.post('/api/user-stats', async (req, res) => {
// 📊 用户模型统计查询接口 - 安全的自查询接口
router.post('/api/user-model-stats', async (req, res) => {
try {
const { apiKey, period = 'monthly' } = req.body;
const { apiKey, apiId, period = 'monthly' } = req.body;
if (!apiKey) {
logger.security(`🔒 Missing API key in user model stats query from ${req.ip || 'unknown'}`);
let keyData;
let keyId;
if (apiId) {
// 通过 apiId 查询
if (typeof apiId !== 'string' || !apiId.match(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i)) {
return res.status(400).json({
error: 'Invalid API ID format',
message: 'API ID must be a valid UUID'
});
}
// 直接通过 ID 获取 API Key 数据
keyData = await redis.getApiKey(apiId);
if (!keyData || Object.keys(keyData).length === 0) {
logger.security(`🔒 API key not found for ID: ${apiId} from ${req.ip || 'unknown'}`);
return res.status(404).json({
error: 'API key not found',
message: 'The specified API key does not exist'
});
}
// 检查是否激活
if (keyData.isActive !== 'true') {
return res.status(403).json({
error: 'API key is disabled',
message: 'This API key has been disabled'
});
}
keyId = apiId;
// 获取使用统计
const usage = await redis.getUsageStats(keyId);
keyData.usage = { total: usage.total };
} else if (apiKey) {
// 通过 apiKey 查询(保持向后兼容)
// 验证API Key
const validation = await apiKeyService.validateApiKey(apiKey);
if (!validation.valid) {
const clientIP = req.ip || req.connection?.remoteAddress || 'unknown';
logger.security(`🔒 Invalid API key in user model stats query: ${validation.error} from ${clientIP}`);
return res.status(401).json({
error: 'Invalid API key',
message: validation.error
});
}
keyData = validation.keyData;
keyId = keyData.id;
} else {
logger.security(`🔒 Missing API key or ID in user model stats query from ${req.ip || 'unknown'}`);
return res.status(400).json({
error: 'API Key is required',
message: 'Please provide your API Key'
error: 'API Key or ID is required',
message: 'Please provide your API Key or API ID'
});
}
// 验证API Key
const validation = await apiKeyService.validateApiKey(apiKey);
if (!validation.valid) {
const clientIP = req.ip || req.connection?.remoteAddress || 'unknown';
logger.security(`🔒 Invalid API key in user model stats query: ${validation.error} from ${clientIP}`);
return res.status(401).json({
error: 'Invalid API key',
message: validation.error
});
}
const keyData = validation.keyData;
logger.api(`📊 User model stats query from key: ${keyData.name} (${keyData.id}) for period: ${period}`);
logger.api(`📊 User model stats query from key: ${keyData.name} (${keyId}) for period: ${period}`);
// 重用管理后台的模型统计逻辑但只返回该API Key的数据
const client = redis.getClientSafe();
@@ -273,8 +447,8 @@ router.post('/api/user-model-stats', async (req, res) => {
const currentMonth = `${new Date().getFullYear()}-${String(new Date().getMonth() + 1).padStart(2, '0')}`;
const pattern = period === 'daily' ?
`usage:${keyData.id}:model:daily:*:${today}` :
`usage:${keyData.id}:model:monthly:*:${currentMonth}`;
`usage:${keyId}:model:daily:*:${today}` :
`usage:${keyId}:model:monthly:*:${currentMonth}`;
const keys = await client.keys(pattern);
const modelStats = [];