Mongoose/NextJS - Model未定义/一旦编译就无法覆盖模型

22

简短摘要: 如果你是从谷歌过来的,这就是解决方案:

module.exports = mongoose.models.User || mongoose.model("User", UserSchema);

如果您需要详细回答,请查看已接受的答案。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

我正在开发一个NextJS网站,后端使用了Mongoose和Express。每当我使用signUp函数时,在后端会出现以下错误:

{"name":"Test","hostname":"DESKTOP-K75DG72","pid":7072,"level":40,"route":"/api/v1/signup","method":"POST","errorMessage":"UserModel is not defined","msg":"","time":"2020-06-17T23:51:34.566Z","v":0}

我怀疑这个错误是因为我在其他控制器中使用了UserModel。在我创建一个新的控制器之前,这个错误并没有发生。所以我的问题是,如何解决这个问题/如何在不同的控制器/中间件中使用同一个模型?
我认为这个问题与node.js - Cannot overwrite model once compiled Mongoose这篇文章有关,我之前遇到过这个错误,但不知怎么修复它。
编辑:错误出现在models/User.js文件中的pre save middleware中,UserModel未定义在该级别上,如何验证用户是否已存在该用户名,如果是,则拒绝新文档?
在controllers/RegisterLogin.js [错误发生的地方]。
const UserModel = require("../models/User");
// More packages...

async function signUp(req, res) {
  try {
    const value = await signUpSchema.validateAsync(req.body);
    const response = await axios({
      method: "POST",
      url: "https://hcaptcha.com/siteverify",
      data: qs.stringify({
        response: value.token,
        secret: process.env.HCAPTCHA,
      }),
      headers: {
        "content-type": "application/x-www-form-urlencoded;charset=utf-8",
      },
    });

    if (!response.data.success) {
      throw new Error(errorHandler.errors.HCAPTCHA_EXPIRED);
    }

    const hashPassword = await new Promise((res, rej) => {
      bcrypt.hash(
        value.password,
        parseInt(process.env.SALTNUMBER, 10),
        function (err, hash) {
          if (err) rej(err);
          res(hash);
        }
      );
    });

    await UserModel.create({
      userName: value.username,
      userPassword: hashPassword,
      userBanned: false,
      userType: "regular",
      registeredIP: req.ip || "N/A",
      lastLoginIP: req.ip || "N/A",
    });

    return res.status(200).json({
      success: true,
      details:
        "Your user has been created successfully! Redirecting in 6 seconds",
    });
  } catch (err) {
    const { message } = err;
    if (errorHandler.isUnknownError(message)) {
      logger.warn({
        route: "/api/v1/signup",
        method: "POST",
        errorMessage: message,
      });
    }

    return res.status(200).json({
      success: false,
      details: errorHandler.parseError(message),
    });
  }
}

module.exports = { signUp };

控制器/个人资料.js [如果我在这里使用UserModel,它会破坏一切]

const UserModel = require("../models/User");
//plus other packages...

async function changePassword(req, res) {
  try {
    const value = await passwordChangeSchema.validateAsync(req.body);

    const username = await new Promise((res, rej) => {
      jwt.verify(value.token, process.env.PRIVATE_JWT, function (err, decoded) {
        if (err) rej(err);
        res(decoded.username);
      });
    });

    const userLookup = await UserModel.find({ userName: username });

    if (userLookup == null || userLookup.length == 0) {
      throw new Error(errorHandler.errors.BAD_TOKEN_PROFILE);
    }

    const userLookupHash = userLookup[0].userPassword;

    try {
      // We wrap this inside a try/catch because the rej() doesnt reach block-level
      await new Promise((res, rej) => {
        bcrypt.compare(value.currentPassword, userLookupHash, function (
          err,
          result
        ) {
          if (err) {
            rej(errorHandler.errors.BAD_CURRENT_PASSWORD);
          }
          if (result == true) {
            res();
          } else {
            rej(errorHandler.errors.BAD_CURRENT_PASSWORD);
          }
        });
      });
    } catch (err) {
      throw new Error(err);
    }

    const hashPassword = await new Promise((res, rej) => {
      bcrypt.hash(
        value.newPassword,
        parseInt(process.env.SALTNUMBER, 10),
        function (err, hash) {
          if (err) rej(err);
          res(hash);
        }
      );
    });

    await UserModel.findOneAndUpdate(
      { userName: username },
      { userPassword: hashPassword }
    );
    return res.status(200).json({
      success: true,
      details: "Your password has been updated successfully",
    });
  } catch (err) {
    const { message } = err;
    if (errorHandler.isUnknownError(message)) {
      logger.warn({
        route: "/api/v1/changepassword",
        method: "POST",
        errorMessage: message,
      });
    }

    return res.status(200).json({
      success: false,
      details: errorHandler.parseError(message),
    });
  }
}

