流式数据平台兼容性处理(H5, 小程序,app)

📅 发布时间:2026/9/24 17:37:47
流式数据平台兼容性处理(H5, 小程序,app)
const getStreamMessage async (currentPicture, fileId) { isPageLoading.value true allChoose.value true isFinish.value false uni.showLoading({ title: 加载中..., mask: true }); // 兼容处理结束 isLoading.value true; // 请求获取数据 const data { fileId: fileId, filePath: currentPicture } try { await homeworkCheckApi(data, (json) { activeTabsShow.value 0 updateRealTimeDisplay(json) }) } catch (error) { isFinish.value true; isLoading.value false; uni.showToast({ title: 网络错误请稍后再试, icon: none }) console.log(error) } } const updateRealTimeDisplay (data) { allChoose.value false isFinish.value true if (data.result) { topicList.value.push(data.result) paperAnswer.value topicList.value } else if ( data.errorResult) { errorList.value data.errorResult } else if (data.token_type stop) { isComplete.value true } } export const homeworkCheckApi (data : any, onChunk) { return new Promise(async (resolve, reject) { try { // 1. 先将图片转换为base64 const base64Data await filePathToBase64(data.filePath); console.log(filePath, data.filePath) // #ifdef H5 await h5StreamRequest(data, base64Data, onChunk, resolve, reject); // #endif // #ifdef APP-PLUS await appStreamRequest(data, base64Data, onChunk, resolve, reject); // #endif // #ifdef MP-WEIXIN await miniprogramStreamRequest(data, base64Data, onChunk, resolve, reject); // #endif } catch (error) { reject(error); } finally { uni.hideLoading(); } }); }; const appStreamRequest async ( data: any, base64Data: any, onChunk: (json: any) void, resolve: () void, reject: (error: any) void ): Promisevoid { return new Promise((innerResolve, innerReject) { const requestData { ...data, file: base64Data, }; const xhr plus.net ? new plus.net.XMLHttpRequest() : new XMLHttpRequest(); xhr.open(POST, ${config.baseURL}/education/model/checkHomeWork); xhr.setRequestHeader(Content-Type, application/json); xhr.setRequestHeader(Authorization, Bearer ${uni.getStorageSync(token)}); xhr.setRequestHeader(Accept, text/event-stream); let receivedLength 0; let buffer ; let fullResponse ; xhr.onreadystatechange function() { console.log(XHR状态: ${xhr.readyState}, 状态码: ${xhr.status}); if (xhr.readyState 3) { // 正在接收数据 if (xhr.responseText xhr.responseText.length fullResponse.length) { const newData xhr.responseText.substring(fullResponse.length); fullResponse xhr.responseText; console.log(收到新数据:, newData.length, 字符); if (newData) { buffer newData; buffer processBuffer(buffer, onChunk); } } } if (xhr.readyState 4) { if (xhr.status 200) { console.log(请求完成状态码:, xhr.status); // 处理最后剩余的数据 if (buffer.trim()) { processBuffer(buffer, onChunk); } resolve(); // 调用外层resolve innerResolve(); // 调用内层resolve } else { const error new Error(Request failed with status ${xhr.status}); reject(error); innerReject(error); } } }; // 使用 onprogress 事件处理流式数据 // xhr.onprogress function(event) { // // 获取新增的数据 // const newData xhr.responseText.substring(receivedLength); // receivedLength xhr.responseText.length; // console.log(收到新数据块:, newData.length, 字符); // if (newData) { // buffer newData; // // 处理缓冲区中的数据 // buffer processBuffer(buffer, onChunk); // } // }; xhr.onerror function() { const error new Error(Network error); reject(error); innerReject(error); }; console.log(开始发送APP流式请求...); xhr.send(JSON.stringify(requestData)); }); }; // 处理缓冲区的通用函数 const processBuffer (buffer: string, onChunk: (json: any) void): string { const lines buffer.split(\n); const remainingLines []; for (const line of lines) { const trimmedLine line.trim(); if (!trimmedLine) continue; if (trimmedLine.startsWith(data:)) { const dataStr trimmedLine.substring(5).trim(); // 过滤掉所有非JSON数据 if (!dataStr || dataStr [DONE] || dataStr Process || !dataStr.startsWith({) || !dataStr.endsWith(})) { console.log(跳过非JSON数据:, dataStr); continue; } try { const jsonData JSON.parse(dataStr); console.log(解析到有效数据:, jsonData); if (typeof onChunk function) { onChunk(jsonData); } } catch (e) { console.warn(解析 JSON 失败跳过该数据块:, dataStr); // 如果解析失败保留这一行到剩余缓冲区可能是不完整的JSON remainingLines.push(line); continue; } } else { // 不以data:开头的行保留到剩余缓冲区 remainingLines.push(line); } } // 返回剩余的不完整行 return remainingLines.join(\n); }; // 小程序环境的流式请求保留你的原始逻辑 const miniprogramStreamRequest (data, base64Data, onChunk, resolve, reject) { return new Promise((resolve, reject) { const requestTask uni.request({ url: ${config.baseURL}/education/model/checkHomeWork, method: POST, header: { Content-Type: application/json, Accept: text/event-stream, Authorization: Bearer ${uni.getStorageSync(token)} }, data: { ...data, file: data:image/png;base64,${base64Data}, }, enableChunked: true, responseType: arraybuffer, success: (res) { uni.hideLoading(); console.log(数据接收成功) if (res.statusCode 200) { resolve(res.data); } else { reject(new Error(请求失败: ${res.statusCode})); } }, fail: () { uni.hideLoading(); reject(new Error(请求失败)); }, }); let buffer ; // UTF-8 解码函数 function utf8ArrayToString(uint8Array) { let str ; let i 0; const len uint8Array.length; while (i len) { const byte1 uint8Array[i]; if (byte1 0x80) { str String.fromCharCode(byte1); i; } else if (byte1 0xc0 byte1 0xe0) { if (i 1 len) break; const byte2 uint8Array[i 1]; str String.fromCharCode(((byte1 0x1f) 6) | (byte2 0x3f)); i 2; } else if (byte1 0xe0 byte1 0xf0) { if (i 2 len) break; const byte2 uint8Array[i 1]; const byte3 uint8Array[i 2]; str String.fromCharCode( ((byte1 0x0f) 12) | ((byte2 0x3f) 6) | (byte3 0x3f) ); i 3; } else if (byte1 0xf0 byte1 0xf8) { if (i 3 len) break; const byte2 uint8Array[i 1]; const byte3 uint8Array[i 2]; const byte4 uint8Array[i 3]; let codePoint ((byte1 0x07) 18) | ((byte2 0x3f) 12) | ((byte3 0x3f) 6) | (byte4 0x3f); if (codePoint 0xffff) { codePoint - 0x10000; str String.fromCharCode(0xd800 | (codePoint 10)); str String.fromCharCode(0xdc00 | (codePoint 0x3ff)); } else { str String.fromCharCode(codePoint); } i 4; } else { i; } } return str; } // 检查是否支持分块接收 if (requestTask typeof requestTask.onChunkReceived function) { requestTask.onChunkReceived((chunk) { const uint8Array new Uint8Array(chunk.data); const textChunk utf8ArrayToString(uint8Array); buffer textChunk; const chunks buffer.split(\n\n); for (let i 0; i chunks.length - 1; i) { const chunkData chunks[i]; if (chunkData.startsWith(data:)) { try { const dataStr chunkData.substring(5).trim(); if (dataStr dataStr ! [DONE]) { const json JSON.parse(dataStr); if (typeof onChunk function) { onChunk(json); } } if (dataStr [DONE]) { console.log(数据接收完毕) } } catch (e) { console.warn(解析 JSON 失败, e); } } } buffer chunks[chunks.length - 1]; }); } else { console.warn(当前环境不支持流式响应); } }); }; // H5 环境的流式请求 const h5StreamRequest (data, base64Data, onChunk, resolve, reject) { return new Promise((resolve, reject) { const requestData { ...data, file: data:image/png;base64,${base64Data}, }; // 使用 Fetch API 进行流式请求 fetch(${config.baseURL}/education/model/checkHomeWork, { method: POST, headers: { Content-Type: application/json, Accept: text/event-stream, Authorization: Bearer ${uni.getStorageSync(token)} }, body: JSON.stringify(requestData), }) .then(response { if (!response.ok) { throw new Error(HTTP error! status: ${response.status}); } const reader response.body.getReader(); const decoder new TextDecoder(); let buffer ; function readStream() { reader.read().then(({ done, value }) { if (done) { resolve(); return; } // 解码并处理数据块 const chunk decoder.decode(value, { stream: true }); buffer chunk; // 按 SSE 格式处理数据 const lines buffer.split(\n); buffer lines.pop() || ; // 保留最后不完整的行 for (const line of lines) { const trimmed line.trim(); if (trimmed.startsWith(data:)) { try { const dataStr trimmed.substring(5).trim(); if (dataStr dataStr ! [DONE]) { const json JSON.parse(dataStr); if (typeof onChunk function) { onChunk(json); } } } catch (e) { console.warn(解析 JSON 失败, e, 数据:, trimmed); } } } // 继续读取 readStream(); }).catch(reject); } readStream(); }) .catch(reject); }); }; // 将文件路径转换为base64 const filePathToBase64 (filePath) { return new Promise((resolve, reject) { // #ifdef MP-WEIXIN // 小程序平台 uni.getFileSystemManager().readFile({ filePath: filePath, encoding: base64, success: (res) { resolve(res.data); }, fail: reject, }); // #endif // #ifdef H5 // H5 平台 convertImageToBase64H5(filePath) .then(resolve) .catch(reject); // #endif // #ifdef APP-PLUS convertImageToBase64App(filePath) .then(resolve) .catch(reject); // #endif }); }; const convertImageToBase64App (filePath) { return new Promise((resolve, reject) { // 使用HTML5 API plus.io.resolveLocalFileSystemURL(filePath, (entry) { entry.file((file) { const fileReader new plus.io.FileReader(); fileReader.onloadend (evt) { // 这里直接返回的就是完整的data URL resolve(evt.target.result); }; fileReader.onerror (error) { reject(new Error(文件读取失败: JSON.stringify(error))); }; fileReader.readAsDataURL(file); }, (error) { reject(new Error(获取文件失败: JSON.stringify(error))); }); }, (error) { reject(new Error(解析文件路径失败: JSON.stringify(error))); }); }); } // H5环境的实现 const convertImageToBase64H5 (url) { return new Promise((resolve, reject) { const img new Image(); img.crossOrigin anonymous; img.onload function () { const canvas document.createElement(canvas); const ctx canvas.getContext(2d); canvas.width img.width; canvas.height img.height; ctx.drawImage(img, 0, 0); try { const dataURL canvas.toDataURL(image/png); // 移除 data:image/png;base64, 前缀 const base64 dataURL.replace(/^data:image\/\w;base64,/, ); resolve(base64); } catch (error) { reject(error); } }; img.onerror reject; img.src url; }); };