前端文件相关总结

前端文件相关总结_第1张图片

  先引用掘金上的一个总结,将前端会遇到的文件相关的知识点间的关系串联了起来。

  前端技术提供了一些高效的解决方案:文件流操作和切片下载与上传。

1. 文件基本操作

1.1 数据流和文件处理的基本概念

数据流是指连续的数据序列,可以从一个源传输到另一个目的地。在前端开发中,文件可以被看作数据流的一种形式,可以通过数据流的方式进行处理。 文件处理涉及读取和写入文件的操作,包括读取文件的内容、写入数据到文件,以及对文件进行删除、重命名等操作。

1.2. Blob 对象和 ArrayBuffer:处理二进制数据

在前端处理文件时,经常需要处理二进制数据。Blob(Binary Large Object)对象是用来表示二进制数据的一个接口,可以存储大量的二进制数据。Blob 对象可以通过构造函数进行创建,也可以通过其他 API 生成,例如通过 FormData 对象获取上传的文件。 而 ArrayBuffer 是 JavaScript 中的一个对象类型,用于表示一个通用的、固定长度的二进制数据缓冲区。我们可以通过 ArrayBuffer 来操作和处理文件的二进制数据。

示例代码:

 1 import React, { useState } from 'react';
 2 
 3 function FileInput() {
 4   const [fileContent, setFileContent] = useState('');
 5 
 6   // 读取文件内容到ArrayBuffer
 7   function readFileToArrayBuffer(file) {
 8     return new Promise((resolve, reject) => {
 9       const reader = new FileReader();
10 
11       // 注册文件读取完成后的回调函数
12       reader.onload = function(event) {
13         const arrayBuffer = event.target.result;
14         resolve(arrayBuffer);
15       };
16 
17       // 读取文件内容到ArrayBuffer
18       reader.readAsArrayBuffer(file);
19     });
20   }
21 
22   // 将ArrayBuffer转为十六进制字符串
23   function arrayBufferToHexString(arrayBuffer) {
24     const uint8Array = new Uint8Array(arrayBuffer);
25     let hexString = '';
26     for (let i = 0; i < uint8Array.length; i++) {
27       const hex = uint8Array[i].toString(16).padStart(2, '0');
28       hexString += hex;
29     }
30     return hexString;
31   }
32 
33   // 处理文件选择事件
34   function handleFileChange(event) {
35     const file = event.target.files[0];  // 获取选中的文件
36 
37     if (file) {
38       readFileToArrayBuffer(file)
39         .then(arrayBuffer => {
40           const hexString = arrayBufferToHexString(arrayBuffer);
41           setFileContent(hexString);
42         })
43         .catch(error => {
44           console.error('文件读取失败:', error);
45         });
46     } else {
47       setFileContent('请选择一个文件');
48     }
49   }
50 
51   return (
52     
53 54
55

文件内容:

56
{fileContent}
57
58
59 ); 60 } 61 62 export default FileInput;

1.3 使用 FileReader 进行文件读取

FileReader 是前端浏览器提供的一个 API,用于读取文件内容。通过 FileReader,我们可以通过异步方式读取文件,并将文件内容转换为可用的数据形式,比如文本数据或二进制数据。 FileReader 提供了一些读取文件的方法,例如 readAsText()、readAsArrayBuffer() 等,可以根据需要选择合适的方法来读取文件内容。

1.4 将文件流展示在前端页面中

一旦我们成功地读取了文件的内容,就可以将文件流展示在前端页面上。具体的展示方式取决于文件的类型。例如,对于文本文件,可以直接将其内容显示在页面的文本框或区域中;对于图片文件,可以使用  标签展示图片;对于音视频文件,可以使用  或  标签来播放。 通过将文件流展示在前端页面上,我们可以实现在线预览和查看文件内容的功能。

