基于nodom框架的大文件上传插件
重新封装了Ajax的post 方法 使用了blob对象的slice 对文件分块
测试地址 上传测试网址
- 由于文件有多个 采用了 手写next方法来实现
static postAll(config) {
let me = this
let arr = []
let result = []
let index = 0
let map = config.params.map(i => {
return () => {
return me.postFile(config, i.file)
}
})
let next = function() {
if (!map.length) {
config.callback()
return
}
let f = map.shift()
f().then(r => {
next()
})
}
next()
}
这里的next方法完全可以使用async 函数来代替
let next=async function(map){
for(let i=0;i<map.length;i+=1){
await map[i]()
}
config.callback()
}
postFile返回的是promise对象
static postFile(config, file) {
let me = this
let url = config.url
let Size = 1024 * 1024 * 2
let slice = Math.ceil(file.size / Size)
let start = 0
let index = 0
let record = 0
let arr = []
let date = new Date().getTime()
while (index < slice) {
let end = (index + 1) * Size
if (end > file.size) end = file.size
arr.push(me.slice(config, date, file, index, start, end, url, slice, Size, file.size))
index += 1
start = end
}
return new Promise((re, rj) => {
me.control(arr, re)
})
}
postFile内部返回的是一个文件全部传完之后 需要resolve 我把 传小文件 每一个都生成了 fn
fn 会返回promise 对象
static slice(config, date, blob, index, start, end, url, all, Size, total) {
let time = config.time || 6000000
let timeF = config.timeout || function() {}
return () => {
return new Promise((re, rj) => {
let xml = new XMLHttpRequest()
let chunk = blob.slice(start, end)
// console.log((end-start)/(1024*1024));
let data = new FormData()
data.append('file', chunk, blob.name)
data.append('name', blob.name)
data.append('all', all)
data.append('index', index)
data.append('date', date)
data.append('size', Size)
data.append('fileSize', total)
xml.open('POST', url)
xml.onload = function() {
re(true)
}
xml.timeout = time
xml.ontimeout = timeF
xml.send(data)
})
}
}
但是考虑到谷歌的并发是6 ie是2 又不能一下子全部生成promise
需要控制 就写了一个 control 内部使用了一个队列 promise 成功之后 再到fn组成的map取一个继续 直到全部 promise 执行完毕
那么就会让postFile的promise对象 成功 就会回到postAll的 await 继续执行下一条的代码了
static control(arr, cb) {
let total = 6
let me = this
let len = arr.length
let data = arr.slice(0, 6)
let result = []
let start = 6
let count = 0
arr = arr.slice(6)
let next = function() {
if (total < 6 && arr.length) {
var item = arr.shift()
total += 1
item.$index = start
start += 1
item().then(r => {
total -= 1
result[item.$index] = r
count += 1
next()
})
}
if (len === count) {
cb()
}
}
for (let i = 0; i < data.length; i += 1) {
data[i]().then(r => {
total -= 1
result[i] = r
count += 1
data.splice(i--, 1)
next()
})
}
}
node 使用的是express
对于每一个文件分片 直接使用了rename拷贝 也可也写高效的方法
postChipe.post('/', function(req, res) {
let file = req.files.file
let index = parseInt(req.body.index)
let date = req.body.date;
let size=parseInt(req.body.size);
let all = parseInt(req.body.all);
let total=parseInt(req.body.fileSize);
if(!map[date]){
fs.mkdirSync(truePath + date);
}
fs.rename(file.path, truePath + date + '/' + index + file.originalFilename, r => {
if (map[date]) {
map[date].index++
} else {
map[date]= {}
map[date].start=[];
map[date].index = 1
map[date].arr = []
map[date].file = file.originalFilename
map[date].path = truePath + date
}
map[date].all=total;
map[date].size=size;
map[date].start[index]=size*index;
map[date].arr[index] = truePath + date + '/' + index + file.originalFilename
if (map[date].index === all) {
merge(map[date], publicPath, () => {
delete map[date];
res.send({ upload: true })
},truePath+date);
} else {
res.send({ result: true })
}
})
})
外部建了一个map 来映射 这里map对象需要存贮的是 index 索引 同时使用一个计数的index 每一片都要记录 blob的size和
一共多少片 以及 每一片复制完毕 都要 使用count计数
当count ===all时 就合并
const merge = function(old, newPath, cb,dirP) {
let map=old.arr.map((i,index)=>{
return new Promise((res,rj)=>{
let re = fs.createWriteStream(newPath + old.file,{start:old.start[index]});
let tem = fs.createReadStream(i)
tem.pipe(re, { end: false })
tem.on('end', () => {
fs.unlinkSync(i);
res();
})
});
})
Promise.all(map).then((r)=>{
console.log(process.memoryUsage());
fs.rmdirSync(dirP);
cb();
return ;
});
}
createWriteStream 有一个偏移量 那么n个分片就可以 多个分片同时合并
合并完毕之后 将 原始文件删除
网友评论