node.js - 重构后 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>
解决方案
恭喜你做到了。在这里,我想向您提出一些建议。
您已经使用加密模块来生成文件的名称。这很好,您可以通过加密图像/文件来使用它的全部功能。这是代码。
加密
//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
在部署中,你必须这样做。否则,它会抛出一些错误(你可能知道这一点)
推荐阅读
- azure - 将平面文件导入 Azure DevOps 源代码控制
- python - 我可以使用 Tkinter 文件对话框保存 Pandas DataFrame 吗?
- javascript - 突出显示 HTML 页面中的重复单词
- google-apps-script - Apps 脚本将新行和值插入到 Google 表格中。但是,如果您提交太快,它会以某种方式跳过行
- c# - 在工具栏中的调试按钮附近添加一个扩展按钮
- r - 如何使用 highchart() 和列表更正空白绘图区域
- wordpress - Woocommerce 产品的 WP_Query 中的 Orderby 价格
- discord.js - 如何禁止在某些角色上使用命令
- r - R包'TranslateR'不显示翻译结果(2)
- reactjs - 将 React、Material-UI 功能组件转换为类组件时出现问题