[toc]

前言

之前无聊做了一款工具app,需要使用到流式响应

因为想着快速启动,所以选择了客户端用uniapp 服务端用uncloud云开发

后来在实现unicloud流式响应给uniapp时,发现没法自定义响应,unicloud的函数只能返回数据,响应动作只能由框架操作,将你云函数响应的数据进行send。

方案一:Uni-push

后来查官网,发现也是有解决方案就是开通他们提供的uni-push服务,其中有一个信道对象ssechannel

我们可以在客户端创建信道,给信道绑定监听几种监听事件函数,比如信道接收消息事件、信道结束事件。

绑定了事件函数后,我们就可以把信道传给后端,后端拿到反序列化后,就可以通过send发送消息,通过end结束信道

这样在客户端那边的两个事件就可以接收到。

整天来说使用也很简单,如下:

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
// 客户端代码
export default {
data() {},
onLoad() {},
methods: {
async testSSE() {
const channel = new uniCloud.SSEChannel() // 创建消息通道

channel.on('message', (message) => { // 监听message事件
console.log('on message', message);
})
channel.on('end', (message) => { // 监听end事件,如果云端执行end时传了message,会在客户端end事件内收到传递的消息
console.log('on end', message);
})

await channel.open() // 等待通道开启

const res = await uniCloud.callFunction({
name: 'myapi',
data: {
channel: channel // 调用云函数时传入通道对象
}
})
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 云函数myapi代码
exports.main = async (event, context) => {
const channel = uniCloud.deserializeSSEChannel(event.channel)
await channel.write({
a: 1
})
await channel.write({
a: 2
})
await channel.write({
a: 3
})
await channel.write({
a: 4
})
await channel.end({
a: 5
})
return {}
};

在使用之后,也是实现了我的功能,接口实时写入,客户端可以相对实时接收。

但是后来发现一个问题:可能是且后台或者锁屏,或者就在使用中,各种各样的情况吧,可能会出现客户端接收不到了的情况,而且一但出现了这种情况后,客户端再第二次第三次触发testSSE…..也就是后续使用均不会收到消息。testSSE被触发,依然是新建信道传递给后端api,api依然进行写入没有任何异常,但是客户端就是监听不到,只能重启就好了。一但出现问题又只能重启,猜测可能是这个组件依赖在全局上有什么东西,一但断开就只能重启了。

方案二:接口迁移腾讯云开发

断断续续查了几周没解决,想到迁移接口吧,于是乎到腾讯云也是开了一个云开发,接口的逻辑代码都调通,准备进行流式响应时,又又遇到了之前的问题,它没给我原始响应对象,不能进行自定义响应。于是乎问了下客户客服说不支持(这点比起uncloud还是很好的,有技术在线可以问)

image-20240801234252420

方案三:接口迁移Serverless

于是乎我又去开通了serverless之前没用过,发现它好像介于传统开发和云开发之间,它的代码就是原技术的代码,比如你选着nodejs 或者 java 得到的就是你正常开发的目录结构,不像云开发还会封装一层有一定学习成本。它控制台就是一个在线vscode,代码以及目录结构就是你选用的技术,写完直接部署就行了。会给一个公网地址映射到你的项目服务端口。因为就是原代码不像云开发的封装了一层,所以很简单就能实现流式响应标准接口,不管是什么后端技术本身都是支持http全部类型的,只是云开发封装了一层没给你原response对象

本来以为到这里就结束了,但是没想到客户端又不支持,本来从来没有想过客户端不支持的,以为解决了接口就行了。

客户端使用了三种,第一种uniapp提供的uni.request有个enableChunked: true的配置开启,但是好像只是支持小程序。到app就找不到监听函数

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
 const response = uni.request({
url: 'Your requested address', // 请求地址
method: "Request method", // 你的请求方法
data: data,
header: {
"Accept": 'text/event-stream',
"Connection": "keep-alive"
},
responseType: "text",
enableChunked: true, // 开启流传输
success: (res) => {
resolve(res)
},
fail: (err) => {
reject(err)
},
})

// 返回请求头信息
response.onHeadersReceived((e)=>{
console.log(e);
})

// 成功回调 返回流传输信息 返回arrayBuffer
response.onChunkReceived((e)={
console.log(e);
})

第二种是fetch也是js自带的,在uniapp环境还是找不到

第三种用xhr完全没反应

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
const xhr = new XMLHttpRequest();
let buffer = '';

xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.setRequestHeader('Accept', 'text/event-stream');

xhr.onprogress = function() {
const newData = xhr.responseText.substr(buffer.length);
console.log(newData);
buffer += newData;
console.log(buffer);
};

xhr.onreadystatechange = function () {
if (xhr.readyState === XMLHttpRequest.LOADING) {
// 处理 HTTP 数据块
console.log(xhr.responseText)
}
}

xhr.onload = function() {
if (xhr.status === 200) {
resolve(buffer);
} else {
reject(new Error(`HTTP error! status: ${xhr.status}`));
}
};

xhr.onerror = function() {
console.log("error")
};

xhr.send(data ? JSON.stringify(data) : null);

后两种基本上就是想尝试用原生js的方式,也是失败告终.

方案四:换协议Websocket

既然不行的话(虽然很离谱不应该不支持的呀),现在就只能用websocket了,这个uniapp客户端应该支持吧。OKOK那再把接口改成websocket的,然后把输出的地方都替换。

然后改下客户端连接websocket,不同的平台uni提供的接口函数可能都不一样,目前测试在鸿蒙上ok

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
let socket = uni.connectSocket({
url: 'wss://you_api_url',
success: function (res) {
console.log('WebSocket 连接创建成功');
},
fail: function (err) {
console.error('WebSocket 连接创建失败', err);
},
complete: function () {
console.log('WebSocket 连接创建完成');
}
});
socket.onOpen(function () {
console.log("连接到了")
socket.send({
data: 'Hello from uni-app!'
});
});

socket.onMessage(function (res) {
console.log('收到服务器内容:' + JSON.stringify(res));
});

socket.onClose(function (res) {
console.log('WebSocket 已关闭!');
});

// 监听 WebSocket 错误事件
socket.onError(function (res) {
console.error('WebSocket 连接错误!');
});

总结

总体来说有点沙雕,既然用websocket的话,那之前的云开发后端也应该可以,等于说绕了一大圈最终其实也可以不迁移这个接口。但问题是本来我的需要并不是一定要用websocket只是单向流式传输就行了,现在因为客户端的问题必须要用websocket,要是一开始无脑用websocket反而不用卡那么久有点逗了