背景
文件传输是一个常见的需求。对于大文件的下载和上传,直接使用传统的方式可能会遇到性能和用户体验方面的问题。幸运的是,前端技术提供了一些高效的解决方案:文件流操作和切片下载与上传。本文将深入探讨这些技术,帮助你理解它们的原理和实现方法,以优化文件传输效率和提升用户体验。
import React, { useState } from 'react'; function FileInput() { const [fileContent, setFileContent] = useState(''); // 读取文件内容到ArrayBuffer function readFileToArrayBuffer(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); // 堆代码 duidaima.com // 注册文件读取完成后的回调函数 reader.onload = function(event) { const arrayBuffer = event.target.result; resolve(arrayBuffer); }; // 读取文件内容到ArrayBuffer reader.readAsArrayBuffer(file); }); } // 将ArrayBuffer转为十六进制字符串 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;上面代码里,我们创建了一个名为 FileInput 的函数式组件。该组件包含一个文件选择框和一个用于显示文件内容的 <pre> 元素。当用户选择文件时,通过 FileReader 将文件内容读取为 ArrayBuffer,然后将 ArrayBuffer 转换为十六进制字符串,并将结果显示在页面上。
G -- 取消上传 --> H(上传取消)
3.断点续传困难:如果下载过程中出现网络故障或者用户中断下载,需要重新下载整个文件,无法继续之前的下载进度。
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; // 设置切片大小为1MB const totalChunks = Math.ceil(fileSize / chunkSize); // 创建FormData对象,并添加文件信息 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); }); } }当涉及到切片上传和下载时,前端使用的技术通常是基于前端库或框架提供的文件处理功能,结合后端服务实现。
在 handleFileUpload 函数中,计算切片数量和每个切片的大小,并创建一个 FormData 对象用于存储文件信息和切片数据。
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); // 创建对象 URL,生成下载链接 const downloadUrl = window.URL.createObjectURL(mergedBlob); // 创建 <a> 元素并设置属性 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属性是文件名,方便点击的时候自动下载文件。
显示完成状态:当所有切片都下载完成后,客户端可以显示下载完成的状态,例如显示一个完成的图标或者文本。
// 处理文件下载事件 function handleFileDownload() { axios.get('/download', { responseType: 'blob', onDownloadProgress: progressEvent => { const progress = Math.round((progressEvent.loaded / progressEvent.total) * 100); setProgress(progress); } }) .then(response => { // 创建一个临时的URL对象用于下载 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>1.当用户点击下载按钮时,通过 handleFileDownload 函数处理文件下载事件。
4.下载完成后,创建一个临时的 URL 对象用于下载,并通过动态创建 <a> 元素模拟点击下载。
4.难以实现上传进度的显示和控制。
4.实现断点续传功能,避免重复上传已上传的部分。
4.在客户端通过监听上传进度事件,在进度条或提示中展示上传进度。
代码示例
const [file, setFile] = useState(null); //用来存放我本地上传的文件 const chunkSize = 1024 * 1024; // 1MB 切片大小 const upload = () => { if (!file) { alert("请选择要上传的文件!"); return; } const chunkSize = 1024 * 1024; // 1MB let start = 0; let end = Math.min(chunkSize, file.size); while (start < file.size) { const chunk = file.slice(start, end); // 创建FormData对象 const formData = new FormData(); formData.append('file', chunk); // 发送切片到服务器 fetch('上传接口xxxx', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { console.log(data); // 处理响应结果 }) .catch(error => { console.error(error); // 处理错误 }); start = end; end = Math.min(start + chunkSize, file.size); } }; return ( <div> <input type="file" onChange={handleFileChange} /> <button onClick={upload}>上传</button> </div> ); }在上面的代码中,创建了一个名为Upload的函数组件。它使用了 React 的useState钩子来管理选中的文件。通过onChange事件监听文件输入框的变化,并在handleFileChange函数中获取选择的文件,并更新file状态。
import React, { useState, useRef, useEffect } from 'react'; function Upload() { const [file, setFile] = useState(null); const [uploadedChunks, setUploadedChunks] = useState([]); const [uploading, setUploading] = useState(false); const uploadRequestRef = useRef(); const handleFileChange = (event) => { const selectedFile = event.target.files[0]; setFile(selectedFile); }; const uploadChunk = (chunk) => { // 创建FormData对象 const formData = new FormData(); formData.append('file', chunk); // 发送切片到服务器 return fetch('your-upload-url', { method: 'POST', body: formData }) .then(response => response.json()) .then(data => { console.log(data); // 处理响应结果 return data; }); }; const upload = async () => { if (!file) { alert("请选择要上传的文件!"); return; } const chunkSize = 1024 * 1024; // 1MB const totalChunks = Math.ceil(file.size / chunkSize); let start = 0; let end = Math.min(chunkSize, file.size); setUploading(true); for (let i = 0; i < totalChunks; i++) { const chunk = file.slice(start, end); const uploadedChunkIndex = uploadedChunks.indexOf(i); if (uploadedChunkIndex === -1) { try { const response = await uploadChunk(chunk); setUploadedChunks((prevChunks) => [...prevChunks, i]); // 保存已上传的切片信息到本地存储 localStorage.setItem('uploadedChunks', JSON.stringify(uploadedChunks)); } catch (error) { console.error(error); // 处理错误 } } start = end; end = Math.min(start + chunkSize, file.size); } setUploading(false); // 上传完毕,清除本地存储的切片信息 localStorage.removeItem('uploadedChunks'); }; useEffect(() => { const storedUploadedChunks = localStorage.getItem('uploadedChunks'); if (storedUploadedChunks) { setUploadedChunks(JSON.parse(storedUploadedChunks)); } }, []); return ( <div> <input type="file" onChange={handleFileChange} /> <button onClick={upload} disabled={uploading}> {uploading ? '上传中...' : '上传'} </button> </div> ); }首先,使用useState钩子创建了一个uploadedChunks状态来保存已上传的切片索引数组。初始值为空数组。
文件上传:后台管理系统中,用户可能需要上传大型文件,如数据导入、文件备份等。使用切片上传可以提高上传效率,分批上传文件切片,并显示上传进度,使用户能够了解上传的状态。
视频上传和预览:对于较大的视频文件,切片上传可以确保上传过程可靠且高效。同时,可以实现上传进度的实时展示。上传完成后,通过切片下载技术,用户可以流畅地观看视频,无需等待整个文件下载完成。