一、前端文件流操作
下面创建一个 fileUpload
的函数式组件,当用户选择文件时,通过FileReader
将文件内容读取为 ArrayButter
,然后将ArrayBuffer
转换为十六进制字符串,并将结果显示在页面上。
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
| import React, { useState } from "react";
function FileInput() { const [fileContent, setFileContent] = useState("");
function readFileToArrayBuffer(file) { return new Promise((resolve, reject) => { const reader = new FileReader();
reader.onload = function (event) { const arrayBuffer = event.target.result; resolve(arrayBuffer); };
reader.readAsArrayBuffer(file); }); }
function arrayBufferToHexString(arrayBuffer) { const uint8Array = new Uint8Array(arrayBuffer); let hexString = ""; for (let i = 0; i < uint8Array.length; i++) { const hex = uint8Array[i].toString(16).padStart(2, "0"); hexString += hex; } return hexString; }
function handleFileChange(event) { const file = event.target.files[0];
if (file) { readFileToArrayBuffer(file) .then((arrayBuffer) => { const hexString = arrayBufferToHexString(arrayBuffer); setFileContent(hexString); }) .catch((error) => { console.error("文件读取失败:", error); }); } else { setFileContent("请选择一个文件"); } }
return ( <div> <input type="file" onChange={handleFileChange} /> <div> <h4>文件内容:</h4> <pre>{fileContent}</pre> </div> </div> ); }
export default FileInput;
|
二、文件切片下载
文件切片下载的好处:
- 快速启动:客户端可以快速开始下载,因为只需要下载第一个切片即可。
- 并发下载:通过使用多个并发请求下载切片,可以充分利用带宽,并提高整体下载速度。
- 断点续传:如果下载中断,客户端只需要重新下载中断的切片,而不需要重新下载整个文件。
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
| const [selectedFile, setSelectedFile] = useState(null); const [progress, setProgress] = useState(0);
function handleFileChange(event) { setSelectedFile(event.target.files[0]); }
function handleFileUpload() { if (selectedFile) { const fileSize = selectedFile.size; const chunkSize = 1024 * 1024; const totalChunks = Math.ceil(fileSize / chunkSize);
const formData = new FormData(); formData.append("file", selectedFile); formData.append("totalChunks", totalChunks);
for (let chunkNumber = 0; chunkNumber < totalChunks; chunkNumber++) { const start = chunkNumber * chunkSize; const end = Math.min(start + chunkSize, fileSize); const chunk = selectedFile.slice(start, end); formData.append(`chunk-${chunkNumber}`, chunk, selectedFile.name); }
axios .post("/upload", formData, { onUploadProgress: (progressEvent) => { const progress = Math.round( (progressEvent.loaded / progressEvent.total) * 100 ); setProgress(progress); }, }) .then((response) => { console.log("文件上传成功:", response.data); }) .catch((error) => { console.error("文件上传失败:", error); }); } }
|
当用户选择文件后,通过 handleFileChange
函数处理文件选择事件,将选择的文件保存在 selectedFile
状态中。
当用户点击上传按钮时,通过 handleFileUpload
函数处理文件上传事件。
在 handleFileUpload
函数中,计算切片数量和每个切片的大小,并创建一个 FormData
对象用于存储文件信息和切片数据。
实现客户端切片下载的方案
实现客户端切片下载的基本方案如下:
- 服务器端将大文件切割成多个切片,并为每个切片生成唯一的标识符。
- 客户端发送请求获取切片列表,同时开始下载第一个切片。
- 客户端在下载过程中,根据切片列表发起并发请求下载其他切片,并逐渐拼接合并下载的数据。
- 当所有切片都下载完成后,客户端将下载的数据合并为完整的文件
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
| function downloadFile() { fetch("/download", { method: "GET", headers: { "Content-Type": "application/json", }, }) .then((response) => response.json()) .then((data) => { const totalSize = data.totalSize; const totalChunks = data.totalChunks;
let downloadedChunks = 0; let chunks = [];
for (let chunkNumber = 0; chunkNumber < totalChunks; chunkNumber++) { fetch(`/download/${chunkNumber}`, { method: "GET", }) .then((response) => response.blob()) .then((chunk) => { downloadedChunks++; chunks.push(chunk);
if (downloadedChunks === totalChunks) { const mergedBlob = new Blob(chunks);
const downloadUrl = window.URL.createObjectURL(mergedBlob);
const link = document.createElement("a"); link.href = downloadUrl; link.setAttribute("download", "file.txt");
link.click();
window.URL.revokeObjectURL(downloadUrl); } }); } }) .catch((error) => { console.error("文件下载失败:", error); }); }
|
我们看下代码,首先使用 BLOB
对象创建一共对象 URL,用于生成下载连接,然后创建 a 标签并且设置 href 的属性为刚刚创建的对象 URL,继续设置 a 标签的 download
属性是文件名,方便点击的时候自动下载文件。
显示下载进度和完成状态
为了显示下载进度和完成状态,可以在客户端实现以下功能:
- 显示进度条:客户端可以通过监听每个切片的下载进度来计算整体下载进度,并实时更新进度条的显示。
- 显示完成状态:当所有切片都下载完成后,客户端可以显示下载完成的状态,例如显示一个完成的图标或者文本。
这里我们可以继续接着切片上传代码示例里的继续写。
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
| function handleFileDownload() { axios.get('/download', { responseType: 'blob', onDownloadProgress: progressEvent => { const progress = Math.round((progressEvent.loaded / progressEvent.total) * 100); setProgress(progress); } }) .then(response => { const url = window.URL.createObjectURL(new Blob([response.data])); const link = document.createElement('a'); link.href = url; link.setAttribute('download', 'file.txt'); document.body.appendChild(link); link.click(); document.body.removeChild(link); }) .catch(error => { console.error('文件下载失败:', error); }); }
<button onClick={handleFileDownload}>下载文件</button> <div>进度:{progress}%</div>
|
- 当用户点击下载按钮时,通过
handleFileDownload
函数处理文件下载事件。
- 在
handleFileDownload
函数中,使用 axios
库发起文件下载请求,并设置 responseType: 'blob'
表示返回二进制数据。
- 通过监听
onDownloadProgress
属性获取下载进度,并更新进度条的显示。
- 下载完成后,创建一个临时的
URL
对象用于下载,并通过动态创建 <a>
元素模拟点击下载。
实现断点续传的技术:记录和恢复上传状态
- 在前端,可以使用
localStorage
或 sessionStorage
来存储已上传的切片信息,包括已上传的切片索引、切片大小等。
- 每次上传前,先检查本地存储中是否存在已上传的切片信息,若存在,则从断点处继续上传。
- 在后端,可以使用一个临时文件夹或数据库来记录已接收到的切片信息,包括已上传的切片索引、切片大小等。
- 在上传完成前,保存上传状态,以便在上传中断后能够恢复上传进度。
在实现大文件上传时要考虑服务器端的处理能力和存储空间,以及安全性问题。同时,为了保障断点续传的准确性,应该尽量避免并发上传相同文件的情况,可以采用文件唯一标识符或用户会话标识符进行区分。