文档章节

NODEJS AND MONGODB 评论列表模型

tulayang
 tulayang
发布于 2014/08/29 09:21
字数 1417
阅读 48
收藏 0

评论列表算是编程领域极具代表性的问题了,算法上使用树结构。现在我们一步一步来使用Javascript、Node、mongodDB来解决评论模型问题。

#Step 1 建立模型

User:
    _id       : OBJECTID 用户ID
    username  : STRING   用户名
    
Article:
    _id       : OBJECTID 文章ID 
    userID    : OBJECTID 用户ID
    username  : STRING   用户名  反常规化
    title     : STRING   标题
    text      : STRING   内容
    
Comment:
    _id       : OBJECTID 评论ID
    userID    : OBJECTID 用户ID
    username  : STRING   用户名  反常规化
    articleID : OBJECTID 文章ID
    parentID  : OBJECTID 上级评论ID,可以没有
    text      : STRING   内容

在这里,使用了反常规化,在Article、Comment中不但存储了用户ID,还存储了用户名。 这样会增加数据文件的容量,但是查询的时候可以不用去关联User,从而减少一次请求,提升速度。所谓的“以空间换时间”。

#Step 2 使用node-mongodb-native,编写MongoDB数据库服务器连接代码 dbm.js:

/////////////////////// dbm.js ///////////////////////

var mongodb = require("mongodb");
// 数据库连接缓存
var cache = {};

// 连接数据库
function connect (url, options) {
    var fns = [];
    var status = 0;
    var _db = cache[url];
    var args;

    return function (f) {
        args = arguments;
        if (_db !== null && typeof _db === "object") {
            f(_db);
            return;
        } 
            
        fns.push(f);
        // 当有一个连接初始化请求时,挂起其他初始化请求
        // 连接池建立完后,使用该连接处理挂起的请求
        if (status === 0) {
            status = 1;
            mongodb.MongoClient.connect(url, options, function (err, db) {
                if (err) { throw err; }
                _db = cache[url] = db;
                for (var i = 0, len = fns.length; i < len; i++) {
                    fns.shift().call(null, _db);
                }
            });
        }
    };
}

// 关闭数据库
function close (url) {
    var db = cache[url]; 
    if (db !== null && typeof db === "object") {
        db.close();
        delete cache[url];
    }
}

exports.connect = connect;
exports.close = close;

这段代码封装了连接MongoDB的细节,只需要提供url和options(详情见MongoDb MongoClient连接配置)。 好处是可以并发访问MongoDB,在展示并发之前,还需要下面这个小工具:

/////////////////////// dbm.js ///////////////////////

function roll (count, f) {
    return function () { 
        count--;
        if (count === 0) {
            f();
        }
    };
}

function rollData (count, f) {
    var cache = {};
    return function (name, data) { 
        count--;
        if (typeof name === "string") {
            cache[name] = data;
        }
        if (count === 0) {
            f(cache);
        }
    };
}

exports.roll = roll;
exports.rollData = rollData;

roll rollData 可以测试并发请求是否全部结束,并在结束后启动回调函数,我们来写个Example:

var dbm = require("./dbm.js");
var connect = dbm.connect("mongodb://127.0.0.1:27017/teste", {
    "server" : {
        "poolSize" : 10  // 10条连接数
    }
});
var roll = dbm.roll(3, function () {  // 3个并发请求
    console.log("并发请求结束.");
});

console.log("并发请求开始.");
connect(function (db) {  // db就是映射的数据库teste
    db.collection("users").find({}, function (err, result) {
        roll();
    });
});
connect(function (db) {  // db就是映射的数据库teste
    db.collection("articles").find({}, function (err, result) {
        roll();
    });
});
connect(function (db) {  // db就是映射的数据库teste
    db.collection("comments").find({}, function (err, result) {
        roll();
    });
});

代码是并列写的,每个connect会向MongoDB服务器发送请求,并侦听结果。 每个返回会测试当前结束请求的个数,并在全部结束后,启动回调函数,输出console.log("并发请求结束.");

#Step 3 编写个查找评论的函数

/////////////////////// dbm.js ///////////////////////

var COLL_COMMENTS = "comments";
var contact = util.connect("mongodb://127.0.0.1:27017/teste", {
    "server" : {
        "poolSize" : 10  // 10条连接数
    }
});

