如何在Node.js控制器中调用multer中间件?

7

我正在尝试在我的服务器上上传图片。 在前端,我正在使用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 没有处理文件。

在客户端,我得到了 Done。

那么我做错了什么呢?如何才能使它与此后端结构一起正常工作呢?

3个回答

19

Express中间件旨在安装在路由级别。事实上,在MVC模型中,express程序员将控制器称为“路由”(我个人更喜欢在我的代码中称其为控制器而不是路由)。从传统的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没有其他东西。路由是位于末尾的中间件。

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中间件配置系统的灵活性。


5

这是一个基于@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的设计方式来使用它。不要将控制器逻辑与控制器(路由)分开。 - slebetman

0
一个在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);
      }
    });
  },
};


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