好的,这一部分就基本介绍完毕,总结一下。前端文件操作流是处理大型文件的一种常见方式,他可以通过数据流的方式对文件进行操作。Blob对象 和 ArrayBuffer是处理二进制数据的重要工具。而FileReader则是读取文件内容的的关键组件。通过这些技术,我们可以方便的在前端页面上进行操作或者文件展示。

2. 文件切片

流程:

A(开始) --> B{选择文件}
B -- 用户选择文件 --> C[切割文件为多个切片]
C --> D{上传切片}
D -- 上传完成 --> E[合并切片为完整文件]
E -- 文件合并完成 --> F(上传成功)
D -- 上传中断 --> G{保存上传进度}
G -- 上传恢复 --> D
G -- 取消上传 --> H(上传取消)

  传统文件整体上传下载的弊端:

  等待较长、网络占用、续传困难。

  切片上传下载好处:

  • 快速启动:客户端可以快速开始下载,因为只需要下载第一个切片即可。
  • 并发下载:通过使用多个并发请求下载切片,可以充分利用带宽,并提高整体下载速度。
  • 断点续传:如果下载中断,客户端只需要重新下载中断的切片,而不需要重新下载整个文件。

2.1 切片上传-借助FormData

 1 const [selectedFile, setSelectedFile] = useState(null); 
 2 const [progress, setProgress] = useState(0);
 3  // 处理文件选择事件
 4  function handleFileChange(event) {
 5    setSelectedFile(event.target.files[0]);
 6  }
 7 
 8  // 处理文件上传事件
 9  function handleFileUpload() {
10    if (selectedFile) {
11      // 计算切片数量和每个切片的大小
12      const fileSize = selectedFile.size;
13      const chunkSize = 1024 * 1024; // 设置切片大小为1MB
14      const totalChunks = Math.ceil(fileSize / chunkSize);
15 
16      // 创建FormData对象,并添加文件信息
17      const formData = new FormData();
18      formData.append('file', selectedFile);
19      formData.append('totalChunks', totalChunks);
20 
21      // 循环上传切片
22      for (let chunkNumber = 0; chunkNumber < totalChunks; chunkNumber++) {
23        const start = chunkNumber * chunkSize;
24        const end = Math.min(start + chunkSize, fileSize);
25        const chunk = selectedFile.slice(start, end);
26        formData.append(`chunk-${chunkNumber}`, chunk, selectedFile.name);
27      }
28 
29      // 发起文件上传请求
30      axios.post('/upload', formData, {
31        onUploadProgress: progressEvent => {
32          const progress = Math.round((progressEvent.loaded / progressEvent.total) * 100);
33          setProgress(progress);
34        }
35      })
36        .then(response => {
37          console.log('文件上传成功:', response.data);
38        })
39        .catch(error => {
40          console.error('文件上传失败:', error);
41        });
42    }
43  }

2.2 切片下载

实现客户端切片下载的基本方案如下:

  1. 服务器端将大文件切割成多个切片,并为每个切片生成唯一的标识符。
  2. 客户端发送请求获取切片列表,同时开始下载第一个切片。
  3. 客户端在下载过程中,根据切片列表发起并发请求下载其他切片,并逐渐拼接合并下载的数据。
  4. 当所有切片都下载完成后,客户端借助blob将下载的数据合并为完整的文件。
 1 function downloadFile() {
 2   // 发起文件下载请求
 3   fetch('/download', {
 4     method: 'GET',
 5     headers: {
 6       'Content-Type': 'application/json',
 7     },
 8   })
 9     .then(response => response.json())
10     .then(data => {
11       const totalSize = data.totalSize;
12       const totalChunks = data.totalChunks;
13 
14       let downloadedChunks = 0;
15       let chunks = [];
16 
17       // 下载每个切片
18       for (let chunkNumber = 0; chunkNumber < totalChunks; chunkNumber++) {
19         fetch(`/download/${chunkNumber}`, {
20           method: 'GET',
21         })
22           .then(response => response.blob())
23           .then(chunk => {
24             downloadedChunks++;
25             chunks.push(chunk);
26 
27             // 当所有切片都下载完成时
28             if (downloadedChunks === totalChunks) {
29               // 合并切片
30               const mergedBlob = new Blob(chunks);
31 
32               // 创建对象 URL,生成下载链接
33               const downloadUrl = window.URL.createObjectURL(mergedBlob);
34 
35               // 创建  元素并设置属性
36               const link = document.createElement('a');
37               link.href = downloadUrl;
38               link.setAttribute('download', 'file.txt');
39 
40               // 模拟点击下载
41               link.click();
42 
43               // 释放资源
44               window.URL.revokeObjectURL(downloadUrl);
45             }
46           });
47       }
48     })
49     .catch(error => {
50       console.error('文件下载失败:', error);
51     });
52 }

