如何在nodejs&中重载猫鼬模型实例打字稿 [英] How to overload mongoose model instance in nodejs & Typescript

查看:46
本文介绍了如何在nodejs&中重载猫鼬模型实例打字稿的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试更改save()方法,但找不到在哪里可以重载它.我使用打字稿和node.js.

I'm trying to change the save() method, but I don't find where I can overload it. I use typescript and node.js.

目前,我有一个UserModel,其中包含mongoose.Schema和mongoose.Model. 当我调用UserModel.getModel()时,我从UserModel中检索mongoose.Model. 我基本上使用DAO来检索Model类对象.

For the moment, I have a UserModel that contains a mongoose.Schema and a mongoose.Model. When I call UserModel.getModel() I retrieve the mongoose.Model from the UserModel. I basically use a DAO to retrieve the Model class object.

user = message.getDataByKey('user');
user.save(function(err, data) {
// do stuff
});

我想自动重载用户对象,以使用我自己的.save()方法检查是否存在错误,并始终以相同的方式处理它们.

I want to overload automatically the user object to use my own .save() method to check if there is error and always handle them by the same way.

设置模型时,我会那样做:

When I set the Model, I do it like that:

public static model: any = model.Models.MongooseModel.getNewInstance(UserModel.modelName, UserModel._schema);

在父级中:

    public static getNewInstance(modelName, schema){
        var Model: any = mongoose.model(modelName, schema);

        // Overload methods.
        //console.log(new Model());

        // Return overloaded Model class.
        return Model;
    }

我想知道是否有任何方法可以使Model重载,以确保模型中的每个新实例都具有我自己的.save方法. 我以为使用了静态方法/方法(实际上,我猜是方法),但是它是空的,或者我知道最终对象将具有保存/删除/更新方法.所以我不知道为什么它还没有进入对象,我尝试了console.log(Model和new Model())但没有save()方法.

I would like to know if there is any way to overload the Model to make sure that each new instance from it will have my own .save method. I thought use the statics/methods (methods actually, I guess) but it's empty or I know that the final object will have save/remove/update methods. So I don't know why it's not already into the object, I tried to console.log(Model and new Model()) but no save() method.

所以我有些失望,也许我错过了一些东西.

So I'm a little desappointed, maybe I missed something.

事实是,我无法直接更新新Model(),因为它们将在以后创建,在另一种情况下,我需要直接更新Model以确保该模型中的新实例具有我的额外功能功能.

The fact is, I can't update directly the new Model() because they will be created later, in another context, I need to update the Model directly to make sure that the new instance from this model will have my extra function.

我不想重写基本的.save()方法,我只想重载它以添加额外的验证.

And I don't want to rewrite the basic .save() method, I just want to overload it to add extra validation.

有什么主意吗?我有点在这里迷路,这不是那么容易.谢谢.

Any idea? I'm kinda lost here, it's not that easy. Thx.

推荐答案

我找到了解决方案,我正在使用打字稿,所以我会将.ts和.js都发布给大家.

I found a solution to do so, I'm using typescript so I'll post both .ts and .js to everybody understand.

我使用CommonJs编译

Model.ts (超级模型,所有模型的父级)

///<reference path='./../../lib/def/defLoader.d.ts'/>

/**
 * Package that contains all Models used to interact with the database.
 * TODO Use options http://mongoosejs.com/docs/guide.html
 */
export module Models {
    /**
     * Interface for all Models, except the parent class.
     */
    export interface IModel{

        /**
         * Name of the model.
         * It's a helper to always get the name, from instance or static.
         * MUST start by uppercase letter!
         */
        modelName: string;

        /**
         * Contains the static value of the public schema as object.
         * It's a helper to always get the schema, from instance or static.
         */
        schema: mongoose.Schema;

        /**
         * Contains the static value of the object used to manipulate an instance of the model.
         * It's a helper to always get the model, from instance or static.
         */
        model: any;
    }

