大文件之分片上传

253 阅读1分钟

大文件上传时可以将文件利用Blob对象的slice方法切分为若干chunk,并发发送upload请求(使用spark-md5生成hash,作为文件的唯一标识),服务器端将接收到的chunk保存在相应的文件夹中;切片上传完成后发送merge_chunk 请求,通知服务器端合并切片,生成最终的文件。

github: github.com/kythen/expr… 

             github.com/kythen/webp…

主要代码:

js

const {fileList} = this.state;
const {name} = values;
const file = fileList[0];
const promiseList = [];
const chunkSize = 4 * 1024 * 1024;
const chunkCount = Math.ceil(file.size / chunkSize);
const hash = await this.hasFile(file);

for (let i = 0; i < chunkCount; i++) {   
         const start = i * chunkSize;       
         const end = Math.min(file.size, start + chunkSize);  
         const formData = new FormData();
         formData.append('file', file.slice(start, end));    
         formData.append('name', file.name);       
         formData.append('total', chunkCount);     
         formData.append('size', file.size);  
         formData.append('index', i);           
         formData.append('hash', hash);         
         promiseList.push(callApi('post', `${prefix}/upload`, '', formData));
}

Promise.all(promiseList).then(res => {   
            this.setState({loading: false});       
            const data = {            
                            size: file.size,         
                            name: file.name,         
                            total: chunkCount,    
                            hash       
                          };      
          callApi('post', `${prefix}/merge_chunks`, '', data).then(res => { 
                       alert('上传成功');        
            })
 });


node

router.post('/upload', upload.single('file'), function(req, res, next) {

     const {     
               name,  
               size,    
               total,  
               hash,    
               index,  
               file 
           } = req.body;  
     const chunksPath = path.join(uploadPath, hash, '/');  
     if (!fs.existsSync(chunksPath)) mkdirsSync(chunksPath);  
     fs.renameSync(req.file.path, chunksPath + hash + '-' + index);    
     res.json({message: 'SUCCEED'});
});

router.post('/merge_chunks', function(req, res, next) { 
   const {      
              size,   
              name,     
              hash,      
              total   
         } = req.body;   
  const chunksPath = path.join(uploadPath, hash, '/');   
  const chunks = fs.readdirSync(chunksPath);   
  const filePath = path.join(uploadPath, name);    
  fs.writeFileSync(filePath, '');   
  if (chunks.length !== total || chunks.length === 0) {    
          res.status = 200;       
          res.end('切片数量不符合');  
  }   
 for(let i = 0; i < total; i++) {    
    fs.appendFileSync(filePath, fs.readFileSync(chunksPath + hash + '-' + i));  
    fs.unlinkSync(chunksPath + hash + '-' + i);  
  }   
 fs.rmdirSync(chunksPath);  
 res.json({message: 'SUCCEED'});
});

ps: 断点续传是基于分片上传的,由于网络故障或用户主动暂停后,下次上传会跳过已经上传的部分,只上传还为上传的部分,有机会再更新断点续传吧。。