Express JS Multer asyn c не работает после рефакторинга - PullRequest
1 голос
/ 12 июля 2020

Я пытался реорганизовать этот код: https://github.com/shubhambattoo/node-js-file-upload на несколько модулей. Мне в основном это удалось, но у меня проблема с возвратом загрузки на домашнюю страницу до завершения загрузки - проблема asyn c. В оригинале проблем нет, но я пробовал много разных подходов и ничего не добился. Я считаю, что проблема в том, что промежуточное ПО загрузки вызывает функцию next () для перенаправления.

app. 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;

index.e js

<!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>

Ответы [ 3 ]

1 голос
/ 24 июля 2020
app.post('/upload',

  (req,res,next) => req.app.locals.upload.single('file')(req,res,next), 
  (req, res, next) => {

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

  }
);

Вы пробовали это?

И я думаю, что последний следующий не сработает, потому что вы вызвали функцию res

res.redirect('/');
next();
1 голос
/ 25 июля 2020

Поздравляю, вы сделали это. Здесь я хотел бы предложить вам кое-что.

Вы использовали криптографический модуль, чтобы просто сгенерировать имя для файла. Это хорошо, и вы можете использовать все его возможности, зашифровав изображения / файлы. Вот код.

Шифрование

//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 При развертывании вы должны это сделать. В противном случае будет выдана ошибка (вы наверняка это знаете)

1 голос
/ 24 июля 2020

Я изменил сохраненную ссылку на загрузку в db. js для ссылки на функцию single (), а затем вызвал функцию загрузки с обратным вызовом (я думаю).

в db. js:

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

и в файлах. js

    const upload = req.app.locals.upload;

    upload(req, res, function (err) {
      if (err) {
        console.log("Error", err); // An error occurred when uploading 
        return
      }
      // Everything went fine 
      next();
    })

db. js:

module.exports = function(app) {

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

  // Connection
  const conn = mongoose.createConnection(mongoURI, {
    useNewUrlParser: true,
    useUnifiedTopology: true
  });
  app.locals.conn = conn;
  
  // init GFS
  let gfs;
  conn.once("open", () => {
    //console.log("init the gfs stream"); // init stream
    gfs = new mongoose.mongo.GridFSBucket(conn.db, {
      bucketName: "uploads"
    });
    app.locals.gfs = gfs;
  });

  // Storage
  const storage = new GridFsStorage({ 
    db: conn,
    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);
        });
      });
    }  
  });
  app.locals.storage = storage;
    
  const upload = multer({ storage:storage, limits: { fileSize: 10000000} }).single('file');
  app.locals.upload = upload;

}

файлов. 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");

    const upload = req.app.locals.upload;

    upload(req, res, function (err) {
      if (err) {
        console.log("Error", err); // An error occurred when uploading 
        return
      }
      // Everything went fine 
      next();
    })
  }, 
  (req, res, next) => {

    console.log('Second /upload middleware - the redirect');
    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;

Весь код здесь: https://github.com/stevegroom/redogridfsstorage

Стив

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...