如何在nodejs的控制器内调用multer中间件?

Posted

技术标签:

【中文标题】如何在nodejs的控制器内调用multer中间件?【英文标题】:How to call multer middleware inside a controller in nodejs? 【发布时间】:2020-02-16 20:41:49 【问题描述】:

我正在尝试在我的服务器中上传一张图片。 在前端,我正在使用 Angular。 前端工作正常,我发帖只是为了向您展示我如何将文件传递到后端!

component.html

<div fxLayout="column" fxLayoutAlign="center center">
  <div>
    <mat-form-field>
      <ngx-mat-file-input placeholder="Only photos" [accept]="'.jpg, .jpeg, .png'" (change)="onChange($event)"></ngx-mat-file-input>
    </mat-form-field>
  </div>
  <div>
    <button mat-button (click)="onSubmit()">Send</button>
  </div>
</div>

component.ts - 函数

  imagem: File;

  constructor(private uploadService: UploadService)  

  onChange(event) 
    this.imagem = event.target.files[0];
  
  onSubmit() 
    this.uploadService.upload(this.imagem);
  

upload.service.ts - 函数

  constructor(private http: HttpClient)  

  upload(file: File) 
    const formData = new FormData();
    formData.append('img', file, file.name);
    this.http.post(environment.apiBaseUrl + '/upload', formData, responseType: 'text').subscribe(
      res => console.log('Done')
    );
  

在后端我有这个结构:

app.js

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

const rtsIndex = require('./routes/index.router');

var app = express();

// middleware
app.use(bodyParser.json());
app.use(cors());
app.use('/api', rtsIndex);

// start server
app.listen(3000, () => console.log('Port: 3000'));

index.router.js

const express = require('express');
const router = express.Router();

const ctrlUpload = require('../controllers/upload.controller');

router.post('/upload', ctrlUpload.send);

module.exports = router;

upload.controller.js

const express = require('express');
const multer = require('multer');

const storage = multer.diskStorage(
    destination: (req, file, cb) => 
        cb(null, 'uploads/');
    ,
    filename: (req, file, cb) => 
        cb(null, Date.now()+'-'+file.originalname);
    
);

const upload = multer( storage );

module.exports.send = (req, res) => 
  upload.single('img');
  console.log(req.body, req.files);
  res.send('ok');

我尝试调用路由内部的中间件,但我认为它不正确,也没有达到目标。算法,上传不是一个。 在服务器端,我得到: undefined 结果,这可能意味着 multer 没有处理文件。 在客户端我得到:完成。

那么我做错了什么?我怎样才能使它与这个后端结构一起工作?

【问题讨论】:

【参考方案1】:

Express 中间件旨在安装在路由级别。事实上,在 MVC 模型中,表达程序员将控制器称为“路由”(我个人更喜欢在我的代码中称它们为控制器而不是路由)。从传统的 MVC 框架来看,将控制器与路由(它们的含义相同)分开并没有什么意义 - 但如果你愿意,你可以这样做。

要使用multer按设计,您需要在index.router.js

index.router.js

const express = require('express');
const router = express.Router();
const multer = require('multer');

const ctrlUpload = require('../controllers/upload.controller');

const storage = multer.diskStorage(
    destination: (req, file, cb) => 
        cb(null, 'uploads/');
    ,
    filename: (req, file, cb) => 
        cb(null, Date.now()+'-'+file.originalname);
    
);

const upload = multer( storage );

router.post('/upload', upload.single('img'), ctrlUpload.send);

module.exports = router;

那你需要把upload.controller.js中所有multer相关的代码去掉


但是,您可以在upload.controller.js 中坚持这样做。这里的关键是了解什么是中间件。

在 Express 中,中间件是带有原型的函数:

function (req, res, next)  // next is optional
    // middleware logic

是的,没错。 upload.controller.js 文件中的代码是一个中间件。您正在自己编写一个中间件,恰好位于中间件链的末端。

你看,Express 只接受中间件。快递没有别的。路由是碰巧在最后的中间件。

Express .use().get().post() 和相关方法接受无限数量的参数。第一个是可选的路由说明符(但不是必需的),其余参数是中间件。例如:

app.get('/foo',
  (req, res, next) => 
    // first middleware
    next(); // next is what allows processing to continue
  ,
  (req, res, next) => 
    // second middleware
    next();
  ,
  (req, res, next) => 
    res.send('hello'); // controller logic - a controller
                       // is just the last middleware

    // Note: if you call next() instead of res.send() in a 
    // controller express will respond with a 500 internal 
    // server error status with whatever string you pass
    // to next() as the error message.
  
);

