Files
WebAI2API/src/server/api/index.js
T

121 lines
4.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @fileoverview API 路由总装配
* @description 统一挂载 /v1 和 /admin 路由
*/
import fs from 'fs';
import path from 'path';
import { createOpenAIRouter } from './openai/routes.js';
import { createAdminRouter } from './admin/routes.js';
import { createAuthMiddleware } from '../middlewares/auth.js';
// MIME 类型映射
const MIME_TYPES = {
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'application/javascript; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf'
};
// WebUI 静态文件目录
const WEBUI_DIR = path.join(process.cwd(), 'webui', 'dist');
/**
* 创建全局路由处理器
* @param {object} context - 路由上下文
* @param {boolean} [context.loginMode] - 登录模式(禁用 OpenAI API
* @returns {Function} 请求处理函数
*/
export function createGlobalRouter(context) {
const { authToken, config, queueManager, tempDir, loginMode } = context;
// 创建鉴权中间件
const checkAuth = createAuthMiddleware(authToken);
// 创建子路由处理器
const handleOpenAIRequest = loginMode ? null : createOpenAIRouter(context);
const handleAdminRequest = createAdminRouter({ config, queueManager, tempDir });
/**
* 主路由处理函数
*/
return async function handleRequest(req, res) {
const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
const pathname = parsedUrl.pathname;
// ==================== 静态文件服务 ====================
if (req.method === 'GET' && !pathname.startsWith('/v1') && !pathname.startsWith('/admin')) {
let filePath = pathname === '/' ? '/index.html' : pathname;
filePath = path.join(WEBUI_DIR, filePath);
// 安全检查
if (!filePath.startsWith(WEBUI_DIR)) {
res.writeHead(403);
res.end('Forbidden');
return;
}
// 检查文件是否存在
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
const ext = path.extname(filePath).toLowerCase();
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
const content = fs.readFileSync(filePath);
res.writeHead(200, { 'Content-Type': contentType });
res.end(content);
return;
}
// SPA 模式 fallback
const indexPath = path.join(WEBUI_DIR, 'index.html');
if (fs.existsSync(indexPath)) {
const content = fs.readFileSync(indexPath);
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(content);
return;
}
}
// ==================== 鉴权检查 ====================
if (!checkAuth(req, res)) {
return; // 鉴权失败,已发送错误响应
}
// ==================== API 路由分发 ====================
// Admin API (/admin)
if (pathname.startsWith('/admin')) {
const adminPath = pathname.slice(6); // 去除 /admin 前缀
await handleAdminRequest(req, res, adminPath);
return;
}
// OpenAI API (/v1)
if (pathname.startsWith('/v1')) {
// 登录模式下禁用 OpenAI API
if (!handleOpenAIRequest) {
res.writeHead(503, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
error: { message: '服务运行在登录模式,OpenAI API 不可用', type: 'service_unavailable' }
}));
return;
}
const v1Path = pathname.slice(3); // 去除 /v1 前缀
await handleOpenAIRequest(req, res, v1Path, parsedUrl);
return;
}
// 404
res.writeHead(404);
res.end();
};
}