models/User.js文件中

const mongoose = require("mongoose");
const errorHandler = require("../helpers/errorHandler");

const Schema = mongoose.Schema;

const UserSchema = new Schema({
  userName: String,
  userPassword: String,
  userBanned: Boolean,
  userType: String,
  registeredDate: { type: Date, default: Date.now },
  registeredIP: String,
  lastLoginDate: { type: Date, default: Date.now },
  lastLoginIP: String,
});

UserSchema.pre("save", async function () {
  const userExists = await UserModel.find({
    userName: this.get("userName"),
  })
    .lean()
    .exec();
  if (userExists.length > 0) {
    throw new Error(errorHandler.errors.REGISTER_USERNAME_EXISTS);
  }
});

module.exports = mongoose.model("User", UserSchema);

errorHandler 是否需要 require() 任何控制器? - thammada.ts
@thammada 不是的,errorHandler有一个名为“errors”的对象,其中包含可以在前端显示的友好错误列表,同时保持关键错误记录。errorHandler没有任何require()。 - Stan Loona
models/User.js 有其他的 require() 吗? - thammada.ts
@thammada 不,只有errorHandler和mongoose本身。 - Stan Loona
我怀疑你可能有循环依赖关系。尝试运行以下命令以查看是否存在:npx madge --circular path/to/controllers/Profile.js - thammada.ts
1
@thammada 成功修复了,没有循环依赖,谢谢! - Stan Loona
3个回答

22

我已经把它修好了。这里有两个问题。

1)在预中间件中,“UserModel”变量不存在。通过实例化“this.constructor”来解决,这显然解决了问题(需要进一步测试)。

2)NextJS似乎存在一个问题,它似乎在我使用UserModel的任何函数时都尝试创建一个新模型。通过导出已创建的模型来解决这个问题。

const mongoose = require("mongoose");
const errorHandler = require("../helpers/errorHandler");

const Schema = mongoose.Schema;

const UserSchema = new Schema({
  userName: String,
  userPassword: String,
  userBanned: Boolean,
  userType: String,
  registeredDate: { type: Date, default: Date.now },
  registeredIP: String,
  lastLoginDate: { type: Date, default: Date.now },
  lastLoginIP: String,
});

UserSchema.pre("save", async function () {
  try {
    const User = this.constructor;
    const userExists = await User.find({
      userName: this.get("userName"),
    })
      .lean()
      .exec();
    if (userExists.length > 0) {
      throw new Error(errorHandler.errors.REGISTER_USERNAME_EXISTS);
    }
  } catch (err) {
    throw new Error(errorHandler.errors.REGISTER_USERNAME_EXISTS);
  }
});

module.exports = mongoose.models.User || mongoose.model("User", UserSchema);

1
谢谢,这解决了一个让我非常烦恼的错误。 - indiehjaerta
1
你是我的救星,真的让我很沮丧。最后一行解决了我的问题。 - Luc-Olsthoorn
3
最后一行正是我所需要的: module.exports = mongoose.models.User || mongoose.model("User", UserSchema); 节省了我很多时间。谢谢! - Gabriel Arghire
3
使用ES6的import和export以及ts,这解决了我的问题: export default UserModel;``` - Konrad Grzyb

6

对我来说,只需要加上 Stan Loona 答案的最后一行:

module.exports = mongoose.models.User || mongoose.model("User", UserSchema);

1

最简单的答案(2023年)

正在发生的是Next.js喜欢不断重建你的代码,导致在Mongoose(mongoose.models)缓存中出现一些问题。

基本上你想做的是,在它重新构建时,而不是每次都创建一个新的模型,检查模型是否已经加载到Mongoose缓存中,如果已经加载了,就直接使用它而不是重新创建。

所以在你的代码中,你可以替换为

mongoose.model('Profile', profileSchema);

使用

mongoose.models.Profile ?? mongoose.model('Profile', profileSchema);

在更简单的代码中,这实际上是在做以下操作:
let Profile;
if(mongoose.models.Profile) Profile = mongoose.models.Profile
else Profile = mongoose.model('Profile', profileSchema);

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