微信小程序接受流返回
发表于:2025-04-29 |

前言

这阵子搞了个小程序,接入 deepseek 的,就学到了如何在小程序中接受流返回。本篇文章就来简单记录一下。

效果展示

这里给大家简单录个屏,我搞得还是比较简单的。所以使用了微信原生语法直接写的。

细节记录

这里有个坑,那就是流返回的时间普遍较长,你需要设置一下超时时间,微信小程序默认是 60s 的,改俩个地方。
这里也贴个文档:https://developers.weixin.qq.com/miniprogram/dev/framework/ability/network.html

app.json

1
2
3
4
5
6
7
8
9
{
"window": {
// xxxx
},
"networkTimeout": {
"request": 1800000,
"downloadFile": 1800000
}
}

请求的时候加上 timeout

这个就不多说了,看我后面内容的 request 就可以知道了

请求发送

这里开启enableChunked以及设置responseTypearraybuffer,然后大家重点看我接收数据的方式,这里我使用了res.onChunkReceived来进行接收,然后通过解密算法来得到数据,这里解密算法你如果要用的话可以直接使用,反正是固定的。后续就是我对于我后端返回内容的处理了,这个没啥好说的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// 使用wx.request发送请求
const res = wx.request({
url: `${baseURL}/wechat/stream`,
method: "POST",
data: param,
enableChunked: true,
responseType: "arraybuffer",
timeout: 1800000,
success: (res) => {
console.log("请求成功");
const lastIndex = updatedMessages.length - 1;
if (lastIndex >= 0) {
const updatedMessagesCopy = [...updatedMessages];
updatedMessagesCopy[lastIndex].isLoading = false;
this.setData({
messages: updatedMessagesCopy,
isLoading: false,
});
}
this.scrollToBottom();
},
fail: (error) => {
console.error("请求失败", error);
const lastIndex = updatedMessages.length - 1;
if (lastIndex >= 0) {
const updatedMessagesCopy = [...updatedMessages];
updatedMessagesCopy[lastIndex].isLoading = false;
this.setData({
messages: updatedMessagesCopy,
isLoading: false,
});
}
wx.showToast({
title: "发送失败,请重试",
icon: "none",
});
},
});

res.onChunkReceived((chunk) => {
console.log(chunk, "chunk");
let text = "";

try {
// 使用微信小程序的API解码
if (wx.arrayBufferToString) {
text = wx.arrayBufferToString(chunk.data);
} else {
// 手动解码UTF-8
const uint8Array = new Uint8Array(chunk.data);
let i = 0;
while (i < uint8Array.length) {
let byte = uint8Array[i++];
if (byte < 0x80) {
text += String.fromCharCode(byte);
} else if (byte < 0xe0) {
if (i < uint8Array.length) {
const byte2 = uint8Array[i++];
text += String.fromCharCode(((byte & 0x1f) << 6) | (byte2 & 0x3f));
}
} else if (byte < 0xf0) {
if (i + 1 < uint8Array.length) {
const byte2 = uint8Array[i++];
const byte3 = uint8Array[i++];
text += String.fromCharCode(
((byte & 0x0f) << 12) | ((byte2 & 0x3f) << 6) | (byte3 & 0x3f)
);
}
}
}
}

// 处理SSE格式数据
const lines = text.split("\n");
let isStop = false;
for (let line of lines) {
if (!line || line.startsWith("data:")) {
// 去除data:
line = line.replace("data:", "");
}
// 如果是空的,直接跳过
if (!line) {
continue;
}

try {
// 取response:"xx",取xx
const pattern = /"response":"(.*?)"/;
const result = line.match(pattern);
if (result) {
const content = result[1];
const lastIndex = updatedMessages.length - 1;

if (lastIndex >= 0) {
const updatedMessagesCopy = [...updatedMessages];
updatedMessagesCopy[lastIndex].isLoading = false;
updatedMessagesCopy[lastIndex].content += content;

const thinkContent = this.getThinkContent(
updatedMessagesCopy[lastIndex].content
);
const replyContent = this.getReplyContent(
updatedMessagesCopy[lastIndex].content
);

updatedMessagesCopy[lastIndex].thinkContent = thinkContent;

updatedMessagesCopy[lastIndex].replyContent = replyContent;

this.setData(
{
messages: updatedMessagesCopy,
isLoading: false,
},
() => {
this.scrollToBottom();
}
);

// 更新聊天记录
const updatedChatList = this.data.chatList.map((chat) => {
if (chat.id === this.data.currentChatId) {
return { ...chat, messages: updatedMessagesCopy };
}
return chat;
});

this.setData({ chatList: updatedChatList });
}
}
} catch (error) {
console.error("JSON parse error:", error, line);
continue;
}
}
} catch (error) {
console.error("解码数据时出错:", error);
const lastIndex = updatedMessages.length - 1;
if (lastIndex >= 0) {
const updatedMessagesCopy = [...updatedMessages];
updatedMessagesCopy[lastIndex].isLoading = false;
this.setData({
messages: updatedMessagesCopy,
isLoading: false,
});
}
}
});
// 滚动到最新消息
this.scrollToBottom();

markdown 处理

如果你要做这个东西需要兼容 markdown 且你和我一样使用的原生微信小程序语法的话(小东西我都习惯开原生写),我建议使用towxml。用法也比较简单,这里给大家贴个地址
https://github.com/sbfkcel/towxml

使用方式

下载下来之后,安装依赖,然后 npm run build,把打包出来的内容放到你的第三方文件夹下面,然后引用就可以了。

结语

本篇文章就到这里了,更多内容敬请期待,债见~

下一篇:
使用EventSource接受后端返回