美文网首页
React+NodeJs实现文件切片上传

React+NodeJs实现文件切片上传

作者: 小进进不将就 | 来源:发表于2020-01-29 23:22 被阅读0次

前端:
① 全局变量

//切片数量
const chunkNumber = 10;
//用来存储文件
const uploadFileData={
    file: {
      name:'unknow'
    }
}

② 获取文件
jsx

 <input type="file" onChange={(e)=>{getFlie(e)}} />

getFlie():通过 input(type=file) 获取文件

//通过 input(type=file) 获取文件
function getFlie(e:object) {
  //获取文件队列的第一个文件
  //写法等同于 const file = e.target.files[0]
  const [file] = e.target.files;
  if (!file) return;
  uploadFileData.file = file;
}

③ 点击按钮,发送文件至server
jsx

<Button onClick={()=>{uploadFile(fetchBigFileData,)}}>上传</Button>

uploadFile():获取文件切片集合,并将每片文件发送给server

// 获取文件切片集合,并将每片文件发送给`server`端
function uploadFile(fetchBigFileData:(item:object) => void,) {
    if (!uploadFileData.file){
      return
    }
    //获取切片集合
    const fileChunkList = createFileChunk(uploadFileData.file,chunkNumber);
    //将每一个切片封装进 obj,并发送给server
    fileChunkList.forEach((item,index) => {
      //没有用 json 的原因是读取 Blob 对象需要使用FileReader的readAsArrayBuffer解析读取,
      //而使用 FormData 的最大优点就是可以存储二进制文件
      //详情请参考:https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader/readAsArrayBuffer

      // const obj:object={}

      //这么写无效,文件流被序列化之后,传给 server 是空对象{}
      //参考:https://www.jianshu.com/p/80e133a16d5e
      // obj.chunk=item

      // obj.hash=uploadFileData.file.name + "-" + index
      // obj.fileName=uploadFileData.file.name
      // obj.chunkNumber=chunkNumber+''

      const obj=new FormData()
      //二进制的片文件
      obj.append('chunk',item)
      //hash 码,标识每一个文件
      obj.append('hash',uploadFileData.file.name + "-" + index)
      //上传的文件名称
      obj.append('fileName',uploadFileData.file.name)
      //文件片数,方便后端标识并合并文件
      obj.append('chunkNumber',chunkNumber+'')
      //请求 server
      fetchBigFileData(obj)

    });

}

注意:
(1) 文件类型是Blob,是二进制格式,参考:
https://developer.mozilla.org/zh-CN/docs/Web/API/FileReader/readAsArrayBuffer

(2) 如果要用Json格式发送片文件的话,需要使用FileReaderreadAsArrayBuffer解析,参考:
使用js FormData传文件流,传json(重点)

为图方便,我们使用FormData来直接存储并发送二进制文件。

(3) Object可以存储Blob类型的对象,但在传输的时候Blob类型文件会被序列化成空对象{ }

(4) 后端知道切片上传是否完全的方式有两种:
第一种就是前端塞了chunkNumber属性告知后端切片的数量,让后端自己去计算;
第二种是前端在切片请求都发完后,再发一个请求告知后端。

createFileChunk():生成文件切片,并返回切片集合

function createFileChunk(file: any, length: number = chunkNumber) {
  const fileChunkList = [];
  //向上取整
  const chunkSize = Math.ceil(file.size / length);
  let cur = 0;
  while (cur < file.size) {
    //将文件切成片
    const fileChunk= file.slice(cur, cur + chunkSize)
    //将每一片文件存进数组中(音频文件是Blob类型)
    fileChunkList.push(fileChunk);
    //是否继续循环的判断
    cur += chunkSize;
  }
  return fileChunkList;
}

fetchBigFileData:发起请求:

function fetchBigFileData(payload: object) {
    dispatch({
      type: "uploadFile/fetchBigFileData",
      payload
    });
  }

*fetchBigFileData({ payload }, { call, put }) {
    yield call(queryBigFileData, payload );
}

export async function queryBigFileData(payload:any) {
  return request(`${API_SERVER}uploadbigfile`, {
    method: "POST",
    data: payload
  });
}

