chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,364 @@
|
||||
|
||||
// ==================== ChatClient 模块定义 (ES6 兼容) ====================
|
||||
layui.define(['jquery', 'layer', 'laytpl', 'util'], function (exports) {
|
||||
"use strict";
|
||||
|
||||
class ChatClient {
|
||||
constructor(config) {
|
||||
this.config = Object.assign({
|
||||
wsUrl: 'wss://echo.websocket.org',
|
||||
token: '',
|
||||
uid: '',
|
||||
reconnectDelay: 3000,
|
||||
maxReconnect: 5
|
||||
}, config);
|
||||
|
||||
this.ws = null;
|
||||
this.reconnectCount = 0;
|
||||
this.messageQueue = [];
|
||||
this.isConnected = false;
|
||||
this.currentSession = { id: '', type: '' };
|
||||
this.requestCallbacks = {};
|
||||
this.heartbeatTimer = null;
|
||||
this.eventHandlers = {};
|
||||
this.init();
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统事件
|
||||
* @param {*} event
|
||||
* @param {*} handler
|
||||
* @returns
|
||||
*/
|
||||
on(event, handler) {
|
||||
if (!this.eventHandlers[event]) this.eventHandlers[event] = [];
|
||||
this.eventHandlers[event].push(handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发
|
||||
* @param {*} event
|
||||
* @param {...any} args
|
||||
* @returns
|
||||
*/
|
||||
emit(event, ...args) {
|
||||
if (this.eventHandlers[event]) {
|
||||
this.eventHandlers[event].forEach(handler => handler.apply(this, args));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化
|
||||
*/
|
||||
init() {
|
||||
this.connect();
|
||||
window.addEventListener('beforeunload', () => this.disconnect());
|
||||
}
|
||||
|
||||
/**
|
||||
* 链接
|
||||
*/
|
||||
connect() {
|
||||
try {
|
||||
this.ws = new WebSocket(this.config.wsUrl);
|
||||
this.ws.onopen = () => {
|
||||
console.log('✅ WebSocket 连接成功');
|
||||
this.isConnected = true;
|
||||
this.reconnectCount = 5;
|
||||
this.sendAuth();
|
||||
this.startHeartbeat();
|
||||
this.emit('connect');
|
||||
// 重发队列消息
|
||||
while (this.messageQueue.length > 0) {
|
||||
const msg = this.messageQueue.shift();
|
||||
this.ws.send(JSON.stringify(msg));
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const packet = JSON.parse(event.data);
|
||||
this.handleMessage(packet);
|
||||
} catch (e) {
|
||||
console.error('❌ 消息解析失败:', e);
|
||||
this.emit('error', e);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
console.error('❌ WebSocket 错误:', error);
|
||||
this.emit('error', error);
|
||||
};
|
||||
|
||||
this.ws.onclose = () => {
|
||||
console.log('⚠️ WebSocket 连接关闭');
|
||||
this.isConnected = false;
|
||||
this.stopHeartbeat();
|
||||
this.emit('close');
|
||||
this.attemptReconnect();
|
||||
};
|
||||
} catch (e) {
|
||||
console.error('❌ WebSocket 初始化失败:', e);
|
||||
this.attemptReconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送验证信息
|
||||
*/
|
||||
sendAuth() {
|
||||
this.send({ content: this.config.token }, 'sys', 'auth');
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始心跳
|
||||
*/
|
||||
startHeartbeat() {
|
||||
this.stopHeartbeat();
|
||||
this.heartbeatTimer = setInterval(() => {
|
||||
if (this.isConnected && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.send({}, 'sys', 'heartbeat');
|
||||
}
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止心跳
|
||||
*/
|
||||
stopHeartbeat() {
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer);
|
||||
this.heartbeatTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重连
|
||||
*/
|
||||
attemptReconnect() {
|
||||
if (this.reconnectCount < this.config.maxReconnect && !this.isConnected) {
|
||||
this.reconnectCount++;
|
||||
console.log(`🔄 尝试重连 (${this.reconnectCount}/${this.config.maxReconnect})...`);
|
||||
setTimeout(() => this.connect(), this.config.reconnectDelay * this.reconnectCount);
|
||||
} else {
|
||||
this.emit('reconnect-failed');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送
|
||||
* @param {*} payload
|
||||
* @param {*} msgType
|
||||
* @param {*} options
|
||||
* @returns
|
||||
*/
|
||||
send(payload, _frame, type, options = {}) {
|
||||
const packet = {
|
||||
header: {
|
||||
id: 'pkt_' + Date.now() + '_' + Math.random().toString(36).substr(2, 5),
|
||||
frame: _frame,
|
||||
type: type,
|
||||
from: this.user?.uid,
|
||||
timestamp: Date.now()
|
||||
},
|
||||
payload: payload || {}
|
||||
};
|
||||
// 存储回调
|
||||
if (typeof options.callback === 'function') {
|
||||
this.requestCallbacks[packet.header.id] = {
|
||||
callback: options.callback,
|
||||
timeout: setTimeout(() => {
|
||||
if (this.requestCallbacks[packet.header.id]) {
|
||||
this.requestCallbacks[packet.header.id].callback({
|
||||
header: { type: 'timeout' },
|
||||
payload: { message: '请求超时' }
|
||||
});
|
||||
delete this.requestCallbacks[packet.header.id];
|
||||
}
|
||||
}, 10000)
|
||||
};
|
||||
}
|
||||
if (this.isConnected && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(JSON.stringify(packet));
|
||||
} else {
|
||||
this.messageQueue.push(packet);
|
||||
if (!this.isConnected) this.connect();
|
||||
}
|
||||
return packet.header.id;
|
||||
}
|
||||
|
||||
handleMessage(packet) {
|
||||
const frame = packet.header?.frame;
|
||||
// if (packet.header?.id) {
|
||||
// const origId = packet.header.id;
|
||||
// if (this.requestCallbacks[origId]) {
|
||||
// clearTimeout(this.requestCallbacks[origId].timeout);
|
||||
// this.requestCallbacks[origId].callback(packet);
|
||||
// delete this.requestCallbacks[origId];
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// 业务消息分发
|
||||
switch (frame) {
|
||||
case 'sys':
|
||||
this.handleSystem(packet);
|
||||
break;
|
||||
case 'chat':
|
||||
this.emit('friend-apply', packet.payload);
|
||||
break;
|
||||
case 'rel':
|
||||
this.emit('message-read', packet.payload);
|
||||
break;
|
||||
case 'ctl':
|
||||
this.emit('friend-apply-result', packet.payload);
|
||||
break;
|
||||
case 'err':
|
||||
this.emit('group-invite', packet.payload);
|
||||
break;
|
||||
case 'auth':
|
||||
this.emit('auth-response', packet.payload);
|
||||
break;
|
||||
case 'heartbeat':
|
||||
this.emit('auth-response', packet.payload);
|
||||
break;
|
||||
default:
|
||||
this.emit('message', packet);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
handleSystem(packet) {
|
||||
const type = packet.header?.type;
|
||||
switch (type) {
|
||||
case 'heartbeat':
|
||||
break;
|
||||
case 'auth':
|
||||
this.user = packet.payload.content ;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 业务方法(只处理数据,不涉及渲染)
|
||||
sendMessage(sessionId, sessionType, content, msgType = 'text') {
|
||||
const message = {
|
||||
header: {
|
||||
id: 'msg_' + Date.now(),
|
||||
type: msgType,
|
||||
subtype: sessionType,
|
||||
from: this.user.uid,
|
||||
to: sessionId,
|
||||
timestamp: new Date().toISOString()
|
||||
},
|
||||
payload: {
|
||||
content: content,
|
||||
}
|
||||
};
|
||||
this.ws.send(JSON.stringify(packet));
|
||||
// // 发送消息到服务器
|
||||
// this.send({
|
||||
// session_id: sessionId,
|
||||
// session_type: sessionType,
|
||||
// content: content,
|
||||
// type: msgType,
|
||||
// id: msgId
|
||||
// }, 'send_message', {
|
||||
// callback: (res) => {
|
||||
// if (res.payload?.success) {
|
||||
// // 更新本地消息状态为已发送
|
||||
// message.read_status = 'sent';
|
||||
// this.emit('message-sent', message);
|
||||
// } else {
|
||||
// message.read_status = 'failed';
|
||||
// this.emit('message-failed', message);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// 立即触发本地消息事件(用于前端渲染)
|
||||
this.emit('local-message', message);
|
||||
return msgId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送图片
|
||||
* @param {*} sessionId
|
||||
* @param {*} sessionType
|
||||
* @param {*} imgUrl
|
||||
* @returns
|
||||
*/
|
||||
sendImage(sessionId, sessionType, imgUrl) {
|
||||
return this.sendMessage(sessionId, sessionType, `[图片]${imgUrl}`, 'image');
|
||||
}
|
||||
|
||||
/**发送读取状态 */
|
||||
sendReadReceipt(msgId, sessionId, sessionType) {
|
||||
this.send({
|
||||
id: msgId,
|
||||
session_id: sessionId,
|
||||
session_type: sessionType
|
||||
}, 'read_receipt');
|
||||
}
|
||||
|
||||
/**设置会话 */
|
||||
setCurrentSession(sessionId, sessionType) {
|
||||
this.currentSession = { id: sessionId, type: sessionType };
|
||||
this.emit('session-changed', this.currentSession);
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索用户
|
||||
* @param {*} keyword
|
||||
* @param {*} callback
|
||||
*/
|
||||
searchUsers(keyword, callback) {
|
||||
// 实际项目中替换为真实API调用
|
||||
setTimeout(() => {
|
||||
const mockUsers = [
|
||||
{ uid: 'u2001', nickname: '设计师小王', avatar: 'W', is_friend: false },
|
||||
{ uid: 'u2002', nickname: '产品经理李', avatar: 'L', is_friend: true },
|
||||
{ uid: 'u2003', nickname: '后端工程师赵', avatar: 'Z', is_friend: false }
|
||||
].filter(u => u.nickname.includes(keyword) || u.uid.includes(keyword));
|
||||
callback({ code: 0, data: mockUsers });
|
||||
}, 300);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加好友
|
||||
* @param {*} targetUid
|
||||
* @param {*} remark
|
||||
* @param {*} callback
|
||||
*/
|
||||
addFriend(targetUid, remark, callback) {
|
||||
this.send({
|
||||
target_uid: targetUid,
|
||||
remark: remark || '你好,加个好友吧~'
|
||||
}, 'add_friend_request', { callback });
|
||||
}
|
||||
|
||||
/**
|
||||
* 接受好友
|
||||
* @param {*} applyId
|
||||
* @param {*} agree
|
||||
* @param {*} callback
|
||||
*/
|
||||
handleFriendApply(applyId, agree, callback) {
|
||||
this.send({
|
||||
apply_id: applyId,
|
||||
action: agree ? 'agree' : 'reject'
|
||||
}, 'handle_friend_apply', { callback });
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.stopHeartbeat();
|
||||
if (this.ws) {
|
||||
this.ws.close();
|
||||
this.ws = null;
|
||||
}
|
||||
this.isConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 导出模块
|
||||
exports('chatClient', ChatClient);
|
||||
});
|
||||
Reference in New Issue
Block a user