星期
前端大文件分片下载(断点续传)
首页 > 我的学习历程    作者:月丶   2021年9月27日 10:11 星期一   热度:7249°   百度已收录  
时间:2021-9-27 10:11   热度:7249° 

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

前端使用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)





二维码加载中...
本文作者:月丶      文章标题: 前端大文件分片下载(断点续传)
本文地址:http://silver.eleuu.com/?post=44
版权声明:若无注明,本文皆为“月丶”原创,转载请保留文章出处。

返回顶部    手机版本    会员注册   
版权所有:月丶    博主: 月丶    团队首页电子乌托邦  博客框架:emlog   蜀ICP备18008322号   
  
//音乐播放器