    /**
     * Parent class for all models.
     * A model contains a mongoose schema and a mongoose model and other things.
     */
    export class Model{
        /**
         * Suffix used to load automatically models.
         */
        public static suffix: string = 'Model';

        /**
         * Suffix used to load automatically models.
         * It's a helper to always get the schema, from instance or static.
         */
        public suffix: string;

        /**
         * Name of the model.
         * MUST start by uppercase letter!
         */
        public static modelName: string = '';

        /**
         * Readable schema as object.
         */
        public static schema: any;

        /**
         * Schema as mongoose Schema type.
         */
        public static Schema: mongoose.Schema;

        /**
         * The mongoose model that uses the mongoose schema.
         */
        public static model: any;

        /**
         * Use static values as instance values.
         */
        constructor(){
            // Use static values as instance values.
            this.suffix = Model.suffix;
        }

        /**
         * Returns a new mongoose.Schema customized instance.
         * @param ChildModel    Child model that made the call.
         * @returns {*}
         * @see http://mongoosejs.com/docs/2.7.x/docs/methods-statics.html
         */
        public static getNewSchemaInstance(ChildModel): mongoose.Schema{
            var schema: any = new mongoose.Schema(ChildModel.schema, {collection: ChildModel.modelName.toLowerCase()});

            // Overload methods.
            //schema.methods.toObject = function(callback){}

            // Return overloaded instance.
            return schema;
        }

        /**
         * Retrieves a new Model instance and overload it to add statics methods available for all Models.
         * @param ChildModel
         * @returns {*}
         * @see http://mongoosejs.com/docs/2.7.x/docs/methods-statics.html
         */
        public static getNewModelInstance(ChildModel): any{
            // Get the Model class.
            var Model: any = mongoose.model(ChildModel.modelName, ChildModel.Schema);

            /**
             **************************************************************************************************
             ************************ Extended Model static methods for all Models ****************************
             **************************************************************************************************
             */

            /**
             * Handler for all database/mongoose errors.
             * @param err           Error.
             * @param data          Data. Contains the model and the emitter. (+ more)
             * @param callback      Callback function to execute.
             */
            Model.errorHandler = (err: any, data: any, callback: (message: (any) => any) => any) => {
                // Extract data.
                var _Model = data.model;
                var __function = data.__function;
                var __line = data.__line;

                // Will contains the error.
                var message:any = [];

                // Mongo error.
                if(err && err.name && err.name == 'MongoError'){
                    var _err = MongoError.parseMongoError(err);

                    if(err.code == 11000){
                        // Duplicate key on create.
                        message[0] = '__19';
                        message[1] = [_err.value, _err.field];
                    }else if(err.code == 11001){
                        // Duplicate key on update.
                        message[0] = '__20';
                        message[1] = [_err.value, _err.field];
                    }else{
                        // Non-managed mongo error.
                        if(dev()){
                            // Return not only the message but also some information about the error.
                            message[0] = [];

                            // Message. [0][1] could be args.
                            message[0][0] = '__21';

                            // Data.
                            message[1] = {
                                err: err,
                                model: _Model.modelName
                            };
                        }else{
                            message = '__21';
                        }
                    }

                    fs.appendFile(__config.path.base + __config.mongo.error.log, new Date() + ': ' + JSON.stringify({error: err, model: _Model.modelName, _err: _err}) + '\n');
                }else if(err && err.name && err.name == 'ValidationError'){
                    // Validation error from mongoose.
                    var _err = MongoError.parseValidationError(err);
                    message[0] = [];

                    // Message. [0][1] could be args.
                    message[0][0] = '__24';
                    message[0][1] = [_err[0].value, _err[0].field, _err[0].type];

                    if(dev()){
                        // Will be send as args but not displayed in the message.
                        message[1] = {
                            err: _err,
                            model: _Model.modelName
                        };
                    }

                    fs.appendFile(__config.path.base + __config.mongo.error.log, new Date() + ': ' + JSON.stringify({error: err, model: _Model.modelName, _err: _err}) + '\n');
                }else{
                    // Another error? I don't know if that could happens, but manage it anyway.
                    message[0] = '__22';
                    if(dev()){
                        message[1] = [err, _Model.modelName];// Will be send as args but not displayed in the message.
                    }
                    fs.appendFile(__config.path.base + __config.mongo.error.log, new Date() + ': ' + JSON.stringify({error: err, model: _Model.modelName}) + '\n');
                }

                callback(message);// return an error.
            };

            /**
             * Check if the object exists and returns it in this case.
             * @param object    Object to find.
             * @param callback  Callback to execute.
             * @return
             *          err     Error if it happens. [null]
             *          found   Found object or false.
             */
            Model.exists = (object, callback): any => {
                // If object is null or false or empty or whatever, don't do the research, the result could be wrong!
                if(!object){
                    callback (null, false);
                }else{
                    Model.findOne(object, function (err, found) {
                        if (err){
                            Model.errorHandler(err, ChildModel, callback);
                        }else if (found){
                            callback(null, found);
                        }else{
                            callback (null, false);
                        }
                    });
                }
            };

            // Return overloaded instance.
            return Model;
        }
    }

