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.fileNameconst fileUrl = item.fileUrlif (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 = urla.download = fileNamea.click()URL.revokeObjectURL(url)
}
// 并发下载
const downloadWithConcurrency = async (fileUrl, fileName, totalChunks, item) => {const downloadedChunks = new Array(totalChunks).fill(null)let completedChunks = 0const 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
}