Express中间件管理疑问:OTP验证逻辑复用方案咨询
Express中间件复用问题:OTP验证同时支持独立路由和前置校验
问题场景
我在开发三步式移动端密码找回模块时遇到了中间件冲突问题:
- 步骤1:用户提交姓名与手机号,获取短信OTP
- 步骤2:用户输入OTP,通过
POST /verifyOTP路由验证并返回结果 - 步骤3:用户重置密码,
POST /resetPassword路由需要先校验OTP有效性,再执行密码重置逻辑
当前的矛盾:
- 若在
verifyOTP函数末尾调用next(),独立的/verifyOTP路由会因后续无处理函数,导致响应异常(Express会默认返回404或报错) - 若不调用
next(),/resetPassword路由无法进入后续的resetPassword处理函数
现有verifyOTP代码:
exports.verifyOTP = catchAsync(async (req, res, next) => { const { otp, email, phone } = req.body; let user; if (req.body.phone) user = await User.findOne({ phone: phone }); if (req.body.email) user = await User.findOne({ email: email }); if (!user || !user.otpCode) { return next( new AppError("No user found or you don't request OTP yet", 400), ); } if (!(await bcrypt.compare(otp, user.otpCode)) || user.otpCodeExpires < Date.now()) { return next(new AppError('OTP code is invalid or has expired', 400)); } res.status(200).json({ status: 'success', message: 'valid OTP !', }); });
解决方案
方案1:通过请求路径动态决定是否调用next()
在verifyOTP函数中,判断当前请求的路由路径,如果是重置密码接口,则调用next()传递控制权;如果是独立验证接口,则直接返回成功响应。
修改后的代码:
exports.verifyOTP = catchAsync(async (req, res, next) => { const { otp, email, phone } = req.body; let user; if (req.body.phone) user = await User.findOne({ phone: phone }); if (req.body.email) user = await User.findOne({ email: email }); if (!user || !user.otpCode) { return next( new AppError("No user found or you don't request OTP yet", 400), ); } if (!(await bcrypt.compare(otp, user.otpCode)) || user.otpCodeExpires < Date.now()) { return next(new AppError('OTP code is invalid or has expired', 400)); } // 根据当前路由决定下一步操作 if (req.path === '/resetPassword') { // 将验证通过的用户挂载到req对象,方便后续重置密码使用 req.user = user; next(); } else { res.status(200).json({ status: 'success', message: 'valid OTP !', }); } });
方案2:拆分函数,分离校验与响应逻辑
把OTP的核心校验逻辑抽成独立中间件,再分别为两个路由编写专属处理函数,职责更清晰,避免耦合。
- 抽离核心校验中间件:
exports.validateOTP = catchAsync(async (req, res, next) => { const { otp, email, phone } = req.body; let user; if (req.body.phone) user = await User.findOne({ phone: phone }); if (req.body.email) user = await User.findOne({ email: email }); if (!user || !user.otpCode) { return next( new AppError("No user found or you don't request OTP yet", 400), ); } if (!(await bcrypt.compare(otp, user.otpCode)) || user.otpCodeExpires < Date.now()) { return next(new AppError('OTP code is invalid or has expired', 400)); } // 将用户信息挂载到req,供后续使用 req.user = user; next(); });
- 独立验证路由的处理函数:
exports.verifyOTP = (req, res) => { res.status(200).json({ status: 'success', message: 'valid OTP !', }); };
- 路由配置:
// 独立OTP验证路由 router.post('/verifyOTP', authController.validateOTP, authController.verifyOTP); // 重置密码路由 router.post('/resetPassword', authController.validateOTP, authController.resetPassword);
方案对比
- 方案1适合快速修改,代码改动小,适合小型项目或临时场景
- 方案2遵循单一职责原则,代码更易维护和扩展,适合中大型项目
内容的提问来源于stack exchange,提问作者The_Situation
相关产品推荐
相关产品推荐