由于用的 ant-design-pro,里面已封装好了,可能会对读者造成疑惑,但本质就是一个 post 请求,注意 body 类型是 form-data。


后端:
① 接口定义为/uploadbigfile,为了方便,我们不连接数据库,直接将片文件存储在文件夹中

② 全局变量:

const {Router} = require('express');
const router = new Router();
const path = require("path");
const fse = require("fs-extra");
const multiparty = require("multiparty");

// 文件片的存储目录
const ChunkFileDir = path.resolve(__dirname, "../../", "uploadFile/chunkFile");
//合成的文件的存储目录
const TotalFileDir = path.resolve(__dirname, "../../", "uploadFile/totalFile");
let fileName = ''
let serverChunkNumber = 0
let clientChunkNumber = 0
let chunkDir = ''

注意:
由于需要解析FormData格式的数据,并操作文件,需要安装multipartyfs-extra

npm i multiparty --save
npm i fs-extra --save

③ 在 POST 请求中接收并存储文件片:

//post 方法接收文件片
router.post("/", (req, res, next) => {
  try {
    //关于multiparty的讲解,请看:https://www.cnblogs.com/wangyinqian/p/7811719.html
    const multipart = new multiparty.Form();
    // 解析FormData数据
    multipart.parse(req, (err, fields, files) => {
      if (err) {
        return;
      }
      //chunk:{
      // path:存储临时文件的路径,
      // size:临时文件的大小,
      // }
      const [chunk] = files.chunk;
      const [hash] = fields.hash;
      //获取切片总数量
      clientChunkNumber = +fields.chunkNumber[0];
      //获取文件名称
      [fileName] = fields.fileName;
      //本次文件的文件夹名称,如 xx/xx/uploadFile/chunkFile/梁博-出现又离开.mp3
      chunkDir = `${ChunkFileDir}/${fileName}`;

      // 切片目录不存在,创建切片目录chunkDir
      if (!fse.existsSync(chunkDir)) {
        fse.mkdirs(chunkDir);
      }
      //将每片文件移动进chunkDir下
      fse.move(chunk.path, `${chunkDir}/${hash}`);
      //server 端计算切片数量,
      serverChunkNumber = serverChunkNumber + 1
      //当到数时,自动合并文件
      if (clientChunkNumber === serverChunkNumber) {
        //这里方便测试,用 get 方法单独来 merge 文件
        // mergeFileChunk(chunkDir)
        serverChunkNumber = 0
      }
      //这么写返回 client 会出现乱码
      // res.end("已接收文件片 "+hash);
      res.status(200).json("已接收文件片 "+hash);

    });

  } catch (err) {
    res.status(400).json(err)
  }
});

注意:
(1) 关于multiparty的讲解,请看:
https://www.cnblogs.com/wangyinqian/p/7811719.html

(2) /uploadFile/chunkFile为存储切片文件的文件夹,/uploadFile/totalFile为合成切片文件的文件夹

(3) 前端上传文件并发送请求后,会生成如下切片文件:

④ 在 GET 请求中合并文件片:
为方便测试,我们将uploadFile()中的mergeFileChunk()注释掉,写一个简单的GET请求来调用mergeFileChunk()

//合并文件
router.get("/", async (req, res, next) => {
  try {
    mergeFileChunk(ChunkFileDir+'/'+fileName,TotalFileDir,fileName)

    res.status(200).json("合并文件成功!");

  } catch (err) {
    res.status(400).json(err, 'err104')
  }
});

mergeFileChunk():合并文件

