首页 > 解决方案 > 重构后 Express JS Multer 异步无法正常工作

问题描述

我一直在尝试将以下代码重构: https ://github.com/shubhambattoo/node-js-file-upload 到几个模块中。我基本上成功了,但是在上传完成之前上传返回主页时遇到问题 - 异步问题。原版没有问题,但我尝试了许多不同的方法,但没有得到任何结果。我觉得问题在于上传中间件调用 next() 函数进行重定向的位置。

应用程序.js

const express = require("express");
const app = express();

const methodOverride = require('method-override');

// Middleware
app.use(methodOverride('_method'));
app.use(express.json());
app.set("view engine", "ejs");

// DB connection then gridfs
require('./startup2/conn')(app);
require('./startup2/storage')(app);

// Application routes
require('./startup2/routes')(app);

// Server
const port = 5000;
app.listen(port, () => {
  console.log("server started on " + port);
});

conn.js


module.exports = function(app) {

  const mongoose = require("mongoose");
  
  // DB
  //const mongoURI = "mongodb://localhost:27017/node-file-upl";
  const mongoURI = 'mongodb://dbuser:pass@0.0.0.0:27017/sro';
  
  // connection
  const conn = mongoose.createConnection(mongoURI, {
    useNewUrlParser: true,
    useUnifiedTopology: true
  });
  conn.collection('uploads');  // Set the name of the MongoDB collection
  app.locals.conn = conn;
  
  // const upload = require('./storage');
  // app.locals.upload = upload;
}

存储.js

const mongoose = require("mongoose");
const multer = require("multer");
const GridFsStorage = require("multer-gridfs-storage");
const mongoURI = 'mongodb://dbuser:pass@0.0.0.0:27017/sro';
const crypto = require("crypto");
const path = require("path");

module.exports = function(app) {

  // Storage
  const storage = new GridFsStorage({
    url: mongoURI,
    file: (req, file) => {
      return new Promise((resolve, reject) => {
        crypto.randomBytes(16, (err, buf) => {
          if (err) {
            return reject(err);
          }
          const filename = buf.toString("hex") + path.extname(file.originalname);
          const fileInfo = {
            filename: filename,
            bucketName: 'uploads' // The MongoDb collection name
          };
          resolve(fileInfo);
        });
      });
    }
  });

  const upload = multer({ storage:storage, limits: { fileSize: 10000000} });
  app.locals.upload = upload;

  conn = app.locals.conn;
  //console.log("Conn is: ", conn);
  conn.once("open", () => {
    //console.log("init the gfs stream"); // init stream
    app.locals.gfs = new mongoose.mongo.GridFSBucket(conn.db, {
      bucketName: "uploads"
    });
  });
}

路由.js

const express = require('express');
const bodyParser = require('body-parser'); 

const files = require('../routes/files');

console.log('Setting up routes in startup/routes.js');

module.exports = function(app) {
  
  const upload = app.locals.upload;
  console.log("Multer upload array", upload);

  app.use('/', files);
}

文件.js

const express = require('express');
const app = express.Router();
const mongoose = require('mongoose');
const util = require('util');

//const config = require('config');

// @route GET / 
// @desc Loads form
app.get('/', (req, res) => {
  gfs = req.app.locals.gfs;
  if(!gfs) {
    console.log('some error occurred, check connection to db');
    res.send('some error occurred, check connection to db');
    process.exit(0);
  }
  gfs.find().toArray((err, files) => {
    // check if files
    if (!files || files.length === 0) {
      return res.render('index', {
        files: false
      });
    } else {
      const f = files
        .map(file => {
          if (
            file.contentType === 'image/png' ||
            file.contentType === 'image/jpeg'
          ) {
            file.isImage = true;
          } else {
            file.isImage = false;
          }
          return file;
        })
        .sort((a, b) => {
          return (
            new Date(b['uploadDate']).getTime() -
            new Date(a['uploadDate']).getTime()
          );
        });

      return res.render('index', {
        files: f
      });
    }

    // return res.json(files);
  });
});

// TODO - async fix needed
//https://stackoverflow.com/questions/45540560/node-js-multer-upload-with-promise 
// Currently file upload completes AFTER redirect to home page :-( 

// @route POST /upload
// @desc Uploads file to DB
app.post('/upload',

  (req,res,next) => {

    console.log('@route POST /upload body',req.body);
    console.log('First /upload middleware - upload single file:', "upload");
    app.use('/upload',req.app.locals.upload.single('file'));
    next();
  }, 
  (req, res, next) => {

    console.log('Second /upload middleware - the redirect');
    //res.json({file : req.body});
    res.redirect('/');
    next();

  }
);

