收藏
回答

在企业微信小程序无法使用websocket,同样的代码在微信小程序中是正常的,请帮忙指教一下?

<template>

  <view class="ai-chat-container">

    <view class="header">

      <text class="title">多轮对话</text>

    </view>

    <view class="input-group">

      <view class="input-row">

        <input type="text" v-model="userId" placeholder="用户ID (必填)" />

        <input type="text" v-model="userName" placeholder="用户名称" />

        <input type="text" v-model="sessionid" placeholder="会话ID" />

      </view>

      <view class="input-row">

        <input type="text" v-model="collection" placeholder="知识库名称(可选)" />

      </view>

      <view class="input-row">

        <textarea v-model="messageInput" placeholder="输入您的问题(支持多行输入)" class="message-input" />

      </view>

      <button @click="handleSend" :disabled="isProcessing" class="send-btn">发送请求</button>

    </view>


    <view class="response-container">

      <text>{{ responseBuffer }}</text>

    </view>

    <view class="status-bar">

      <text>连接状态:{{ connectionStatus }}</text>

    </view>

    <view v-if="errorMessage" class="error-message">

      <text>{{ errorMessage }}</text>

    </view>

  </view>

</template>


<script>

import { ref, onMounted, onUnmounted } from 'vue';

import StompWx from '@/utils/stomp-wx.js';