2.3 显示下载进度和完成状态

为了显示下载进度和完成状态,可以在客户端实现以下功能:

  1. 显示进度条:客户端可以通过监听每个切片的下载进度来计算整体下载进度,并实时更新进度条的显示。
  2. 显示完成状态:当所有切片都下载完成后,客户端可以显示下载完成的状态,例如显示一个完成的图标或者文本。

这里我们可以继续接着切片上传代码示例里的继续写。

 1 function handleFileDownload() {
 2     axios.get('/download', {
 3       responseType: 'blob',
 4       onDownloadProgress: progressEvent => {
 5         const progress = Math.round((progressEvent.loaded / progressEvent.total) * 100);
 6         setProgress(progress);
 7       }
 8     })
 9       .then(response => {
10         // 创建一个临时的URL对象用于下载
11         const url = window.URL.createObjectURL(new Blob([response.data]));
12         const link = document.createElement('a');
13         link.href = url;
14         link.setAttribute('download', 'file.txt');
15         document.body.appendChild(link);
16         link.click();
17         document.body.removeChild(link);
18       })
19       .catch(error => {
20         console.error('文件下载失败:', error);
21       });
22   }
23   
24   
25   
26   
进度:{progress}%

3. 大文件上传下载

3.1 大文件切片上传

- 使用 JavaScript 的 `File API` 获取文件对象,并使用 `Blob.prototype.slice()` 方法将文件切割为多个切片。

  • 使用 FormData 对象将切片数据通过 AJAX 或 Fetch API 发送到服务器。
  • 在后端服务器上接收切片并保存到临时存储中,等待后续合并。
  • 在客户端通过监听上传进度事件,在进度条或提示中展示上传进度。 代码示例
 1 const [file, setFile] = useState(null);  //用来存放我本地上传的文件
 2 
 3 const chunkSize = 1024 * 1024; // 1MB 切片大小
 4 
 5   const upload = () => {
 6     if (!file) {
 7       alert("请选择要上传的文件!");
 8       return;
 9     }
10 
11     const chunkSize = 1024 * 1024; // 1MB
12 
13     let start = 0;
14     let end = Math.min(chunkSize, file.size);
15 
16     while (start < file.size) {
17       const chunk = file.slice(start, end);
18       
19       // 创建FormData对象
20       const formData = new FormData();
21       formData.append('file', chunk);
22 
23       // 发送切片到服务器
24       fetch('上传接口xxxx', {
25         method: 'POST',
26         body: formData
27       })
28       .then(response => response.json())
29       .then(data => {
30         console.log(data);
31         // 处理响应结果
32       })
33       .catch(error => {
34         console.error(error);
35         // 处理错误
36       });
37 
38       start = end;
39       end = Math.min(start + chunkSize, file.size);
40     }
41   };
42   
43  return (
44     
45 46 47
48 ); 49 }

在上面的代码中,创建了一个名为Upload的函数组件。它使用了 React 的useState钩子来管理选中的文件。

通过onChange事件监听文件输入框的变化,并在handleFileChange函数中获取选择的文件,并更新file状态。

点击“上传”按钮时,调用upload函数。它与之前的示例代码类似,将文件切割为多个大小相等的切片,并使用FormData对象和fetch函数发送切片数据到服务器。

