之前写了一篇文章是关于nodejs mongodb模块的一些简单Demo,但是如果是需要结合项目一起使用的话,那肯定是对其进行二次封装是最好的,这样能方便自己,也能学到一些东西。

所以依照网上的一些文章和官方文档简单地封装了一下。虽然说调用起来并没有多大的区别,但至少我们把重复的部门提炼出来,定义为函数,调用起来显得更方便。结合Promise能更大程度降低代码的复杂性,脱离回调地狱,使代码显得不那么臃肿。

个人觉得模块的封装并不是说一定要最大限度地简化方法的调用之类的,比如说你只是独立一个小功能,那么你只需要把接口之类的暴露出来就行了;如果是想数据库操作的模块封装那么可以考虑简化一下数据库语言的书写(比如MySQL),但这只是自己使用的时候有用,团队开发最好不要这么干,当然这么做也有其意义,能学到更深层面的东西。

在找文章的时候看到了一个人用prototype这个属性来封装模块,之前看到的封装都是直接定义一个对象,然后添加方法等

moudule.exports = {
    insertOne() {},
    insertMany() {}
    ....
}

上面用到了ES6语法的属性名简介表示,详情可以看看这个 ECMAScript 6 入门 ,在对象扩展部分。本次封装用了一些ES6的语法,看不懂的话可以去链接里找找

那么什么是prototype呢?JavaScript中的prototype是一个原型对象,那么什么是原型呢?在这里我们不做详细的解释,感兴趣的小伙伴可以去百度找找答案。我们都知道,js中函数也是一个对象,emmm,其中牵扯有点大,貌似涉及原型原型链,暂且这么认为吧。

我们都知道,已存在的对象的构造器中是不能添加新的属性,除非在构造器中添加。

所有的 JavaScript 对象都会从一个 prototype(原型对象)中继承属性和方法:
Date 对象从 Date.prototype 继承。
Array 对象从 Array.prototype 继承。
Person 对象从 Person.prototype 继承。
所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。
JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。
Date 对象, Array 对象, 以及 Person 对象从 Object.prototype 继承。
添加属性和方法
有的时候我们想要在所有已经存在的对象添加新的属性或方法。
另外,有时候我们想要在对象的构造函数中添加属性或方法。
-----菜鸟教程

那么我们就可以通过prototype来为一个对象添加属性与方法

说了这么多,直接上代码吧

const { MongoClient } = require("mongodb");

const connectionConfig = {
    host: 'localhost',
    username: '',
    password: '',
    port: '27017',
    database: '',
    authMechanism: 'DEFAULT'
};

// 某版本之后规定用户名密码需要URI编码
const username = encodeURIComponent(connectionConfig.username);
const password = encodeURIComponent(connectionConfig.password);

// 实例化db对象
const db = function () {
    this.dbClient = null;
    this.MongoClient = null;
};

db.prototype.getConnection = function () {
    return new Promise((resolve, reject) => {
        if (!this.dbClient) {
            if (connectionConfig.username != '' && connectionConfig.password != '') {
                let url = `mongodb://${username}:${password}@${connectionConfig.host}:${connectionConfig.port}/?authMechanism=${connectionConfig.authMechanism}`;
                try {
                    let mongoClient = new MongoClient(url, {
                        useNewUrlParser: true,
                        useUnifiedTopology: true,
                        poolSize: 10,
                        authSource: connectionConfig.database,
                        // user : connectionConfig.username,
                        // password : connectionConfig.password, 不是必须
                        authMechanism: connectionConfig.authMechanism
                    });
                    this.MongoClient = mongoClient;
                    mongoClient.connect();
                    this.dbClient = mongoClient.db(connectionConfig.database);
                    resolve(this.dbClient);
                } catch (error) {
                    reject(error);
                }
            } else {
                let url = `mongodb://${connectionConfig.host}:${connectionConfig.port}/`;
                try {
                    let mongoClient = new MongoClient(url, {
                        useNewUrlParser: true,
                        useUnifiedTopology: true
                    });
                    this.MongoClient = mongoClient;
                    mongoClient.connect();
                    this.dbClient = mongoClient.db(connectionConfig.database);
                    resolve(this.dbClient);
                } catch (error) {
                    reject(error);
                }
            }
        }
        resolve(this.dbClient);
    });
}

/**
 * 插入一个文档
 * @param collection_name 集合名称
 * @param obj 插入的文档数据
 * @param options 一些可选操作
 */