知道了这一点,我们就知道函数upload.single('img') 返回了什么。 该函数不执行中间件逻辑。相反,它返回中间件函数:

let middleware = upload.single('img');

// middleware is now a function with the prototype:
// (req, res, next) => 

所以要执行中间件逻辑,我们必须调用它(express 会自动调用它作为路由处理的一部分,就像它调用控制器函数的方式一样,但如果我们想自己做,我们可以)。

如果你想在upload.controller.js中实现中间件,你需要这样做:

module.exports.send = (req, res, next) => 
  upload.single('img')(req, res, () => 
      // Remember, the middleware will call it's next function
      // so we can inject our controller manually as the next()

      console.log(req.body, req.files);
      res.send('ok');
  );

要解压的东西很多。如果我们稍微重构一下,我们可以让代码更容易理解:

let middleware = upload.single('img');

module.exports.send = (req, res, next) => 
  // Define the controller here to capture
  // req and res in a closure:
  let controller = () => 
      console.log(req.body, req.files);
      res.send('ok');
  ;

  middleware(req, res, controller); // call the middleware with
                                    // our controller as callback

但这是非常不标准的,并且对于经验丰富的 Express.js 程序员来说是非常出乎意料的。即使有可能,我也不会这样做。它还将中间件与控制器紧密耦合,完全否定了 Express 中间件配置系统非常灵活的特性。

【讨论】:

【参考方案2】:

一个基于 @slebetman 答案的 Multer 中间件分离文件示例

./middlewares/multer.js

const multer = require('multer')
const ErrorMessages = require('../constants/ErrorMessages')

function makeid (length) 
  var result = ''
  var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
  var charactersLength = characters.length
  for (var i = 0; i < length; i++) 
    result += characters.charAt(Math.floor(Math.random() * charactersLength))
  
  return result


const DIR = './uploads/'
const storage = multer.diskStorage(
  destination: (req, file, cb) => 
    cb(null, DIR)
  ,
  filename: (req, file, cb) => 
    const fileName = file.originalname.toLowerCase().split(' ').join('-')
    cb(null, makeid(16) + '_' + fileName)
  
)
const upload = multer(
  storage: storage,
  fileFilter: (req, file, cb) => 
    if (file.mimetype === 'image/png' || file.mimetype === 'application/pdf') 
      cb(null, true)
     else 
      cb(null, false)
      return cb(new Error('Only .png, .jpg, .mp4 and .jpeg format allowed!'))
    
  
)

module.exports.send = (req, res, next) => 
  return upload.single('file')(req, res, () => 
    // Remember, the middleware will call it's next function
    // so we can inject our controller manually as the next()

    if (!req.file) return res.json( error: ErrorMessages.invalidFiletype )
    next()
  )

./routes.js

routes.post('/object', multer.send, ObjectController.createObject)

这避免了错误文件类型的状态 500 希望对某人有所帮助:D

【讨论】:

但重点是存在的。永远不要这样做。按照设计使用的方式使用 Express。不要将控制器逻辑与控制器(路由)分开【参考方案3】:

如何在 expressjs 处理程序中使用它的工作示例

import multer from 'multer';

export default 
  async upload(req: Request, res: Response, next: NextFunction) 
    const middleware = upload.single('photo');

    return middleware(req, res, () => 
      try 
        const file = req.file;

        console.log('req.file', req.file);

        if (!file) 
          throw new ResourceValidationError('media-library', [
            
              property: 'avatar',
              constraints: 
                isNotEmpty: 'avatar should not be empty',
              ,
            ,
          ]);
        

        console.log('filename:', file.filename);

        res.status(StatusCodes.OK).json(
          status:  code: StatusCodes.OK, phrase: ReasonPhrases.OK ,
        );
       catch (error) 
        next(error);
      
    );
  ,
;

【讨论】:

以上是关于如何在nodejs的控制器内调用multer中间件?的主要内容,如果未能解决你的问题,请参考以下文章

nodejs--express的中间件multer实现图片文件上传--XUEBIG

nodeJs学习-08 multer中间件,解析post文件,上传文件

使用 multer 和 nodejs 将图像上传到谷歌云存储时出错

nodeJs上传附件

不能 app.use(multer)。 “需要中间件功能”错误

如何在 NodeJS 中保存和显示使用 Multer 包保存的图片?