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

迁移邮箱验证器至validators.js后MongoDB findOne失效修复

修复MongoDB邮箱查重验证迁移后的查询失效问题

将邮箱查重逻辑从auth.js迁移至validators.js后,User.findOne()无法正常查询用户,但用户注册的save功能仍可正常工作,核心问题出在模型引用错误、异步操作未正确处理以及模型未模块化导出三个方面。

问题根源

  • 模型引用错误:validators.js中const User = require('./auth')导入的是auth.js导出的路由对象(router),而非mongoose的User模型,导致调用的findOne并非数据库查询方法。
  • 异步操作未等待:User.findOne()是异步Promise操作,未加await直接判断existingUser,此时existingUser为Promise对象(属于真值),会导致无论邮箱是否存在都抛出错误,且实际未执行有效查询。
  • 模型未对外导出:auth.js中定义的User模型仅在内部使用,未对外导出,无法被其他模块正确引用。

修复步骤

步骤1:抽离User模型到独立文件

新建models/User.js文件,统一管理User模型,避免重复定义和引用混乱:

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  email: String,
  password: String,
  passwordConfirmation: String,
});

module.exports = mongoose.model('User', userSchema);

步骤2:修正validators.js的模型引用与异步处理

修改validators.js,导入正确的User模型,并在验证器中用await等待异步查询结果:

const { check } = require('express-validator');
// 导入统一的User模型
const User = require('../models/User');

module.exports = {
  requireEmail: check('email')
    .trim()
    .normalizeEmail()
    .isEmail()
    .withMessage('Must be a valid email')
    .custom(async (email) => {
      // 等待异步查询完成
      const existingUser = await User.findOne({ email: email });
      if (existingUser) {
        throw new Error('Email in use');
      }
    }),
  requirePassword: check('password')
    .trim()
    .isLength({ min: 4, max: 20 })
    .withMessage('Must be between 4 and 20 characters'),
  requirePasswordConfirmation: check('passwordConfirmation')
    .trim()
    .isLength({ min: 4, max: 20 })
    .withMessage('Must be between 4 and 20 characters')
    .custom(async (passwordConfirmation, { req }) => {
      if (passwordConfirmation !== req.body.password) {
        throw new Error('Passwords must match');
      }
    }),
};

步骤3:更新auth.js的模型引用与逻辑顺序

修改auth.js,导入抽离后的User模型,移除内部重复定义的模型,并优化异步逻辑顺序(先检查验证错误再执行加密与保存):

const express = require('express');
const { check, validationResult } = require('express-validator');
const bcrypt = require('bcrypt');
const saltRounds = 10;
const signupTemplate = require('../../views/admin/auth/signup');
const signinTemplate = require('../../views/admin/auth/signin');
const {
  requireEmail,
  requirePassword,
  requirePasswordConfirmation,
} = require('./validators');
// 导入统一的User模型
const User = require('../models/User');

const router = express.Router();

router.get('/signup', (req, res) => {
  res.send(signupTemplate({ req }));
});

router.post(
  '/signup',
  [requireEmail, requirePassword, requirePasswordConfirmation],
  async (req, res) => {
    try {
      // 先检查验证错误
      const errors = validationResult(req);
      if (!errors.isEmpty()) {
        return res.send(signupTemplate({ errors: errors.array() }));
      }
      // 异步加密密码
      const hash = await bcrypt.hash(req.body.password, saltRounds);
      // 创建并保存用户
      const newUser = new User({
        email: req.body.email,
        password: hash,
      });
      await newUser.save();
      console.log('success');
      res.send('Account created!!!');
    } catch (err) {
      console.log(err);
      res.send('Error creating account');
    }
  }
);

module.exports = router;

补充说明

  • 抽离模型是Node.js + MongoDB开发的最佳实践,能有效避免重复定义和跨模块引用错误。
  • 所有异步数据库操作必须用await等待结果,否则无法获取实际查询/操作数据。
  • 优化后的auth.js改用async/await替代嵌套回调,代码可读性更强,逻辑顺序更合理(先验证再执行核心操作)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 20:35:38