chore: 删除原 node 服务器

This commit is contained in:
肖应宇 2026-03-03 14:47:23 +08:00
parent 192013bd65
commit c32b50584d
17 changed files with 9 additions and 1885 deletions

View File

@ -1,6 +0,0 @@
# 阿里云百炼 API Key
# 请在百炼控制台申请并填入此处
ALIYUN_API_KEY=your_api_key_here
# 本地中转服务器运行端口
PORT=3000

View File

@ -22,17 +22,20 @@
1. **克隆或复制代码**
2. **安装Python依赖**
```bash
cd server_python
cd server
pip install -r requirements.txt
```
3. **配置环境变量**
```bash
cp .env.example .env
```
编辑`.env`文件填入您的阿里云百炼API密钥
```
ALIYUN_API_KEY=your_actual_api_key_here
```
@ -40,11 +43,13 @@
## 启动服务器
### 方法一:直接运行
```bash
python run_server.py
```
### 方法二使用uvicorn
```bash
uvicorn app:app --host 0.0.0.0 --port 8000 --reload
```
@ -103,4 +108,4 @@ server: {
- Python服务器提供了与Node.js服务器相同的功能和API接口
- 保留了原有的日志记录机制
- 对话数据仍存储在内存中,生产环境建议使用数据库
- 支持与原前端应用无缝集成
- 支持与原前端应用无缝集成

View File

@ -1,101 +0,0 @@
# 日志系统文档
## 概述
本项目实现了统一的后端日志系统,为所有接口提供了详细的日志记录功能,包括请求、响应、错误等状态。
## 日志系统结构
### 1. logger.js
- 统一日志管理模块
- 支持多种日志级别ERROR, WARN, INFO, DEBUG
- 支持控制台和文件双重输出
- 包含HTTP请求专用日志方法
### 2. 日志文件位置
- 存储在 `server/logs/` 目录
- 按日期命名:`server-YYYY-MM-DD.log`
- JSON格式便于查询和分析
## 日志级别
- `ERROR`: 错误和异常情况
- `WARN`: 警告和潜在问题
- `INFO`: 重要操作和状态信息
- `DEBUG`: 详细调试信息
## 接口日志详情
### 1. 对话接口 (`/api/chat-ui/chat`)
- 记录请求代理开始和结束
- 记录目标API响应状态
- 记录代理错误
### 2. 模型列表接口 (`/api/chat-ui/models`)
- 记录请求基本信息
- 记录返回的模型数量
### 3. 对话管理接口 (`/api/chat-ui/conversations/*`)
- 记录操作类型(获取/创建/删除)
- 记录对话ID
- 记录操作结果
### 4. 文件上传接口 (`/api/chat-ui/upload`)
- 记录上传文件信息(名称、大小、类型)
- 记录上传结果和生成的URL
### 5. 停止生成接口 (`/api/chat-ui/stop/*`)
- 记录停止请求的ID
- 记录操作结果
### 6. HTTP通用日志
- 记录所有请求的方法、URL、状态码
- 记录请求耗时
- 记录客户端IP和User-Agent
- 根据状态码自动分类日志级别
## 使用方法
### 环境变量
```bash
LOG_LEVEL=DEBUG # 设置日志级别默认为INFO
```
### 在代码中使用
```javascript
const { logger } = require('./logger');
logger.info('Some info message', { additional: 'data' });
logger.error('Error occurred', { error: error.message });
logger.debug('Debug info', { variable: value });
```
## 日志示例
### HTTP请求日志
```json
{
"timestamp": "2026-03-03T03:26:58.631Z",
"level": "INFO",
"message": "HTTP GET /api/chat-ui/models 200 15ms",
"method": "GET",
"url": "/api/chat-ui/models",
"statusCode": 200,
"duration": "15ms",
"userAgent": "Mozilla/5.0...",
"ip": "::1"
}
```
### 特定接口日志
```json
{
"timestamp": "2026-03-03T03:27:12.123Z",
"level": "INFO",
"message": "Successfully uploaded file",
"filename": "1642342342-file.jpg",
"originalName": "file.jpg",
"url": "http://localhost:3000/uploads/1642342342-file.jpg",
"size": 123456
}
```

View File

@ -1,96 +0,0 @@
const fs = require('fs');
const path = require('path');
// 日志级别枚举
const LOG_LEVELS = {
ERROR: 0,
WARN: 1,
INFO: 2,
DEBUG: 3,
};
// 创建logs目录
const logsDir = path.join(__dirname, 'logs');
if (!fs.existsSync(logsDir)) {
fs.mkdirSync(logsDir);
}
// 日志记录器类
class Logger {
constructor(level = 'INFO') {
this.level = LOG_LEVELS[level.toUpperCase()] || LOG_LEVELS.INFO;
this.logFilePath = path.join(logsDir, `server-${new Date().toISOString().split('T')[0]}.log`);
}
_shouldLog(level) {
return LOG_LEVELS[level.toUpperCase()] <= this.level;
}
_writeLog(level, message, meta = {}) {
if (!this._shouldLog(level)) return;
const timestamp = new Date().toISOString();
const logEntry = {
timestamp,
level,
message,
...meta,
};
// 控制台输出
console.log(`[${timestamp}] [${level}] ${message}`, meta);
// 文件输出
try {
const logLine = JSON.stringify(logEntry) + '\n';
fs.appendFileSync(this.logFilePath, logLine);
} catch (err) {
console.error('Failed to write to log file:', err);
}
}
error(message, meta = {}) {
this._writeLog('ERROR', message, meta);
}
warn(message, meta = {}) {
this._writeLog('WARN', message, meta);
}
info(message, meta = {}) {
this._writeLog('INFO', message, meta);
}
debug(message, meta = {}) {
this._writeLog('DEBUG', message, meta);
}
// HTTP请求日志专用方法
http(req, res, startTime, error = null) {
const duration = Date.now() - startTime;
const logMeta = {
method: req.method,
url: req.url,
statusCode: res.statusCode,
duration: `${duration}ms`,
userAgent: req.headers['user-agent'],
ip: req.ip || req.connection.remoteAddress,
...(error ? { error: error.message || error } : {})
};
const statusCategory = Math.floor(res.statusCode / 100);
let level = 'INFO';
if (error || statusCategory >= 5) {
level = 'ERROR';
} else if (statusCategory >= 4) {
level = 'WARN';
}
this._writeLog(level, `HTTP ${req.method} ${req.url} ${res.statusCode} ${duration}ms`, logMeta);
}
}
// 创建全局日志实例
const logger = new Logger(process.env.LOG_LEVEL || 'INFO');
module.exports = { logger, LOG_LEVELS };

View File

@ -13,7 +13,7 @@ from fastapi.responses import JSONResponse
# 导入模块
import sys
sys.path.append('/home/mt/project/ai-chat-ui/server_python')
sys.path.append('/home/mt/project/ai-chat-ui/server')
from api.chat_routes import (
chat_endpoint_handler,

1304
server/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +0,0 @@
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"cors": "^2.8.6",
"dotenv": "^17.3.1",
"express": "^5.2.1",
"http-proxy-middleware": "^3.0.5",
"morgan": "^1.10.1",
"multer": "^2.1.0",
"uuid": "^13.0.0"
}
}