// @route GET /files
// @desc Display all files in JSON
app.get('/files', (req, res) => {
  gfs = req.app.locals.gfs;
  gfs.find().toArray((err, files) => {
    // check if files
    if (!files || files.length === 0) {
      return res.status(404).json({
        err: 'no files exist'
      });
    }

    return res.json(files);
  });
});

// @route GET /files/:filename
// @desc Display single file object
app.get('/files/:filename', (req, res) => {
  gfs = req.app.locals.gfs;
  gfs.find(
    {
      filename: req.params.filename
    },
    (err, file) => {
      if (!file) {
        return res.status(404).json({
          err: 'no file exists'
        });
      }

      return res.json(file);
    }
  );
});

// @route GET /files/:filename
// @desc Display Image
app.get('/image/:filename', (req, res) => {
  // console.log('id', req.params.id)
  gfs = req.app.locals.gfs;
  const file = gfs
    .find({
      filename: req.params.filename
    })
    .toArray((err, files) => {
      if (!files || files.length === 0) {
        return res.status(404).json({
          err: 'no files exist'
        });
      }
      gfs.openDownloadStreamByName(req.params.filename).pipe(res);
    });
});

// @route DELETE /files/del/:id
// @desc Delete chunks from the db
app.delete('/files/:id', (req, res) => {
  gfs = req.app.locals.gfs;
  gfs.delete(new mongoose.Types.ObjectId(req.params.id), (err, data) => {
    if (err) return res.status(404).json({ err: err.message });
    res.redirect('/');
  });
});

module.exports = app;

索引.ejs

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" 
  integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
    crossorigin="anonymous">
  <style>
    img {
      width: 100%;
    }
  </style>
  <title>Mongo File Uploads</title>
</head>

<body>
  <div class="container">
    <div class="row">
      <div class="col-md-6 m-auto">
        <h1 class="text-center display-4 my-4">Mongo File Uploads</h1>
        <form action="/upload" method="POST" enctype="multipart/form-data">
          <div class="custom-file mb-3">
            <input type="file" name="file" id="file" class="custom-file-input">
            <label for="file" class="custom-file-label">Choose File</label>
          </div>
          <input type="submit" value="Submit" class="btn btn-primary btn-block">
        </form>
        <hr>
        <% if(files){ %>
          <% files.forEach(function(file) { %>
            <div class="card card-body mb-3">
              <% if(file.isImage) { %>
                <img src="image/<%= file.filename %>" alt="">
                <% } else { %>
                  <%= file.filename %>
                    <% } %>
                      <form method="POST" action="/files/<%= file._id %>?_method=DELETE">
                        <button class="btn btn-danger btn-block mt-4">Delete</button>
                      </form>
            </div>
            <% }) %>
              <% } else { %>
                <p>No files to show</p>
                <% } %>
      </div>
    </div>
  </div>

  <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN"
    crossorigin="anonymous"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q"
    crossorigin="anonymous"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl"
    crossorigin="anonymous"></script>
</body>

</html>

标签: node.jsexpressasynchronousmulter

解决方案


恭喜你做到了。在这里,我想向您提出一些建议。

您已经使用加密模块来生成文件的名称。这很好,您可以通过加密图像/文件来使用它的全部功能。这是代码。

加密

//I use aes-256-ctr encryption
let iv = crypto.randomBytes(16);
let pass = "vhg%^yg*i993DE$%G3d$f^g&|][jup(]"//Password should be 32 chars long
let cipher = crypto.createCipheriv('aes-256-ctr', pass, iv)
//crypted is the encrypted data
let crypted = Buffer.concat([iv, cipher.update(Your image/file), cipher.final()]);

解密

iv = your image/file.buffer.slice(0, 16);
chunk = your imge/file.buffer.slice(16);
var decipher = crypto.createDecipheriv('aes-256-ctr', "vhg%^yg*i993DE$%G3d$f^g&|][jup(]", iv)
//decrypted data(dec)
var dec = Buffer.concat([decipher.update(chunk), decipher.final()]);
let buffer = new Buffer.from(dec)

而在你的 GitHub 代码中,你刚刚定义port=3000了但更好的实现将是let port=process.env.port||3000在部署中,你必须这样做。否则,它会抛出一些错误(你可能知道这一点)


推荐阅读