出现的问题:1.ios苹果手机都没问题
2.安卓手机:开发版本没问题,在开发版本真机预览安卓手机也没问题。但是在体验版安卓手机真机测试不行。
安卓手机上流式数据参考资料吐完,后面的内容就会中断,直接走fail回调了。而且是必现的。
//这是 @/utils/chunk-res文件下的代码
/**
* 微信http流式响应处理
* ```
* // Example:
* const chunkRes = ChunkRes()
* // can`t use ref() to save task; it will lost task info
* const task = wx.request({
* //...other params
* enableChunked: true,
* success: (res) => {
* const lastResTexts:string[] | undefined = chunkRes.onComplateReturn()
* // dosomething
* }
* })
* task.onChunkReceived(res => {
* const resTexts:string[] | undefined = chunkRes.onChunkReceivedReturn(res)
* // dosomething
* })
* ```
* @returns
*/
export const ChunkRes = () => {
/**
* 分段返回开始
*/
const CHUNK_START = "data:";
/**
* 分段返回中断
*/
const SPLIT_WORD = "\ndata:";
/**
* 保存返回文本
*/
let lastText = "";
/**
* 保存解码异常的数据
*/
let lastData = new Uint8Array();
/**
* 返回数据转文本
* @param res
* @returns
*/
const getChunkText = (data) => {
// let data = res.data;
// console.log('getSeeResData:', data)
// 兼容处理,真机返回的的是 ArrayBuffer
if (data instanceof ArrayBuffer) {
data = new Uint8Array(data);
}
let text = data;
// Uint8Array转码
if (typeof data != "string") {
// 兼容处理 微信小程序不支持TextEncoder/TextDecoder
try {
//但是,请注意,如果数据量很大(比如你提供的Uint8Array有1163374个元素),使用`String.fromCharCode.apply(null, uint8Array)`可能会因为参数过多而报错(因为apply的参数个数有限制)
console.log("lastData", lastData);
text = decodeURIComponent(escape(String.fromCharCode(...lastData, ...data)));
lastData = new Uint8Array();
} catch (error) {
text = "";
console.log("解码异常", data);
// Uint8Array 拼接
let swap = new Uint8Array(lastData.length + data.length);
swap.set(lastData, 0);
swap.set(data, lastData.length);
// lastData = lastData.concat(data)
lastData = swap;
}
}
return text;
};
/**
* 判断是否被拆分
* @param text
* @returns
*/
const isStartString = (text) => {
return text.substring(0, 5) == CHUNK_START;
};
/**
* 对被合并的多段请求拆分
* @param text
*/
const splitText = (text) => {
return text
.replaceAll(`\n\n${SPLIT_WORD}`, `\n${SPLIT_WORD}`)
.replaceAll(`\n${SPLIT_WORD}`, `${SPLIT_WORD}`)
.split(SPLIT_WORD)
.filter((str) => !!str);
};
/**
* 返回数据集
* @param res
* @param onSuccess
*/
const onChunkReceived = (res, onSuccess) => {
let text = getChunkText(res);
console.log("onChunkReceived", text);
if (isStartString(text) && lastText) {
console.log("onSuccess", lastText);
onSuccess(splitText(removeStartText(lastText)));
// 存储本次的数据
lastText = text;
} else {
lastText = lastText + text;
}
};
/**
* 返回数据集(返回数据)
* @param res
* @param onSuccess
*/
const onChunkReceivedReturn = function (res) {
let text = getChunkText(res);
console.log("onChunkReceived", text);
if (isStartString(text) && lastText) {
// console.log("onSuccess", lastText);
// onSuccess();
let swap = lastText;
// 存储本次的数据
lastText = text;
return splitText(removeStartText(swap));
} else {
lastText = lastText + text;
}
};
/**
* 删除文本的开始的 data:
* @param text
* @returns
*/
const removeStartText = (text) => {
if (text.substring(0, CHUNK_START.length) == CHUNK_START) {
return text.substring(CHUNK_START.length);
}
return text;
};
/**
* 请求完成调用一下
* @param onSuccess
*/
const onComplate = (onSuccess) => {
if (lastText) {
onSuccess(splitText(removeStartText(lastText)));
lastText = "";
}
};
/**
* 请求完成调用一下(返回数据)
* @param onSuccess
*/
const onComplateReturn = () => {
if (lastText) {
let swap = lastText;
lastText = "";
return splitText(removeStartText(swap));
}
};
return {
getChunkText,
onChunkReceived,
onChunkReceivedReturn,
onComplateReturn,
onComplate
};
};
// 这是核心的实现类似sse流式数据对话封装的组件
// chat-sse-client 组件
<script>
// import { ChunkRes } from "chunk-res";
import { ChunkRes } from "@/utils/chunk-res";
let requestTask;
let isReqAborted = false; //设置请求中止标志
let chunkRes;
export default {
props: {},
data() {
return {
lastText: ""
};
},
methods: {
stopChat() {
console.log("requestTask", requestTask);
//设置终止标志
isReqAborted = true;
requestTask.offChunkReceived(this.listener);
requestTask.abort();
this.$emit("onFinish");
requestTask = null;
},
/**
* 开始chat对话
* @param body
* @param url
* @param headers
*/
startChat({ body, url, headers = {} }) {
isReqAborted = false;
chunkRes = ChunkRes();
requestTask = uni.request({
url: url,
method: "POST",
header: {
Accept: "text/event-stream",
...headers
},
data: body,
enableChunked: true,
responseType: "arraybuffer",
enableHttp2: true,
timeout: 1000 * 60 * 50,
success: (res) => {
//这个是流式数据的最后一条 会触发这个回调
if (this.isReqAbortedHandle("忽略success响应")) return;
const lastResTexts = chunkRes.onComplateReturn();
console.log("[ success lastResTexts] >", lastResTexts);
lastResTexts?.forEach((item) => {
this.$emit("onMessage", item);
});
// dosomething
},
fail: (error) => {
if (this.isReqAbortedHandle("忽略错误")) return;
console.log("[ error ] >", error);
this.$emit("onError", error);
},
complete: (res) => {
//完成了 success之后就走这个
if (this.isReqAbortedHandle("忽略完成事件")) return;
console.log("[ complete 请求完成 ] >", res);
this.$emit("onFinish");
}
});
requestTask.onChunkReceived(this.listener);
this.$emit("onOpen");
},
isReqAbortedHandle(info) {
if (isReqAborted) {
console.log(`请求已中止,${info}`);
return true;
}
return false;
},
listener(res) {
if (this.isReqAbortedHandle("忽略分块数据")) return;
const resTexts = chunkRes.onChunkReceivedReturn(res.data);
// console.log("[ resTexts ] >", resTexts);
resTexts?.forEach((item) => {
this.$emit("onMessage", item);
});
}
}
};
</script>
<template>
<view />
</template>
//调用
<chat-sse-client
ref="chatSSEClientRef"
@onOpen="openChat"
@onError="errorChat"
@onFinish="finishChat"
@onMessage="messageChat"></chat-sse-client>
