基于Websocket实现导出二进制文件

前言:最近做了一个导出功能,基于Websocket和后端通信,简单记录一下。业务场景是要导出一份文件,文件中带有附件,数据资源可能会很大,后端分成切片返回,前端再组装成完整文件,通过websocket这种长链接方式实现。

WebSocket是什么?

Websocket是基于TCP/IP协议,独立于HTTP协议的通信协议。双向通讯,客户端一个或多个与服务端一个或多个双向实时响应。

业务中如何使用Websocket

建立连接,初始化Websocket

在我的业务场景中,调取后端接口返回某一种状态码时开启链接,创建Websocket。

 initSocket() {
 	// 判断一下用户浏览器是否支持Websocket,websocket的兼容性还是挺好的,具体MDN看一下。
   if (typeof WebSocket === 'undefined') {
     console.log('您的浏览器不支持websocket');
     return;
   }
   // 传入建立连接地址
   this.socket = new WebSocket(`${this.websocketUrl}`);
   // websocket四种属性,下面介绍
   this.socket.onopen = this.onOpen;
   this.socket.onmessage = this.onMesssage;
   this.socket.onerror = this.onError;
   this.socket.onclose = this.onClose;
 },
websocke四种属性如何使用
  • onclose:当websocket连接关闭时触发;
  • onerror:当websocket连接因为错误而关闭时触发;
  • onmessage:当websocket收到数据时触发;
  • onopen:当websocket连接成功时触发;

根据上面对每个属性的描述可以知道,websocket在什么时候进到哪个方法。在创建websocket对象建立连接成功后,通过onopen属性绑定方法发送请求。

 onOpen(event) {
   console.log('onOpen---', event); 
   const data = { event: 'add', data: this.form };
   this.socket.send(JSON.stringify(data));
 },

开启连接后,后端开始数据处理并不断地发送通知信息。在我的业务场景中后端要此时开始下载指定数据的附件信息,并不断的向我发送下载进度。这时候就要通过onMessage属性来接收后端传递过来的信息。

通信过程如图,绿色箭头代表我发送通知,红色代表我接受到的信息。
在这里插入图片描述

onMessage属性中更多的是业务处理,通过双方规定的通信数据结构,在接收到不同event事件是进行哪种处理。

 onMesssage(message) {
   console.log('onMessage---', message);
   const { event, data, code, message: msg } = JSON.parse(message.data);
   if (event === 'add') {
     console.log('开始导出');	
   } else if (event === 'update-progress') {
     console.log('更新进度');
   } else if (event === 'ready-download') {
   	 console.log('准备下载');
   } else if (event === 'finish-download') {
	console.log('下载结束')
   }
 },

onErroronClose事件就是用错误捕获和连接结束时触发,根据业务场景使用。下面到了本文章重点对二进制文件切片处理。

二进制文件切片拼接处理

截图看一下完整的连接过程。在图中可以看到,当我第二次向后端发送信息后,后端开始返回二进制消息。而这些二进制消息是无序的,需要拼接成完整文件格式。下面逐步解析一下从后端准备好文件,到我拼装好文件的过程。

在这里插入图片描述
在后端准备好文件后,会返回ready-download的event名称,并且会把文件大小和文件名称一并带回来。此时根据event名称判断,会进入onReadyDownload()方法中。

onReadyDownload()事件中要记录文件名称,计算文件被拆分成几个切片。都是为了后续拼装文件做准备,之后通知后端准备好接收文件了。

 onReadyDownload(data) {
   this.$message.success('导出数据已就绪,即将自动导出');
   this.fileName = data.fileId; // 存储文件名称
   this.piece = Math.ceil(data.fileSize / (1024 * 1024)); // 计算切片个数
   const tempObj = {.....};
   const readyData = { event: 'ready-download', data: tempObj };
   this.socket.send(JSON.stringify(readyData)); // 通知后端开始传输吧
 },

现在后端开始传输二进制文件,依旧通过onMessage()事件来捕获,判断后端传输的文件类型是Blob时存储到数组中去(后面好排序),计算进度。

