|
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()); |
|
|
|
|
|
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); |
|
|
|
|
|
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(); |
|
}); |
|
}; |
|
|
|
|
|
|
|
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(); |
|
|
|
|
|
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: '邮箱或密码错误' }); |
|
} |
|
|
|
|
|
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: '服务器错误' }); |
|
} |
|
}); |
|
|
|
|
|
const connectedUsers = new Map(); |
|
|
|
io.on('connection', (socket) => { |
|
console.log('用户连接:', socket.id); |
|
|
|
|
|
socket.on('join', async (userData) => { |
|
try { |
|
|
|
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}`); |
|
}); |
|
|