function checkId (id) {
    return String(id).length === 24;
}

function findComment (id, f) {
// id : STRING, Comment ID
// f(err, result)
//     err    : OBJECT | NULL 服务器错误
//                            id格式错误
//     result : OBJECT 评论文档
//              NULL   没有该评论文档
    if(!checkId(id)) { return f({ name:"IDError", err:"Comment id invalid" }); }
    var selector = { _id: new mongodb.ObjectID(id) };
    contact(function (db) {
        db.collection(COLL_COMMENTS).findOne(selector, f);
    });
}

exports.findComment = findComment;

#Step 4 第2、3步都不是本章要关注的,现在才是我们主要关心的: 树渲染

当我们取到评论列表的时候应该如何渲染?

取到的结果会是这样的:

comments:
[
    { _id:"1", text:"foo", ... },
    { _id:"2", text:"foo", parentID:"1", ... },
    { _id:"3", text:"foo", parentID:"1", ... },
    { _id:"4", text:"foo", ... },
    { _id:"5", text:"foo", parentID:"2", ... },
    { _id:"6", text:"foo", parentID:"5", ... },
    ...
]

如何解决嵌套评论?

A lalala
  B lalala
    C lalala
D lalala
  E lalala
F lalala

这是个树递归的问题,每个项会有parentID来指定当前的判断KEY,如果没有parentID,那么这个KEY就是undefined.

=> 每当用KEY去comments列表查找,会找到一组结果。 => 我们再跳到结果第一个,保存上次的KEY,并设置KEY=当前的查找parentID。 => 又找到一组结果,然后保存上次的KEY,跳到新的结果,并设置KEY=当前的查找parentID。 => ... => 当树最左边的底层叶子完成的时候,向上回跳进行下一个叶子。 => ...

画成图形,可以是这样的过程:

[]
[]                            []   []
[          ][][] 
[完成]

[]
[]                            []   []
[          ][][] 
[完成][完成]

[]
[]                            []   []
[   完成    ][          ][] 
[完成][完成]  [完成]

[] 
[]                            []   []
[   完成    ][          ][] 
[完成][完成]  [完成][完成]

...

[] 
[]                            []   []
[   完成    ][     完成     ][] 
[完成][完成]  [完成][完成]

...

[] 
[完成]                         [完成]   [开始]

...

[]
[完成]                         [完成]   [完成]
...

从树的最左一支,最底点完成,向上向右破动,依次完成。 每个点在自己的栈内存中,跳到上一级会提取当前栈保存的KEY。

代码很简单,如下:

/////////////////////// dbm.js ///////////////////////

function serilize (comments, format) {
    var tree = { childs:[] }; 
    var UNDEFINED;

    function walk (key, value) {
        var i = 0;
        var comment = comments[i]; 
        while (comment) {
            var parentID = comment.parentID ? String(comment.parentID) : comment.parentID;
            if (parentID === key) {
                var child = {};
                var childKey = String(comment._id);
                var childValue = format(comment);
                childValue.childs = [];
                child[childKey] = childValue; 
                value.childs.push(child);
                walk(childKey, childValue);
            }
            comment = comments[++i];
        }
    }

    walk(UNDEFINED, tree);
    return tree;
}

exports.serilize = serilize;

当然了,我们可以写一个测试,看看ta是不是能成功渲染一棵树:

       var serilize = require("./dbm.js").serilize;
       var assert = require("assert");       

       var samples = [
            {
                _id: "id1",
                text: "foo 1"
            },
            {
                _id: "id2",
                parentID: "id1",
                text: "foo 2"
            },
            {
                _id: "id3",
                text: "foo 3"
            },
            {
                _id: "id4",
                parentID: "id2",
                text: "foo 4"
            },
            {
                _id: "id5",
                parentID: "id2",
                text: "foo 5"
            },
            {
                _id: "id6",
                parentID: "id4",
                text: "foo 6"
            }
        ];

        var result = serilize(samples, function (cmt) {
            return {
                text: cmt.text,
            };
        });
             
        assert.strictEqual(result["childs"][0]["id1"]["childs"][0]["id2"]["text"], "foo 2");
        assert.strictEqual(result["childs"][0]["id1"]["childs"][0]["id2"]["childs"][0]["id4"]["text"], "foo 4");
        assert.strictEqual(result["childs"][1]["id3"]["text"], "foo 3");