    /**
     * Class that manage MongoDb errors, used statically.
     */
    export class MongoError{
        /**
         * Parse a mongo error to returns data from it because Mongo returns really bad errors.
         * @param err       The mongo error.
         * @returns {*}
         */
        public static parseMongoError(err): any{
            var _err: any = {};
            var _message: string = err.err;

            if(err.code == 11000 || err.code == 11001){
                var message = _message.split(':');

                // Get the table where the error was generated.
                _err.table = message[1].split('.')[1];

                // Get the field name where the error was generated.
                _err.field = message[1].split('.')[2].split(' ')[0].replace('$', '');
                _err.field = _err.field.substr(0, _err.field.lastIndexOf('_'));

                // Get the
                _err.value = message[3].split('"')[1].replace('\\', '');
            }

            return _err;
        }

        /**
         * Parse a mongoose validation error, probably generated during a save/update function.
         * @param err       The mongoose error.
         * @returns {*}
         */
        public static parseValidationError(err): any{
            var _errors: any = new Array();
            var i = 0;

            for(var error in err.errors){
                _errors[i] = [];
                _errors[i]['field'] = err.errors[error]['path'];
                _errors[i]['value'] = err.errors[error]['value'];
                _errors[i]['type'] = err.errors[error]['type'];
                i++;
            }

            return _errors;
        }
    }
}

JS版本: http://pastebin.com/xBTr1ZVe

错误消息(__21等)是

Error messages (__21, etc.) are:

    "__19": "Unable to add the element, the value **_$0** for the field _$1 already exists, it cannot be duplicated.",
    "__20": "Unable to update the element, the value **_$0** for the field _$1 already exists, it cannot be duplicated.",
    "__21": "Unable to execute the requested operation. Database error 21. Please report to an administrator.",
    "__22": "Unable to execute the requested operation. Database error 22. Please report to an administrator.",
    "__23": "Validation error, the requested operation was aborted. Please check that all your data are correct and retry. Please report to an administrator. (code 23)",
    "__24": "Unable to perform the operation, the value ***_$0** for the field _$1 didn't pass successfully the validation. Error: _$2",

基本上,我的所有模型当然都应该自己管理这些异常.但是,如果我忘记这样做,那么我会得到一个更好的托管异常.

Basically all my models should manage by themself these exception, of course. But if I forgot to do it, I'll get a managed exception, better.

现在,我将发布一个真实的模型, UserModel ,继承父 Model .

Now I'll post a real Model, UserModel inheriting the parent Model.

///<reference path='./../../lib/def/defLoader.d.ts'/>

import model = require('./Model');

