背景
对于大文件 视频,音频的上传,可以采用分片上传,其中前端代码如下
其中 browser-md5-file 需要自行install
import {
checkFile, uploadFile, uploadFileSmall } from 'services/upload'
import BMF from 'browser-md5-file'
import Tool from '../utils/tool'
import {
reject } from 'lodash'
const bmf = new BMF()
/**
* 文件唯一标识符
* @param file
* @returns {string}
*/
const getFileUniqueKey = file => {
return new Promise(resolve => {
let fileUniqueKey = null
bmf.md5(file, (err, md5) => {
console.log('err:', err)
console.log('md5 string:', md5)
const key10 = parseInt(md5, 16)
fileUniqueKey = Tool._10to62(key10)
resolve(fileUniqueKey)
})
})
}
/* 获取视频或者音频时长 */
const getDuration = file => {
var binaryData = []
// 传入file中raw
binaryData.push(file)
// 获取视频或者音频时长
var fileurl = URL.createObjectURL(new Blob(binaryData))
// 经测试,发现audio也可获取视频的时长
var audioElement = new Audio(fileurl)
audioElement.addEventListener('loadedmetadata', function(_event) {
// audioElement.duration就是视频时长
file.duration = audioElement.duration
})
}
const getFileShard = (shardIndex, shardSize, file) => {
const start = (shardIndex - 1) * shardSize // 当前分片起始位置
const end = Math.min(file.size, start + shardSize) // 当前分片结束位置
const fileShard = file.slice(start, end) // 从文件中截取当前的分片数据
return fileShard
}
const upload = (param, file) => {
return new Promise(resolve => {
const imageUrls = []
const shardIndex = param.shardIndex
const shardTotal = param.shardTotal
const shardSize = param.shardSize
const fileShard = getFileShard(shardIndex, shardSize, file) // 从文件中截取当前的分片数据
// 将文件转唯base64
const fileReader = new FileReader()
console.log('进度', parseInt((shardIndex - 1) * 100) / shardTotal)
fileReader.onload = function(e) {
const base64 = e.target.result
param.shard = base64
uploadFile(param).then(res => {
console.log('uploadFile', param)
console.log('进度', parseInt(shardIndex * 100) / shardTotal)
imageUrls.push(res)
if (shardIndex < shardTotal) {
// 上传下一个分片
param.shardIndex = param.shardIndex + 1
upload(param, file)
console.log('分片 ', shardIndex)
} else {
}
resolve(imageUrls[0])
})
}
fileReader.readAsDataURL(fileShard)
})
}
/**
* 检查文件状态,是否已上传过?传到第几个分片?
*/
// insertFn 非必传
const check = (param, file, insertFn) => {
return new Promise((resolve, reject) => {
checkFile(param.key).then(response => {
console.log('response', response)
if (response.message == '') {
param.shardIndex = 1
console.log('没有找到文件记录,从分片1开始上传')
upload(param, file).then(res => {
console.log('upload上传后返回的数据', res)
// if (res.message.path && insertFn) insertFn(res.message.path)
if (res.message.path) {
if (insertFn) insertFn(res.message.path)
resolve(res.message)
}
})
} else if (response.message.shardIndex === response.message.shardTotal) {
// 已上传分片 = 分片总数,说明已全部上传完,不需要再上传
console.log(
'文件极速秒传成功!',
response.shardIndex,
response.shardTotal
)
response.message.duration = param.duration
console.log('upload上传后返回的数据', response)
if (response.message.path) {
if (insertFn) insertFn(response.message.path)
resolve(response.message)
}
} else {
param.shardIndex = response.shardIndex + 1
console.log('找到文件记录,从分片' + param.shardIndex + '开始上传')
upload(param, file)
}
})
})
}
/**
* 图片上传/音视频上传 调用uploadFunc() 即可
*
* @param file
* @param suffixs Array 多媒体类型 ['mp3']
* @param usage String 用途,必传值general, avatar, picture, voice, pdf, txt, video
* @function insertFn 非必须 富文本多媒体customUpload需要, 其他非必须
*
*/
const uploadFunc = (file, suffixs, usage, insertFn) => {
// const suffixs = ['jpg', 'jpeg', 'png']
// const usage = 'picture' // 注意这里要注明用途,必传值general, avatar, picture, voice, pdf, txt, video
var duration = getDuration(file)
let filename = file.name
const suffix = filename
.substring(filename.lastIndexOf('.') + 1, filename.length)
.toLowerCase()
let validateSuffix = false
for (let i = 0; i < suffixs.length; i++) {
if (suffixs[i].toLowerCase() === suffix) {
validateSuffix = true
break
}
}
if (!validateSuffix) {
console.log('文件格式不正确!只支持上传:' + suffixs.join(','))
return
}
return new Promise((resolve, reject) => {
getFileUniqueKey(file).then(res => {
let key = res
// 文件分片
const shardSize = 5 * 1024 * 1024 // 以5MB为一个分片
const shardIndex = 1 // 分片索引,1表示第1个分片
const size = file.size
const shardTotal = Math.ceil(size / shardSize) // 总片数
const param = {
shard: '',
shardIndex,
shardSize,
shardTotal,
usage,
filename,
suffix,
size,
key,
duration:file.duration,
}
if (insertFn) {
check(param, file, insertFn)
} else {
check(param, file).then(data => {
if (data) resolve(data)
})
}
})
})
}
tool.js ↓
/**
* 10进制转62进制
* @param number
* @returns {string}
* @private
*/
_10to62: function (number) {
let chars = '0123456789abcdefghigklmnopqrstuvwxyzABCDEFGHIGKLMNOPQRSTUVWXYZ';
let radix = chars.length;
let arr = [];
do {
let mod = number % radix;
number = (number - mod) / radix;
arr.unshift(chars[mod]);
} while (number);
return arr.join('');
}