前端大文件分片下载(断点续传)
思路就是后端将文件按照前端传递分片大小切片,然后传递给前端,传递完成后前端进行文件的拼接合成,上传一样的道理,只不过前端分片,后端合成。
前端使用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)
相关文章