Vue中实现大文件的切片下载
切片下载需要后端提供两个接口,第一个接口用来获取当前下载文件的总切片数,第二个接口用来获取具体某一个切片的内容。
界面展示
数据流展示
代码
接口
// 切片下载-获取文件的总切片数
export function getChunkDownloadMetaInfo(queryParams) {
return request({
url: `/resource/chunkDownloadMetaInfoByUrl`,
method: 'get',
params: queryParams
})
}
// 切片下载-获取当前切片的文件内容
export function getChunkDownload(queryParams) {
return request({
url: `/resource/chunkDownloadByUrl`,
method: 'get',
headers: {
'Content-Type': 'application/json; application/octet-stream'
},
responseType: 'blob', //响应数据格式配置
params: queryParams
})
}
下载
// 下载
const downloadItem = async item => {
const fileName = item.fileName
const fileUrl = item.fileUrl
if (item.isDownloading) {
ElMessage({
type: 'error',
message: `${fileName}正在下载中请稍等`
})
return
}
item.progress = 0 // 下载进度
item.isDownloading = true // 是否正在下载
item.isAborted = false // 是否中止下载
try {
// 获取文件信息
const fileInfo = await getFileInfo(fileUrl)
const totalChunks = fileInfo.totalChunkNum
// 并发下载所有切片
const downloadedChunks = await downloadWithConcurrency(fileUrl, fileName, totalChunks, item)
// 合并并下载文件
const mergedBlob = mergeBlobs(downloadedChunks)
triggerDownload(mergedBlob, fileName)
} catch (error) {
console.error('下载失败:', error)
ElMessage({
type: 'error',
message: `${fileName}文件下载失败`
})
} finally {
item.isDownloading = false
}
}
// 获取文件信息
const getFileInfo = async fileUrl => {
try {
const response = await getChunkDownloadMetaInfo({ url: fileUrl })
return response.data
} catch (error) {
throw new Error(`获取文件信息失败: ${error.message}`)
}
}
// 下载单个切片
const downloadChunk = async (fileUrl, fileName, chunkIndex) => {
try {
const response = await getChunkDownload({
url: fileUrl,
chunkNumber: chunkIndex
})
return response
} catch (error) {
throw new Error(`下载切片失败: ${error.message}`)
}
}
// 合并 Blob
const mergeBlobs = blobs => {
return new Blob(blobs)
}
// 触发文件下载
const triggerDownload = (blob, fileName) => {
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = fileName
a.click()
URL.revokeObjectURL(url)
}
// 并发下载
const downloadWithConcurrency = async (fileUrl, fileName, totalChunks, item) => {
const downloadedChunks = new Array(totalChunks).fill(null)
let completedChunks = 0
const downloadNextChunk = async chunkIndex => {
if (item.isAborted) return // 如果已中止,直接返回
try {
const chunk = await downloadChunk(fileUrl, fileName, chunkIndex)
console.log(chunk, '123123123123123123')
downloadedChunks[chunkIndex] = chunk
// 更新下载进度
completedChunks++
item.progress = Math.round((completedChunks / totalChunks) * 100)
} catch (error) {
item.isAborted = true // 中止下载
throw error
}
}
// 启动并发下载
const workers = []
for (let i = 0; i < totalChunks; i++) {
if (item.isAborted) break // 如果已中止,停止启动新任务
if (workers.length >= maxConcurrency) {
// 等待一个任务完成后再启动新任务
await Promise.race(workers)
}
const worker = downloadNextChunk(i).finally(() => {
workers.splice(workers.indexOf(worker), 1)
})
workers.push(worker)
}
await Promise.all(workers) // 等待所有任务完成
return downloadedChunks
}