月丶
前端大文件分片下载(断点续传)
2021-9-27 月丶


思路就是后端将文件按照前端传递分片大小切片,然后传递给前端,传递完成后前端进行文件的拼接合成,上传一样的道理,只不过前端分片,后端合成。



前端使用indexDB存储分片文件信息。此处使用的传递方式是将文件流转成了base64传递



base64转blob





/**


* 将base64数据转换成Blob对象


* @param base64 {String}[necessary] base64数据


* @param type {String}[necessary] 转换的数据类型


*/


function base64toBlob(base64, type) {


const bstr = window.atob(base64, type)


let n = bstr.length


const u8arr = new Uint8Array(n)


while (n--) {


u8arr[n] = bstr.charCodeAt(n)


}


return new Blob([u8arr])


}




indexDB操作








const dbConfig = {


baseName: 'shardingDownload',


table: 'file'


}




// 初始化indexedDB


function openDB() {


const { baseName, table } = dbConfig


return new Promise(resolve => {


const request = window.indexedDB.open(baseName, 2)


request.onupgradeneeded = function (event) {


const db = event.target.result


let store


if (!db.objectStoreNames.contains(table)) {


store = db.createObjectStore(table, { keyPath: 'id', autoIncrement: true })


store.createIndex('id', 'id', { unique: true })


}


resolve({


statusCode: 200,


message: '操作成功',


data: db,


})


}


request.onsuccess = event => {


const db = event.target.result


this.db = db


resolve({


statusCode: 200,


message: '操作成功',


data: db,


})


}


})


}




function clear() {


const { table } = dbConfig


return new Promise(resolve => {


const objectStore = this.db.transaction(table, 'readwrite').objectStore(table)


objectStore.clear().onsuccess = function (event) {


resolve({


statusCode: 200,


message: '清空成功',


data: event,


})


}


})


}




// 插入数据


function insert(data) {


const { table } = dbConfig


return new Promise(resolve => {


const request = this.db.transaction([table], 'readwrite')


.objectStore(table)


.add(data)




request.onsuccess = event => {


resolve({


statusCode: 200,


message: '插入成功',


data: event,


})


}




request.onerror = event => {


resolve({


statusCode: -1,


message: '插入失败',


data: event,


})


}


})


}




// 更新数据(根据id)


function update(data) {


const { table } = dbConfig


return new Promise(resolve => {


const request = this.db.transaction([table], 'readwrite')


.objectStore(table)


.put(data)


request.onsuccess = event => {


resolve({


statusCode: 200,


message: '更新成功',


data: event,


})


}


request.onerror = event => {


resolve({


statusCode: -1,


message: '更新失败',


data: event,


})


}


})


}




// 删除数据


function del(id) {


const { table } = dbConfig


return new Promise(resolve => {


const request = this.db.transaction([table], 'readwrite')


.objectStore(table)


.delete(id)


request.onsuccess = function (event) {


resolve({


statusCode: 200,


message: '删除成功',


data: event,


})


}


request.onerror = function (event) {


resolve({


statusCode: -1,


message: '删除失败',


data: event,


})


}


})


}




// 根据id获取数据


function getDataById(id) {


const { table } = dbConfig


return new Promise(resolve => {


const transaction = this.db.transaction([table], 'readonly')


const store = transaction.objectStore(table)


const index = store.index('id')


const request = index.get(id)




request.onsuccess = e => {


const { result } = e.target


if (result) {


resolve({


statusCode: 200,


message: '获取数据成功',


data: result,


})


} else {


resolve({


statusCode: 200,


message: '没有找到数据',


data: null,


})


}


}




request.onerror = function (event) {


resolve({


statusCode: -1,


message: '获取数据失败',


data: event,


})


}


})


}




export default {


getDataById,


del,


update,


insert,


openDB,


clear,


}









下载文件









import axios from 'axios'


import DB from './db'


import CommenFunc from './index'





const { base64toBlob } = CommenFunc


let times = 0




