学习应用于Mongoose模型的信息专家原则

75 阅读4分钟

在编程中,信息专家,或称专家原则,是一种用来确定将责任委托给哪里的方法。换句话说,你应该把完成特定任务的代码放在哪里。信息专家原则将帮助开发者把责任放在具有完成该任务所需的最多信息的类中。在本教程中,我们将清理生成JSON网络令牌的过程,使我们的代码更清晰,更容易维护。


删除重复的代码

如果你一直在关注我们的Node.js用户注册教程,你就知道我们目前在不止一个地方生成JWT。users.js和auth.js都在使用cookie cutter代码完成一项任务。我们应该提取这种逻辑,以便只有一个地方生成令牌。这样,如果将来有什么变化,你可以在一个地方进行修改,而不是很多地方。在我们的案例中,我们可以在Mongoose模型中添加一个方法来做到这一点。


为Mongoose模型添加一个方法

现在,用户模型看起来像这样。


/models/user.js

const Joi = require('joi');
const mongoose = require('mongoose');

const User = mongoose.model('User', new mongoose.Schema({
    name: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 50
    },
    email: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 255,
        unique: true
    },
    password: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 1024
    }
}));

function validateUser(user) {
    const schema = {
        name: Joi.string().min(5).max(50).required(),
        email: Joi.string().min(5).max(255).required().email(),
        password: Joi.string().min(5).max(255).required()
    };
    return Joi.validate(user, schema);
}

exports.User = User;
exports.validate = validateUser;

在上面的代码中,mongoose.model()函数的第二个参数是mongoose.Schema()的一个新实例。我们需要做的第一件事是像这样将其提取为自己的常量。

const userSchema = new mongoose.Schema({
    name: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 50
    },
    email: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 255,
        unique: true
    },
    password: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 1024
    }
});

有了这一点,现在设置用户是一个简单的问题,这一行。

const User = mongoose.model('User', userSchema);

将该方法添加到mongoose模型中

我们现在可以像这样把方法添加到userSchema中。

userSchema.methods.generateAuthToken = function () {
    const token = jwt.sign({_id: this._id}, config.get('PrivateKey'));
    return token;
};

用户模型现在需要与config和jsonwebtoken包一起工作,所以我们要确保在文件的顶部包括这些包。剩下的代码是不言自明的,就是提取用户模式,添加一个新的方法,然后创建一个新的用户模型。

const config = require('config');
const jwt = require('jsonwebtoken');
const Joi = require('joi');
const mongoose = require('mongoose');

// Extract Schema to it's own constant
const userSchema = new mongoose.Schema({
    name: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 50
    },
    email: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 255,
        unique: true
    },
    password: {
        type: String,
        required: true,
        minlength: 5,
        maxlength: 1024
    },
});

// Information Expert Principle (add method to model)
userSchema.methods.generateAuthToken = function () {
    const token = jwt.sign({ _id: this._id }, config.get('PrivateKey'));
    return token;
};

// Create new user model
const User = mongoose.model('User', userSchema);

function validateUser(user) {
    const schema = {
        name: Joi.string().min(5).max(50).required(),
        email: Joi.string().min(5).max(255).required().email(),
        password: Joi.string().min(5).max(255).required()
    };
    return Joi.validate(user, schema);
}

exports.User = User;
exports.validate = validateUser;

现在我们有了一个很好的方法,可以在我们代码的其他地方使用。所以,现在请注意auth.js和users.js的变化。我们基本上已经删除了注释过的代码,取而代之的是对user.generateAuthToken()的调用。


/routes/auth.js

const Joi = require('joi');
const bcrypt = require('bcrypt');
const _ = require('lodash');
const { User } = require('../models/user');
const express = require('express');
const router = express.Router();

router.post('/', async (req, res) => {
    // First Validate The HTTP Request
    const { error } = validate(req.body);
    if (error) {
        return res.status(400).send(error.details[0].message);
    }

    //  Now find the user by their email address
    let user = await User.findOne({ email: req.body.email });
    if (!user) {
        return res.status(400).send('Incorrect email or password.');
    }

    // Then validate the Credentials in MongoDB match
    // those provided in the request
    const validPassword = await bcrypt.compare(req.body.password, user.password);
    if (!validPassword) {
        return res.status(400).send('Incorrect email or password.');
    }
    // const token = jwt.sign({ _id: user._id }, config.get('PrivateKey'));
    const token = user.generateAuthToken();
    res.send(token);
});

function validate(req) {
    const schema = {
        email: Joi.string().min(5).max(255).required().email(),
        password: Joi.string().min(5).max(255).required()
    };

    return Joi.validate(req, schema);
}

module.exports = router;

/routes/users.js

const bcrypt = require('bcrypt');
const _ = require('lodash');
const { User, validate } = require('../models/user');
const express = require('express');
const router = express.Router();

router.post('/', async (req, res) => {
    // First Validate The Request
    const { error } = validate(req.body);
    if (error) {
        return res.status(400).send(error.details[0].message);
    }

    // Check if this user already exisits
    let user = await User.findOne({ email: req.body.email });
    if (user) {
        return res.status(400).send('That user already exisits!');
    } else {
        // Insert the new user if they do not exist yet
        user = new User(_.pick(req.body, ['name', 'email', 'password']));
        const salt = await bcrypt.genSalt(10);
        user.password = await bcrypt.hash(user.password, salt);
        await user.save();
        // const token = jwt.sign({ _id: user._id }, config.get('PrivateKey'));
        const token = user.generateAuthToken();
        res.header('x-auth-token', token).send(_.pick(user, ['_id', 'name', 'email']));
    }
});

module.exports = router;

太棒了!


用Postman测试重构的结果

这是一个很好的重构,但我们需要确保API仍能按预期工作。在这里,我们用Postman测试,向http://localhost:4000/api/users/ 发送一个新的Post请求,在正文中包含一个新用户的JSON对象。注意,我们在响应中得到了一个合适的用户对象,所以这意味着它起作用了!
mongoose model custom method working great


为Mongoose模型添加方法总结

在本教程中,我们快速了解了如何向Mongoose模型添加方法,以减少应用程序中其他区域的重复代码。只要有意义并遵循信息专家原则,你可以根据需要添加任意多的方法。