164 lines
5.9 KiB
JavaScript
164 lines
5.9 KiB
JavaScript
const WebSocket = require('ws')
|
|
const { v4: uuidv4 } = require('uuid')
|
|
|
|
class ChatServer {
|
|
constructor(server) {
|
|
this.wss = new WebSocket.Server({ server })
|
|
this.clients = new Map() // 存储所有连接的客户端
|
|
this.messageHistory = [] // 存储消息历史
|
|
this.setupWebSocket()
|
|
this.setupHeartbeat()
|
|
}
|
|
|
|
setupWebSocket() {
|
|
this.wss.on('connection', (ws, req) => {
|
|
const clientId = uuidv4()
|
|
const userInfo = { id: clientId, name: '未命名用户' }
|
|
|
|
// 存储客户端连接
|
|
this.clients.set(clientId, { ws, userInfo, lastPing: Date.now() })
|
|
|
|
// 广播新用户加入
|
|
this.broadcast({
|
|
type: 'system',
|
|
action: 'join',
|
|
user: userInfo,
|
|
users: Array.from(this.clients.values()).map(client => client.userInfo),
|
|
timestamp: Date.now()
|
|
})
|
|
|
|
// 发送在线用户列表
|
|
ws.send(JSON.stringify({
|
|
type: 'system',
|
|
action: 'userList',
|
|
users: Array.from(this.clients.values()).map(client => client.userInfo),
|
|
timestamp: Date.now()
|
|
}))
|
|
|
|
// 发送历史消息
|
|
ws.send(JSON.stringify({
|
|
type: 'system',
|
|
action: 'history',
|
|
messages: this.messageHistory.slice(-50), // 最近50条消息
|
|
timestamp: Date.now()
|
|
}))
|
|
|
|
// 处理消息
|
|
ws.on('message', (data) => {
|
|
try {
|
|
const message = JSON.parse(data)
|
|
const client = this.clients.get(clientId)
|
|
if (client) {
|
|
client.lastPing = Date.now() // 更新最后活动时间
|
|
}
|
|
|
|
switch (message.type) {
|
|
case 'chat':
|
|
// 处理聊天消息
|
|
const chatMessage = {
|
|
id: uuidv4(),
|
|
type: 'chat',
|
|
from: userInfo,
|
|
to: message.to, // null表示群发
|
|
content: message.content,
|
|
timestamp: Date.now()
|
|
}
|
|
this.messageHistory.push(chatMessage)
|
|
if (message.to) {
|
|
// 私聊消息
|
|
const targetClient = this.clients.get(message.to.id)
|
|
if (targetClient) {
|
|
targetClient.ws.send(JSON.stringify(chatMessage))
|
|
ws.send(JSON.stringify(chatMessage)) // 发送给自己
|
|
}
|
|
} else {
|
|
// 群发消息
|
|
this.broadcast(chatMessage)
|
|
}
|
|
break
|
|
|
|
case 'user':
|
|
// 更新用户信息
|
|
if (message.action === 'update') {
|
|
userInfo.name = message.name
|
|
this.broadcast({
|
|
type: 'system',
|
|
action: 'userUpdate',
|
|
user: userInfo,
|
|
users: Array.from(this.clients.values()).map(client => client.userInfo),
|
|
timestamp: Date.now()
|
|
})
|
|
}
|
|
break
|
|
|
|
case 'ping':
|
|
// 处理心跳消息
|
|
ws.send(JSON.stringify({
|
|
type: 'pong',
|
|
timestamp: Date.now()
|
|
}))
|
|
break
|
|
}
|
|
} catch (error) {
|
|
console.error('处理消息时出错:', error)
|
|
}
|
|
})
|
|
|
|
// 处理连接关闭
|
|
ws.on('close', () => {
|
|
this.clients.delete(clientId)
|
|
this.broadcast({
|
|
type: 'system',
|
|
action: 'leave',
|
|
user: userInfo,
|
|
users: Array.from(this.clients.values()).map(client => client.userInfo),
|
|
timestamp: Date.now()
|
|
})
|
|
})
|
|
|
|
// 处理错误
|
|
ws.on('error', (error) => {
|
|
console.error('WebSocket错误:', error)
|
|
this.clients.delete(clientId)
|
|
})
|
|
})
|
|
}
|
|
|
|
// 设置心跳检测
|
|
setupHeartbeat() {
|
|
const HEARTBEAT_INTERVAL = 30000 // 30秒检查一次
|
|
const CLIENT_TIMEOUT = 60000 // 60秒超时
|
|
|
|
setInterval(() => {
|
|
const now = Date.now()
|
|
this.clients.forEach((client, clientId) => {
|
|
if (now - client.lastPing > CLIENT_TIMEOUT) {
|
|
console.log(`客户端 ${clientId} 超时断开`)
|
|
client.ws.terminate()
|
|
this.clients.delete(clientId)
|
|
}
|
|
})
|
|
}, HEARTBEAT_INTERVAL)
|
|
}
|
|
|
|
// 广播消息给所有客户端
|
|
broadcast(message) {
|
|
const messageStr = JSON.stringify(message)
|
|
this.clients.forEach(client => {
|
|
if (client.ws.readyState === WebSocket.OPEN) {
|
|
client.ws.send(messageStr)
|
|
}
|
|
})
|
|
}
|
|
|
|
// 清理历史消息
|
|
cleanHistory() {
|
|
const oneDay = 24 * 60 * 60 * 1000 // 一天的毫秒数
|
|
const now = Date.now()
|
|
this.messageHistory = this.messageHistory.filter(msg => {
|
|
return (now - msg.timestamp) < oneDay
|
|
})
|
|
}
|
|
}
|
|
|
|
module.exports = ChatServer |