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'); } });