db.prototype.insertOne = async function (collectionName, obj, options) {
    options = options || {};
    return new Promise((resolve, reject) => {
        this.getConnection().then(e => {
            e.collection(collectionName).insertOne(obj, options, (err, res) => {
                if (err) reject(err);
                this.MongoClient.close();
                this.MongoClient = this.dbClient = null;
                resolve(res);
            });
        }).catch(error => reject(error));
    });
}

/**
 * 插入多个文档
 * @param collection_name 集合名称
 * @param arr 插入的文档数据集合
 * @param options 一些可选操作
 * 
 * option:
 *   ordered:如果为true,则在插入失败时,不要执行其余的写操作。 如果为false,则在失败时继续执行其余的插入操作。
 */
db.prototype.insertMany = async function (collectionName, arr, options) {
    return new Promise((resolve, reject) => {
        options = options || {};
        this.getConnection().then(e => {
            e.collection(collectionName).insertMany(arr, options, (err, res) => {
                if (err) reject(err);
                this.MongoClient.close();
                this.MongoClient = this.dbClient = null;
                resolve(res);
            });
        }).catch(error => reject(error));
    });
}

/**
 * 查找文档(范围不止一条)
 * @param collection_name 集合名称
 * @param obj 查询数据的一些操作信息
 * 传空对象会查询集合所有数据
 * obj:
 *   whereObj:条件,默认是{}
 *   options:
 *     projection:指定字段 0不显示 1显示
 *     sort:排序,默认是{}
 *   limit:显示提定条数,默认是0
 *   skip:跳过指定条数,默认是0
 * 
 * js中逻辑与和其他语言不太一样,如果第一个操作数是true(或者能够转为true),
 * 计算结果就是第二个操作数,如果第一个操作数是false,结果就是false(短路计算),
 * 对于一些特殊数值不遵循以上规则.
 * 
 * ObjectID 生成系统自动生成的_id
 * sort也可以写进option
 * 
 * 以下写法等价
 * collection.find({ runtime: { $lt: 15 } }, { sort: { title: 1 }, projection: { _id: 0, title: 1, imdb: 1 }});
 * collection.find({ runtime: { $lt: 15 } }).sort({ title: 1}).project({ _id: 0, title: 1, imdb: 1 });
 **/
db.prototype.find = async function (collectionName, obj) {
    return new Promise((resolve, reject) => {
        obj.whereObj = obj.whereObj || {};
        obj.options = obj.options || {};
        obj.limit = obj.limit || 0;
        obj.skip = obj.skip || 0;
        this.getConnection().then(e => {
            e.collection(collectionName)
                .find(obj.whereObj, obj.options)
                .sort(obj.sortObj)
                .limit(obj.limit)
                .skip(obj.skip)
                .toArray((err, res) => {
                    if (err) reject(err);
                    this.MongoClient.close();
                    this.MongoClient = this.dbClient = null;
                    resolve(res);
                });
        }).catch(error => reject(error));
    });
}

/**
 * 修改单个文档
 * @param collection_name 集合名称
 * @param obj 修改数据的一些操作信息
 * @param options 一些可选操作
 * obj:
 *   whereObj:条件,默认是{}
 *   updateObj:修改语句,NOT NULL
 * 
 * 还有很多操作,但必须加上$xxx
 * 如果应用程序在更新后需要文档,请考虑使用 collection.findOneAndUpdate()。方法,该方法具有与 updateOne ()类似的接口,但也返回原始文档或更新的文档。
 * updateOne和updateMany与update不同,需要加上$操作(Update document requires atomic operators)$set $inc
 **/
db.prototype.updateOne = async function (collectionName, obj, options) {
    obj.whereObj = obj.whereObj || {};
    obj.updateObj = obj.updateObj || {};
    options = options || {};
    return new Promise((resolve, reject) => {
        this.getConnection().then(e => {
            e.collection(collectionName)
                .updateOne(obj.whereObj, obj.updateObj, options, (err, res) => {
                    if (err) reject(err);
                    this.MongoClient.close();
                    this.MongoClient = this.dbClient = null;
                    resolve(res);
                });
        }).catch(error => reject(error));
    });
}


/**
 * 修改多个文档
 * @param collection_name 集合名称
 * @param obj 修改数据的一些操作信息
 * @param options 一些可选操作
 * obj:
 *   whereObj:条件,默认是{}
 *   updateObj:修改语句,NOT NULL
 * 
 * 还有很多操作,但必须加上$xxx
 * updateOne和updateMany与update不同,需要加上$操作(Update document requires atomic operators) $set $inc
 **/