async function initDownloadDB() {


await DB.openDB()


}




function finishDownload(params) {


if (params.url !== '') {


axios(params)


}


}




async function getDataById(id) {


const resData = await DB.getDataById(id)


return resData


}




// 删除任务


async function deleteFile(id) {


const res = await DB.del(id)


return res


}




// 保存文件


async function saveFile(id) {


const file = await getDataById(id)


if (file.statusCode === 200) {


console.log('文件分片下载完成,开始合并文件。')


const item = file.data


const { fileName, fileBlobs } = item


const data = new Blob(fileBlobs, { type: 'application/octet-stream' })


const elink = document.createElement('a')


elink.download = fileName


elink.style.display = 'none'


elink.href = window.URL.createObjectURL(data)


document.body.appendChild(elink)


elink.click()


window.URL.revokeObjectURL(elink.href) // 释放URL对象


document.body.removeChild(elink)


// 自动删除文件


return setTimeout(() => {


deleteFile(item.id)


}, 10000)


}


console.log('获取文件信息失败')




return false


}









/**


* 下载方法


* @param {下载文件参数} fileData 包含参数:url 分片下载地址,perSize 分片大小, total 文件总大小,id 下载id, fileId 文件ID,fileName 下载文件保存名称


* @returns


*/





async function download(fileData) {


times++


console.log(`分片下载开始,第${times}片下载开始。`)


const { url, perSize: psize, total, id, fileId, fileName } = fileData


const insertData = {


id,


fileId,


fileName,


size: 0,


total,


fileBlobs: []


}


const res = await DB.getDataById(id)


if (res.statusCode !== 200) {


return {


statusCode: 200,


message: '本地indexDB查询出错',


}


}


const perSize = psize || 10 * 1024 * 1024 // 此处分片大小必须按字节数分片,不能为任意整数


let index = 0


let isLast = total <= perSize


let curSize = total >= perSize ? perSize : total


const { data: file } = res


if (file){


index = file.size


if (index + curSize - total >= 1) {


isLast = true


curSize = total - file.size


}


}




return axios({


method: 'GET',


url,


params: {


id: fileId,


index,


size: curSize,


}


}).then(async request => {


const { status, data: responseData } = request


if (status === 200 && responseData.statusCode === 200) {


console.log(`分片下载开始,第${times}片下载完成。`)


let textArray = responseData.data


let blobs = []


// 检查返回的json内容是不是数组,若为数组要进行转换


if (textArray.indexOf('[') !== -1) {


textArray = JSON.parse(responseData.data)


blobs = textArray.map(textItem => base64toBlob(textItem, 'text/plain'))


} else {


blobs = [base64toBlob(textArray, 'text/plain')]


}




// 当前下载的文件size


let size = 0


blobs.forEach(bitem => {


size += bitem.size


})


let fileRes = null


if (file){


size += file.size


fileRes = await DB.update({


...insertData,


size,


fileBlobs: [...file.fileBlobs, ...blobs]


})


} else {


fileRes = await DB.insert({


...insertData,


size,


fileBlobs: [...blobs]


})


}


if (fileRes.statusCode === 200 && !isLast) {


// 如果没有下载完成


download({ ...fileData, size })


} else if (isLast) {


// 如果最后一个文件下载完毕,自动保存文件


const iRes = await DB.update({


...insertData,


size,


fileBlobs: file ? [...file.fileBlobs, ...blobs] : [...blobs]


})


if (iRes) {


const { resultUrl = '', resultParams = '' } = insertData


saveFile(id)


// 下载成功后回调


finishDownload({


method: 'POST',


url: resultUrl,


data: resultParams,


})


}


}


} else {


deleteFile(insertData.id)


}


}).catch(err => {


deleteFile(insertData.id)


console.log(err)


})


}




export default {


initDownloadDB,


download


}






使用:



1、初始化 initDownloadDB



2、下载文件



download(fileData)

















发表评论:
昵称

邮件地址 (选填)

个人主页 (选填)

内容