export default {

  name: 'AIChat',

  setup() {

    // 状态变量

    const userId = ref('Q1100325');

    const userName = ref('macroswang');

    const sessionid = ref('6624663456435635463456435');

    const collection = ref('');

    const messageInput = ref('你好');

    const responseBuffer = ref('');

    const connectionStatus = ref('未连接');

    const errorMessage = ref('');

    const isProcessing = ref(false);


    // STOMP客户端

    let stompClient = null;

    let currentSubscription = null;

    let currentUserId = null;

    

    // 添加心跳检测机制

    let heartbeatInterval = null;


    // 连接WebSocket

    const connectWebSocket = async (userIdValue) => {

      return new Promise((resolve, reject) => {

        // 如果已连接且用户ID相同,直接复用连接

        if (stompClient && stompClient.connected && currentUserId === userIdValue) {

          resolve();

          return;

        }


        // 断开旧连接(如果存在)

        if (stompClient && stompClient.connected) {

          stompClient.disconnect();

        }


        // 创建新连接

        const wsUrl = import.meta.env.VITE_APP_BASE_WEBSOCKET_HOST;

        console.log("WebSocket URL:", wsUrl);

        

        // 确保URL使用wss协议(企业微信要求)

        const secureWsUrl = wsUrl.replace('ws://', 'wss://');


        // 检测是否在企业微信环境

        let isEnterpriseWx = false;

        let cropId = '';


        // 基础请求头

        const headers = {

          'userId': userIdValue,

          'crop_id':'ww40f48251ec0cd3b2',

          'Authorization': 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJcImFIWkJoYUJGaWNkR0NkRVhYbVU3UUtNMTZUVFwiIiwiZXhwIjoxNzQyMDE3OTA5fQ.yx4vkjbZEDt_den6V04CXSPAPD9qiyix4apJlzM4SXE'

        };

        

        // 添加其他企业微信可能需要的头信息

        headers['x-client-type'] = 'wxwork';

        headers['x-client-version'] = '1.2';

        

        console.log("创建连接,使用头信息:", headers);

        

        // 创建STOMP客户端,针对企业微信环境优化配置

        stompClient = new StompWx(secureWsUrl, { 

          headers,

          // 企业微信环境下的特殊配置

          heartbeat: {

            outgoing: isEnterpriseWx ? 5000 : 10000, // 企业微信环境下更频繁的心跳

            incoming: isEnterpriseWx ? 5000 : 10000

          },

          debug: true,

          // 添加重连配置

          reconnectDelay: isEnterpriseWx ? 2000 : 5000,

          maxReconnectAttempts: isEnterpriseWx ? 10 : 5

        });

        

        console.log("STOMP客户端创建完成,准备连接");

        

        // 减少连接超时时间

        let connectionTimeout = setTimeout(() => {

          if (!stompClient.connected) {

            console.error("WebSocket连接超时");

            showError("连接超时,正在重试...");

            

            // 超时后自动重试一次

            clearTimeout(connectionTimeout);

            stompClient.disconnect();

            

            // 短暂延迟后重试

            setTimeout(() => {

              console.log("尝试重新连接...");

              // 使用新的客户端重试

              stompClient = new StompWx(secureWsUrl, { 

                headers,

                heartbeat: {

                  outgoing: 5000, // 进一步减少心跳间隔

                  incoming: 5000

                },

                debug: true

              });

              

              stompClient.connect(() => {

                console.log("重试连接成功");

                currentUserId = userIdValue;

                updateStatus('已连接(重试成功)');

                setupSubscription(userIdValue);

                resolve();

              }, (retryError) => {

                console.error("重试连接失败:", retryError);

                showError(`连接失败,请检查网络环境或刷新页面`);

                reject(new Error("重试连接失败"));

              });

            }, 2000);

          }

        }, isEnterpriseWx ? 8000 : 10000); // 企业微信环境下超时时间更短

        

        console.log("开始连接WebSocket...");

        stompClient.connect(() => {

          console.log("WebSocket连接成功");

          clearTimeout(connectionTimeout);

          currentUserId = userIdValue;

          updateStatus('已连接');

          setupSubscription(userIdValue);

          resolve();

        }, (error) => { // 添加错误回调

          console.error("连接错误:", error);

          clearTimeout(connectionTimeout);

          showError(`连接失败: ${error?.headers?.message || '未知错误'}`);

          

          setTimeout(() => {

            updateStatus('正在重连...');

            connectWebSocket(userIdValue).catch(err => {

              console.error("重连失败:", err);

            });

          }, 3000);

          

          reject(error);

        });

      });

    };


    // 设置消息订阅

    const setupSubscription = (userIdValue) => {

      if (currentSubscription) {

        try {

          stompClient.unsubscribe(currentSubscription);

        } catch (e) {

          console.warn("取消订阅失败:", e);

        }

        currentSubscription = null;

      }


      const sessionidValue = sessionid.value.trim();

      console.log("检查订阅" + sessionidValue);


      const destination = '/user/' + sessionidValue + '/topic/data';

      console.log("准备订阅:", destination);

      

      try {

        currentSubscription = stompClient.subscribe(destination, (message) => {

          console.log("收到消息:", message);

          

          // 添加消息接收检测

          if (!message) {

            console.warn("接收到空消息");

            return;

          }

          

          if (!message.content) {

            console.warn("消息内容为空:", message);

            // 尝试解析消息体

            try {

              const body = message.body || message.data;

              if (body) {

                handleStreamMessage(body);

                return;

              }

            } catch (e) {

              console.error("解析消息失败:", e);

            }

            return;

          }

          

          handleStreamMessage(message.content);

        });

        

        // 添加订阅确认日志

        console.log("已订阅目标:", destination, "订阅ID:", currentSubscription);

        

        // 发送一个测试消息确认连接状态

        setTimeout(() => {

          if (stompClient && stompClient.connected) {

            console.log("发送测试消息");

            try {

              stompClient.send("/app/ping", JSON.stringify({

                userId: userIdValue,

                timestamp: Date.now()

              }), {});

            } catch (e) {

              console.error("发送测试消息失败:", e);

            }

          } else {

            console.warn("无法发送测试消息,连接未就绪");

          }

        }, 1000);

      } catch (e) {

        console.error("订阅失败:", e);

        showError("订阅失败,请重试");

      }

    };


    // 处理发送请求

    const handleSend = async () => {

      const requestData = validateInputs();

      if (!requestData) return;


      try {

        // 确保连接的是当前用户

        if (currentUserId !== requestData.userId) {

          await connectWebSocket(requestData.userId);

        }


        isProcessing.value = true;

        updateStatus('连接中...');


        if (!stompClient || !stompClient.connected) {

          await connectWebSocket(requestData.userId);

        }


        sendRequest(requestData);

        clearResponse();

      } catch (error) {

        showError(`请求失败: ${error.message}`);

        resetState();

      }

    };


    // 验证输入

    const validateInputs = () => {

      const userIdValue = userId.value.trim();

      const messageValue = messageInput.value.trim();


      if (!userIdValue) {

        showError("用户ID不能为空");

        return null;

      }


      if (!messageValue) {

        showError("消息内容不能为空");

        return null;

      }


      return {

        userId: userIdValue,

        userName: userName.value.trim() || '匿名用户',

        messages: [{

          role: "user",

          content: messageValue

        }],

        sessionid: sessionid.value.trim(),

        collection: collection.value.trim()

      };

    };


    // 发送请求体

    const sendRequest = (data) => {

      const headers = {

        'userId': data.userId,

      };

      stompClient.send("/app/streamData", JSON.stringify(data), headers);

      updateStatus('等待响应...');

    };


    // 处理流式响应

    const handleStreamMessage = (chunk) => {

      console.log("收到数据块:", chunk);

      

      if (!chunk) {

        console.warn("收到空数据块");

        return;

      }

      

      if (chunk === '流数据发送完毕') {

        finalizeResponse();

        console.log("发送完毕");

        return;

      }


      // 尝试解析JSON格式的消息

      try {

        const jsonData = JSON.parse(chunk);

        if (jsonData.content) {

          responseBuffer.value += jsonData.content;

          return;

        }

      } catch (e) {

        // 不是JSON格式,按普通文本处理

      }


      responseBuffer.value += chunk;

    };


    // 完成处理

    const finalizeResponse = () => {

      updateStatus('响应完成');

      console.log('完整响应内容:', responseBuffer.value);

      resetState();

    };


    // 重置状态

    const resetState = () => {

      isProcessing.value = false;

    };


    // 清空响应区

    const clearResponse = () => {

      responseBuffer.value = '';

    };


    // 更新状态显示

    const updateStatus = (text) => {

      connectionStatus.value = text;

    };


    // 显示错误信息

    const showError = (message) => {

      errorMessage.value = message;

      setTimeout(() => {

        errorMessage.value = "";

      }, 5000);

    };


    onMounted(() => {

      // 设置心跳检测,保持连接活跃

      heartbeatInterval = setInterval(() => {

        if (stompClient && stompClient.connected) {

          try {

            stompClient.send("/app/heartbeat", JSON.stringify({

              timestamp: Date.now(),

              userId: userId.value

            }), {});

            console.log("发送心跳");

          } catch (e) {

            console.error("发送心跳失败:", e);

            // 心跳失败,尝试重连

            if (currentUserId) {

              console.log("心跳失败,尝试重连");

              connectWebSocket(currentUserId).catch(err => {

                console.error("心跳重连失败:", err);

              });

            }

          }

        }

      }, 15000); // 减少到15

    });


    // 组件卸载时断开连接

    onUnmounted(() => {

      if (heartbeatInterval) {

        clearInterval(heartbeatInterval);

      }

      

      if (stompClient && stompClient.connected) {

        stompClient.disconnect();

        currentUserId = null;

      }

    });


    return {

      userId,

      userName,

      sessionid,

      collection,

      messageInput,

      responseBuffer,

      connectionStatus,

      errorMessage,

      isProcessing,

      handleSend

    };

  }

};