// onMessage 完整代码
onMesssage(message) { 
  console.log('onMessage---', message);
  if (message.data instanceof Blob) { // 判断是二进制文件
    this.myblobs.push(message.data);
    this.blobCount += 1; // 计算进度;
    this.progress = Math.floor(this.blobCount / this.piece * 100); 
    return;
  }
  const { event, data, code, message: msg } = JSON.parse(message.data);
  if (event === 'add') {
    console.log('更新进度');
  } else if (event === 'update-progress') {
    console.log('更新进度');
  } else if (event === 'ready-download') {
    this.onReadyDownload(data);
  } else if (event === 'finish-download') {
    this.onFinishDownload(data);
  }
},

当后端返回finish-download时,表示文件已经转传输完毕,此时断开连接开始对所有二进制信息拼接生成完成文件,进入onFinishDownload()方法。

onFinishDownload()调用handlePieceBlobV2()方法处理无序二进制信息,再将处理过的信息排序,最后调用handleBlobsV1()方法创建超链接开始下载。

onFinishDownload(data) {
    Promise.all([...this.myblobs.map((blob) => this.handlePieceBlobV2(blob))]).then((results) => {
        const blobs = this.sortBlobPiece(results);
        this.handleBlobsV1(blobs);
      });
}

handlePieceBlobV2()方法

 async handlePieceBlobV2(blob) {
   const buffer = await blob.slice(0, 8).arrayBuffer(); // 截取前八位切片序号,转buffer
   const index = +(new Uint8Array(buffer)).reduce((p, c) => (p + String.fromCharCode(c)), ''); // 字符编码值转对应字符 ; 或者通过blob.text();??
   const data = blob.slice(8, 1024 * 1024 + 8); // end -1 和mdn描述的不一样,会缺少1个字节;
   return {
     index,
     data,
   };
 },

sortBlobPiece()切片排序

 sortBlobPiece(arr) {
   const res = Array(arr.length);
   arr.forEach(({ index, data }) => {
     res[index - 1] = data;
   });
   return res;
 },

handleBlobsV1()创建超链接下载

handleBlobsV1(blobs) {
  const blob = new Blob([...blobs]);
  const blobUrl = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.download = this.fileName || Math.random();
  a.href = blobUrl;
  document.body.appendChild(a);
  a.click();
  a.remove();
  URL.revokeObjectURL(blobUrl);
  console.log('blobCount', this.blobCount);
},

对于handlePieceBlobV2()这个方法我还有比较多的疑问,后续有时间会搞清楚arrayBufferUint8Array这些对象的使用,以及如何根据前八位切片顺序匹配这些事件,就先记录到这里。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
WebSocket 协议可以传输二进制数据和文本数据。在处理二进制数据时,我们需要使用 BinaryWebSocketHandler 类来处理二进制数据。 具体步骤如下: 1. 创建 BinaryWebSocketHandler 类并重写 handleBinaryMessage 方法来处理二进制数据。 ```java public class MyBinaryWebSocketHandler extends BinaryWebSocketHandler { @Override public void handleBinaryMessage(WebSocketSession session, BinaryMessage message) { // 处理二进制消息 } } ``` 2. 修改 WebSocketConfig 类中注册 WebSocket 处理器的方法,将处理器修改为 BinaryWebSocketHandler 类型,并指定 WebSocket 路径。 ```java @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(binaryWebSocketHandler(), "/ws").setAllowedOrigins("*"); } @Bean public WebSocketHandler binaryWebSocketHandler() { return new MyBinaryWebSocketHandler(); } } ``` 3. 在前端页面中使用 WebSocket 发送二进制数据。 ```javascript var socket = new WebSocket("ws://localhost:8080/ws"); socket.binaryType = "arraybuffer"; socket.onopen = function() { // 连接成功 }; socket.onmessage = function(event) { var data = new Uint8Array(event.data); // 处理二进制消息 }; socket.onclose = function() { // 连接关闭 }; var data = new Uint8Array([1, 2, 3, 4]); socket.send(data.buffer); ``` 在前端页面中,我们需要通过设置 socket.binaryType = "arraybuffer" 来告诉 WebSocket 接收二进制数据。在接收到二进制数据时,我们需要将 event.data 转换为 Uint8Array 类型来处理二进制数据。 注意:在发送二进制数据时,需要通过 data.buffer 来获取 ArrayBuffer 类型的数据。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值