export module Models {
    /**
     * Model used to manage users.
     * The model is primary static, but, to make it easy to use, some things are also stored for each instance.
     * That allows the code to use both Model or instance of Model such as:
     *      Model.schema
     *      model.Schema
     */
    export class UserModel extends model.Models.Model implements model.Models.IModel{
        /**
         *************************************************************************************************
         ****************************** Public methods & attributes **************************************
         *************************************************************************************************
         */

        /**
         * Name of the model.
         * MUST start by uppercase letter!
         */
        public static modelName: string = 'User';

        /**
         * Readable schema as object.
         */
        public static schema: any = require('../schemas/userSchema.js');

        /**
         * Schema as mongoose Schema type.
         */
        public static Schema: mongoose.Schema = model.Models.Model.getNewSchemaInstance(UserModel);

        /**
         * The mongoose Model that uses the mongoose schema.
         */
        public static model: any = model.Models.Model.getNewModelInstance(UserModel);

        /**
         * Helpers to always get the property, from instance or static.
         */
        public modelName: string = UserModel.modelName;
        public schema: mongoose.Schema = UserModel.schema;
        public model: mongoose.Model<any> = UserModel.model;

        /**
         *************************************************************************************************
         ***************************** Extended methods & attributes **************************************
         *************************************************************************************************
         */

        /**
         * These fields are protected, the user password is required to access to them.
         * These fields are basically shared between applications.
         * @private
         */
        private static _protectedFields: string[] = [
            'login',
            'email'
        ];

        /**
         * Method to use to hash the user password.
         */
        private static _passwordHashMethod: string = 'sha256';

        /**
         * Digest to use to hash the user password.
         */
        private static _passwordDigest: string = 'hex';

        /**
         * Returns the protected fields.
         * @returns {string[]}
         */
        public static getProtectedFields(): string[]{
            return this._protectedFields;
        }

        /**
         * Hash a user password depending on the password hash configuration. Currently SHA256 in hexadecimal.
         * Assuming crypto is global.
         * @param password      User password.
         * @returns {string}    Hashed password.
         */
        public static hashPassword(password: string): string{
            return crypto
                .createHash(UserModel._passwordHashMethod)
                .update(password)
                .digest(UserModel._passwordDigest)
        }
    }

    /**
     * Don't forget that some methods such as exists() are written in the Model class and available for all Models.
     * The following methods belong ONLY to the mongoose model instance, not to the Model class itself!
     *
     *************************************************************************************************
     ******************************** Extended Model methods *****************************************
     *************************************************************************************************
     */

    /**
     * Connect a user to the game.
     * @param user      User to check. {}
     * @param callback  Callback to execute.
     */
    UserModel.model.checkAuthentication = (user, callback) => {
        // Force to provide login and password.
        UserModel.model.exists({login: user.login, password: UserModel.hashPassword(user.password)}, function(err, userFound){
            // Load public profile.
            UserModel.model._getProtectedInformation(userFound, function(userPublic){
                // Provides only public fields.
                callback(new __message("__17", {err: err, user: userPublic}, !err && userFound ? true: false));
            });
        });
    };

    /**
     * Get the protected fields for the found user.
     * @param user      User to find.
     * @param callback  Callback to execute.
     */
    UserModel.model.getProtectedInformation = (user, callback) => {
        // We are looking for an unique user.
        UserModel.model.exists(user, function(err, userFound){
            if(err){
                UserModel.model.errorHandler(err, UserModel, callback);
            }else{
                // Load public profile.
                UserModel.model._getProtectedInformation(userFound, function(userPublic){
                    // Provides only public fields.
                    callback(new __message('', {err: err, user: userPublic}, err ? false: true));
                });
            }
        });
    };

    /**
     * Get the protected fields of a user.
     * @param user  Instance of model.
     * @param callback  Callback to execute.
     * @private
     */
    UserModel.model.hashPassword = (user, callback): any => {
        var err = false;
        if(user && user.password){
            user.password = UserModel.hashPassword(user.password);
        }else{
            err = true;
        }
        callback(new __message(err ? '__18': '', {user: user}, err ? false: true));
    };