</script>


<style>

.ai-chat-container {

  font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;

  max-width: 750rpx;

  margin: 20rpx auto;

  padding: 20rpx;

  background-color: #f5f5f5;

}


.header {

  margin-bottom: 20rpx;

}


.title {

  font-size: 36rpx;

  font-weight: bold;

}


.input-group {

  margin-bottom: 20rpx;

  background: white;

  padding: 20rpx;

  border-radius: 8rpx;

  box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);

}


.input-row {

  margin: 10rpx 0;

  display: flex;

  gap: 10rpx;

}


input {

  flex: 1;

  padding: 16rpx;

  border: 1rpx solid #ddd;

  border-radius: 4rpx;

  min-width: 120rpx;

}


.message-input {

  width: 100%;

  min-height: 120rpx;

  padding: 20rpx;

  border: 1rpx solid #ddd;

  border-radius: 4rpx;

}


.send-btn {

  padding: 20rpx 40rpx;

  background: #2196F3;

  color: white;

  border: none;

  border-radius: 4rpx;

  margin-top: 10rpx;

}


.send-btn:active {

  background: #1976D2

}


.response-container {

  background: white;

  padding: 20rpx;

  border-radius: 8rpx;

  min-height: 400rpx;

  max-height: 1000rpx;

  overflow-y: auto;

  white-space: pre-wrap;

  box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.1);

  line-height: 1.6;

}


