Spaces:
Paused
Paused
| const express = require('express'); | |
| const http = require('http'); | |
| const socketIo = require('socket.io'); | |
| const cors = require('cors'); | |
| const mongoose = require('mongoose'); | |
| const jwt = require('jsonwebtoken'); | |
| const bcrypt = require('bcryptjs'); | |
| require('dotenv').config(); | |
| const app = express(); | |
| const server = http.createServer(app); | |
| const io = socketIo(server, { | |
| cors: { | |
| origin: process.env.CLIENT_URL || "http://localhost:3000", | |
| methods: ["GET", "POST"] | |
| } | |
| }); | |
| // 中间件 | |
| app.use(cors()); | |
| app.use(express.json()); | |
| // MongoDB连接 | |
| const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://mongo:27017/chatapp'; | |
| mongoose.connect(MONGODB_URI) | |
| .then(() => console.log('MongoDB连接成功')) | |
| .catch(err => console.error('MongoDB连接失败:', err)); | |
| // 用户模型 | |
| const userSchema = new mongoose.Schema({ | |
| username: { type: String, required: true, unique: true }, | |
| email: { type: String, required: true, unique: true }, | |
| password: { type: String, required: true }, | |
| avatar: { type: String, default: '' }, | |
| createdAt: { type: Date, default: Date.now } | |
| }); | |
| const User = mongoose.model('User', userSchema); | |
| // 消息模型 | |
| const messageSchema = new mongoose.Schema({ | |
| content: { type: String, required: true }, | |
| sender: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, | |
| room: { type: String, default: 'general' }, | |
| timestamp: { type: Date, default: Date.now } | |
| }); | |
| const Message = mongoose.model('Message', messageSchema); | |
| // JWT验证中间件 | |
| const authenticateToken = (req, res, next) => { | |
| const authHeader = req.headers['authorization']; | |
| const token = authHeader && authHeader.split(' ')[1]; | |
| if (!token) { | |
| return res.sendStatus(401); | |
| } | |
| jwt.verify(token, process.env.JWT_SECRET || 'fallback-secret', (err, user) => { | |
| if (err) return res.sendStatus(403); | |
| req.user = user; | |
| next(); | |
| }); | |
| }; | |
| // API路由 | |
| // 健康检查端点 | |
| app.get('/api/health', (req, res) => { | |
| res.status(200).json({ | |
| status: 'ok', | |
| timestamp: new Date().toISOString(), | |
| uptime: process.uptime(), | |
| mongodb: mongoose.connection.readyState === 1 ? 'connected' : 'disconnected' | |
| }); | |
| }); | |
| // 用户注册 | |
| app.post('/api/register', async (req, res) => { | |
| try { | |
| const { username, email, password } = req.body; | |
| // 检查用户是否已存在 | |
| const existingUser = await User.findOne({ | |
| $or: [{ email }, { username }] | |
| }); | |
| if (existingUser) { | |
| return res.status(400).json({ message: '用户名或邮箱已存在' }); | |
| } | |
| // 加密密码 | |
| const hashedPassword = await bcrypt.hash(password, 10); | |
| // 创建新用户 | |
| const user = new User({ | |
| username, | |
| email, | |
| password: hashedPassword | |
| }); | |
| await user.save(); | |
| // 生成JWT token | |
| const token = jwt.sign( | |
| { userId: user._id, username: user.username }, | |
| process.env.JWT_SECRET || 'fallback-secret', | |
| { expiresIn: '24h' } | |
| ); | |
| res.status(201).json({ | |
| message: '注册成功', | |
| token, | |
| user: { | |
| id: user._id, | |
| username: user.username, | |
| email: user.email, | |
| avatar: user.avatar | |
| } | |
| }); | |
| } catch (error) { | |
| console.error('注册错误:', error); | |
| res.status(500).json({ message: '服务器错误' }); | |
| } | |
| }); | |
| // 用户登录 | |
| app.post('/api/login', async (req, res) => { | |
| try { | |
| const { email, password } = req.body; | |
| // 查找用户 | |
| const user = await User.findOne({ email }); | |
| if (!user) { | |
| return res.status(400).json({ message: '邮箱或密码错误' }); | |
| } | |
| // 验证密码 | |
| const isValidPassword = await bcrypt.compare(password, user.password); | |
| if (!isValidPassword) { | |
| return res.status(400).json({ message: '邮箱或密码错误' }); | |
| } | |
| // 生成JWT token | |
| const token = jwt.sign( | |
| { userId: user._id, username: user.username }, | |
| process.env.JWT_SECRET || 'fallback-secret', | |
| { expiresIn: '24h' } | |
| ); | |
| res.json({ | |
| message: '登录成功', | |
| token, | |
| user: { | |
| id: user._id, | |
| username: user.username, | |
| email: user.email, | |
| avatar: user.avatar | |
| } | |
| }); | |
| } catch (error) { | |
| console.error('登录错误:', error); | |
| res.status(500).json({ message: '服务器错误' }); | |
| } | |
| }); | |
| // 获取历史消息 | |
| app.get('/api/messages', authenticateToken, async (req, res) => { | |
| try { | |
| const { room = 'general', limit = 50 } = req.query; | |
| const messages = await Message.find({ room }) | |
| .populate('sender', 'username avatar') | |
| .sort({ timestamp: -1 }) | |
| .limit(parseInt(limit)); | |
| res.json(messages.reverse()); | |
| } catch (error) { | |
| console.error('获取消息错误:', error); | |
| res.status(500).json({ message: '服务器错误' }); | |
| } | |
| }); | |
| // Socket.IO连接处理 | |
| const connectedUsers = new Map(); | |
| io.on('connection', (socket) => { | |
| console.log('用户连接:', socket.id); | |
| // 用户加入 | |
| socket.on('join', async (userData) => { | |
| try { | |
| // 验证token | |
| const decoded = jwt.verify(userData.token, process.env.JWT_SECRET || 'fallback-secret'); | |
| const user = await User.findById(decoded.userId); | |
| if (user) { | |
| socket.userId = user._id.toString(); | |
| socket.username = user.username; | |
| connectedUsers.set(socket.id, { | |
| userId: user._id.toString(), | |
| username: user.username, | |
| avatar: user.avatar | |
| }); | |
| socket.join('general'); | |
| // 广播用户上线 | |
| socket.broadcast.emit('userJoined', { | |
| username: user.username, | |
| avatar: user.avatar | |
| }); | |
| // 发送在线用户列表 | |
| const onlineUsers = Array.from(connectedUsers.values()); | |
| io.emit('onlineUsers', onlineUsers); | |
| } | |
| } catch (error) { | |
| console.error('用户加入错误:', error); | |
| socket.emit('error', { message: '认证失败' }); | |
| } | |
| }); | |
| // 发送消息 | |
| socket.on('sendMessage', async (messageData) => { | |
| try { | |
| if (!socket.userId) { | |
| socket.emit('error', { message: '未认证用户' }); | |
| return; | |
| } | |
| const message = new Message({ | |
| content: messageData.content, | |
| sender: socket.userId, | |
| room: messageData.room || 'general' | |
| }); | |
| await message.save(); | |
| await message.populate('sender', 'username avatar'); | |
| // 广播消息到房间 | |
| io.to(messageData.room || 'general').emit('newMessage', { | |
| id: message._id, | |
| content: message.content, | |
| sender: { | |
| id: message.sender._id, | |
| username: message.sender.username, | |
| avatar: message.sender.avatar | |
| }, | |
| timestamp: message.timestamp, | |
| room: message.room | |
| }); | |
| } catch (error) { | |
| console.error('发送消息错误:', error); | |
| socket.emit('error', { message: '发送消息失败' }); | |
| } | |
| }); | |
| // 用户断开连接 | |
| socket.on('disconnect', () => { | |
| console.log('用户断开连接:', socket.id); | |
| const userData = connectedUsers.get(socket.id); | |
| if (userData) { | |
| connectedUsers.delete(socket.id); | |
| // 广播用户下线 | |
| socket.broadcast.emit('userLeft', { | |
| username: userData.username | |
| }); | |
| // 更新在线用户列表 | |
| const onlineUsers = Array.from(connectedUsers.values()); | |
| io.emit('onlineUsers', onlineUsers); | |
| } | |
| }); | |
| }); | |
| const PORT = process.env.PORT || 5000; | |
| server.listen(PORT, () => { | |
| console.log(`服务器运行在端口 ${PORT}`); | |
| }); | |