You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的核心校验逻辑抽成独立中间件,再分别为两个路由编写专属处理函数,职责更清晰,避免耦合。

  1. 抽离核心校验中间件:
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();
});
  1. 独立验证路由的处理函数:
exports.verifyOTP = (req, res) => {
 res.status(200).json({
  status: 'success',
  message: 'valid OTP !',
 });
};
  1. 路由配置:
// 独立OTP验证路由
router.post('/verifyOTP', authController.validateOTP, authController.verifyOTP);
// 重置密码路由
router.post('/resetPassword', authController.validateOTP, authController.resetPassword);

方案对比

  • 方案1适合快速修改,代码改动小,适合小型项目或临时场景
  • 方案2遵循单一职责原则,代码更易维护和扩展,适合中大型项目

内容的提问来源于stack exchange,提问作者The_Situation

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 17:53:17