- 添加 SLS 日志上报模块(sls-logger.js) - 支持批量上报(每10条或5秒间隔) - 环境变量缺失时静默降级 - 自动重试失败的日志 - 新增日志信息提取器(log-extractor.js) - 提取 Token 使用统计(input_tokens, output_tokens) - 提取用户标识信息(user_id, session_id, ip) - 提取请求参数(temperature, max_tokens, stream) - 提取消息摘要(message_count, role_distribution, tool_names) - 增强所有 API 端点的日志记录 - /v1/chat/completions - /v1/responses - /v1/messages - /v1/messages/count_tokens - 修复日志字段序列化问题 - 扁平化嵌套对象字段,避免 [object Object] - 数组字段转换为逗号分隔字符串 - 添加阿里云环境变量配置到 docker-compose.yml - ALIYUN_ACCESS_KEY_ID - ALIYUN_ACCESS_KEY_SECRET - ALIYUN_SLS_ENDPOINT - ALIYUN_SLS_PROJECT - ALIYUN_SLS_LOGSTORE - 修改认证配置为自动刷新 Token 机制 - 使用 DROID_REFRESH_KEY 替代固定的 FACTORY_API_KEY - 实现每6小时自动刷新(Token有效期8小时) - Token 持久化到 auth.json
160 lines
4.8 KiB
JavaScript
160 lines
4.8 KiB
JavaScript
import express from 'express';
|
|
import { loadConfig, isDevMode, getPort } from './config.js';
|
|
import { logInfo, logError } from './logger.js';
|
|
import router from './routes.js';
|
|
import { initializeAuth } from './auth.js';
|
|
import { initializeUserAgentUpdater } from './user-agent-updater.js';
|
|
import './sls-logger.js'; // 初始化阿里云日志服务
|
|
|
|
const app = express();
|
|
|
|
app.use(express.json({ limit: '50mb' }));
|
|
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
|
|
|
|
app.use((req, res, next) => {
|
|
res.header('Access-Control-Allow-Origin', '*');
|
|
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key, anthropic-version');
|
|
|
|
if (req.method === 'OPTIONS') {
|
|
return res.sendStatus(200);
|
|
}
|
|
next();
|
|
});
|
|
|
|
app.use(router);
|
|
|
|
app.get('/', (req, res) => {
|
|
res.json({
|
|
name: 'droid2api',
|
|
version: '1.0.0',
|
|
description: 'OpenAI Compatible API Proxy',
|
|
endpoints: [
|
|
'GET /v1/models',
|
|
'POST /v1/chat/completions',
|
|
'POST /v1/responses',
|
|
'POST /v1/messages',
|
|
'POST /v1/messages/count_tokens'
|
|
]
|
|
});
|
|
});
|
|
|
|
// 404 处理 - 捕获所有未匹配的路由
|
|
app.use((req, res, next) => {
|
|
const errorInfo = {
|
|
timestamp: new Date().toISOString(),
|
|
method: req.method,
|
|
url: req.originalUrl || req.url,
|
|
path: req.path,
|
|
query: req.query,
|
|
params: req.params,
|
|
body: req.body,
|
|
headers: {
|
|
'content-type': req.headers['content-type'],
|
|
'user-agent': req.headers['user-agent'],
|
|
'origin': req.headers['origin'],
|
|
'referer': req.headers['referer']
|
|
},
|
|
ip: req.ip || req.connection.remoteAddress
|
|
};
|
|
|
|
console.error('\n' + '='.repeat(80));
|
|
console.error('❌ 非法请求地址');
|
|
console.error('='.repeat(80));
|
|
console.error(`时间: ${errorInfo.timestamp}`);
|
|
console.error(`方法: ${errorInfo.method}`);
|
|
console.error(`地址: ${errorInfo.url}`);
|
|
console.error(`路径: ${errorInfo.path}`);
|
|
|
|
if (Object.keys(errorInfo.query).length > 0) {
|
|
console.error(`查询参数: ${JSON.stringify(errorInfo.query, null, 2)}`);
|
|
}
|
|
|
|
if (errorInfo.body && Object.keys(errorInfo.body).length > 0) {
|
|
console.error(`请求体: ${JSON.stringify(errorInfo.body, null, 2)}`);
|
|
}
|
|
|
|
console.error(`客户端IP: ${errorInfo.ip}`);
|
|
console.error(`User-Agent: ${errorInfo.headers['user-agent'] || 'N/A'}`);
|
|
|
|
if (errorInfo.headers.referer) {
|
|
console.error(`来源: ${errorInfo.headers.referer}`);
|
|
}
|
|
|
|
console.error('='.repeat(80) + '\n');
|
|
|
|
logError('Invalid request path', errorInfo);
|
|
|
|
res.status(404).json({
|
|
error: 'Not Found',
|
|
message: `路径 ${req.method} ${req.path} 不存在`,
|
|
timestamp: errorInfo.timestamp,
|
|
availableEndpoints: [
|
|
'GET /v1/models',
|
|
'POST /v1/chat/completions',
|
|
'POST /v1/responses',
|
|
'POST /v1/messages',
|
|
'POST /v1/messages/count_tokens'
|
|
]
|
|
});
|
|
});
|
|
|
|
// 错误处理中间件
|
|
app.use((err, req, res, next) => {
|
|
logError('Unhandled error', err);
|
|
res.status(500).json({
|
|
error: 'Internal server error',
|
|
message: isDevMode() ? err.message : undefined
|
|
});
|
|
});
|
|
|
|
(async () => {
|
|
try {
|
|
loadConfig();
|
|
logInfo('Configuration loaded successfully');
|
|
logInfo(`Dev mode: ${isDevMode()}`);
|
|
|
|
// Initialize User-Agent version updater
|
|
initializeUserAgentUpdater();
|
|
|
|
// Initialize auth system (load and setup API key if needed)
|
|
// This won't throw error if no auth config is found - will use client auth
|
|
await initializeAuth();
|
|
|
|
const PORT = getPort();
|
|
logInfo(`Starting server on port ${PORT}...`);
|
|
|
|
const server = app.listen(PORT)
|
|
.on('listening', () => {
|
|
logInfo(`Server running on http://localhost:${PORT}`);
|
|
logInfo('Available endpoints:');
|
|
logInfo(' GET /v1/models');
|
|
logInfo(' POST /v1/chat/completions');
|
|
logInfo(' POST /v1/responses');
|
|
logInfo(' POST /v1/messages');
|
|
logInfo(' POST /v1/messages/count_tokens');
|
|
})
|
|
.on('error', (err) => {
|
|
if (err.code === 'EADDRINUSE') {
|
|
console.error(`\n${'='.repeat(80)}`);
|
|
console.error(`ERROR: Port ${PORT} is already in use!`);
|
|
console.error('');
|
|
console.error('Please choose one of the following options:');
|
|
console.error(` 1. Stop the process using port ${PORT}:`);
|
|
console.error(` lsof -ti:${PORT} | xargs kill`);
|
|
console.error('');
|
|
console.error(' 2. Change the port in config.json:');
|
|
console.error(' Edit config.json and modify the "port" field');
|
|
console.error(`${'='.repeat(80)}\n`);
|
|
process.exit(1);
|
|
} else {
|
|
logError('Failed to start server', err);
|
|
process.exit(1);
|
|
}
|
|
});
|
|
} catch (error) {
|
|
logError('Failed to start server', error);
|
|
process.exit(1);
|
|
}
|
|
})();
|