在Node.js中使用Multer追踪文件上传进度

19
如何跟踪上传到NodeJs服务器的文件的进度。我在服务器端使用multer上传文件。是否需要向客户端发送某种信息,以便客户端获取上传进度,或者这是在内部处理的,客户端可以自行跟踪进度。
以下是我用于上传文件的代码:
var multer = require('multer');
app.use(multer({dest:'./tmp',limits: {fileSize: 4*1024*1024}}).single('upload'));


router.post('/upload',function(req,res){
 console.log(req.file);
});

你解决了这个问题吗?我需要展示上传进度,但使用Multer无法实现。 - Koder
5个回答

8

Multer在跟踪文件上传进度时不太灵活。 我尝试了很多方法,使用progress-stream库将请求导入管道,编写自己的函数计算百分比并将进度流式传输到socket.io等等。 通过socket.io的方法(将百分比流式传输到socket,然后再返回给客户端)在一定程度上可以工作,但主要问题在于Multer在文件上传到服务器之前不会返回执行控制权, 所以如果你将其设置为中间件函数如下:

this.router.post('/upload', upload.single('file'), function(req: Request, res: Response) {...}

如果您尝试在 upload.single 前添加中间件(如 this.router.post('/upload', dosomething, upload.single('file')...), 那么您将无法获取进度或 req.file,因为此时 req.file 将不存在,只有在之后才能完全访问 req.file。这就是中间件的工作原理。

如果您需要跟踪进度,但不需要将其发送回客户端(如果您需要一个进度条,则不适用),那么您可以像我下面做的那样去做。

但是!这将起作用,因为您将计算请求大小而不是 req.file 大小(req.file 仅存在于 Multer 的上下文中)。

虽然这没有太大区别,但 req 和 req.file 是在同一请求中传递的,因此如果您使用以下代码,您将看到百分比,然后运行使用 Multer 保存文件的代码。

服务器端跟踪进度的示例:

var uploadImages = multer({ dest: ...}).single('...');

//more code ...

this.router.post('/upload', (req: Request, res: Response) => {
    let progress = 0;
    let fileSize = req.headers['content-length'] ? parseInt(req.headers['content-length']) : 0;
    req.on('data', (chunk) => {
        progress += chunk.length;
        res.write((`${Math.floor((progress * 100) / fileSize)} `));
        if (progress === fileSize) {
            console.log('Finished', progress, fileSize)
        }
    });
})
//And calling the Multer function down here...

uploadImages(req,res...){}

在使用Multer上传文件时,可以通过调用中间件函数而不是在路由声明中使用来指定错误处理,Multer的文档中有详细说明。

"如果您想捕获来自Multer的特定错误,可以自己调用中间件函数。"

req.file的值仅存在于数据已传输到Multer进行处理和保存的上下文中。因此,建议任何想要在服务器端监听进度并需要在前端执行此操作的人,如果可以,则可以使用Axios。它具有非常好的钩子来跟踪Promise的进度。

发送表单数据并跟踪进度的示例:

saveFile(file,url): Promise<...>  {
    let formData = new FormData();
     formData.append('file', file);

     const config = {
        onUploadProgress: (progressEvent) => {
        var percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
            //do something with the percentCompleted
            //I used an observable to pass the data to a component and subscribed to it, to fill the progressbar
        }
       }

      return axios.post(url, formData, config)
}

希望这符合您的需求,有助于避免烦恼。

6
您可以简单地使用中间件来跟踪文件上传的进度。
我们在 multer 中间件之前使用此中间件。
通过此中间件,我们为 req 对象设置一个事件监听器以跟踪上传进度。然后我们调用 next 来调用下一个中间件(Multer)。
以下是代码:
const multer = require('multer');
const upload = multer({dest:'./tmp',limits: {fileSize: 4*1024*1024}});

function progress_middleware(req, res, next){
    let progress = 0;
    const file_size = req.headers["content-length"];
    
    // set event listener
    req.on("data", (chunk) => {
        progress += chunk.length;
        const percentage = (progress / file_size) * 100;
        // other code ...
    });

    // invoke next middleware
    next();
}

router.post('/upload', progress_middleware, upload.single('upload'), (req,res) => {
 console.log(req.file);
});



1
如何从中间件将连续的上传响应传递给客户端,并在文件上传后执行其余操作? - Sandeep Jadhav
1
如何从中间件将连续的上传响应,即百分比传递给客户端,并在文件上传后执行其余操作? - Sandeep Jadhav
1
@user3273891,你可以在前端处理它。你可以通过将发送的字节数除以总文件大小来查看发送了多少文件。 - Ali Pirpiran
1
正确的答案,我没有测试过,但这是正确的方法。 - Anthony

6

以下是项目 Github 页面上 LinusU 的 回答(他建议使用 progress-stream):

将请求传递给该模块并将其交给 Multer。

    var p = progress()
    var upload = multer().single('file')

    req.pipe(p)
    p.headers = req.headers

    p.on('progress', _)
    upload(p, res, _)

2
你能详细说明一下这种方法吗?它似乎是我需要的,但进度事件只在100%时触发一次。 - libzz

6

工作代码

使用Hooks的REACTJS代码(前端)

upload.jsupload.jsx

import React, { useState } from "react";
import Axios from "axios";
import { Progress } from "reactstrap";

const Upload = () => {
  const [uploadPercentage, setUploadPercentage] = useState(0);
  const [showProgressBar, setProgressBarVisibility] = useState(false);
  const onSubmit = e => {
    e.preventDefault();
    setProgressBarVisibility(true);
    const demo = document.getElementById("demo");
    const bodyFormData = new FormData(demo);
    Axios({
      headers: {
        "Content-Type": "multipart/form-data",
      },
      method: "POST",
      data: bodyFormData,
      url: "/profile", // route name
      baseURL: "http://localhost:5000/api/upload", //local url
      onUploadProgress: progress => {
        const { total, loaded } = progress;
        const totalSizeInMB = total / 1000000;
        const loadedSizeInMB = loaded / 1000000;
        const uploadPercentage = (loadedSizeInMB / totalSizeInMB) * 100;
        setUploadPercentage(uploadPercentage.toFixed(2));
        console.log("total size in MB ==> ", totalSizeInMB);
        console.log("uploaded size in MB ==> ", loadedSizeInMB);
      },
      encType: "multipart/form-data",
    });
  };

const handleFormClick = () => {
    setProgressBarVisibility(false);
    setUploadPercentage(0);
  };

  return (
    <div>
      <form onSubmit={e => onSubmit(e)} id="demo">
        <input type="file" name="avatar" id="avatar" />
        <input type="submit" value="Submit" />
      </form>
    {showProgressBar ? (
        <>
          <div className="text-center">
            {parseInt(uploadPercentage) !== 100
              ? `Upload percentage - ${uploadPercentage}`
              : "File successfully uploaded"}
          </div>
          <Progress
            animated={parseInt(uploadPercentage) !== 100}
            color="success"
            value={uploadPercentage}
          />
        </>
      ) : null}
    </div>
  );
};

export default Upload;

NODEJS代码(后端)

upload.js

(上传.js)
const express = require("express");
const multer = require("multer");
const router = express.Router();

var storage = multer.diskStorage({
  destination: "./uploads/",
  filename: function(req, file, cb) {
    cb(null, file.originalname);
  },
});

const upload = multer({ storage });

router.post("/profile", upload.single("avatar"), function(req, res, next) {
  console.log(req.file);
  if (req.file) {
    return res.status(200).json(req.file);
  }
  return res.status(400).json({ msg: "PLEASE UPLOAD FILE" });
});

module.exports = router;


0

React 前端

const formData = new FormData();
formData.append('myImage', this.state.imageData);
const config = {
    onUploadProgress: progressEvent => console.log(progressEvent.loaded), // TO SHOW UPLOAD STATUS
    headers: {
        'content-type': 'multipart/form-data'
    }
};
axios.post("/api/api/save-media", formData, config)
    .then((response) => {
        // do whatever you want
    }).catch((error) => {
        console.log(error)
    });

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接