Files
xk-client-wx/utils/ws/websocket.js
2025-12-30 10:04:30 +08:00

263 lines
7.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export class WebSocketManager {
constructor(url) {
this.url = url;
this.socket = null;
this.isConnected = false;
this.isAuthenticated = false; // 是否已认证
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 1000;
this.messageHandlers = [];
this.pingInterval = null;
this.cachedToken = null; // 缓存的token
}
/**
* 获取当前用户的token
* @returns {string|null}
*/
getToken() {
// 优先使用缓存的token
if (this.cachedToken) {
return this.cachedToken;
}
// 从本地存储获取token
let token = uni.getStorageSync('token');
if (token) {
// 移除Bearer前缀如果有
token = token.replace('Bearer ', '');
this.cachedToken = token;
}
return token || null;
}
connect() {
if (this.isConnected) return;
console.log('正在连接WebSocket...');
this.socket = uni.connectSocket({
url: this.url,
success: () => {
console.log('WebSocket连接成功');
},
fail: (err) => {
console.error('WebSocket连接失败:', err);
this.handleReconnect();
}
});
this.socket.onOpen(() => {
console.log('WebSocket连接已打开');
this.isConnected = true;
this.reconnectAttempts = 0;
// 开始心跳检测
this.startHeartbeat();
// 绑定当前用户
const userId = uni.getStorageSync('userId');
if (userId) {
this.bindUser(userId);
}
});
this.socket.onMessage((res) => {
try {
const data = JSON.parse(res.data);
console.log('收到消息:', data);
this.handleMessage(data);
} catch (e) {
console.error('解析消息失败:', e);
}
});
this.socket.onClose((res) => {
console.log('WebSocket连接关闭:', res);
this.isConnected = false;
this.stopHeartbeat();
this.handleReconnect();
});
this.socket.onError((err) => {
console.error('WebSocket错误:', err);
});
}
/**
* 绑定用户携带token进行认证
* @param {string} userId 用户ID
*/
bindUser(userId) {
const token = this.getToken();
if (!token) {
console.error('绑定用户失败token不存在');
return false;
}
return this.send({
request_type: 'bind',
user_type: 'user',
sender_user_id: userId,
token: token
});
}
/**
* 发送消息自动携带token
* @param {Object} message 消息对象
* @param {boolean} requireAuth 是否需要认证默认true
* @returns {boolean} 是否发送成功
*/
send(message, requireAuth = true) {
if (!this.isConnected) {
console.error('WebSocket未连接');
return false;
}
try {
// 如果需要认证且消息中没有token自动添加token
if (requireAuth && !message.token && message.request_type !== 'ping') {
const token = this.getToken();
if (token) {
message.token = token;
}
}
const messageStr = JSON.stringify(message);
this.socket.send({
data: messageStr,
success: () => {
console.log('消息发送成功:', message);
},
fail: (err) => {
console.error('消息发送失败:', err);
}
});
return true;
} catch (err) {
console.error('发送消息出错:', err);
return false;
}
}
addMessageHandler(handler) {
this.messageHandlers.push(handler);
}
removeMessageHandler(handler) {
const index = this.messageHandlers.indexOf(handler);
if (index !== -1) {
this.messageHandlers.splice(index, 1);
}
}
/**
* 获取用户ID并绑定客户端携带token
* @returns {string|null} 用户ID
*/
getUserIdAndBindClientId() {
const userId = uni.getStorageSync('user_id'); // 实际项目中从存储获取
const token = this.getToken();
if (userId == null || userId === 'user-' || userId === '') {
// 等待10秒后重试
setTimeout(() => {
this.getUserIdAndBindClientId();
}, 10000)
} else if (!token) {
// token不存在等待后重试
console.warn('token不存在等待后重试绑定');
setTimeout(() => {
this.getUserIdAndBindClientId();
}, 5000)
} else {
const bindMessage = {
request_type: 'bind',
user_type: 'user',
sender_user_id: `user-${userId}`,
token: token
};
this.send(bindMessage);
}
return userId;
}
handleMessage(data) {
// 处理认证响应
if (data.auth_status !== undefined) {
if (data.auth_status === 'success') {
console.log('WebSocket认证成功');
this.isAuthenticated = true;
} else if (data.auth_status === 'failed') {
console.error('WebSocket认证失败:', data.message);
this.isAuthenticated = false;
// 清除缓存的token
this.cachedToken = null;
} else if (data.auth_status === 'token_expired') {
console.warn('Token已过期需要重新认证');
this.isAuthenticated = false;
this.cachedToken = null;
// 通知应用层token过期
uni.$emit('ws-token-expired');
// 尝试重新绑定
this.getUserIdAndBindClientId();
}
return;
}
// 如果对象中有clientId则给websocket发送绑定请求
if (data.clientId) {
this.getUserIdAndBindClientId(data.clientId);
}
// 处理聊天消息或其他消息
this.messageHandlers.forEach(handler => {
try {
handler(data);
} catch (e) {
console.error('消息处理出错:', e);
}
});
}
startHeartbeat() {
this.pingInterval = setInterval(() => {
if (this.isConnected) {
this.send({type: 'ping'});
}
}, 300000);
}
stopHeartbeat() {
if (this.pingInterval) {
clearInterval(this.pingInterval);
this.pingInterval = null;
}
}
handleReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.log('已达到最大重连次数');
return;
}
setTimeout(() => {
this.reconnectAttempts++;
this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000);
console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
this.connect();
}, this.reconnectDelay);
}
disconnect() {
if (this.socket) {
this.socket.close();
this.socket = null;
}
this.isConnected = false;
this.stopHeartbeat();
}
}
// 全局WebSocket实例
export const webSocketManager = new WebSocketManager('ws://127.0.0.1:12080/ws');
// export const webSocketManager = new WebSocketManager('wss://api.ws.g.xiaokang88.com/ws');