如何用类编写Express中间件?类实现Multer中间件失效排查
问题描述
原本使用函数形式的Multer中间件可以正常工作:
const storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, "public"); }, filename: function (req, file, cb) { cb(null, req.params.id + "_" + file.originalname); }, }); export const multerUploadSingle = (req: Request, res: Response, next: NextFunction) => { const upload = multer({ storage: storage }).single("file"); upload(req, res, (error: unknown) => { if (error instanceof multer.MulterError) { const message = `file upload fail: ${error.message}`; next(new HttpException(message, HttpStatus.BadRequest)); } else if (error instanceof Error) { const message = `file upload fail: ${error.message}`; next(new HttpException(message, HttpStatus.InternalServerError)); } else { next(); } }); }
路由使用方式:
FileRouter.post("/upload/:id", multerUploadSingle, (req, res) => {...});
重构为类形式后,中间件完全无法工作,uploadSingle方法似乎未被调用:
export class Multer { private readonly storage: multer.StorageEngine; constructor() { this.storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, "public"); }, filename: function (req, file, cb) { cb(null, req.params.id + "_" + file.originalname); }, }); } uploadSingle(req: Request, res: Response, next: NextFunction) { const upload = multer({ storage: this.storage }).single("file"); upload(req, res, (error: unknown) => { if (error instanceof multer.MulterError) { const message = `file upload fail: ${error.message}`; next(new HttpException(message, HttpStatus.BadRequest)); } else if (error instanceof Error) { const message = `file upload fail: ${error.message}`; next(new HttpException(message, HttpStatus.InternalServerError)); } else { next(); } }); } }
路由使用方式:
const multer = new Multer(); FileRouter.post("/upload/:id", multer.uploadSingle, (req, res) => {...});
疑问:类的使用是否存在错误?还是Express仅支持函数定义的中间件?
问题原因与解决方案
问题出在this绑定丢失,而非Express不支持类形式的中间件。
当你把multer.uploadSingle直接传给Express路由时,Express调用该方法时不会保留类实例的this上下文,导致方法内部的this变成undefined(严格模式下),无法访问this.storage,最终multer上传实例创建失败,上传逻辑自然不会执行。
以下三种方案可以修复这个问题:
方案1:在路由中绑定this
直接在传递中间件时,用bind方法把this绑定到类实例:
const multerInstance = new Multer(); FileRouter.post("/upload/:id", multerInstance.uploadSingle.bind(multerInstance), (req, res) => {...});
方案2:在构造函数中绑定this
在类的构造函数里提前把uploadSingle方法的this绑定到当前实例:
export class Multer { private readonly storage: multer.StorageEngine; constructor() { this.storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, "public"); }, filename: function (req, file, cb) { cb(null, req.params.id + "_" + file.originalname); }, }); // 绑定this到uploadSingle方法 this.uploadSingle = this.uploadSingle.bind(this); } uploadSingle(req: Request, res: Response, next: NextFunction) { const upload = multer({ storage: this.storage }).single("file"); upload(req, res, (error: unknown) => { if (error instanceof multer.MulterError) { const message = `file upload fail: ${error.message}`; next(new HttpException(message, HttpStatus.BadRequest)); } else if (error instanceof Error) { const message = `file upload fail: ${error.message}`; next(new HttpException(message, HttpStatus.InternalServerError)); } else { next(); } }); } }
之后路由使用方式不变:
const multer = new Multer(); FileRouter.post("/upload/:id", multer.uploadSingle, (req, res) => {...});
方案3:使用箭头函数定义类方法
把uploadSingle定义为类的箭头函数属性,箭头函数会自动捕获当前类实例的this上下文:
export class Multer { private readonly storage: multer.StorageEngine; constructor() { this.storage = multer.diskStorage({ destination: function (req, file, cb) { cb(null, "public"); }, filename: function (req, file, cb) { cb(null, req.params.id + "_" + file.originalname); }, }); } uploadSingle = (req: Request, res: Response, next: NextFunction) => { const upload = multer({ storage: this.storage }).single("file"); upload(req, res, (error: unknown) => { if (error instanceof multer.MulterError) { const message = `file upload fail: ${error.message}`; next(new HttpException(message, HttpStatus.BadRequest)); } else if (error instanceof Error) { const message = `file upload fail: ${error.message}`; next(new HttpException(message, HttpStatus.InternalServerError)); } else { next(); } }); } }
路由使用方式同样保持不变即可。
内容的提问来源于stack exchange,提问作者Ray Kim
相关产品推荐
相关产品推荐

