lis3456/droid2api
0
1import 'dotenv/config';2import express from 'express';3import { loadConfig, isDevMode, getPort } from './config.js';4import { logInfo, logError } from './logger.js';5import router from './routes.js';6import { initializeAuth } from './auth.js';7import adminRouter from './api/admin-routes.js';8 9const app = express();10 11app.use(express.json({ limit: '50mb' }));12app.use(express.urlencoded({ extended: true, limit: '50mb' }));13 14// 老王:覆盖res.json方法,强制所有JSON响应使用UTF-8编码,不然中文显示成SB乱码15app.use((req, res, next) => {16 const originalJson = res.json.bind(res);17 res.json = function(data) {18 res.setHeader('Content-Type', 'application/json; charset=utf-8');19 return originalJson(data);20 };21 next();22});23// API访问控制中间件24function apiKeyAuth(req, res, next) {25 // 跳过管理API、根路径和静态文件26 const skipPaths = [27 '/admin', // 管理API28 '/', // 根路径29 '/index.html', // HTML文件30 '/style.css', // CSS文件31 '/app.js', // JS文件32 '/favicon.ico' // 网站图标33 ];34 35 // 检查是否是需要跳过的路径36 if (skipPaths.some(path => req.path === path || req.path.startsWith('/admin'))) {37 return next();38 }39 40 const clientApiKey = req.headers['x-api-key'] || req.headers['authorization'];41 const validApiKey = process.env.API_ACCESS_KEY;42 43 // 如果未配置访问密钥,跳过验证44 if (!validApiKey || validApiKey === 'your-secure-access-key-here') {45 return next();46 }47 48 // 验证密钥格式: Bearer xxx 或直接 xxx49 const cleanClientKey = clientApiKey?.replace('Bearer ', '').trim();50 const cleanValidKey = validApiKey.trim();51 52 if (!cleanClientKey || cleanClientKey !== cleanValidKey) {53 return res.status(401).json({54 error: 'Unauthorized',55 message: 'Invalid or missing API access key'56 });57 }58 59 next();60}61 // 应用API访问控制62app.use(apiKeyAuth);63 64app.use((req, res, next) => {65 res.header('Access-Control-Allow-Origin', '*');66 res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');67 res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key, X-Admin-Key, anthropic-version');68 69 if (req.method === 'OPTIONS') {70 return res.sendStatus(200);71 }72 next();73});74 75app.use(router);76 // 管理API路由77 app.use('/admin', adminRouter);78 79 // 静态文件服务 (前端管理界面)80 app.use(express.static('public'));81 82app.get('/', (req, res) => {83 res.json({84 name: 'droid2api',85 version: '1.0.0',86 description: 'OpenAI Compatible API Proxy',87 endpoints: [88 'GET /v1/models',89 'POST /v1/chat/completions',90 'POST /v1/responses',91 'POST /v1/messages'92 93 ]94 });95});96 97// 处理favicon.ico请求,避免404日志刷屏(这个SB浏览器总是自动请求)98app.get('/favicon.ico', (req, res) => {99 res.status(204).end(); // 204 No Content,不返回任何内容100});101 102// 404 处理 - 捕获所有未匹配的路由103app.use((req, res, next) => {104 const errorInfo = {105 timestamp: new Date().toISOString(),106 method: req.method,107 url: req.originalUrl || req.url,108 path: req.path,109 query: req.query,110 params: req.params,111 body: req.body,112 headers: {113 'content-type': req.headers['content-type'],114 'user-agent': req.headers['user-agent'],115 'origin': req.headers['origin'],116 'referer': req.headers['referer']117 },118 ip: req.ip || req.connection.remoteAddress119 };120 121 console.error('\n' + '='.repeat(80));122 console.error('❌ 非法请求地址');123 console.error('='.repeat(80));124 console.error(`时间: ${errorInfo.timestamp}`);125 console.error(`方法: ${errorInfo.method}`);126 console.error(`地址: ${errorInfo.url}`);127 console.error(`路径: ${errorInfo.path}`);128 129 if (Object.keys(errorInfo.query).length > 0) {130 console.error(`查询参数: ${JSON.stringify(errorInfo.query, null, 2)}`);131 }132 133 if (errorInfo.body && Object.keys(errorInfo.body).length > 0) {134 console.error(`请求体: ${JSON.stringify(errorInfo.body, null, 2)}`);135 }136 137 console.error(`客户端IP: ${errorInfo.ip}`);138 console.error(`User-Agent: ${errorInfo.headers['user-agent'] || 'N/A'}`);139 140 if (errorInfo.headers.referer) {141 console.error(`来源: ${errorInfo.headers.referer}`);142 }143 144 console.error('='.repeat(80) + '\n');145 146 logError('Invalid request path', errorInfo);147 148 res.status(404).json({149 error: 'Not Found',150 message: `路径 ${req.method} ${req.path} 不存在`,151 timestamp: errorInfo.timestamp,152 availableEndpoints: [153 'GET /v1/models',154 'POST /v1/chat/completions',155 'POST /v1/responses',156 'POST /v1/messages'157 ]158 });159});160 161// 错误处理中间件162app.use((err, req, res, next) => {163 logError('Unhandled error', err);164 res.status(500).json({165 error: 'Internal server error',166 message: isDevMode() ? err.message : undefined167 });168});169 170(async () => {171 try {172 loadConfig();173 logInfo('Configuration loaded successfully');174 logInfo(`Dev mode: ${isDevMode()}`);175 176 // Initialize auth system (load and setup API key if needed)177 // This won't throw error if no auth config is found - will use client auth178 await initializeAuth();179 180 const PORT = getPort();181 logInfo(`Starting server on port ${PORT}...`);182 183 const server = app.listen(PORT)184 .on('listening', () => {185 logInfo(`Server running on http://localhost:${PORT}`);186 logInfo('Available endpoints:');187 logInfo(' GET /v1/models');188 logInfo(' POST /v1/chat/completions');189 logInfo(' POST /v1/responses');190 logInfo(' POST /v1/messages');191 logInfo(' GET /admin/* (Key Pool Management)');192 })193 .on('error', (err) => {194 if (err.code === 'EADDRINUSE') {195 console.error(`\n${'='.repeat(80)}`);196 console.error(`ERROR: Port ${PORT} is already in use!`);197 console.error('');198 console.error('Please choose one of the following options:');199 console.error(` 1. Stop the process using port ${PORT}:`);200 console.error(` lsof -ti:${PORT} | xargs kill`);201 console.error('');202 console.error(' 2. Change the port in config.json:');203 console.error(' Edit config.json and modify the "port" field');204 console.error(`${'='.repeat(80)}\n`);205 process.exit(1);206 } else {207 logError('Failed to start server', err);208 process.exit(1);209 }210 });211 } catch (error) {212 logError('Failed to start server', error);213 process.exit(1);214 }215})();216 