做网站卖东西赚钱吗湖南企业竞价优化首选
文章目录
- 前端分块上传流程
- 分块上传代码
- 重点
- 1. `let start = currentChunk * chunkSize;`
- 2. `let end = Math.min(file.size, start + chunkSize);`
- 3. `let chunk = file.slice(start, end);`
- 结合断点续传
- 注意事项
大文件上传是一个复杂的过程,尤其是在前端,我们需要考虑用户体验、网络状况、文件完整性等多个方面。
以下是一个使用HTML5的
File API
和
XMLHttpRequest
进行大文件分块上传的详解和示例代码。
前端分块上传流程
-
选择文件:使用
<input type="file">
元素让用户选择文件。 -
读取文件:使用
FileReader API
读取文件内容。 -
分块文件:根据设定的大小将文件切割成多个小块。
-
上传分块:使用
XMLHttpRequest
或fetch API
将每个分块上传到服务器。 -
合并文件:服务器接收到所有分块后,将其合并成完整的文件。
分块上传代码
以下是一个简单的示例代码,展示了如何使用JavaScript实现大文件的分块上传。
<!DOCTYPE html>
<html lang="en">
<head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>大文件分块上传</title>
</head>
<body> <input type="file" id="fileInput"> <button id="uploadButton">上传</button> <script> const chunkSize = 1024 * 1024; // 每块大小设置为1MB let fileInput = document.getElementById('fileInput'); let uploadButton = document.getElementById('uploadButton'); uploadButton.addEventListener('click', function () { let file = fileInput.files[0]; let chunks = Math.ceil(file.size / chunkSize); // 计算文件块数(文件总大小 / 每块大小)let currentChunk = 0; // 当前上传的块数 function uploadChunk() { if (currentChunk < chunks) { let start = currentChunk * chunkSize; // 计算当前要上传的分片的起始字节位置(已经上传的字节)let end = Math.min(file.size, start + chunkSize); // 计算当前要上传的分片的结束字节位置(当前准备上传的字节)let chunk = file.slice(start, end); let formData = new FormData(); formData.append('chunk', chunk); formData.append('fileName', file.name); formData.append('totalChunks', chunks); formData.append('currentChunk', currentChunk); let xhr = new XMLHttpRequest(); xhr.open('POST', '/upload', true); // 替换为你的上传接口 xhr.upload.onprogress = function (e) { // 监听上传进度if (e.lengthComputable) { console.log((e.loaded / e.total * 100) + '%'); } }; xhr.onload = function () { if (xhr.status === 200) { currentChunk++; uploadChunk(); // 递归上传下一个块 } else { console.error('上传失败'); } }; xhr.send(formData); } else { console.log('上传完成'); } } uploadChunk(); // 开始上传第一个块 }); </script>
</body>
</html>
重点
1. let start = currentChunk * chunkSize;
-
currentChunk
:当前要上传的文件分片的索引
如果这是第一个分片,currentChunk 通常是0;如果是第二个分片,它是1,依此类推。
每当当前分片上传成功,便会累加 -
chunkSize
:每个分片的大小(以字节为单位)
这通常是一个固定的值,比如1MB(即1024 * 1024字节)或其他合适的值,取决于你的应用需求和网络条件 -
start
:currentChunk
和chunkSize
相乘,你得到了当前分片开始的字节位置。
例如,假设你有一个10MB的文件,并且你决定每个分片大小为1MB:
对于第一个分片(currentChunk = 0
),start 会是0 * 1MB = 0
,即从文件的开头开始。
对于第二个分片(currentChunk = 1
),start 会是1 * 1MB = 1MB
,即从文件的1MB位置开始。
对于第三个分片(currentChunk = 2
),start 会是2 * 1MB = 2MB
,以此类推。
.
2. let end = Math.min(file.size, start + chunkSize);
end 是一个变量,用于表示文件分片上传时的结束字节位置。
这个计算确保了分片不会超出文件的实际大小,并且在最后一个分片时能够正确地设置结束位置。
-
start + chunkSize
:计算当前分片应该结束的位置,也就是当前需要上传的文件分片 -
file.size
:获取文件的总大小 -
Math.min(...)
:取上述两个值中的较小者作为当前分片的结束位置
这样做的原因是,当处理文件的最后一个分片时,start + chunkSize
可能会超出文件的实际大小。
为了避免这种情况,我们使用 Math.min
函数来确保 end 不会大于文件的实际大小(file.size
)。
或者可以这么理解: let end = start + chunkSize > file.size ? file.size : start + chunkSize
例如,假设你有一个13MB的文件,并且你决定每个分片大小为5MB。即:
-
file.size =
13*1024*1024 => 文件大小为 13631488 字节
-
chunkSize =
5*1024*1024 => 每个分片大小为 5242880 字节
-
let chunks =
Math.ceil(file.size / chunkSize) => 文件块数为3
这时:
-
第一个分片:
start + chunkSize
将分别是 0 + 5MB -
第二个分片:
start + chunkSize
将分别是 5MB + 5MB -
第三个分片:如果简单地使用 10MB + 5MB,你会得到一个超出文件大小的结束位置(15MB>13MB)。
通过 Math.min,你可以确保第三个分片的结束位置正确地设置为文件的末尾(即13MB):
Math.min(file.size, start + chunkSize)
=>Math.min(13*1024*1024, (10+5)*1024*1024)
=>13*1024*1024 字节
=> 13MB
.
3. let chunk = file.slice(start, end);
在JavaScript中,file.slice(start, end)
是 Blob 对象的一个方法,用于创建一个新的 Blob 对象,该对象包含源 Blob 对象中指定范围内的数据。这通常用于文件操作,特别是当你需要处理文件的一部分(例如,分片上传)时。
file 是一个 Blob 或 File 对象(File 继承自 Blob),而 start 和 end 是指定切片范围的参数:
- start(包含):开始切片的字节偏移量。
- end(不包含):结束切片的字节偏移量。(如果 end 被省略或大于 Blob 的大小,则切片会一直到 Blob 的末尾。)
例如,如果你有一个 File 对象 file,你可以使用 slice 方法来获取文件的部分字节:
var slice1 = file.slice(0, 100); // 获取文件的前100个字节var slice2 = file.slice(100, 200); // 获取从第101个字节开始到第200个字节结束的部分
注意:
- slice 方法不会改变原始的 Blob 或 File 对象
- 它返回一个新的 Blob 对象,该对象包含指定范围内的数据
结合断点续传
断点续传(Resume Download)是一种在网络传输中常见的功能,它允许在下载大文件时,如果由于某种原因(如网络中断、程序崩溃等)导致下载中断,可以从之前中断的地方继续下载,而不是重新下载整个文件。这可以大大节省时间和带宽资源。
// 假设你已经通过某种方式获取了文件对象以及要续传的起始字节位置 startByte
const file = document.querySelector('input[type="file"]').files[0];
const chunkSize = 1024 * 1024; // 1MB 分片大小
const startByte = // 从服务器获取的已下载字节数,用于断点续传
let currentChunk = Math.ceil(startByte / chunkSize); // 计算当前应该开始下载的分片索引 // 分片函数
function createChunk(file, start, end) { const chunk = file.slice(start, end); return new Blob([chunk], { type: file.type });
} // 创建分片并发送请求的函数
function uploadChunk(chunk, chunkIndex) { const formData = new FormData(); formData.append('file', chunk); formData.append('chunkIndex', chunkIndex); formData.append('fileName', file.name); formData.append('chunkSize', chunkSize); formData.append('totalSize', file.size); axios.post('/upload-chunk', formData, { headers: { 'Content-Type': 'multipart/form-data' }, onUploadProgress: progressEvent => { const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total); console.log(`Chunk ${chunkIndex + 1} uploaded: ${percentCompleted}%`); }, onDownloadProgress: progressEvent => { // 可以在这里处理下载进度,如果需要的话 } }) .then(response => { if (response.data.success) { console.log(`Chunk ${chunkIndex + 1} uploaded successfully.`); // 检查是否所有分片都已上传完成 if (chunkIndex < Math.ceil(file.size / chunkSize) - 1) { // 上传下一个分片 const nextChunkStart = chunkIndex * chunkSize + chunkSize; const nextChunkEnd = Math.min(nextChunkStart + chunkSize, file.size); const nextChunk = createChunk(file, nextChunkStart, nextChunkEnd); uploadChunk(nextChunk, chunkIndex + 1); } else { // 所有分片上传完成,可以通知服务器合并文件或进行其他操作 console.log('All chunks uploaded successfully.'); } } else { console.error('Chunk upload failed:', response.data.error); } }) .catch(error => { console.error('Chunk upload error:', error); // 可以在这里处理错误,比如重试上传当前分片或全部重新开始 });
} // 开始上传
const firstChunkStart = currentChunk * chunkSize;
const firstChunkEnd = Math.min(firstChunkStart + chunkSize, file.size);
const firstChunk = createChunk(file, firstChunkStart, firstChunkEnd);
uploadChunk(firstChunk, currentChunk);
在这个示例中:
-
createChunk
函数用于创建文件的分片 -
uploadChunk
函数用于发送包含分片数据的 POST 请求到服务器 -
startByte
是从服务器获取的已下载字节数,用于确定从哪里开始上传分片
每次上传一个分片后,如果成功,则检查是否还有剩余的分片需要上传,如果有,则继续上传下一个分片。
请注意,这个示例仅涵盖了前端的部分逻辑。为了实现完整的断点续传功能,你还需要在后端实现相应的逻辑来处理分片上传、合并分片以及存储和检索已上传的分片信息。此外,还需要考虑如何安全地验证和授权上传请求,以避免安全风险。
注意事项
-
文件完整性校验:上传完成后,服务器需要对合并后的文件进行完整性校验,确保文件没有损坏。
-
断点续传:在实际应用中,还需要考虑断点续传的功能,即当上传中断时,可以从上次中断的地方继续上传。
-
上传进度显示:在上传过程中,可以通过
XMLHttpRequest
的upload.onprogress
事件来显示上传进度。 -
错误处理:在上传过程中可能会遇到各种错误,如网络错误、服务器错误等,需要妥善处理这些错误,并给用户友好的提示。
-
安全性:在处理文件上传时,需要注意安全性问题,如防止恶意文件上传、防止跨站脚本攻击等。