.status-bar {

  margin-top: 15rpx;

  color: #666;

  font-size: 28rpx;

}


.error-message {

  color: #f44336;

  margin: 10rpx 0;

  font-size: 28rpx;

}

</style>


// 新版导入方式

import { Client } from '@stomp/stompjs'

import '@/utils/text-encoder-polyfill.js'

// 微信小程序 WebSocket 适配器

class WxWebSocket {

  constructor(url) {

    this.url = url

    this.socketTask = null

    this.onopen = null

    this.onmessage = null

    this.onclose = null

    this.onerror = null

  }


  connect() {

    try {

      console.log('正在连接到 WebSocket:', this.url)


      this.socketTask = uni.connectSocket({

        url: this.url,

        header: {

          'content-type': 'application/json'

        },

        success: () => console.log('WebSocket 连接请求已发送'),

        fail: err => {

          console.error('WebSocket 连接失败', err)

          if (this.onerror) this.onerror(err)

        }

      })


      if (this.socketTask) {

        this.socketTask.onOpen(() => {

          console.log('WebSocket 已打开,准备触发 onopen 事件')

          if (this.onopen) {

            console.log('调用 onopen 回调')

            this.onopen({ target: this })

          }

        })


        this.socketTask.onMessage(res => {

          console.log('WebSocket 收到消息:', typeof res.data, res.data.length > 100 ? res.data.substring(0, 100) + '...' : res.data)


          // 确保消息数据是字符串

          let messageData = res.data

          if (typeof messageData !== 'string') {

            try {

              // 如果是对象,尝试转换为字符串

              messageData = JSON.stringify(messageData)

            } catch (e) {

              console.error('消息数据转换失败:', e)

            }

          }


          if (this.onmessage) {

            this.onmessage({

              data: messageData,

              target: this

            })

          }

        })


        this.socketTask.onClose(() => {

          console.log('WebSocket 已关闭')

          if (this.onclose) {

            this.onclose({ target: this })

          }

        })


        this.socketTask.onError(err => {

          console.error('WebSocket 错误:', err)

          if (this.onerror) {

            this.onerror({ error: err, target: this })

          }

        })

      } else {

        console.error('WebSocket 任务创建失败')

        if (this.onerror) {

          this.onerror({ error: new Error('WebSocket 任务创建失败'), target: this })

        }

      }

    } catch (error) {

      console.error('WebSocket 连接过程中发生异常:', error)

      if (this.onerror) {

        this.onerror({ error, target: this })

      }

    }

  }


  send(data) {

    console.log('发送数据:', typeof data, data.length > 100 ? data.substring(0, 100) + '...' : data)

    if (this.socketTask) {

      this.socketTask.send({

        data,

        success: () => console.log('数据发送成功'),

        fail: err => {

          console.error('发送消息失败', err)

          if (this.onerror) {

            this.onerror({ error: err, target: this })

          }

        }

      })

    } else {

      console.error('无法发送数据:socketTask 不存在')

    }

  }


  close() {

    if (this.socketTask) {

      this.socketTask.close({

        success: () => console.log('WebSocket 关闭成功'),

        fail: err => console.error('WebSocket 关闭失败', err)

      })

    }

  }


  addEventListener(event, callback) {

    console.log('添加事件监听器:', event)

    if (event === 'open') {

      this.onopen = callback

    } else if (event === 'message') {

      this.onmessage = callback

    } else if (event === 'close') {

      this.onclose = callback

    } else if (event === 'error') {

      this.onerror = callback

    }

  }


  removeEventListener(event, callback) {

    console.log('移除事件监听器:', event)

    if (event === 'open' && this.onopen === callback) {

      this.onopen = null

    } else if (event === 'message' && this.onmessage === callback) {

      this.onmessage = null

    } else if (event === 'close' && this.onclose === callback) {

      this.onclose = null

    } else if (event === 'error' && this.onerror === callback) {

      this.onerror = null

    }

  }

}


