特别提醒
大文件上传的文件切片逻辑, 包括如何将分片文件上传到本地服务器, 请查看之前的文章: 前端大文件上传 - 总结(Vue3 + hook + Web Worker实现,通过多个Worker线程大大提高Hash计算的速度), 本篇仅实现如何将大文件分块上传到S3.
后面写一篇文章实现选中多个文件,显示上传列表可取消上传,以及整合这两篇文件到一篇里,因为涉及的逻辑比较多
流程图
实现逻辑
上节的代码实现中的 uploadFile 函数,我们只需要在对应的代码中添加 加粗样式就行
// 例如下面
async function uploadFile() {
const data = await checkFile()
if (!data) return
const { chunk_upload, upload_id } = data
uploadId.value = upload_id
if (chunk_upload.length === 0) {
// 上传整个文件
// 上传到S3
if (isS3) return await handleUploadS3Request()
}
if (chunk_upload.length !== chunkTotal.value) {
// 上传未上传的分片 - 断点续传
if (isS3) return await handleUploadS3Request(chunk_upload)
}
// 无论是上传到S3还是本地服务器可能存在合并失败的问题
// 这里应该处理未合并的情况,值需要发送合并请求
code...
// 上传完成 - 秒传(可能需要发起合并请求)
code...
}
handleUploadS3Request函数
提醒: 获取ETag需要配置对应的桶策略,ExposeHeaders数组里配置ETag。
// 记录上传到S3的分块
const s3UploadedChunks = ref([])
// 上传到S3
async function handleUploadS3Request(uploadedChunks = []) {
s3UploadedChunks.value = JSON.parse(JSON.stringify(uploadedChunks))
for (let i = 0; i < fileChunkList.value.length; i++) {
if (uploadedChunks.indexOf(i + 1) === -1) {
// 申请上传S3的url
const { code, data, msg } = await applyS3UrlFn({
part_no: i + 1,
upload_id: uploadId.value,
})
if (code === 0) {
try {
await uploadS3Chunk(data.signed_url, i)
} catch (error) {
// 上传失败
...
return false
}
} else {
// 申请失败
...
return false
}
}
}
}
// 上传S3文件某个分块
async function uploadS3Chunk(url, i) {
const etag = await createXhr(url, i)
// 记录已上传的分块
s3UploadedChunks.value.push(i + 1)
// 上传S3文件某个分块完成
await s3ChunkDoneFn({
part_no: i + 1,
upload_id: uploadId.value,
etag,
})
if (s3UploadedChunks.value.length === chunkTotal.value) {
// 上传S3文件所有分块完成
await s3AllChunkDoneFn({ upload_id: uploadId.value })
// 上传成功
callback && callback()
}
}
let xhr = null
// 使用XMLHttpRequest上传Chunk
async function createXhr(url, i) {
return new Promise((resolve, reject) => {
xhr = new XMLHttpRequest()
xhr.open('PUT', url)
xhr.upload.onprogress = (e) => {
getFileProgress(e, i)
}
xhr.onload = () => {
if (xhr.status === 200) {
// 返回ETag
resolve(xhr.getResponseHeader('ETag'))
} else {
reject(new Error('File upload failed'))
}
}
xhr.onerror = () => {
reject(new Error('File upload failed'))
}
xhr.onabort = () => {
cancelFn({ upload_id: uploadId.value })
reject(new Error('File upload aborted'))
}
xhr.send(fileChunkList.value[i])
})
}