    /**
     *************************************************************************************************
     *************************** Methods to use only locally (private) *******************************
     *************************************************************************************************
     */

    /**
     * Get the protected fields of a user.
     * @param user  Instance of model.
     * @param callback  Callback to execute.
     * @private
     */
    UserModel.model._getProtectedInformation = (user, callback): any => {
        var userPublic = {};

        // Get fields to share.
        var publicFields = UserModel.getProtectedFields();

        // Fill the userPublic var with public fields only.
        for(var field in publicFields){
            userPublic[publicFields[field]] = user[publicFields[field]];
        }

        callback(userPublic);
    };

}

JS版本: http://pastebin.com/0hiaMH25

架构:

/**
 * Schema ued to create a user.
 * @see http://mongoosejs.com/docs/2.7.x/docs/schematypes.html
 */
module.exports = userSchema = {
    /**
     * User Login, used as id to connect between all our platforms.
     */
    login: {
        type: 'string',
        //match: /^[a-zA-Z0-9_-]{'+userSchema.login.check.minLength+','+userSchema.login.check.maxLength+'}$/,
        trim: true,
        required: true,
        notEmpty: true,
        unique: true,
        check: {
            minLength: 4,
            maxLength: 16
        }
    },

    /**
     * User email.
     */
    email: {
        type: 'string',
        lowercase: true,
        match: /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/,
        required: true,
        notEmpty: true,
        unique: true,
        check: {
            minLength: 6,
            maxLength: 30
        }
    },

    /**
     * User private password, the one hashed in SHA512 and stored on the database.
     */
    password: {
        type: 'string',
        required: true,
        check: {
            length: 128
        }
    },

    /**
     * Salt to use to decrypt the password.
     */
    passwordSalt: {
        type: 'string',
        check: {
            length: 64
        }
    },

    /**
     * Password sent from user interface but hashed before be send on the network.
     * Used to basically connect an user or generate the final password.
     * Not stored in the DB.
     */
    passwordProtected: {
        type: 'string',
        check: {
            length: 64
        }
    },

    /**
     * Password wrote by the user on the GUI, not hashed or encrypted.
     * Will be encrypted to respect the "passwordProtected" rules.
     * Not stored in the DB.
     */
    passwordPublic: {
        type: 'string',
        check: {
            minLength: 8,
            maxLength: 25
        }
    },

    /**
     * User banned status (Temporary of Definitive)
     */
    banned: {
        temporary : {
            type : "number",
            default : Date.now
        },

        definitive: {
            type: 'boolean',
            default: false
        }
    },

    /**
     * User right
     */
    right : {
        admin : {
            type : "boolean",
            default : false,
            required: true
        },
        moderator : {
            type : "boolean",
            default : false,
            required: true
        }
    }
};

那么,代码的作用是什么?

So, what the code does?

基本上,在Model.getNewModelInstance()中,如果在控制器中发现DB错误,我将调用errorHandler方法绑定到创建的模型.

Basically, in the Model.getNewModelInstance() I bind to the created model the errorHandler method that I will call if I found a DB error in the controller.

**UserController.js**
User.exists({email: user.email}, function(err, emailFound){
    // If we got an err => Don't find couple User/pass
        if (err) {
            User.errorHandler(err, {model: User, __filename: __filename,__function: __function || 'subscription#exists', __line: __line}, function(err){
                res.json(__format.response(err));
            });
        )
});

__ filename等是我用来获取当前数据的全局函数,对调试很有用.我仍在寻找一种自动添加此内容的方法,但到目前为止我还没有.当函数匿名时,__ function不存在.但这可以帮助我调试.

The __filename and so on are global functions that I use to get the current data, useful to debug. I'm still looking for a way to add this automatically but so far I couldn't. The __function doesn't exists when the function is anonymous. But it helps me to debug.

有什么建议吗?那是很多代码.

Any suggestion? That's a lot of piece of code.

这篇关于如何在nodejs&amp;中重载猫鼬模型实例打字稿的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