// StompWx - 包装 STOMP 客户端

class StompWx {

  constructor(url, options = {}) {

    this.url = url

    this.options = options

    this.client = null

    this.connected = false

    this.callbacks = {

      connect: [],

      error: [],

      disconnect: []

    }

    this.subscriptions = {}


    this._init()

  }


  _init() {

    // 创建自定义的 WebSocket 适配器

    const webSocketFactory = () => {

      console.log('创建 WebSocket 连接...')

      const socket = new WxWebSocket(this.url)

      socket.connect()

      return socket

    }


    this.client = new Client({

      webSocketFactory,

      connectHeaders: this.options.headers || {},

      debug: str => console.log('[STOMP]', str),

      reconnectDelay: 5000,

      heartbeatIncoming: 4000,

      heartbeatOutgoing: 4000

    })


    this.client.onConnect = frame => {

      console.log('✅ STOMP 连接成功', frame)

      this.connected = true

      this.callbacks.connect.forEach(callback => callback(frame))

    }


    this.client.onStompError = frame => {

      console.error('❌ STOMP 连接错误', frame)

      this.callbacks.error.forEach(callback => callback(frame))

    }


    this.client.onDisconnect = () => {

      console.log('STOMP 连接已断开')

      this.connected = false

      this.callbacks.disconnect.forEach(callback => callback())

    }

  }


  connect(callback) {

    if (callback) {

      this.callbacks.connect.push(callback)

    }


    if (!this.client.active) {

      console.log('激活 STOMP 客户端...')

      this.client.activate()

    } else if (this.connected) {

      // 如果已经连接,直接调用回调

      callback && callback()

    }


    return this

  }


  disconnect(callback) {

    if (callback) {

      this.callbacks.disconnect.push(callback)

    }


    if (this.client.active) {

      this.client.deactivate()

    }


    return this

  }


  subscribe(destination, callback, headers = {}) {

    const id = Math.random().toString(36).substring(2, 15)


    const doSubscribe = () => {

      if (!this.connected) {

        console.warn('尚未连接,订阅将在连接后进行')

        return

      }


      console.log(`订阅主题: ${destination}`)

      const subscription = this.client.subscribe(

        destination,

        message => {

          console.log('收到订阅消息:', message)

          try {

            // 尝试解析 JSON

            const body = message.body

            let data

            try {

              data = JSON.parse(body)

            } catch (e) {

              data = body

            }

            callback({ id: message.headers.id, content: data })

          } catch (error) {

            console.error('处理订阅消息时出错:', error)

            callback({ error: error.message })

          }

        },

        headers

      )


      this.subscriptions[id] = subscription

      console.log('订阅成功,ID:', id)

    }


    if (this.connected) {

      doSubscribe()

    } else {

      // 如果尚未连接,添加连接回调

      this.callbacks.connect.push(doSubscribe)

    }


    return id

  }


  unsubscribe(id) {

    if (this.subscriptions[id]) {

      this.subscriptions[id].unsubscribe()

      delete this.subscriptions[id]

      console.log('取消订阅成功,ID:', id)

    }

  }


  send(destination, body, headers = {}) {

    if (!this.connected) {

      console.error('未连接,无法发送消息')

      return false

    }


    try {

      console.log('发送消息到:', destination)


      // 确保 body 是字符串

      let messageBody = body

      if (typeof body === 'object') {

        messageBody = JSON.stringify(body)

      }


      this.client.publish({

        destination: destination,

        headers: headers,

        body: messageBody

      })


      console.log('消息发送成功')

      return true

    } catch (error) {

      console.error('发送消息失败:', error)

      return false

    }

  }


  onError(callback) {

    if (callback) {

      this.callbacks.error.push(callback)

    }

    return this

  }

}


export default StompWx



回答关注问题邀请回答
收藏

1 个回答

  • 社区技术运营专员-Jahozheng
    社区技术运营专员-Jahozheng
    03-11

    你好,请移步企微官方讨论区:https://developer.work.weixin.qq.com/community/question

    03-11
    有用
    回复
登录 后发表内容