3.2 实现断点续传的技术:记录和恢复上传状态

  • 在前端,可以使用 localStorage 或 sessionStorage 来存储已上传的切片信息,包括已上传的切片索引、切片大小等。
  • 每次上传前,先检查本地存储中是否存在已上传的切片信息,若存在,则从断点处继续上传。
  • 在后端,可以使用一个临时文件夹或数据库来记录已接收到的切片信息,包括已上传的切片索引、切片大小等。
  • 在上传完成前,保存上传状态,以便在上传中断后能够恢复上传进度
 1 import React, { useState, useRef, useEffect } from 'react';
 2 
 3 function Upload() {
 4   const [file, setFile] = useState(null);
 5   const [uploadedChunks, setUploadedChunks] = useState([]);
 6   const [uploading, setUploading] = useState(false);
 7   const uploadRequestRef = useRef();
 8 
 9   const handleFileChange = (event) => {
10     const selectedFile = event.target.files[0];
11     setFile(selectedFile);
12   };
13 
14   const uploadChunk = (chunk) => {
15     // 创建FormData对象
16     const formData = new FormData();
17     formData.append('file', chunk);
18 
19     // 发送切片到服务器
20     return fetch('your-upload-url', {
21       method: 'POST',
22       body: formData
23     })
24     .then(response => response.json())
25     .then(data => {
26       console.log(data);
27       // 处理响应结果
28       return data;
29     });
30   };
31 
32   const upload = async () => {
33     if (!file) {
34       alert("请选择要上传的文件!");
35       return;
36     }
37 
38     const chunkSize = 1024 * 1024; // 1MB
39     const totalChunks = Math.ceil(file.size / chunkSize);
40 
41     let start = 0;
42     let end = Math.min(chunkSize, file.size);
43 
44     setUploading(true);
45 
46     for (let i = 0; i < totalChunks; i++) {
47       const chunk = file.slice(start, end);
48       const uploadedChunkIndex = uploadedChunks.indexOf(i);
49 
50       if (uploadedChunkIndex === -1) {
51         try {
52           const response = await uploadChunk(chunk);
53           setUploadedChunks((prevChunks) => [...prevChunks, i]);
54 
55           // 保存已上传的切片信息到本地存储
56           localStorage.setItem('uploadedChunks', JSON.stringify(uploadedChunks));
57         } catch (error) {
58           console.error(error);
59           // 处理错误
60         }
61       }
62 
63       start = end;
64       end = Math.min(start + chunkSize, file.size);
65     }
66 
67     setUploading(false);
68 
69     // 上传完毕,清除本地存储的切片信息
70     localStorage.removeItem('uploadedChunks');
71   };
72 
73   useEffect(() => {
74     const storedUploadedChunks = localStorage.getItem('uploadedChunks');
75 
76     if (storedUploadedChunks) {
77       setUploadedChunks(JSON.parse(storedUploadedChunks));
78     }
79   }, []);
80 
81   return (
82     
83 84 87
88 ); 89 }

首先,使用useState钩子创建了一个uploadedChunks状态来保存已上传的切片索引数组。初始值为空数组。

然后,我们使用useRef钩子创建了一个uploadRequestRef引用,用于存储当前的上传请求。

handleFileChange函数中,我们更新了file状态以选择要上传的文件。

uploadChunk函数中,我们发送切片到服务器,并返回一个Promise对象来处理响应结果。

upload函数中,我们添加了断点续传的逻辑。首先,我们获取切片的总数,并设置uploading状态为true来禁用上传按钮。

然后,我们使用for循环遍历所有切片。对于每个切片,我们检查uploadedChunks数组中是否已经包含该索引,如果不包含,则进行上传操作。

在上传切片之后,我们将已上传的切片索引添加到uploadedChunks数组,并使用localStorage保存已上传的切片信息。

最后,在上传完毕后,我们将uploading状态设为false,并清除本地存储的切片信息。

本文参考掘金:https://juejin.cn/post/7255189826226602045

你可能感兴趣的:(前端)