import qs from 'qs'; // 导入我们之前改写的日志模块 /** * 验证WebSocket服务器URL格式 * @param {string} wsUrl * @returns {boolean} */ function validateWsUrl(wsUrl) { if (!wsUrl) { console.log('WebSocket服务器地址不能为空', 'error'); return false; } if (!wsUrl.startsWith('ws://') && !wsUrl.startsWith('wss://')) { console.log('URL格式错误,必须以ws://或wss://开头', 'error'); return false; } return true; } /** * 验证设备配置 * @param {object} config * @returns {boolean} */ function validateConfig(config) { if (!config.deviceMac) { console.log('设备MAC地址不能为空', 'error'); return false; } if (!config.clientId) { console.log('客户端ID不能为空', 'error'); return false; } return true; } /** * 发送OTA请求,验证设备状态 * @param {string} otaUrl * @param {object} config * @returns {Promise} - 返回一个Promise,resolve为true表示成功,false表示失败 */ async function sendOTA(otaUrl, config) { console.log('正在进行OTA状态验证...', 'info'); try { // 【修正】uni.request 的 await 返回的是单个 res 对象 const res = await uni.request({ url: otaUrl, method: 'POST', header: { 'Content-Type': 'application/json', 'Device-Id': config.deviceMac, 'Client-Id': config.clientId }, data: { version: 0, uuid: '', application: { name: 'xiaozhi-uniapp-test', version: '1.0.0', compile_time: '2025-04-16 10:00:00', idf_version: '4.4.3', elf_sha256: '1234567890abcdef1234567890abcdef1234567890abcdef' }, ota: { label: 'xiaozhi-uniapp-test' }, board: { type: 'xiaozhi-uniapp-test', ssid: 'xiaozhi-uniapp-test', rssi: 0, channel: 0, ip: '192.168.1.1', mac: config.deviceMac }, flash_size: 0, minimum_free_heap_size: 0, mac_address: config.deviceMac, chip_model_name: '', chip_info: { model: 0, cores: 0, revision: 0, features: 0 }, partition_table: [{ label: '', type: 0, subtype: 0, address: 0, size: 0 }] } }); console.log(res,'res'); let _res = res[1]||null; if(!_res) return false; if (_res.statusCode >= 200 && _res.statusCode < 300) { // 【新增判断】检查返回的数据体中是否包含error字段 if (_res.data && _res.data.error) { // 如果有error字段,说明业务逻辑失败了 throw new Error(`OTA业务错误: ${_res.data.error}`); } // 只有当状态码正确且没有业务错误时,才算真正成功 console.log(`OTA验证成功: ${JSON.stringify(_res.data)}`, 'success'); return true; } else { throw new Error(`HTTP错误: ${_res.statusCode}`); } } catch (err) { const errorMessage = err.errMsg || err.message || '未知网络错误'; console.log(`OTA验证失败: ${errorMessage}`, 'error'); return false; } } /** * 【修正版 - 使用qs库】构建带参数的URL * @param {string} baseUrl * @param {object} params * @returns {string} */ function buildUrlWithParams(baseUrl, params) { const paramString = qs.stringify(params, { addQueryPrefix: true }); // addQueryPrefix 会自动加上 '?' return baseUrl + paramString; } /** * 执行OTA检查并连接WebSocket服务器 * @param {string} otaUrl - OTA服务器地址 * @param {string} wsUrl - WebSocket服务器地址 * @param {object} config - 设备配置对象 * @returns {Promise} - 返回一个Promise。 * - 如果连接前置步骤成功,resolve为uniapp的SocketTask对象。 * - 如果任何步骤失败,resolve为null。 */ export async function webSocketConnect(otaUrl, wsUrl, config) { if (!validateWsUrl(wsUrl) || !validateConfig(config)) { return null; // 验证失败 } // 1. 执行OTA检查 console.log(otaUrl, config); const otaOk = await sendOTA(otaUrl, config); console.log(otaOk); if (!otaOk) { // OTA失败,直接返回,由调用方处理UI状态 return null; } // 2. 构建连接URL // 小程序环境没有内置URL对象,需要手动拼接 const params = { 'device-id': config.deviceMac, 'client-id': config.clientId }; const finalUrl = buildUrlWithParams(wsUrl, params); console.log(`准备连接WebSocket: ${finalUrl}`, 'info'); // 3. 使用 uni.connectSocket 替换 new WebSocket() // 它会立即返回一个 SocketTask 对象,连接过程是异步的 const socketTask = uni.connectSocket({ url: finalUrl, // success/fail回调只代表API调用是否成功,不代表连接是否成功 success: () => {}, fail: (err) => { console.log(`uni.connectSocket API调用失败: ${JSON.stringify(err)}`, 'error'); } }); // 4. 返回 SocketTask 对象 // 调用此函数的组件需要负责监听 onOpen, onMessage, onError, onClose 事件 return socketTask; }