#Step 5 完成的评论查找测试

var dbm = require("./dbm.js");
var assert = require("assert");

dbm.findComments("53fded391f6769586a5e7fe1", function (err, result) {    
    var tree = dbm.serilize(result, function (cmt) {
        return {
            text: cmt.text,
            username: cmt.username
        };
    });
    console.log("%j", tree); 
    assert.ok(result instanceof Array);
    assert.ok(result.length > 0);
});

这里有个文章评论创建查找修改的代码,太长了,如果你感兴趣,可以来github: node-dbm看看。 当然,单元测试在这儿node-dbm-test

© 著作权归作者所有

tulayang
粉丝 2
博文 7
码字总数 6034
作品 3
崇明
私信 提问
福利丨MongoDB赎金事件的背后,DBA们该如何反思?

最近MongoDB赎金事件闹得沸沸扬扬,一些技术实践者的观点是,本次安全事件的根源不在MongoDB,可能在于不规范的操作和使用,而一些安全战略师则反驳这是意料之中的事故。对此,你怎么看?DBA...

DBAplus社群
2017/01/12
0
0
使用express框架时,用MongoDB存放session时,出现错误,解决方法

使用express框架时,用MongoDB存放session时,出现错误如下: D:WorkSpacenodejsusernodemodulesconnect-mongolibconnect-mongo.js:153 throw new Error('Error connecting to database'); ......

maweitao
2014/04/11
100
0
MongooseJS 4.6.6 发布,MongoDB 连接包

MongooseJS 4.6.6 发布了。MongooseJS 是基于 node.js,使用 JavaScript 编程,连接 MongoDB 数据库的软件包,使MongoDB 的文档数据模型变得优雅起来,方便对 MongoDB 文档型数据库的连接和增...

达尔文
2016/11/04
512
0
MongooseJS 4.5.7 发布,MongoDB 连接包

MongooseJS 4.5.7 发布了,MongooseJS是基于nodejs,使用javascript编程,连接mongodb数据库的软件包,使mongodb的文档数据模型变的优雅起来,方便对mongodb文档型数据库的连接和增删改查等常...

oschina
2016/07/26
614
2
MongooseJS 4.7.0 发布,MongoDB 连接包

MongooseJS 4.7.0 发布了。MongooseJS 是基于 node.js,使用 JavaScript 编程,连接 MongoDB 数据库的软件包,使MongoDB 的文档数据模型变得优雅起来,方便对 MongoDB 文档型数据库的连接和增...

达尔文
2016/11/24
762
0

没有更多内容

加载失败,请刷新页面

加载更多

golang-字符串-地址分析

demo package mainimport "fmt"func main() {str := "map.baidu.com"fmt.Println(&str, str)str = str[0:5]fmt.Println(&str, str)str = "abc"fmt.Println(&s......

李琼涛
今天
4
0
Spring Boot WebFlux 增删改查完整实战 demo

03:WebFlux Web CRUD 实践 前言 上一篇基于功能性端点去创建一个简单服务,实现了 Hello 。这一篇用 Spring Boot WebFlux 的注解控制层技术创建一个 CRUD WebFlux 应用,让开发更方便。这里...

泥瓦匠BYSocket
今天
6
0
从0开始学FreeRTOS-(列表与列表项)-3

FreeRTOS列表&列表项的源码解读 第一次看列表与列表项的时候,感觉很像是链表,虽然我自己的链表也不太会,但是就是感觉很像。 在FreeRTOS中,列表与列表项使用得非常多,是FreeRTOS的一个数...

杰杰1号
今天
8
0
Java反射

Java 反射 反射是框架设计的灵魂(使用的前提条件:必须先得到代表的字节码的 Class,Class 类 用于表示.class 文件(字节码)) 一、反射的概述 定义:JAVA 反射机制是在运行状态中,对于任...

zzz1122334
今天
5
0
聊聊nacos的LocalConfigInfoProcessor

序 本文主要研究一下nacos的LocalConfigInfoProcessor LocalConfigInfoProcessor nacos-1.1.3/client/src/main/java/com/alibaba/nacos/client/config/impl/LocalConfigInfoProcessor.java p......

go4it
昨天
9
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部