db.prototype.updateMany = async function (collectionName, obj, options) {
    obj.whereObj = obj.whereObj || {};
    obj.updateObj = obj.updateObj || {};
    options = options || {};
    return new Promise((resolve, reject) => {
        this.getConnection().then(e => {
            e.collection(collectionName)
                .updateMany(obj.whereObj, obj.updateObj, options, (err, res) => {
                    if (err) reject(err);
                    this.MongoClient.close();
                    this.MongoClient = this.dbClient = null;
                    resolve(res);
                });
        }).catch(error => reject(error));
    });
}

/**
 * 替换单个文档
 * @param collection_name 集合名称
 * @param obj 修改数据的一些操作信息
 * @param options 一些可选操作
 * obj:
 *   whereObj:条件,默认是{}
 *   updateObj:修改语句,NOT NULL
 * 
 * replaceOne ()接受查询文档和替换文档。如果查询与集合中的某个文档匹配,它将用提供的替换文档替换匹配该查询的第一个文档。此操作删除原始文档中的所有字段和值,并用替换文档中的字段和值替换它们。除非在替换文档中为 _ id 显式指定一个新值,否则 _ id 字段的值保持不变。
 **/
db.prototype.replaceOne = async function (collectionName, obj, options) {
    obj.whereObj = obj.whereObj || {};
    obj.updateObj = obj.updateObj || {};
    options = options || {};
    return new Promise((resolve, reject) => {
        this.getConnection().then(e => {
            e.collection(collectionName)
                .replaceOne(obj.whereObj, obj.updateObj, options, (err, res) => {
                    if (err) reject(err);
                    this.MongoClient.close();
                    this.MongoClient = this.dbClient = null;
                    resolve(res);
                });
        }).catch(error => reject(error));
    });
}

/**
 * 删除单个文档
 * @param collection_name 集合名称
 * @param obj 修改数据的一些操作信息
 * @param options 一些可选操作
 * obj:
 *   whereObj:条件,NOT NULL,为空会删除第一条
 * 
 * 如果不提供查询文档(或者提供空文档) ,MongoDB 将匹配集合中的所有文档并删除第一个匹配。
 * 如果应用程序在删除后需要被删除的文档,请考虑使用 collection.findOneAndDelete ()。方法,它具有与 deleteOne ()类似的接口,但也返回被删除的文档。
 * 
 **/
db.prototype.deleteOne = async function (collectionName, obj, options) {
    obj.whereObj = obj.whereObj;
    options = options || {};
    return new Promise((resolve, reject) => {
        this.getConnection().then(e => {
            e.collection(collectionName)
                .deleteOne(obj.whereObj, options, (err, res) => {
                    if (err) reject(err);
                    this.MongoClient.close();
                    this.MongoClient = this.dbClient = null;
                    resolve(res);
                });
        }).catch(error => reject(error));
    });
}

/**
 * 删除多个文档
 * @param collection_name 集合名称
 * @param obj 修改数据的一些操作信息
 * @param options 一些可选操作
 * obj:
 *   whereObj:条件,NOT NULL,为空会删除第一条除_id外的数据
 * 
 * 如果不提供查询文档(或者提供空文档) ,MongoDB 将匹配集合中的所有文档并删除它们。
 * 虽然可以使用 deleteMany ()删除集合中的所有文档,但是为了获得更好的性能和更清晰的代码,可以考虑使用 drop ()。
 * 
 **/
db.prototype.deleteMany = async function (collectionName, obj, options) {
    obj.whereObj = obj.whereObj;
    options = options || {};
    return new Promise((resolve, reject) => {
        this.getConnection().then(e => {
            e.collection(collectionName)
                .deleteMany(obj.whereObj, options, (err, res) => {
                    if (err) reject(err);
                    this.MongoClient.close();
                    this.MongoClient = this.dbClient = null;
                    resolve(res);
                });
        }).catch(error => reject(error));
    });
}

/**
 * 左连接
 * @param collection_name 集合名称
 * @param arr 左连接所需的参数
 * arr:
 *   from:右集合
 *   localField:左集合 join 字段
 *   foreignField:右集合 join 字段
 *   as:新生成字段(类型array)
 * 
 **/
db.prototype.aggregate = async function (collectionName, arr) {
    return new Promise((resolve, reject) => {
        this.getConnection().then(e => {
            e.collection(collectionName)
                .aggregate(arr)
                .toArray((err, res) => {
                    if (err) reject(err);
                    this.MongoClient.close();
                    this.MongoClient = this.dbClient = null;
                    resolve(res);
                });
        }).catch(error => reject(error));
    });
}


module.exports = new db();

参考以下文章

Node.js操作MongoDB数据库的基本用法

nodejs操作mongodb数据库封装DB类

Node.js封装对mongodb操作的模块

NodeJS操作 Mongodb 并且对 Mongodb 类库进行封装