// 合并切片
const mergeFileChunk =async (chunkDir,totalDir,fileName) => {
  //指定合成的文件名及位置
  const totalPaths=totalDir+'/'+'合成-'+fileName
  // await fse.writeFile(totalDir+'/'+'合成-'+fileName,'')
  //
  // const chunkPaths=await new Promise((resolve, reject)=>{
  //   console.log(chunkDir,'chunkDir26')
  //   fse.readdir(chunkDir,(err, chunkPaths) => {
  //     if(err){
  //       reject(err)
  //     }
  //     resolve(chunkPaths)
  //     //也可以直接在这里循环
  //
  //
  //   })
  // })
  //生成合成的空文件
  fse.writeFileSync(totalPaths,'')
  //注意:readFile是异步方法,有 callback,同步的方法-readFileSync 没有回调
  // fse.readFile(`${chunkDir}/${chunkPath}`,(err,data)=>{
  //   fse.appendFileSync(a+'/a.mp3', data);
  // })
  //读取切片文件目录,返回切片文件集合
  const chunkPaths=fse.readdirSync(chunkDir)
  //循环读取切片文件内容并合并进totalPaths中
  chunkPaths.forEach(chunkPath => {
    //获取单个切片文件目录
    const chunkFilePath=`${chunkDir}/${chunkPath}`
    //xxx/xxx/uploadFile/chunkFile/梁博-出现又离开.mp3-0
    //同步按顺序读取文件切片,这样才能保证是按顺序将切片合成一整首歌
    const data = fse.readFileSync(chunkFilePath)

    //xxx/xxx/uploadFile/totalFile/梁博-出现又离开.mp3
    //将每个文件片合进单一文件中
    fse.appendFileSync(totalPaths, data);

    //删除文件
    // fse.unlinkSync(chunkFilePath);
  });

  //删除切片的目录
  // fse.rmdirSync(chunkDir);

}

注意:
(1) 如果不调用readFileSync(),而是readFile()的话,会导致合成的文件顺序错误:

(2) writeFileSyncwriteFile/readdirSyncreaddir
它们的区别是:
writeFilereaddir异步方法,它们可以直接获取到方法返回的结果
writeFileSyncreaddirSync同步方法,它们是一个Promise对象,必须在callback中才能获取结果

const chunkPaths=fse.readdirSync(chunkDir)

等同于

const chunkPaths=await new Promise((resolve, reject)=>{
    fse.readdir(chunkDir,(err, chunkPaths) => {
      if(err){
        reject(err)
      }
      resolve(chunkPaths)
      //也可以直接在这里循环
    })
  })

(3) 执行get请求

合并结果

打开音乐播放器听听吧。


(完)

相关文章

  • React+NodeJs实现文件切片上传

    前端:① 全局变量 ② 获取文件jsx: getFlie():通过 input(type=file) 获取文件 ③...

  • Vue Axios 切片上传文件含实时进度

    切片上传一般用于大型文件上传,切成多个部分分开上传。 整体实现思路: 预先设置切片大小。 计算总切片次数 循环调用...

  • iOS将文件切片储存并上传仿断点续传机制

    iOS将文件切片储存并上传仿断点续传机制 iOS将文件切片储存并上传仿断点续传机制

  • 视频切片上传

    主要用于大视频的切片上传 主要实现步骤: 声明变量 获取视频信息的工具类 视频切片 分配文件名:目前只做了mp4格...

  • (javascript)100行代码实现可重试可并发的异步任务队

    背景 昨天我们团队项目中有需要控制并发数,并且将大文件切片上传的需求,期望是并发的去上传文件切片,于是乎构思了一下...

  • php文件上传

    单文件上传实现: 文件上传代码参考:文件上传 多文件上传实现: 前台调用: shell方式 浏览器方式: 后台处理...

  • 网络03

    NSURLSession实现文件上传 实现文件上传的步骤 文件上传设置请求体的格式 使用NSURLSession上...

  • iOS上传大文件(切片上传)

    在开发项目中,我们有可能会遇到上传大文件,比如几百兆,甚至于一个G,因此我们不能直接拿到文件的Path直接转化成N...

  • iOS 上传大文件(切片上传)

    在开发项目中,我们有可能会遇到上传大文件,比如几百兆,甚至于一个G,因此我们不能直接拿到文件的Path直接转化成N...

  • spring-boot 文件上传下载

    1.文件上传配置 2.前端写法 3.后端实现单文件上传 4.后端实现多文件上传 5.后端实现文件下载

网友评论

      本文标题:React+NodeJs实现文件切片上传

      本文链接:https://www.haomeiwen.com/subject/jjsezctx.html