test-g / index.js
kuefr's picture
Update index.js
c73ceaa verified
import express from 'express';
import fetch from 'node-fetch';
import dotenv from 'dotenv';
import { v4 as uuidv4 } from 'uuid';
import cors from 'cors';
import fs from 'fs';
import path from 'path';
import crypto from 'crypto';
// 初始化环境变量
dotenv.config();
/**
* 日志管理类
*/
class LogManager {
constructor() {
this.logs = new Map(); // key: authKey, value: array of logs
this.logFile = path.join(process.cwd(), 'api_logs.json');
this.enableFileLogging = process.env.ENABLE_FILE_LOGGING !== 'false'; // 默认启用
if (this.enableFileLogging) {
this.loadLogs();
}
}
/**
* 设置是否启用文件日志
*/
setFileLogging(enabled) {
this.enableFileLogging = enabled;
if (!enabled) {
// 如果禁用文件日志,清空已有的文件
try {
if (fs.existsSync(this.logFile)) {
fs.unlinkSync(this.logFile);
console.log('📄 日志文件已删除,切换到内存模式');
}
} catch (error) {
console.error('删除日志文件失败:', error);
}
}
}
/**
* 获取文件日志状态
*/
getFileLoggingStatus() {
return this.enableFileLogging;
}
/**
* 加载日志文件
*/
loadLogs() {
try {
if (fs.existsSync(this.logFile)) {
const data = fs.readFileSync(this.logFile, 'utf8');
const logsData = JSON.parse(data);
this.logs = new Map(Object.entries(logsData));
console.log('✅ 日志文件加载成功');
}
} catch (error) {
console.warn('⚠️ 加载日志文件失败:', error.message);
this.logs = new Map();
}
}
/**
* 保存日志到文件
*/
saveLogs() {
if (!this.enableFileLogging) {
return; // 不启用文件日志时不保存
}
try {
const logsData = Object.fromEntries(this.logs);
fs.writeFileSync(this.logFile, JSON.stringify(logsData, null, 2));
} catch (error) {
console.error('❌ 保存日志文件失败:', error.message);
}
}
/**
* 计算文本token数量(简单估算)
*/
countTokens(text) {
if (!text) return 0;
// 简单的token计算:中文字符按1.5计算,英文单词按1计算,标点按0.5计算
const chineseChars = (text.match(/[\u4e00-\u9fa5]/g) || []).length;
const englishWords = (text.match(/[a-zA-Z]+/g) || []).length;
const punctuation = (text.match(/[^\w\s\u4e00-\u9fa5]/g) || []).length;
return Math.ceil(chineseChars * 1.5 + englishWords + punctuation * 0.5);
}
/**
* 记录请求日志
*/
logRequest(authKey, requestData) {
if (!this.logs.has(authKey)) {
this.logs.set(authKey, []);
}
// 计算响应内容的token数量
const responseTokens = this.countTokens(requestData.responseContent);
const log = {
id: uuidv4(),
timestamp: new Date().toISOString(),
requestTime: requestData.requestTime,
responseTime: requestData.responseTime,
duration: requestData.duration,
requestType: requestData.requestType,
model: requestData.model,
responseContent: requestData.responseContent,
responseTokens: responseTokens,
status: requestData.status,
error: requestData.error
};
this.logs.get(authKey).push(log);
// 保持每个key最多1000条日志
if (this.logs.get(authKey).length > 1000) {
this.logs.get(authKey).shift();
}
this.saveLogs();
}
/**
* 获取指定key的日志
*/
getLogs(authKey, page = 1, pageSize = 50) {
const logs = this.logs.get(authKey) || [];
const total = logs.length;
const totalPages = Math.ceil(total / pageSize);
const start = (page - 1) * pageSize;
const end = start + pageSize;
return {
logs: logs.slice(start, end).reverse(), // 最新的在前
pagination: {
page,
pageSize,
total,
totalPages
}
};
}
/**
* 获取所有日志(用于导出)
*/
getAllLogs() {
const allLogs = {};
for (const [authKey, logs] of this.logs) {
allLogs[authKey] = logs;
}
return allLogs;
}
/**
* 获取所有日志统计
*/
getStatistics() {
const stats = {};
for (const [authKey, logs] of this.logs) {
const keyStats = {
totalRequests: logs.length,
streamRequests: logs.filter(log => log.requestType === 'stream').length,
normalRequests: logs.filter(log => log.requestType === 'normal').length,
fakeStreamRequests: logs.filter(log => log.requestType === 'fake-stream').length,
errorRequests: logs.filter(log => log.error).length,
modelUsage: {}
};
// 统计模型使用情况
logs.forEach(log => {
if (log.model) {
keyStats.modelUsage[log.model] = (keyStats.modelUsage[log.model] || 0) + 1;
}
});
stats[authKey] = keyStats;
}
return stats;
}
/**
* 清除指定key的日志
*/
clearLogs(authKey) {
this.logs.delete(authKey);
this.saveLogs();
}
/**
* 清除所有日志
*/
clearAllLogs() {
this.logs.clear();
this.saveLogs();
}
/**
* 导出日志为JSON格式
*/
exportLogsAsJson() {
return JSON.stringify(this.getAllLogs(), null, 2);
}
/**
* 导出日志为CSV格式
*/
exportLogsAsCsv() {
let csv = 'AuthKey,Timestamp,RequestTime,ResponseTime,Duration,RequestType,Model,Status,ResponseTokens,ResponseContent,Error\n';
for (const [authKey, logs] of this.logs) {
logs.forEach(log => {
const row = [
authKey,
log.timestamp,
log.requestTime || '',
log.responseTime || '',
log.duration || '',
log.requestType || '',
log.model || '',
log.status || '',
log.responseTokens || 0,
`"${(log.responseContent || '').replace(/"/g, '""')}"`,
`"${(log.error || '').replace(/"/g, '""')}"`
].join(',');
csv += row + '\n';
});
}
return csv;
}
}
/**
* 认证Key管理类
*/
class AuthKeyManager {
constructor() {
this.keys = new Map(); // key: token, value: {alias, enabled, createdAt}
this.adminPassword = process.env.ADMIN_PASSWORD || 'admin123';
this.initializeKeys();
}
/**
* 初始化认证Keys
*/
initializeKeys() {
const authTokensEnv = process.env.AUTH_TOKENS;
if (authTokensEnv) {
// 支持格式: alias1:token1,alias2:token2 或者 token1,token2
const tokens = authTokensEnv.split(',').map(item => item.trim());
tokens.forEach((token, index) => {
if (token.includes(':')) {
const [alias, key] = token.split(':');
this.keys.set(key.trim(), {
alias: alias.trim(),
enabled: true,
createdAt: new Date().toISOString()
});
} else {
this.keys.set(token, {
alias: `Key-${index + 1}`,
enabled: true,
createdAt: new Date().toISOString()
});
}
});
} else {
// 默认key
const defaultToken = process.env.AUTH_TOKEN || 'sk-123456';
this.keys.set(defaultToken, {
alias: 'Default-Key',
enabled: true,
createdAt: new Date().toISOString()
});
}
console.log(`✅ 成功加载 ${this.keys.size} 个认证Keys`);
}
/**
* 验证认证Key
*/
validateAuth(authHeader) {
if (!authHeader) {
return { valid: false, key: null };
}
const token = authHeader.replace('Bearer ', '');
const keyInfo = this.keys.get(token);
if (keyInfo && keyInfo.enabled) {
return { valid: true, key: token, alias: keyInfo.alias };
}
return { valid: false, key: null };
}
/**
* 添加新的认证Key
*/
addKey(alias, token) {
if (this.keys.has(token)) {
return { success: false, message: 'Key已存在' };
}
this.keys.set(token, {
alias,
enabled: true,
createdAt: new Date().toISOString()
});
return { success: true, message: 'Key添加成功' };
}
/**
* 更新Key别名
*/
updateKeyAlias(token, newAlias) {
if (!this.keys.has(token)) {
return { success: false, message: 'Key不存在' };
}
if (!newAlias || newAlias.trim() === '') {
return { success: false, message: '别名不能为空' };
}
// 检查别名是否与其他Key重复(可选)
for (const [existingToken, existingInfo] of this.keys) {
if (existingToken !== token && existingInfo.alias === newAlias.trim()) {
return { success: false, message: '别名已存在,请使用其他别名' };
}
}
const keyInfo = this.keys.get(token);
keyInfo.alias = newAlias.trim();
return { success: true, message: '别名更新成功' };
}
/**
* 更新Key状态
*/
updateKeyStatus(token, enabled) {
if (!this.keys.has(token)) {
return { success: false, message: 'Key不存在' };
}
this.keys.get(token).enabled = enabled;
return { success: true, message: `Key已${enabled ? '启用' : '禁用'}` };
}
/**
* 删除Key
*/
deleteKey(token) {
if (!this.keys.has(token)) {
return { success: false, message: 'Key不存在' };
}
this.keys.delete(token);
return { success: true, message: 'Key删除成功' };
}
/**
* 获取所有Keys
*/
getAllKeys() {
const result = [];
for (const [token, info] of this.keys) {
result.push({
token,
alias: info.alias,
enabled: info.enabled,
createdAt: info.createdAt
});
}
return result;
}
/**
* 导出环境变量格式
*/
exportToEnv() {
const tokens = [];
for (const [token, info] of this.keys) {
if (info.enabled) {
tokens.push(`${info.alias}:${token}`);
}
}
return `AUTH_TOKENS=${tokens.join(',')}`;
}
/**
* 验证管理员密码
*/
validateAdminPassword(password) {
return password === this.adminPassword;
}
}
/**
* 配置管理类
*/
class Config {
constructor() {
this.initializeApiKeys();
// 已使用的API Key池
this.usedApiKeys = [];
// 失效的API Key池
this.invalidApiKeys = [];
// Gemini安全设置
this.geminiSafety = [
{
category: 'HARM_CATEGORY_HARASSMENT',
threshold: 'OFF',
},
{
category: 'HARM_CATEGORY_HATE_SPEECH',
threshold: 'OFF',
},
{
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
threshold: 'OFF',
},
{
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
threshold: 'OFF',
},
{
category: 'HARM_CATEGORY_CIVIC_INTEGRITY',
threshold: 'OFF',
},
];
}
/**
* 初始化API Keys
*/
initializeApiKeys() {
const apiKeysEnv = process.env.GEMINI_API_KEYS;
if (!apiKeysEnv) {
console.error('❌ 错误: 未找到 GEMINI_API_KEYS 环境变量');
process.exit(1);
}
// 通过换行符分割API Keys
this.apiKeys = apiKeysEnv
.split('\n')
.map(key => key.trim())
.filter(key => key.length > 0);
if (this.apiKeys.length === 0) {
console.error('❌ 错误: 没有找到有效的API Keys');
process.exit(1);
}
console.log(`✅ 成功加载 ${this.apiKeys.length} 个API Keys`);
}
/**
* 获取可用的API Key(负载均衡)
*/
getApiKey() {
if (this.apiKeys.length === 0) {
if (this.usedApiKeys.length > 0) {
this.apiKeys.push(...this.usedApiKeys);
this.usedApiKeys = [];
} else {
return null;
}
}
const apiKey = this.apiKeys.shift();
this.usedApiKeys.push(apiKey);
return apiKey;
}
/**
* 获取第一个可用的API Key(用于模型列表请求)
*/
getFirstAvailableApiKey() {
if (this.apiKeys.length > 0) {
return this.apiKeys[0];
}
if (this.usedApiKeys.length > 0) {
return this.usedApiKeys[0];
}
return null;
}
/**
* 将API Key标记为失效
*/
markKeyAsInvalid(apiKey) {
const usedIndex = this.usedApiKeys.indexOf(apiKey);
if (usedIndex !== -1) {
this.usedApiKeys.splice(usedIndex, 1);
}
const mainIndex = this.apiKeys.indexOf(apiKey);
if (mainIndex !== -1) {
this.apiKeys.splice(mainIndex, 1);
}
if (!this.invalidApiKeys.includes(apiKey)) {
this.invalidApiKeys.push(apiKey);
}
console.warn(`⚠️ API Key 已标记为失效: ${apiKey.substring(0, 10)}...`);
}
/**
* 将API Key移回已使用池
*/
moveToUsed(apiKey) {
if (!this.usedApiKeys.includes(apiKey)) {
this.usedApiKeys.push(apiKey);
}
}
/**
* 获取可用API Key数量
*/
getAvailableKeysCount() {
return this.apiKeys.length + this.usedApiKeys.length;
}
}
/**
* 图片处理器类
*/
class ImageProcessor {
/**
* 从data URL中提取MIME类型和base64数据
*/
static parseDataUrl(dataUrl) {
try {
// 匹配data:image/jpeg;base64,<base64data>格式
const match = dataUrl.match(/^data:([^;]+);base64,(.+)$/);
if (!match) {
throw new Error('无效的data URL格式');
}
const mimeType = match[1];
const base64Data = match[2];
// 验证MIME类型是否为支持的图片格式
const supportedMimeTypes = [
'image/jpeg',
'image/jpg',
'image/png',
'image/gif',
'image/webp',
'image/bmp',
'image/tiff'
];
if (!supportedMimeTypes.includes(mimeType.toLowerCase())) {
throw new Error(`不支持的图片格式: ${mimeType}`);
}
return {
mimeType,
data: base64Data
};
} catch (error) {
console.error('解析图片data URL错误:', error);
throw error;
}
}
/**
* 验证base64数据是否有效
*/
static validateBase64(base64String) {
try {
// 基本格式检查
if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64String)) {
return false;
}
// 长度检查(base64编码长度应该是4的倍数)
return base64String.length % 4 === 0;
} catch (error) {
return false;
}
}
/**
* 从URL下载图片并转换为base64
*/
static async fetchImageAsBase64(imageUrl) {
try {
const response = await fetch(imageUrl, {
timeout: 30000, // 30秒超时
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
});
if (!response.ok) {
throw new Error(`获取图片失败: HTTP ${response.status}`);
}
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.startsWith('image/')) {
throw new Error(`URL返回的不是图片类型: ${contentType}`);
}
const buffer = await response.buffer();
const base64Data = buffer.toString('base64');
return {
mimeType: contentType,
data: base64Data
};
} catch (error) {
console.error('下载图片错误:', error);
throw error;
}
}
}
/**
* 消息转换器类(增强版)
*/
class MessageConverter {
/**
* 将OpenAI格式的消息转换为Gemini格式(支持图片)
*/
static async convertMessages(openaiMessages) {
const geminiMessages = [];
let currentRole = null;
let currentParts = [];
for (const message of openaiMessages) {
let role = message.role;
let content = message.content;
// 角色转换
if(role == 'system'){
role = 'user';
}
if (role === 'assistant') {
role = 'model';
}
// 处理内容
let parts = [];
if (typeof content === 'string') {
// 简单文本消息
parts = [{ text: content }];
} else if (Array.isArray(content)) {
// 多模态消息(包含文本和图片)
parts = await this.convertContentArray(content);
} else {
// 其他格式,转为文本
parts = [{ text: String(content) }];
}
// 合并相同角色的连续消息
if (role === currentRole) {
currentParts.push(...parts);
} else {
// 保存上一个角色的消息
if (currentRole !== null && currentParts.length > 0) {
geminiMessages.push({
role: currentRole,
parts: currentParts
});
}
// 开始新角色
currentRole = role;
currentParts = [...parts];
}
}
// 添加最后一个消息
if (currentRole !== null && currentParts.length > 0) {
geminiMessages.push({
role: currentRole,
parts: currentParts
});
}
return geminiMessages;
}
/**
* 转换OpenAI的content数组为Gemini的parts格式
*/
static async convertContentArray(contentArray) {
const parts = [];
for (const item of contentArray) {
try {
if (item.type === 'text') {
// 文本内容
parts.push({ text: item.text || '' });
} else if (item.type === 'image_url') {
// 图片内容
const imagePart = await this.convertImageContent(item);
if (imagePart) {
parts.push(imagePart);
}
} else {
// 其他类型,尝试转为文本
console.warn(`未知的内容类型: ${item.type},将转为文本处理`);
parts.push({ text: JSON.stringify(item) });
}
} catch (error) {
console.error('转换内容项错误:', error);
// 出错时跳过该项,避免整个请求失败
continue;
}
}
return parts;
}
/**
* 转换图片内容为Gemini格式
*/
static async convertImageContent(imageItem) {
try {
const imageUrl = imageItem.image_url?.url;
if (!imageUrl) {
throw new Error('缺少图片URL');
}
let imageData;
if (imageUrl.startsWith('data:')) {
// 处理base64数据URL
imageData = ImageProcessor.parseDataUrl(imageUrl);
// 验证base64数据
if (!ImageProcessor.validateBase64(imageData.data)) {
throw new Error('无效的base64图片数据');
}
} else if (imageUrl.startsWith('http://') || imageUrl.startsWith('https://')) {
// 处理网络图片URL
imageData = await ImageProcessor.fetchImageAsBase64(imageUrl);
} else {
throw new Error(`不支持的图片URL格式: ${imageUrl}`);
}
// 返回Gemini格式的图片数据
return {
inlineData: {
mimeType: imageData.mimeType,
data: imageData.data
}
};
} catch (error) {
console.error('转换图片内容错误:', error);
// 返回错误信息文本,而不是抛出异常
return { text: `[图片处理失败: ${error.message}]` };
}
}
/**
* 从OpenAI请求中提取参数
*/
static extractParams(openaiRequest) {
return {
model: openaiRequest.model || 'gemini-2.5-flash',
messages: openaiRequest.messages || [],
stream: openaiRequest.stream || false,
temperature: openaiRequest.temperature,
maxTokens: openaiRequest.max_tokens,
topP: openaiRequest.top_p
};
}
}
/**
* 模型管理类
*/
class ModelManager {
constructor(config) {
this.config = config;
this.cachedModels = null;
this.cacheExpiry = null;
this.cacheTimeout = 5 * 60 * 1000; // 5分钟缓存
}
/**
* 获取模型列表
*/
async getModels() {
if (this.cachedModels && this.cacheExpiry && Date.now() < this.cacheExpiry) {
return { success: true, data: this.cachedModels };
}
const apiKey = this.config.getFirstAvailableApiKey();
if (!apiKey) {
return {
success: false,
error: '没有可用的API Key',
status: 503
};
}
try {
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
if (!response.ok) {
return {
success: false,
error: `获取模型列表失败: ${response.status}`,
status: response.status
};
}
const geminiResponse = await response.json();
const filteredModels = this.filterModels(geminiResponse.models || []);
this.cachedModels = filteredModels;
this.cacheExpiry = Date.now() + this.cacheTimeout;
return { success: true, data: filteredModels };
} catch (error) {
console.error('获取模型列表错误:', error);
return {
success: false,
error: '网络请求失败',
status: 500
};
}
}
/**
* 过滤模型 - 保持原始Gemini模型名,并增加视觉模型支持
*/
filterModels(models) {
const allowedPrefixes = [
'models/gemini-2.5-flash',
'models/gemini-2.0-flash',
'models/gemini-1.5-flash'
];
const excludedModels = [
'models/gemini-1.5-flash-8b'
];
const filteredModels = models.filter(model => {
const modelName = model.name;
if (excludedModels.some(excluded => modelName.startsWith(excluded))) {
return false;
}
if (modelName == "models/gemini-2.5-pro") {
return true;
}
return allowedPrefixes.some(prefix => modelName.startsWith(prefix));
});
// 转换为OpenAI格式但保持Gemini模型名
const processedModels = filteredModels.map(model => {
const modelId = model.name.replace('models/', '');
return {
id: modelId, // 直接使用Gemini模型名
object: 'model',
created: Math.floor(Date.now() / 1000),
owned_by: 'google',
permission: [
{
id: `modelperm-${modelId}`,
object: 'model_permission',
created: Math.floor(Date.now() / 1000),
allow_create_engine: false,
allow_sampling: true,
allow_logprobs: false,
allow_search_indices: false,
allow_view: true,
allow_fine_tuning: false,
organization: '*',
group: null,
is_blocking: false
}
],
root: modelId,
parent: null
};
});
return {
object: 'list',
data: processedModels
};
}
}
/**
* Gemini API 请求构建器类
*/
class GeminiRequestBuilder {
constructor(config) {
this.config = config;
}
/**
* 构建Gemini API请求体
*/
buildRequestBody(geminiMessages, params) {
const requestBody = {
contents: geminiMessages,
safetySettings: this.config.geminiSafety,
generationConfig: {}
};
if (params.temperature !== undefined) {
requestBody.generationConfig.temperature = params.temperature;
}
if (params.maxTokens !== undefined) {
requestBody.generationConfig.maxOutputTokens = params.maxTokens;
}
if (params.topP !== undefined) {
requestBody.generationConfig.topP = params.topP;
}
if (params.model == "gemini-2.5-pro") {
requestBody.generationConfig.thinkingConfig = {
thinkingBudget: 128
};
}
return requestBody;
}
/**
* 构建Gemini API URL
*/
buildApiUrl(model, apiKey, isStream = false) {
const method = isStream ? 'streamGenerateContent' : 'generateContent';
return `https://generativelanguage.googleapis.com/v1beta/models/${model}:${method}?key=${apiKey}`;
}
}
/**
* 响应转换器类
*/
class ResponseConverter {
/**
* 将Gemini流式响应块转换为OpenAI格式
*/
static convertStreamChunk(geminiData, requestId, model) {
try {
if (geminiData.candidates && geminiData.candidates[0]) {
const candidate = geminiData.candidates[0];
if (candidate.content && candidate.content.parts) {
const text = candidate.content.parts[0]?.text || '';
const openaiChunk = {
id: requestId,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: model, // 使用当前请求的模型名
choices: [{
index: 0,
delta: { content: text },
finish_reason: candidate.finishReason === 'STOP' ? 'stop' : null
}]
};
return `data: ${JSON.stringify(openaiChunk)}\n\n`;
}
}
return '';
} catch (error) {
console.error('转换流响应块错误:', error);
return '';
}
}
/**
* 将Gemini非流式响应转换为OpenAI格式
*/
static convertNormalResponse(geminiResponse, requestId, model) {
const openaiResponse = {
id: requestId,
object: 'chat.completion',
created: Math.floor(Date.now() / 1000),
model: model, // 使用当前请求的模型名
choices: [],
usage: {
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0
}
};
if (geminiResponse.candidates && geminiResponse.candidates[0]) {
const candidate = geminiResponse.candidates[0];
if (candidate.content && candidate.content.parts) {
const text = candidate.content.parts.map(part => part.text).join('');
openaiResponse.choices.push({
index: 0,
message: {
role: 'assistant',
content: text
},
finish_reason: candidate.finishReason === 'STOP' ? 'stop' : 'length'
});
}
}
// 尝试从usage信息中获取token使用量
if (geminiResponse.usageMetadata) {
openaiResponse.usage = {
prompt_tokens: geminiResponse.usageMetadata.promptTokenCount || 0,
completion_tokens: geminiResponse.usageMetadata.candidatesTokenCount || 0,
total_tokens: geminiResponse.usageMetadata.totalTokenCount || 0
};
}
return openaiResponse;
}
/**
* 检查响应是否包含有效文本
*/
static hasValidTextContent(geminiResponse) {
if (!geminiResponse.candidates || !geminiResponse.candidates[0]) {
return false;
}
const candidate = geminiResponse.candidates[0];
if (!candidate.content || !candidate.content.parts) {
return false;
}
const text = candidate.content.parts
.map(part => part.text || '')
.join('')
.trim();
return text.length > 0;
}
/**
* 将文本拆分为假流式块
*/
static splitTextToFakeStream(text, requestId, model) {
const chunks = [];
const chunkSize = 3; // 每个块包含的字符数
for (let i = 0; i < text.length; i += chunkSize) {
const chunk = text.slice(i, i + chunkSize);
const isLast = i + chunkSize >= text.length;
const openaiChunk = {
id: requestId,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: model,
choices: [{
index: 0,
delta: { content: chunk },
finish_reason: isLast ? 'stop' : null
}]
};
chunks.push(`data: ${JSON.stringify(openaiChunk)}\n\n`);
}
return chunks;
}
}
/**
* Gemini实时流式响应解析器
*/
class GeminiRealtimeStreamParser {
constructor(response, onChunk) {
this.response = response;
this.onChunk = onChunk;
this.buffer = '';
this.bufferLv = 0;
this.inString = false;
this.escapeNext = false;
this.decoder = new TextDecoder();
}
async start() {
try {
for await (const chunk of this.response.body) {
const text = this.decoder.decode(chunk, { stream: true });
await this.processText(text);
}
await this.handleRemainingBuffer();
} catch (error) {
console.error('流式解析错误:', error);
throw error;
}
}
async processText(text) {
for (const char of text) {
if (this.escapeNext) {
if (this.bufferLv > 1) {
this.buffer += char;
}
this.escapeNext = false;
continue;
}
if (char === '\\' && this.inString) {
this.escapeNext = true;
if (this.bufferLv > 1) {
this.buffer += char;
}
continue;
}
if (char === '"') {
this.inString = !this.inString;
}
if (!this.inString) {
if (char === '{' || char === '[') {
this.bufferLv++;
} else if (char === '}' || char === ']') {
this.bufferLv--;
}
}
if (this.bufferLv > 1) {
if (this.inString && char === '\n') {
this.buffer += '\\n';
} else {
this.buffer += char;
}
} else if (this.bufferLv === 1 && this.buffer) {
this.buffer += '}';
try {
const bufferJson = JSON.parse(this.buffer);
await this.onChunk(bufferJson);
} catch (parseError) {
console.error('解析Gemini流数据错误:', parseError);
}
this.buffer = '';
}
}
}
async handleRemainingBuffer() {
if (this.buffer.trim() && this.bufferLv >= 1) {
try {
if (!this.buffer.endsWith('}')) {
this.buffer += '}';
}
const bufferJson = JSON.parse(this.buffer);
await this.onChunk(bufferJson);
} catch (parseError) {
console.error('解析最后的缓冲区数据错误:', parseError);
}
}
}
}
/**
* 认证中间件
*/
class AuthMiddleware {
constructor(authKeyManager, logManager) {
this.authKeyManager = authKeyManager;
this.logManager = logManager;
}
middleware() {
return (req, res, next) => {
// 跳过健康检查、预检请求和管理页面
if (req.path === '/health' ||
req.method === 'OPTIONS' ||
req.path.startsWith('/admin') ||
req.path === '/logs.html') {
return next();
}
const authHeader = req.headers.authorization;
const authResult = this.authKeyManager.validateAuth(authHeader);
if (!authResult.valid) {
return res.status(401).json({
error: {
message: 'Invalid authentication credentials',
type: 'invalid_request_error',
code: 'invalid_api_key'
}
});
}
// 将认证信息添加到请求对象
req.authKey = authResult.key;
req.authAlias = authResult.alias;
next();
};
}
}
/**
* API代理服务类
*/
class ApiProxyService {
constructor() {
this.config = new Config();
this.requestBuilder = new GeminiRequestBuilder(this.config);
this.modelManager = new ModelManager(this.config);
this.logManager = new LogManager();
this.authKeyManager = new AuthKeyManager();
this.authMiddleware = new AuthMiddleware(this.authKeyManager, this.logManager);
}
/**
* 记录请求日志
*/
logRequest(authKey, requestData) {
this.logManager.logRequest(authKey, requestData);
}
/**
* 处理聊天API请求(支持图片)
*/
async handleChatRequest(req, res) {
const requestTime = new Date().toISOString();
const startTime = Date.now();
let logData = {
requestTime,
requestType: 'normal',
model: '',
responseContent: '',
status: 'success',
error: null
};
try {
const requestId = `chatcmpl-${uuidv4()}`;
const params = MessageConverter.extractParams(req.body);
logData.model = params.model;
logData.requestType = params.stream ? 'stream' : 'normal';
// 异步转换消息(支持图片处理)
const geminiMessages = await MessageConverter.convertMessages(params.messages);
if (!geminiMessages || geminiMessages.length === 0) {
logData.status = 'error';
logData.error = '无效的消息格式或消息为空';
this.logRequest(req.authKey, {
...logData,
responseTime: new Date().toISOString(),
duration: Date.now() - startTime
});
return res.status(400).json({
error: {
message: '无效的消息格式或消息为空',
type: 'invalid_request_error',
code: 'invalid_messages'
}
});
}
const requestBody = this.requestBuilder.buildRequestBody(geminiMessages, params);
if (params.stream) {
const result = await this.handleStreamRequest(requestBody, params, requestId, res, req.authKey, logData, startTime);
if (!result.success) {
res.status(result.status || 500).json({ error: result.error });
}
} else {
const result = await this.executeNormalRequest(requestBody, params, requestId);
logData.responseTime = new Date().toISOString();
logData.duration = Date.now() - startTime;
if (result.success) {
logData.responseContent = result.data.choices[0]?.message?.content || '';
this.logRequest(req.authKey, logData);
res.json(result.data);
} else {
logData.status = 'error';
logData.error = result.error;
this.logRequest(req.authKey, logData);
res.status(result.status || 500).json({ error: result.error });
}
}
} catch (error) {
console.error('处理聊天请求错误:', error);
logData.status = 'error';
logData.error = error.message;
logData.responseTime = new Date().toISOString();
logData.duration = Date.now() - startTime;
this.logRequest(req.authKey, logData);
res.status(500).json({
error: {
message: '内部服务器错误: ' + error.message,
type: 'internal_server_error',
code: 'server_error'
}
});
}
}
/**
* 处理假流式聊天API请求
*/
async handleFakeStreamChatRequest(req, res) {
const requestTime = new Date().toISOString();
const startTime = Date.now();
let logData = {
requestTime,
requestType: 'fake-stream',
model: '',
responseContent: '',
status: 'success',
error: null
};
try {
const requestId = `chatcmpl-${uuidv4()}`;
const params = MessageConverter.extractParams(req.body);
logData.model = params.model;
// 异步转换消息(支持图片处理)
const geminiMessages = await MessageConverter.convertMessages(params.messages);
if (!geminiMessages || geminiMessages.length === 0) {
logData.status = 'error';
logData.error = '无效的消息格式或消息为空';
this.logRequest(req.authKey, {
...logData,
responseTime: new Date().toISOString(),
duration: Date.now() - startTime
});
return res.status(400).json({
error: {
message: '无效的消息格式或消息为空',
type: 'invalid_request_error',
code: 'invalid_messages'
}
});
}
const requestBody = this.requestBuilder.buildRequestBody(geminiMessages, params);
if (params.stream) {
// 假流式处理:使用非流式请求,然后模拟流式响应
await this.handleFakeStreamRequest(requestBody, params, requestId, res, req.authKey, logData, startTime);
} else {
// 非流式请求和原来一样
const result = await this.executeNormalRequest(requestBody, params, requestId);
logData.responseTime = new Date().toISOString();
logData.duration = Date.now() - startTime;
if (result.success) {
logData.responseContent = result.data.choices[0]?.message?.content || '';
this.logRequest(req.authKey, logData);
res.json(result.data);
} else {
logData.status = 'error';
logData.error = result.error;
this.logRequest(req.authKey, logData);
res.status(result.status || 500).json({ error: result.error });
}
}
} catch (error) {
console.error('处理假流式聊天请求错误:', error);
logData.status = 'error';
logData.error = error.message;
logData.responseTime = new Date().toISOString();
logData.duration = Date.now() - startTime;
this.logRequest(req.authKey, logData);
// 修复:确保在错误情况下正确响应
if (!res.headersSent) {
res.status(500).json({
error: {
message: '内部服务器错误: ' + error.message,
type: 'internal_server_error',
code: 'server_error'
}
});
}
}
}
/**
* 处理假流式请求(修复版)
*/
async handleFakeStreamRequest(requestBody, params, requestId, res, authKey, logData, startTime) {
let responseStarted = false;
let pingInterval = null;
try {
// 执行非流式请求
const result = await this.executeNormalRequest(requestBody, params, requestId);
logData.responseTime = new Date().toISOString();
logData.duration = Date.now() - startTime;
if (!result.success) {
logData.status = 'error';
logData.error = result.error;
this.logRequest(authKey, logData);
// 发送错误响应为流式格式
if (!res.headersSent) {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
});
responseStarted = true;
}
if (responseStarted) {
const errorChunk = {
id: requestId,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: params.model,
choices: [{
index: 0,
delta: { content: `错误: ${result.error}` },
finish_reason: 'stop'
}]
};
res.write(`data: ${JSON.stringify(errorChunk)}\n\n`);
res.write('data: [DONE]\n\n');
res.end();
}
return;
}
// 获取响应文本
const responseText = result.data.choices[0]?.message?.content || '';
logData.responseContent = responseText;
this.logRequest(authKey, logData);
// 设置流式响应头
if (!res.headersSent) {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
});
responseStarted = true;
// 开始发送ping消息保持连接活跃
pingInterval = setInterval(() => {
try {
if (!res.destroyed) {
res.write(': ping\n\n');
}
} catch (error) {
clearInterval(pingInterval);
}
}, 1000);
}
if (responseText && responseStarted) {
// 将文本拆分为假流式块
const chunks = ResponseConverter.splitTextToFakeStream(responseText, requestId, params.model);
// 逐步发送块,模拟流式响应
for (const chunk of chunks) {
if (res.destroyed) break;
res.write(chunk);
// 添加小延迟以模拟真实的流式响应
await new Promise(resolve => setTimeout(resolve, 50));
}
}
// 停止ping并结束响应
if (pingInterval) {
clearInterval(pingInterval);
}
if (responseStarted && !res.destroyed) {
res.write('data: [DONE]\n\n');
res.end();
}
} catch (error) {
console.error('处理假流式请求错误:', error);
logData.status = 'error';
logData.error = error.message;
logData.responseTime = new Date().toISOString();
logData.duration = Date.now() - startTime;
this.logRequest(authKey, logData);
// 停止ping
if (pingInterval) {
clearInterval(pingInterval);
}
// 如果还没有开始响应,设置流式响应头
if (!res.headersSent && !responseStarted) {
try {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
});
responseStarted = true;
} catch (headerError) {
console.error('设置响应头失败:', headerError);
return;
}
}
// 发送错误信息
if (responseStarted && !res.destroyed) {
try {
const errorChunk = {
id: requestId,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: params.model,
choices: [{
index: 0,
delta: { content: `错误: ${error.message}` },
finish_reason: 'stop'
}]
};
res.write(`data: ${JSON.stringify(errorChunk)}\n\n`);
res.write('data: [DONE]\n\n');
res.end();
} catch (writeError) {
console.error('写入错误响应失败:', writeError);
}
}
}
}
/**
* 处理流式请求(优化后的重试逻辑)
*/
async handleStreamRequest(requestBody, params, requestId, res, authKey, logData, startTime, retryCount = 0) {
const maxRetries = 3;
let apiKey = null;
let response = null;
// 在429错误或内容被阻止时尝试使用不同的API Key
for (let keyAttempt = 0; keyAttempt < maxRetries; keyAttempt++) {
console.log(`尝试请求 (${keyAttempt + 1}/${maxRetries})`);
apiKey = this.config.getApiKey();
if (!apiKey) {
if (this.config.getAvailableKeysCount() === 0) {
logData.status = 'error';
logData.error = '目前暂无可用的API Key';
logData.responseTime = new Date().toISOString();
logData.duration = Date.now() - startTime;
this.logRequest(authKey, logData);
return { success: false, error: '目前暂无可用的API Key', status: 503 };
}
continue;
}
console.log(`使用API Key: ${apiKey?.substring(0, 10)}...`);
try {
const apiUrl = this.requestBuilder.buildApiUrl(params.model, apiKey, true);
response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestBody)
});
if (response.status === 403) {
this.config.markKeyAsInvalid(apiKey);
console.log(`API Key失效,尝试下一个 (${keyAttempt + 1}/${maxRetries})`);
continue;
}
if (response.status === 400) {
this.config.markKeyAsInvalid(apiKey);
console.log(`API Key过期,尝试下一个 (${keyAttempt + 1}/${maxRetries})`);
continue;
}
if (response.status === 429) {
this.config.moveToUsed(apiKey);
console.log(`请求频率限制,切换API Key (${keyAttempt + 1}/${maxRetries})`);
continue;
}
if (response.status === 500) {
this.config.moveToUsed(apiKey);
logData.status = 'error';
logData.error = '目前服务器繁忙,请稍后重试';
logData.responseTime = new Date().toISOString();
logData.duration = Date.now() - startTime;
this.logRequest(authKey, logData);
return { success: false, error: '目前服务器繁忙,请稍后重试', status: 500 };
}
if (!response.ok) {
const errorText = await response.text();
console.error(`API请求失败: ${response.status}, 错误信息: ${errorText}`);
logData.status = 'error';
logData.error = `API请求失败: ${response.status}`;
logData.responseTime = new Date().toISOString();
logData.duration = Date.now() - startTime;
this.logRequest(authKey, logData);
return { success: false, error: `API请求失败: ${response.status}`, status: response.status };
}
// 创建一个新的解析器来预检第一个chunk
let firstChunkData = null;
let isProhibitedContent = false;
let responseStarted = false;
let responseContent = '';
const parser = new GeminiRealtimeStreamParser(response, async (geminiData) => {
// 如果是第一个chunk,检查是否被阻止
if (firstChunkData === null) {
firstChunkData = geminiData;
// 检查是否因为内容被禁止而阻止
if (geminiData.promptFeedback &&
geminiData.promptFeedback.blockReason === 'PROHIBITED_CONTENT') {
isProhibitedContent = true;
console.log(`内容被阻止 (PROHIBITED_CONTENT),切换API Key重试 (${keyAttempt + 1}/${maxRetries})`);
return; // 不处理这个chunk
}
// 第一个chunk正常,开始发送响应头
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
});
responseStarted = true;
// 发送第一个chunk的内容
const convertedChunk = ResponseConverter.convertStreamChunk(geminiData, requestId, params.model);
if (convertedChunk) {
res.write(convertedChunk);
// 收集响应内容用于日志
if (geminiData.candidates && geminiData.candidates[0] &&
geminiData.candidates[0].content && geminiData.candidates[0].content.parts) {
responseContent += geminiData.candidates[0].content.parts[0]?.text || '';
}
}
} else {
// 后续的chunks,如果响应已开始,直接发送
if (responseStarted && !isProhibitedContent) {
const convertedChunk = ResponseConverter.convertStreamChunk(geminiData, requestId, params.model);
if (convertedChunk) {
res.write(convertedChunk);
// 收集响应内容用于日志
if (geminiData.candidates && geminiData.candidates[0] &&
geminiData.candidates[0].content && geminiData.candidates[0].content.parts) {
responseContent += geminiData.candidates[0].content.parts[0]?.text || '';
}
}
}
}
});
await parser.start();
// 如果内容被阻止,尝试下一个API Key
if (isProhibitedContent) {
this.config.moveToUsed(apiKey);
if (keyAttempt < maxRetries - 1) {
continue;
}
logData.status = 'error';
logData.error = '内容被阻止';
logData.responseTime = new Date().toISOString();
logData.duration = Date.now() - startTime;
this.logRequest(authKey, logData);
return { success: false, error: '内容被阻止', status: 503 };
}
// 如果响应已开始,发送结束标记并记录日志
if (responseStarted) {
res.write('data: [DONE]\n\n');
res.end();
logData.responseContent = responseContent;
logData.responseTime = new Date().toISOString();
logData.duration = Date.now() - startTime;
this.logRequest(authKey, logData);
return { success: true };
}
// 如果没有开始响应(异常情况),返回错误
logData.status = 'error';
logData.error = '未收到有效响应';
logData.responseTime = new Date().toISOString();
logData.duration = Date.now() - startTime;
this.logRequest(authKey, logData);
return { success: false, error: '未收到有效响应', status: 204 };
} catch (error) {
console.error(`执行流式请求错误 (尝试 ${keyAttempt + 1}/${maxRetries}):`, error);
this.config.moveToUsed(apiKey);
if (keyAttempt < maxRetries - 1) {
continue;
}
logData.status = 'error';
logData.error = '网络请求失败: ' + error.message;
logData.responseTime = new Date().toISOString();
logData.duration = Date.now() - startTime;
this.logRequest(authKey, logData);
return { success: false, error: '网络请求失败: ' + error.message, status: 500 };
}
}
logData.status = 'error';
logData.error = '所有重试均失败';
logData.responseTime = new Date().toISOString();
logData.duration = Date.now() - startTime;
this.logRequest(authKey, logData);
return { success: false, error: '所有重试均失败', status: 500 };
}
/**
* 处理非流式请求(优化后的重试逻辑)
*/
async executeNormalRequest(requestBody, params, requestId, retryCount = 0) {
const maxRetries = 3;
let apiKey = null;
let response = null;
let geminiResponse = null;
// 修改循环条件,使其能够重试
for (let keyAttempt = 0; keyAttempt < maxRetries; keyAttempt++) {
console.log(`尝试请求 (${keyAttempt + 1}/${maxRetries})`);
apiKey = this.config.getApiKey();
if (!apiKey) {
if (this.config.getAvailableKeysCount() === 0) {
return { success: false, error: '目前暂无可用的API Key', status: 503 };
}
continue;
}
console.log(`使用API Key: ${apiKey?.substring(0, 10)}...`);
try {
const apiUrl = this.requestBuilder.buildApiUrl(params.model, apiKey, false);
response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestBody)
});
if (response.status === 403) {
this.config.markKeyAsInvalid(apiKey);
console.log(`API Key失效,尝试下一个 (${keyAttempt + 1}/${maxRetries})`);
continue;
}
if (response.status === 400) {
this.config.markKeyAsInvalid(apiKey);
console.log(`API Key过期,尝试下一个 (${keyAttempt + 1}/${maxRetries})`);
continue;
}
if (response.status === 429) {
this.config.moveToUsed(apiKey);
console.log(`请求频率限制,切换API Key (${keyAttempt + 1}/${maxRetries})`);
continue;
}
if (response.status === 500) {
this.config.moveToUsed(apiKey);
return { success: false, error: '目前服务器繁忙,请稍后重试', status: 500 };
}
if (!response.ok) {
const errorText = await response.text();
console.error(`API请求失败: ${response.status}, 错误信息: ${errorText}`);
return { success: false, error: `API请求失败: ${response.status}`, status: response.status };
}
geminiResponse = await response.json();
// 检查是否因为内容被禁止而阻止
if (geminiResponse.candidates &&
geminiResponse.candidates.finishReason === 'PROHIBITED_CONTENT') {
console.log(`内容被阻止 (PROHIBITED_CONTENT),切换API Key重试 (${keyAttempt + 1}/${maxRetries})`);
this.config.moveToUsed(apiKey);
if (keyAttempt < maxRetries - 1) {
continue;
}
return { success: false, error: '内容被阻止', status: 503 };
}
// 检查响应是否包含有效文本
if (!ResponseConverter.hasValidTextContent(geminiResponse)) {
console.log(`响应无文本内容,切换API Key重试 (${keyAttempt + 1}/${maxRetries})`);
this.config.moveToUsed(apiKey);
if (keyAttempt < maxRetries - 1) {
continue;
}
return { success: false, error: '响应无文本内容', status: 204 };
}
const openaiResponse = ResponseConverter.convertNormalResponse(geminiResponse, requestId, params.model);
return { success: true, data: openaiResponse };
} catch (error) {
console.error(`执行非流式请求错误 (尝试 ${keyAttempt + 1}/${maxRetries}):`, error);
this.config.moveToUsed(apiKey);
if (keyAttempt < maxRetries - 1) {
continue;
}
return { success: false, error: '网络请求失败: ' + error.message, status: 500 };
}
}
return { success: false, error: '所有重试均失败', status: 500 };
}
/**
* 处理模型列表请求
*/
async handleModelsRequest(req, res) {
try {
const result = await this.modelManager.getModels();
if (result.success) {
res.json(result.data);
} else {
res.status(result.status || 500).json({ error: result.error });
}
} catch (error) {
console.error('处理模型列表请求错误:', error);
res.status(500).json({ error: '内部服务器错误' });
}
}
}
/**
* Express 服务器
*/
class Server {
constructor() {
this.app = express();
this.apiProxy = new ApiProxyService();
this.setupMiddleware();
this.setupRoutes();
}
setupMiddleware() {
// CORS配置
this.app.use(cors({
origin: '*',
credentials: true,
optionsSuccessStatus: 200
}));
// JSON解析 - 增加大小限制以支持图片
this.app.use(express.json({ limit: '50mb' }));
this.app.use(express.urlencoded({ limit: '50mb', extended: true }));
// 静态文件服务(用于管理页面)
this.app.use('/static', express.static(path.join(process.cwd(), 'public')));
// 认证中间件
this.app.use(this.apiProxy.authMiddleware.middleware());
// 请求日志中间件
this.app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.path} - ${res.statusCode} [${duration}ms]`);
});
next();
});
}
setupRoutes() {
// 原始聊天接口(支持图片)
this.app.post('/v1/chat/completions', (req, res) => {
this.apiProxy.handleChatRequest(req, res);
});
// 假流式聊天接口
this.app.post('/fakestream/v1/chat/completions', (req, res) => {
this.apiProxy.handleFakeStreamChatRequest(req, res);
});
// 原始模型列表接口
this.app.get('/v1/models', (req, res) => {
this.apiProxy.handleModelsRequest(req, res);
});
// 假流式模型列表接口(逻辑相同)
this.app.get('/fakestream/v1/models', (req, res) => {
this.apiProxy.handleModelsRequest(req, res);
});
// 管理员认证
this.app.post('/admin/auth', (req, res) => {
const { password } = req.body;
if (this.apiProxy.authKeyManager.validateAdminPassword(password)) {
const token = crypto.randomBytes(32).toString('hex');
res.json({ success: true, token });
} else {
res.status(401).json({ success: false, message: '密码错误' });
}
});
// 管理页面路由
this.app.get('/logs.html', (req, res) => {
res.sendFile(path.join(process.cwd(), 'logs.html'));
});
// API路由 - 获取日志
this.app.get('/admin/logs/:authKey', (req, res) => {
const { authKey } = req.params;
const page = parseInt(req.query.page) || 1;
const pageSize = parseInt(req.query.pageSize) || 50;
const result = this.apiProxy.logManager.getLogs(authKey, page, pageSize);
res.json(result);
});
// API路由 - 获取所有日志(用于前端显示)
this.app.get('/admin/all-logs', (req, res) => {
const allLogs = [];
const authKeys = this.apiProxy.authKeyManager.getAllKeys();
for (const keyInfo of authKeys) {
const keyLogs = this.apiProxy.logManager.getLogs(keyInfo.token, 1, 10000); // 获取大量日志
keyLogs.logs.forEach(log => {
allLogs.push({
...log,
authKey: keyInfo.token,
keyAlias: keyInfo.alias
});
});
}
// 按时间排序(最新的在前)
allLogs.sort((a, b) => new Date(b.timestamp || b.requestTime) - new Date(a.timestamp || a.requestTime));
res.json(allLogs);
});
// API路由 - 获取统计信息
this.app.get('/admin/statistics', (req, res) => {
const stats = this.apiProxy.logManager.getStatistics();
res.json(stats);
});
// API路由 - 获取所有认证Keys
this.app.get('/admin/keys', (req, res) => {
const keys = this.apiProxy.authKeyManager.getAllKeys();
res.json(keys);
});
// API路由 - 添加认证Key
this.app.post('/admin/keys', (req, res) => {
const { alias, token } = req.body;
const result = this.apiProxy.authKeyManager.addKey(alias, token);
res.json(result);
});
// API路由 - 更新Key别名
this.app.put('/admin/keys/:token/alias', (req, res) => {
const { token } = req.params;
const { alias } = req.body;
if (!alias || alias.trim() === '') {
return res.status(400).json({
success: false,
message: '别名不能为空'
});
}
const result = this.apiProxy.authKeyManager.updateKeyAlias(decodeURIComponent(token), alias.trim());
if (result.success) {
res.json(result);
} else {
res.status(400).json(result);
}
});
// API路由 - 更新Key状态
this.app.put('/admin/keys/:token/status', (req, res) => {
const { token } = req.params;
const { enabled } = req.body;
const result = this.apiProxy.authKeyManager.updateKeyStatus(decodeURIComponent(token), enabled);
res.json(result);
});
// API路由 - 删除Key
this.app.delete('/admin/keys/:token', (req, res) => {
const { token } = req.params;
const result = this.apiProxy.authKeyManager.deleteKey(decodeURIComponent(token));
res.json(result);
});
// API路由 - 导出环境变量
this.app.get('/admin/export', (req, res) => {
const envString = this.apiProxy.authKeyManager.exportToEnv();
res.json({ envString });
});
// API路由 - 清除日志
this.app.delete('/admin/logs/:authKey', (req, res) => {
const { authKey } = req.params;
this.apiProxy.logManager.clearLogs(authKey);
res.json({ success: true, message: '日志已清除' });
});
// API路由 - 下载日志文件
this.app.get('/admin/download-logs', (req, res) => {
const format = req.query.format || 'json';
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
try {
if (format === 'json') {
const jsonData = this.apiProxy.logManager.exportLogsAsJson();
res.setHeader('Content-Disposition', `attachment; filename=api-logs-${timestamp}.json`);
res.setHeader('Content-Type', 'application/json');
res.send(jsonData);
} else if (format === 'csv') {
const csvData = this.apiProxy.logManager.exportLogsAsCsv();
res.setHeader('Content-Disposition', `attachment; filename=api-logs-${timestamp}.csv`);
res.setHeader('Content-Type', 'text/csv');
res.send(csvData);
} else {
res.status(400).json({ error: '不支持的格式' });
}
} catch (error) {
console.error('导出日志失败:', error);
res.status(500).json({ error: '导出日志失败' });
}
});
// API路由 - 获取文件日志设置状态
this.app.get('/admin/file-logging-status', (req, res) => {
const enabled = this.apiProxy.logManager.getFileLoggingStatus();
res.json({ enabled });
});
// API路由 - 设置文件日志状态
this.app.post('/admin/file-logging', (req, res) => {
const { enabled } = req.body;
this.apiProxy.logManager.setFileLogging(enabled);
res.json({ success: true, message: `文件日志已${enabled ? '启用' : '禁用'}` });
});
// 健康检查接口
this.app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
availableKeys: this.apiProxy.config.apiKeys.length,
usedKeys: this.apiProxy.config.usedApiKeys.length,
invalidKeys: this.apiProxy.config.invalidApiKeys.length,
authKeys: this.apiProxy.authKeyManager.getAllKeys().length,
fileLoggingEnabled: this.apiProxy.logManager.getFileLoggingStatus(),
version: '2.3.1',
features: ['text', 'vision', 'stream', 'fake_stream', 'load_balancing', 'auto_retry', 'logging', 'multi_auth', 'file_export']
});
});
// 404处理
this.app.use('*', (req, res) => {
res.status(404).json({
error: {
message: 'Not Found',
type: 'invalid_request_error',
code: 'not_found'
}
});
});
// 全局错误处理
this.app.use((err, req, res, next) => {
console.error('服务器错误:', err);
res.status(500).json({
error: {
message: '内部服务器错误',
type: 'internal_server_error',
code: 'server_error'
}
});
});
}
start(port = 3000) {
this.app.listen(port, () => {
console.log(`🚀 OpenAI to Gemini Proxy Server (Enhanced) 启动在端口 ${port}`);
console.log(`📍 聊天API: http://localhost:${port}/v1/chat/completions`);
console.log(`📍 假流式聊天API: http://localhost:${port}/fakestream/v1/chat/completions`);
console.log(`📋 模型列表: http://localhost:${port}/v1/models`);
console.log(`📋 假流式模型列表: http://localhost:${port}/fakestream/v1/models`);
console.log(`🔍 健康检查: http://localhost:${port}/health`);
console.log(`📊 日志管理: http://localhost:${port}/logs.html`);
console.log(`📄 文件日志状态: ${process.env.ENABLE_FILE_LOGGING !== 'false' ? '启用' : '禁用'}`);
});
}
}
// 启动服务器
const server = new Server();
const port = process.env.PORT || 3000;
server.start(port);