|
|
@@ -0,0 +1,321 @@
|
|
|
+/**
|
|
|
+ * 骑手端WebSocket管理工具
|
|
|
+ * 统一管理WebSocket连接、消息处理和状态维护
|
|
|
+ *
|
|
|
+ * @author kxmall
|
|
|
+ * @date 2025-01-08
|
|
|
+ */
|
|
|
+
|
|
|
+class RiderWebSocketManager {
|
|
|
+ constructor() {
|
|
|
+ this.websocket = null;
|
|
|
+ this.heartbeatTimer = null;
|
|
|
+ this.reconnectTimer = null;
|
|
|
+ this.isManualClose = false; // 是否手动关闭
|
|
|
+ this.reconnectAttempts = 0; // 重连尝试次数
|
|
|
+ this.maxReconnectAttempts = 5; // 最大重连次数
|
|
|
+ this.reconnectInterval = 5000; // 重连间隔
|
|
|
+ this.heartbeatInterval = 30000; // 心跳间隔
|
|
|
+
|
|
|
+ // 消息处理回调
|
|
|
+ this.onMessageCallbacks = new Map();
|
|
|
+ this.onStatusChangeCallback = null;
|
|
|
+
|
|
|
+ // 绑定方法上下文
|
|
|
+ this.connect = this.connect.bind(this);
|
|
|
+ this.disconnect = this.disconnect.bind(this);
|
|
|
+ this.send = this.send.bind(this);
|
|
|
+ this.handleMessage = this.handleMessage.bind(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 连接WebSocket
|
|
|
+ * @param {Object} userInfo 用户信息
|
|
|
+ * @param {String} baseUrl 基础URL
|
|
|
+ */
|
|
|
+ connect(userInfo, baseUrl) {
|
|
|
+ if (!userInfo || !userInfo.id) {
|
|
|
+ console.error('用户信息不存在,无法连接WebSocket');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.websocket) {
|
|
|
+ console.warn('WebSocket已存在,先断开再重连');
|
|
|
+ this.disconnect();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建WebSocket连接地址
|
|
|
+ const socketUrl = `ws://${baseUrl.replace('http://', '').replace('https://', '')}/websocket/rider/${userInfo.id}`;
|
|
|
+ console.log('连接WebSocket:', socketUrl);
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.websocket = uni.connectSocket({
|
|
|
+ url: socketUrl,
|
|
|
+ success: () => {
|
|
|
+ console.log('WebSocket连接请求发送成功');
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ console.error('WebSocket连接请求失败:', err);
|
|
|
+ this._scheduleReconnect(userInfo, baseUrl);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ this._setupEventHandlers(userInfo, baseUrl);
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('创建WebSocket连接失败:', error);
|
|
|
+ this._scheduleReconnect(userInfo, baseUrl);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 设置事件处理器
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ _setupEventHandlers(userInfo, baseUrl) {
|
|
|
+ if (!this.websocket) return;
|
|
|
+
|
|
|
+ // 监听连接打开
|
|
|
+ this.websocket.onOpen(() => {
|
|
|
+ console.log('WebSocket连接已打开');
|
|
|
+ this.reconnectAttempts = 0; // 重置重连次数
|
|
|
+ this._startHeartbeat();
|
|
|
+ this._notifyStatusChange('connected');
|
|
|
+ });
|
|
|
+
|
|
|
+ // 监听消息
|
|
|
+ this.websocket.onMessage((res) => {
|
|
|
+ console.log('收到WebSocket消息:', res.data);
|
|
|
+ this.handleMessage(res.data);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 监听错误
|
|
|
+ this.websocket.onError((err) => {
|
|
|
+ console.error('WebSocket发生错误:', err);
|
|
|
+ this._notifyStatusChange('error', err);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 监听关闭
|
|
|
+ this.websocket.onClose((res) => {
|
|
|
+ console.log('WebSocket连接关闭:', res);
|
|
|
+ this._stopHeartbeat();
|
|
|
+ this._notifyStatusChange('disconnected');
|
|
|
+
|
|
|
+ // 如果不是手动关闭且用户仍然有效,则尝试重连
|
|
|
+ if (!this.isManualClose && userInfo && userInfo.state) {
|
|
|
+ this._scheduleReconnect(userInfo, baseUrl);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 断开WebSocket连接
|
|
|
+ */
|
|
|
+ disconnect() {
|
|
|
+ this.isManualClose = true;
|
|
|
+ this._stopHeartbeat();
|
|
|
+ this._stopReconnect();
|
|
|
+
|
|
|
+ if (this.websocket) {
|
|
|
+ this.websocket.close();
|
|
|
+ this.websocket = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('WebSocket手动断开');
|
|
|
+ this._notifyStatusChange('disconnected');
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 发送消息
|
|
|
+ * @param {String} message 消息内容
|
|
|
+ */
|
|
|
+ send(message) {
|
|
|
+ if (!this.websocket) {
|
|
|
+ console.warn('WebSocket未连接,无法发送消息');
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ this.websocket.send({
|
|
|
+ data: message,
|
|
|
+ success: () => {
|
|
|
+ console.log('WebSocket消息发送成功:', message);
|
|
|
+ },
|
|
|
+ fail: (err) => {
|
|
|
+ console.error('WebSocket消息发送失败:', err);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理接收到的消息
|
|
|
+ * @param {String} data 消息数据
|
|
|
+ */
|
|
|
+ handleMessage(data) {
|
|
|
+ try {
|
|
|
+ // 处理心跳响应
|
|
|
+ if (data === '连接成功' || data === 'pong') {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 解析JSON消息
|
|
|
+ const message = typeof data === 'string' ? JSON.parse(data) : data;
|
|
|
+
|
|
|
+ // 调用对应的消息处理回调
|
|
|
+ const callback = this.onMessageCallbacks.get(message.type);
|
|
|
+ if (callback && typeof callback === 'function') {
|
|
|
+ callback(message);
|
|
|
+ } else {
|
|
|
+ console.warn('未找到消息类型处理器:', message.type);
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('处理WebSocket消息失败:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 注册消息处理回调
|
|
|
+ * @param {String} messageType 消息类型
|
|
|
+ * @param {Function} callback 回调函数
|
|
|
+ */
|
|
|
+ onMessage(messageType, callback) {
|
|
|
+ this.onMessageCallbacks.set(messageType, callback);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 注册状态变化回调
|
|
|
+ * @param {Function} callback 回调函数
|
|
|
+ */
|
|
|
+ onStatusChange(callback) {
|
|
|
+ this.onStatusChangeCallback = callback;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 移除消息处理回调
|
|
|
+ * @param {String} messageType 消息类型
|
|
|
+ */
|
|
|
+ offMessage(messageType) {
|
|
|
+ this.onMessageCallbacks.delete(messageType);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清除所有回调
|
|
|
+ */
|
|
|
+ clearCallbacks() {
|
|
|
+ this.onMessageCallbacks.clear();
|
|
|
+ this.onStatusChangeCallback = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取连接状态
|
|
|
+ */
|
|
|
+ isConnected() {
|
|
|
+ return this.websocket !== null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 开始心跳
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ _startHeartbeat() {
|
|
|
+ this._stopHeartbeat();
|
|
|
+ this.heartbeatTimer = setInterval(() => {
|
|
|
+ this.send('ping');
|
|
|
+ }, this.heartbeatInterval);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 停止心跳
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ _stopHeartbeat() {
|
|
|
+ if (this.heartbeatTimer) {
|
|
|
+ clearInterval(this.heartbeatTimer);
|
|
|
+ this.heartbeatTimer = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计划重连
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ _scheduleReconnect(userInfo, baseUrl) {
|
|
|
+ if (this.isManualClose || this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
|
+ console.log('不再尝试重连');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ this._stopReconnect();
|
|
|
+
|
|
|
+ this.reconnectAttempts++;
|
|
|
+ const delay = this.reconnectInterval * this.reconnectAttempts;
|
|
|
+
|
|
|
+ console.log(`${delay/1000}秒后尝试第${this.reconnectAttempts}次重连`);
|
|
|
+
|
|
|
+ this.reconnectTimer = setTimeout(() => {
|
|
|
+ console.log(`开始第${this.reconnectAttempts}次重连`);
|
|
|
+ this.connect(userInfo, baseUrl);
|
|
|
+ }, delay);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 停止重连
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ _stopReconnect() {
|
|
|
+ if (this.reconnectTimer) {
|
|
|
+ clearTimeout(this.reconnectTimer);
|
|
|
+ this.reconnectTimer = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 通知状态变化
|
|
|
+ * @private
|
|
|
+ */
|
|
|
+ _notifyStatusChange(status, data = null) {
|
|
|
+ if (this.onStatusChangeCallback && typeof this.onStatusChangeCallback === 'function') {
|
|
|
+ this.onStatusChangeCallback(status, data);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重置连接状态(用于手动重连)
|
|
|
+ */
|
|
|
+ reset() {
|
|
|
+ this.isManualClose = false;
|
|
|
+ this.reconnectAttempts = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 销毁实例
|
|
|
+ */
|
|
|
+ destroy() {
|
|
|
+ this.disconnect();
|
|
|
+ this.clearCallbacks();
|
|
|
+ this._stopReconnect();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 消息类型常量
|
|
|
+export const MESSAGE_TYPES = {
|
|
|
+ NEW_ORDER: 'NEW_ORDER',
|
|
|
+ ORDER_CANCEL: 'ORDER_CANCEL',
|
|
|
+ ORDER_STATUS_UPDATE: 'ORDER_STATUS_UPDATE',
|
|
|
+ SYSTEM_MESSAGE: 'SYSTEM_MESSAGE'
|
|
|
+};
|
|
|
+
|
|
|
+// 连接状态常量
|
|
|
+export const CONNECTION_STATUS = {
|
|
|
+ CONNECTING: 'connecting',
|
|
|
+ CONNECTED: 'connected',
|
|
|
+ DISCONNECTED: 'disconnected',
|
|
|
+ ERROR: 'error'
|
|
|
+};
|
|
|
+
|
|
|
+// 导出单例实例
|
|
|
+const riderWebSocketManager = new RiderWebSocketManager();
|
|
|
+
|
|
|
+export default riderWebSocketManager;
|