小练手~
自己写的一个大文件切片文件上传,待优化~
实例化对象时传入的配置只是切片上传的一些通用属性,至于文件名和文件则是传入upload方法,这样实例化对象后就可以给多个不同的文件进行切片上传;
优化点:
1.并行发送未实现(但不一定会实现,因为一个文件如果实在有一小个片段出问题,个人认为基本上这个文件也就废了所以我的代码中就是一旦有出现文件失败重传多次还失败的就直接返回失败的结果)
2.监听进度
...
export default class BigFileUpload {
/**
* 大文件上传封装版
* upload方法目前只实现上传完成后返回是否成功(所有都成功才叫成功,有重发机制,重发失败五次还失败的就不重发了,直接失败)
* @param option: {
* chunkSize {Number} 每一段的大小
* uploadUrl {String} 上传地址
* noExtFileName {String} 文件名,不包括后缀名
* }
*/
constructor(option) {
this.fileNameWithExt = "" // 最终要上传的文件名
this.allowExt = {
"text/plain": ".txt",
"image/png": ".png",
"image/jpeg": ".jpg",
"video/mp4": ".mp4"
}
this.option = {
chunkSize: 60 * 1024, // 60kb
uploadUrl: "http://localhost:12580/file",
noExtFileName: new Date().getTime(),
}
if (option) { // 用户如果传入配置,那么就覆盖原对象
Object.assign(this.option, option)
}
}
/**
* @param file {Object} 文件对象
* @param uploadFileName {String} 要上传的文件的名字取为什么(不包含.后缀名)
*/
async upload(file, uploadFileName) {
if (!file) {
throw new TypeError("arguments[0] except a object but got a null")
}
let transferredSize = 0 // 已上传的大小
const {chunkSize, noExtFileName} = this.option
uploadFileName = uploadFileName || noExtFileName
const failChunks = [] // 上传失败的片段
const {size, type} = file
this.fileNameWithExt = uploadFileName + this.allowExt[type] // 设置最终文件名
while (transferredSize < size) { // 当且仅当上传进度小于总进度才进行上传
const fileChunk = file.slice(transferredSize, chunkSize)
// 生成formData
const formData = BigFileUpload._generateFormData({
"type": type,
"file": fileChunk,
"name": this.fileNameWithExt,
})
try {
// 发送请求
const response = await this._request(formData)
if (response.code !== 0) {
failChunks.push(fileChunk)
}
} catch (error) {
failChunks.push(fileChunk)
} finally {
// 已传size进行处理
transferredSize += chunkSize
}
}
// 失败重发
if (failChunks.length) {
// 返回是否重发成功
return await this._reUploadErrorFile(failChunks, this.fileNameWithExt, type)()
} else {
return true
}
}
// 生成formData
static _generateFormData(fileObj) {
const formData = new FormData()
for (const key in fileObj) {
formData.append(
key,
fileObj[key]
)
}
return formData
}
_reUploadErrorFile(fileChunks, fileName, type) {
// 初始化变量:五次重发还失败的放到数组里面
let reUploadedFail = []
let failChunksMap = new Map()
for (let chunk of fileChunks) {
failChunksMap.set(chunk, 0)
}
const chunks = failChunksMap.keys()
let chunk = chunks.next() // 当前的chunk
const _executeReUpload = async () => {
if (chunk.done) {
return !reUploadedFail.length;
} else {
const formData = BigFileUpload._generateFormData({
name: fileName,
file: chunk.value,
repeat: 1,
type
})
const response = await this._request(formData)
if (response.code !== 0) {
const failCount = failChunksMap.get(chunk.value) + 1
failChunksMap.set(chunk.value, failCount)
if (failCount >= 5) { // 已经是第五次失败了或者大于第五次
reUploadedFail.push(chunk.value)
return false
}
}
return await _executeReUpload()
}
}
return _executeReUpload
}
async _request(formData) {
const res = await fetch(this.option.uploadUrl, {
method: "POST",
body: formData
})
return await res.json()
}
}
使用: