import express from 'express'; import { generateToken, authMiddleware } from '../auth/jwt.js'; import tokenManager from '../auth/token_manager.js'; import quotaManager from '../auth/quota_manager.js'; import oauthManager from '../auth/oauth_manager.js'; import config, { getConfigJson, saveConfigJson } from '../config/config.js'; import logger from '../utils/logger.js'; import { parseEnvFile, updateEnvFile } from '../utils/envParser.js'; import { reloadConfig } from '../utils/configReloader.js'; import { deepMerge } from '../utils/deepMerge.js'; import { getModelsWithQuotas } from '../api/client.js'; import { getEnvPath } from '../utils/paths.js'; import dotenv from 'dotenv'; const envPath = getEnvPath(); const router = express.Router(); // 登录速率限制 - 防止暴力破解 const loginAttempts = new Map(); // IP -> { count, lastAttempt, blockedUntil } const MAX_LOGIN_ATTEMPTS = 5; const BLOCK_DURATION = 5 * 60 * 1000; // 5分钟 const ATTEMPT_WINDOW = 15 * 60 * 1000; // 15分钟窗口 function getClientIP(req) { return req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.headers['x-real-ip'] || req.connection?.remoteAddress || req.ip || 'unknown'; } function checkLoginRateLimit(ip) { const now = Date.now(); const attempt = loginAttempts.get(ip); if (!attempt) return { allowed: true }; // 检查是否被封禁 if (attempt.blockedUntil && now < attempt.blockedUntil) { const remainingSeconds = Math.ceil((attempt.blockedUntil - now) / 1000); return { allowed: false, message: `登录尝试过多,请 ${remainingSeconds} 秒后重试`, remainingSeconds }; } // 清理过期的尝试记录 if (now - attempt.lastAttempt > ATTEMPT_WINDOW) { loginAttempts.delete(ip); return { allowed: true }; } return { allowed: true }; } function recordLoginAttempt(ip, success) { const now = Date.now(); if (success) { // 登录成功,清除记录 loginAttempts.delete(ip); return; } // 登录失败,记录尝试 const attempt = loginAttempts.get(ip) || { count: 0, lastAttempt: now }; attempt.count++; attempt.lastAttempt = now; // 超过最大尝试次数,封禁 if (attempt.count >= MAX_LOGIN_ATTEMPTS) { attempt.blockedUntil = now + BLOCK_DURATION; logger.warn(`IP ${ip} 因登录失败次数过多被暂时封禁`); } loginAttempts.set(ip, attempt); } // 登录接口 router.post('/login', (req, res) => { const clientIP = getClientIP(req); // 检查速率限制 const rateCheck = checkLoginRateLimit(clientIP); if (!rateCheck.allowed) { return res.status(429).json({ success: false, message: rateCheck.message, retryAfter: rateCheck.remainingSeconds }); } const { username, password } = req.body; // 验证输入 if (!username || !password || typeof username !== 'string' || typeof password !== 'string') { return res.status(400).json({ success: false, message: '用户名和密码必填' }); } // 限制输入长度防止 DoS if (username.length > 100 || password.length > 100) { return res.status(400).json({ success: false, message: '输入过长' }); } if (username === config.admin.username && password === config.admin.password) { recordLoginAttempt(clientIP, true); const token = generateToken({ username, role: 'admin' }); res.json({ success: true, token }); } else { recordLoginAttempt(clientIP, false); res.status(401).json({ success: false, message: '用户名或密码错误' }); } }); // Token管理API - 需要JWT认证 router.get('/tokens', authMiddleware, async (req, res) => { try { const tokens = await tokenManager.getTokenList(); res.json({ success: true, data: tokens }); } catch (error) { logger.error('获取Token列表失败:', error.message); res.status(500).json({ success: false, message: error.message }); } }); router.post('/tokens', authMiddleware, async (req, res) => { const { access_token, refresh_token, expires_in, timestamp, enable, projectId, email } = req.body; if (!access_token || !refresh_token) { return res.status(400).json({ success: false, message: 'access_token和refresh_token必填' }); } const tokenData = { access_token, refresh_token, expires_in }; if (timestamp) tokenData.timestamp = timestamp; if (enable !== undefined) tokenData.enable = enable; if (projectId) tokenData.projectId = projectId; if (email) tokenData.email = email; try { const result = await tokenManager.addToken(tokenData); res.json(result); } catch (error) { logger.error('添加Token失败:', error.message); res.status(500).json({ success: false, message: error.message }); } }); router.put('/tokens/:refreshToken', authMiddleware, async (req, res) => { const { refreshToken } = req.params; const updates = req.body; try { const result = await tokenManager.updateToken(refreshToken, updates); res.json(result); } catch (error) { logger.error('更新Token失败:', error.message); res.status(500).json({ success: false, message: error.message }); } }); router.delete('/tokens/:refreshToken', authMiddleware, async (req, res) => { const { refreshToken } = req.params; try { const result = await tokenManager.deleteToken(refreshToken); res.json(result); } catch (error) { logger.error('删除Token失败:', error.message); res.status(500).json({ success: false, message: error.message }); } }); router.post('/tokens/reload', authMiddleware, async (req, res) => { try { await tokenManager.reload(); res.json({ success: true, message: 'Token已热重载' }); } catch (error) { logger.error('热重载失败:', error.message); res.status(500).json({ success: false, message: error.message }); } }); // 刷新指定Token的access_token router.post('/tokens/:refreshToken/refresh', authMiddleware, async (req, res) => { const { refreshToken } = req.params; try { logger.info('正在刷新token...'); const tokens = await tokenManager.getTokenList(); const tokenData = tokens.find(t => t.refresh_token === refreshToken); if (!tokenData) { return res.status(404).json({ success: false, message: 'Token不存在' }); } // 调用 tokenManager 的刷新方法 const refreshedToken = await tokenManager.refreshToken(tokenData); res.json({ success: true, message: 'Token刷新成功', data: { expires_in: refreshedToken.expires_in, timestamp: refreshedToken.timestamp } }); } catch (error) { logger.error('刷新Token失败:', error.message); res.status(500).json({ success: false, message: error.message }); } }); router.post('/oauth/exchange', authMiddleware, async (req, res) => { const { code, port } = req.body; if (!code || !port) { return res.status(400).json({ success: false, message: 'code和port必填' }); } try { const account = await oauthManager.authenticate(code, port); const message = account.hasQuota ? 'Token添加成功' : 'Token添加成功(该账号无资格,已自动使用随机ProjectId)'; res.json({ success: true, data: account, message, fallbackMode: !account.hasQuota }); } catch (error) { logger.error('认证失败:', error.message); res.status(500).json({ success: false, message: error.message }); } }); // 获取配置 router.get('/config', authMiddleware, (req, res) => { try { const envData = parseEnvFile(envPath); const jsonData = getConfigJson(); res.json({ success: true, data: { env: envData, json: jsonData } }); } catch (error) { logger.error('读取配置失败:', error.message); res.status(500).json({ success: false, message: error.message }); } }); // 更新配置 router.put('/config', authMiddleware, (req, res) => { try { const { env: envUpdates, json: jsonUpdates } = req.body; if (envUpdates) updateEnvFile(envPath, envUpdates); if (jsonUpdates) saveConfigJson(deepMerge(getConfigJson(), jsonUpdates)); dotenv.config({ override: true }); reloadConfig(); logger.info('配置已更新并热重载'); res.json({ success: true, message: '配置已保存并生效(端口/HOST修改需重启)' }); } catch (error) { logger.error('更新配置失败:', error.message); res.status(500).json({ success: false, message: error.message }); } }); // 获取轮询策略配置 router.get('/rotation', authMiddleware, (req, res) => { try { const rotationConfig = tokenManager.getRotationConfig(); res.json({ success: true, data: rotationConfig }); } catch (error) { logger.error('获取轮询配置失败:', error.message); res.status(500).json({ success: false, message: error.message }); } }); // 更新轮询策略配置 router.put('/rotation', authMiddleware, (req, res) => { try { const { strategy, requestCount } = req.body; // 验证策略值 const validStrategies = ['round_robin', 'quota_exhausted', 'request_count']; if (strategy && !validStrategies.includes(strategy)) { return res.status(400).json({ success: false, message: `无效的策略,可选值: ${validStrategies.join(', ')}` }); } // 更新内存中的配置 tokenManager.updateRotationConfig(strategy, requestCount); // 保存到config.json const currentConfig = getConfigJson(); if (!currentConfig.rotation) currentConfig.rotation = {}; if (strategy) currentConfig.rotation.strategy = strategy; if (requestCount) currentConfig.rotation.requestCount = requestCount; saveConfigJson(currentConfig); // 重载配置到内存 reloadConfig(); logger.info(`轮询策略已更新: ${strategy || '未变'}, 请求次数: ${requestCount || '未变'}`); res.json({ success: true, message: '轮询策略已更新', data: tokenManager.getRotationConfig() }); } catch (error) { logger.error('更新轮询配置失败:', error.message); res.status(500).json({ success: false, message: error.message }); } }); // 获取指定Token的模型额度 router.get('/tokens/:refreshToken/quotas', authMiddleware, async (req, res) => { try { const { refreshToken } = req.params; const forceRefresh = req.query.refresh === 'true'; const tokens = await tokenManager.getTokenList(); let tokenData = tokens.find(t => t.refresh_token === refreshToken); if (!tokenData) { return res.status(404).json({ success: false, message: 'Token不存在' }); } // 检查token是否过期,如果过期则刷新 if (tokenManager.isExpired(tokenData)) { try { tokenData = await tokenManager.refreshToken(tokenData); } catch (error) { logger.error('刷新token失败:', error.message); // 使用 400 而不是 401,避免前端误认为 JWT 登录过期 return res.status(400).json({ success: false, message: 'Google Token已过期且刷新失败,请重新登录Google账号' }); } } // 先从缓存获取(除非强制刷新) let quotaData = forceRefresh ? null : quotaManager.getQuota(refreshToken); if (!quotaData) { // 缓存未命中或强制刷新,从API获取 const token = { access_token: tokenData.access_token, refresh_token: refreshToken }; const quotas = await getModelsWithQuotas(token); quotaManager.updateQuota(refreshToken, quotas); quotaData = { lastUpdated: Date.now(), models: quotas }; } // 转换时间为北京时间 const modelsWithBeijingTime = {}; Object.entries(quotaData.models).forEach(([modelId, quota]) => { modelsWithBeijingTime[modelId] = { remaining: quota.r, resetTime: quotaManager.convertToBeijingTime(quota.t), resetTimeRaw: quota.t }; }); res.json({ success: true, data: { lastUpdated: quotaData.lastUpdated, models: modelsWithBeijingTime } }); } catch (error) { logger.error('获取额度失败:', error.message); res.status(500).json({ success: false, message: error.message }); } }); export default router;