View File

@ -1,351 +0,0 @@
const express = require('express');
const cors = require('cors');
const { createProxyMiddleware } = require('http-proxy-middleware');
const multer = require('multer');
const { v4: uuidv4 } = require('uuid');
const path = require('path');
const fs = require('fs');
const morgan = require('morgan');
require('dotenv').config();
// 引入自定义日志系统
const { logger } = require('./logger');
const app = express();
const PORT = process.env.PORT || 3000;
// 配置全局请求日志中间件
app.use((req, res, next) => {
const startTime = Date.now();
// 保存原始的res.end方法
const originalEnd = res.end;
res.end = function(chunk, encoding) {
// 调用自定义日志记录
logger.http(req, res, startTime);
// 调用原始的res.end
originalEnd.call(this, chunk, encoding);
};
next();
});
// 配置 CORS允许前端项目的跨域请求
app.use(cors());
// --- 1. 流式对话请求代理配置 ---
app.use('/api/chat-ui/chat', (req, res, next) => {
logger.info('Received chat request', {
method: req.method,
url: req.url,
headers: {
'content-type': req.headers['content-type'],
'content-length': req.headers['content-length']
}
});
const apiKey = process.env.ALIYUN_API_KEY;
if (!apiKey) {
logger.error("【错误】发送代理请求前未配置 ALIYUN_API_KEY !");
} else {
req.headers['authorization'] = `Bearer ${apiKey}`;
logger.debug('Added authorization header for chat request');
}
next();
});
// 注意:代理中间件需要在 body-parser (express.json) 之前,不然代理会导致请求体丢失
app.use(
'/api/chat-ui/chat',
createProxyMiddleware({
// 阿里云百炼由于兼容 OpenAI 格式,所以代理到此接口
target: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
changeOrigin: true,
// 去除路径前缀,确保发往阿里云的路径是纯正的 completions 路径
pathRewrite: {
'^/api/chat-ui/chat': '',
},
// 代理日志
onProxyReq: (proxyReq, req, res) => {
logger.info('Proxying chat request to DashScope API', {
target: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
method: req.method,
url: req.url
});
},
onProxyRes: (proxyRes, req, res) => {
logger.info('Received response from DashScope API', {
statusCode: proxyRes.statusCode,
headers: proxyRes.headers
});
},
onError: (err, req, res) => {
logger.error('Error occurred during proxying chat request', {
error: err.message,
method: req.method,
url: req.url
});
res.status(500).json({ error: 'Proxy error: ' + err.message });
}
})
);
// --- 下面的路由专门走 Node.js 业务逻辑,因此需要解析 JSON Body ---
app.use(express.json());
// 内存中暂时存放对话数据用于 Mock
const conversationsDB = {};
// --- 2. 获取模型列表 ---
app.get('/api/chat-ui/models', (req, res) => {
logger.info('Getting models list', {
method: req.method,
url: req.url,
ip: req.ip || req.connection.remoteAddress
});
try {
const models = [
{
id: "qwen-max",
name: "通义千问 Max",
description: "最强大的模型",
maxTokens: 8192,
provider: "Aliyun"
},
{
id: "qwen-plus",
name: "通义千问 Plus",
description: "能力均衡",
maxTokens: 8192,
provider: "Aliyun"
}
];
res.json(models);
logger.info('Successfully returned models list', { count: models.length });
} catch (error) {
logger.error('Error getting models list', { error: error.message });
res.status(500).json({ error: 'Internal server error' });
}
});
// --- 3. 获取所有对话历史 ---
app.get('/api/chat-ui/conversations', (req, res) => {
logger.info('Getting all conversations', {
method: req.method,
url: req.url,
ip: req.ip || req.connection.remoteAddress
});
try {
const conversations = Object.values(conversationsDB);
res.json(conversations);
logger.info('Successfully returned conversations', { count: conversations.length });
} catch (error) {
logger.error('Error getting conversations', { error: error.message });
res.status(500).json({ error: 'Internal server error' });
}
});
// --- 4. 获取单个对话 ---
app.get('/api/chat-ui/conversations/:id', (req, res) => {
const { id } = req.params;
logger.info('Getting conversation by ID', {
method: req.method,
url: req.url,
conversationId: id,
ip: req.ip || req.connection.remoteAddress
});
try {
const conversation = conversationsDB[id];
if (conversation) {
res.json(conversation);
logger.info('Successfully returned conversation', { conversationId: id });
} else {
res.status(404).json({ error: '对话不存在' });
logger.warn('Conversation not found', { conversationId: id });
}
} catch (error) {
logger.error('Error getting conversation by ID', {
conversationId: id,
error: error.message
});
res.status(500).json({ error: 'Internal server error' });
}
});
// --- 5. 保存或更新对话 ---
app.post('/api/chat-ui/conversations', (req, res) => {
logger.info('Creating or updating conversation', {
method: req.method,
url: req.url,
body: {
hasId: !!req.body.id,
title: req.body.title
},
ip: req.ip || req.connection.remoteAddress
});
try {
const data = req.body;
if(!data.id) {
data.id = uuidv4();
logger.debug('Generated new conversation ID', { conversationId: data.id });
}
conversationsDB[data.id] = data;
res.json(data);
logger.info('Successfully saved conversation', { conversationId: data.id });
} catch (error) {
logger.error('Error saving conversation', { error: error.message });
res.status(500).json({ error: 'Internal server error' });
}
});
// --- 6. 删除对话 ---
app.delete('/api/chat-ui/conversations/:id', (req, res) => {
const { id } = req.params;
logger.info('Deleting conversation', {
method: req.method,
url: req.url,
conversationId: id,
ip: req.ip || req.connection.remoteAddress
});
try {
if (conversationsDB[id]) {
delete conversationsDB[id];
res.json({ success: true, message: "删除成功" });
logger.info('Successfully deleted conversation', { conversationId: id });
} else {
res.status(404).json({ error: '对话不存在' });
logger.warn('Attempted to delete non-existent conversation', { conversationId: id });
}
} catch (error) {
logger.error('Error deleting conversation', {
conversationId: id,
error: error.message
});
res.status(500).json({ error: 'Internal server error' });
}
});
// 为了存储上传文件而建立临时目录
const uploadDir = path.join(__dirname, 'uploads');
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir);
logger.info('Created uploads directory', { path: uploadDir });
}
// 配置文件上传
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, uploadDir);
},
filename: function (req, file, cb) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, uniqueSuffix + '-' + file.originalname);
}
});
const upload = multer({ storage: storage });
// 提供静态文件访问支持
app.use('/uploads', express.static(uploadDir));
// --- 7. 上传文件 ---
app.post('/api/chat-ui/upload', upload.single('file'), (req, res) => {
logger.info('Processing file upload', {
method: req.method,
url: req.url,
originalFilename: req.file ? req.file.originalname : 'none',
mimetype: req.file ? req.file.mimetype : 'none',
size: req.file ? req.file.size : 'none',
ip: req.ip || req.connection.remoteAddress
});
if (!req.file) {
const errorMsg = '没有文件上传';
logger.warn(errorMsg, {
method: req.method,
url: req.url
});
return res.status(400).json({ error: errorMsg });
}
try {
// 返回供前端使用和访问的 URL
const response = {
url: `http://localhost:${PORT}/uploads/${req.file.filename}`,
name: req.file.originalname,
size: req.file.size,
mimeType: req.file.mimetype
};
res.json(response);
logger.info('Successfully uploaded file', {
filename: req.file.filename,
originalName: req.file.originalname,
url: response.url,
size: req.file.size
});
} catch (error) {
logger.error('Error processing file upload', {
originalFilename: req.file.originalname,
error: error.message
});
res.status(500).json({ error: 'File upload processing failed' });
}
});
// --- 8. 停止生成 ---
app.post(['/api/chat-ui/stop', '/api/chat-ui/stop/:id'], (req, res) => {
const id = req.params.id;
logger.info('Stop generation request received', {
method: req.method,
url: req.url,
messageId: id,
ip: req.ip || req.connection.remoteAddress
});
try {
res.json({ success: true, message: "已发出停止指令" });
logger.info('Stop generation request processed', { messageId: id });
} catch (error) {
logger.error('Error processing stop generation request', {
messageId: id,
error: error.message
});
res.status(500).json({ error: 'Internal server error' });
}
});
// 其他所有路由返回404
app.use((req, res) => {
logger.warn('Route not found', {
method: req.method,
url: req.url,
ip: req.ip || req.connection.remoteAddress
});
res.status(404).json({ error: 'Endpoint not found' });
});
app.listen(PORT, () => {
logger.info('Server started successfully', {
port: PORT,
timestamp: new Date().toISOString()
});
console.log('====================================');
console.log(`本地代理服务器已启动,监听端口: ${PORT}`);
console.log('====================================');
if (!process.env.ALIYUN_API_KEY) {
console.log('⚠️ 警告: 未在 .env 文件中检测到 ALIYUN_API_KEY!');
console.log('请在 server/.env 中添加您的百炼 API Key。');
} else {
console.log('✅ 检测到了 API Key。');
logger.info('API Key detected in environment');
}
});

View File

@ -10,7 +10,7 @@ if lsof -Pi :8000 -sTCP:LISTEN -t >/dev/null; then
fi
# 切换到服务器目录
cd /home/mt/project/ai-chat-ui/server_python
cd /home/mt/project/ai-chat-ui/server
# 检查虚拟环境是否存在
if [ ! -d ".venv" ]; then