Python调C++三种方案对比:pybind11、ctypes、Python C API选型指南
2026/9/24 20:36:45
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 ): Promise<void> => { 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; }); };