美文网首页
(原创) 一个Restify+Mongoose+Redis的no

(原创) 一个Restify+Mongoose+Redis的no

作者: mona_alwyn | 来源:发表于2017-09-10 21:52 被阅读0次

    在以往使用python进行web开发工作时,接触过Django、Flask、Tornado等框架;而现在投入nodejs的怀抱,自然而然接触到了Express和Restify框架,下面就根据自己的入门经验,写一个restify+mongoose+redis进行web开发的简单示例。

    零、开发环境

    1.使用的node版本为v6.11.3 LTS(特此声明:示例代码使用了ES6的特性);
    2.MongoDB和Redis请自行下载(为什么要同时使用这两个呢?因为懒
    得写restify+mongoose和restify+redis两篇);
    3.依赖的node_modules包括:restify、mongoose、redis,
    4.安装好node后自带包管理工具npm(比Python的pip更强大一点点)
    -- npm install xxx -g 全局安装
    -- npm install xxx --save 安装在本项目
    一个名为package.json的文件是它的好基友,不信自己慢慢看

    一、创建一个使用restify API的新服务器

    假设启动文件为app.js,直接上代码

    var restify = require('restify');
    
    // 可配置项
    var addr = '127.0.0.1';
    var port = '8888';
    
    var server = restify.createServer({
        name: 'test',
        version: '0.0.1'
    });
    
    // 使用插件
    server.use(restify.pre.userAgentConnection());          // work around for curl  
    server.use(restify.plugins.acceptParser(server.acceptable));
    server.use(restify.plugins.queryParser());
    server.use(restify.plugins.bodyParser());
    
    server.listen(port, addr, function() {
        console.log("server %s is listening on port <%s>", server.name, server.url);
    });
    

    运行node app.js,本机的8888端口就开启了一个使用restify API的服务器,因为没有增加任何接口,所以无从访问。

    • use部分使用的是restify内置的功能插件,当前引入的是用来解析参数,更具体更丰富的插件功能看这里
    • createServer的参数当然不只这两个,具体可以参考这里

    下面即将进入业务逻辑的开发,先假设业务为两个部分:使用MongoDB管理用户数据、使用redis管理会话中的Cookie。

    二、使用mongoose访问MongoDB

    mongoose对于一个pythoner来说,使用起来并不是很顺手,因为它跟mongoengine很是不同,后者使用Documentation来定义数据模型,而它用的是SchemaModel,至于具体怎么个弄法,还是看这里

    我们的示例呢,简单地继续,创建文件mongo.js,代码如下:

    var mongoose = require('mongoose');
    var Schema = mongoose.Schema;
    
    // 使用node的Promise替换mongoose的Promise,否则会报错
    mongoose.Promise = global.Promise;
    
    // 可配置项
    var host = '127.0.0.1';
    var port = '27017';
    var dbName = 'test';
    
    var dbUri = `${host}:${port}/${dbName}`;
    var dbConnection = mongoose.createConnection(dbUri);
    dbConnection.on('error', (err) => {
        console.log('connect mongodb failed: ' + err);
        process.exit();
    });
    
    var dbSchemas = {
        // 定义user的Schema
        // 包括身份证号码、姓名、性别、手机等
        userSchema: {
            idno: {type: String, require: true, unique: true},
            name: {type: String, require: true},
            gender: {type: Number, require: true},
            phone: {type: String},
            created: {type: Date, default: Date.now}
        }
    };
    
    // 定义UserModel
    class UserModel {
        constructor(connection, schema) {
            this.name = 'user';
            this.schema = new Schema(schema);
            this.model = connection.model(`userModel`, this.schema, this.name);
        }
    
        // 封装查询
        findByIdno(idno) {
            return new Promise((resolve, reject) => {
                this.model.findOne({'idno': {$eq: idno}}, (err, record) => {
                    if (err) {
                        reject(err);
                    } else {
                        resolve(record);
                    }
                });
            });
        }
    
        // 封装插入or更新
        insertOrUpdateByIdno(data) {
            return new Promise((resolve, reject) => {
                this.model.findOne({'idno': {$eq: data.idno}}, (err, record) => {
                    if (err) {
                        reject(err);
                    } else {
                        if (record == undefined) {
                            // 插入
                            var instance = new this.model(data);
                            instance.save((err) => {
                                if (err) {
                                    reject(err);
                                } else {
                                    resovle(data);
                                }
                            });
                        } else {
                            // 更新
                            this.model.findOneAndUpdate({'idno': {$eq: data.idno}}, data, {new: true, upsert: true}, (err, res) => {
                                if (err) {
                                    reject(err);
                                } else {
                                    resolve(res);
                                }
                            });
                        }
                    }
                });
            });
        }
    }
    
    var user = new UserModel(dbConnection, dbSchemas.userSchema);
    
    exports.user = user;
    

    上面代码中使用到了ES6的诸多特性,包括Promise机制、Class以及“箭头”函数等,做的工作则有:

    1. 建立与MongoDB的连接dbConnection
    2. 使用Node的Promise来替换mongoose的Promise(被弃用)
    3. 声明user的数据结构userSchema
    4. 构造UserModel的类
    5. 在UserModel中封装了对MongoDB的查询(findOne)、插入(save)、更新(findOneAndUpdate)操作
    6. 实例化UserModel对象,并export以供调用

    三、使用redis访问Redis

    Redis相比较MongoDB而言,速度更快,因为存储在内存中嘛(可持久化),因而比MongoDB更适合用来管理Session。
    Redis的使用也更简单,不论是写代码还是在redis-cli中敲命令,支持7种数据类型的读写(之前写的5种是因为我也是看的二手资料,现改过),每种数据类型的命令都不同,具体的还是看一下原味的redis数据类型介绍,直接看一手讯息以免入坑,然后代码交互的话可以参考一下node-redis.

    这边示例继续,创建redisClient.js,代码如下:

    var redis = require('redis');
    
    // 可配置项
    var host = '127.0.0.1';
    var port = 6379;
    
    var client = redis.createClient(port, host, {});
    
    client.on('ready', (res) => {
        console.log('redis ready: ' + res);
    });
    
    client.on('error', (err) => {
        console.log('connect redis error: ' + err);
        process.exit(2);
    });
    
    // 假设cookie为哈希,token为string
    class Session {
        constructor(client) {
            this.client = client;
        }
    
        // 设置/更新Cookie以及设置/重设有效期,默认10分钟。
        updateCookie(uuid, cookie, t) {
            t = t ? t: 600;    // 默认10分钟
            return new Promise((resolve, reject) => {
                let key = uuid + ':cookie';
                this.client.hmset(key, cookie, (err, res1) => {
                    if (err) {
                        console.log('redis hmset error: ' + err);
                        reject(err);
                    } else {
                        // 设置有效期
                        this.client.expire(key, t, (err, res2) => {
                            if (err) {
                                console.log('erdis hmset expire error: ' + err);
                                reject(err);
                            } else {
                                resolve(res2 === 1);
                            }
                        });
                    }
                });
            });
        }
    
        // 查询Cookie
        getCookie(uuid) {
            return new Promise((resolve, reject) => {
                let key = uuid + ':cookie';
                this.client.hgetall(key, (err, record) => {
                    if (err) {
                        console.log('redis hgetall error: ' + err);
                    } else {
                        resolve(record);
                    }
                });
            });
        }
    
        // 设置or更新token,同样设置有效期,默认10分钟
        updateToken(uuid, token, t) {
            t = t ? t : 600;
            return new Promise((resolve, reject) => {
                let key = uuid + ':token';
                this.client.set(key, token, (err, res1) => {
                    if (err) {
                        console.log('redis set error: ' + err);
                        reject(err);
                    } else {
                        this.client.expire(key, t, (err, res2) => {
                            if (err) {
                                console.log('redis set expire error: ' + err);
                                reject(err);
                            } else {
                                resolve(res2 === 1);
                            }
                        });
                    }
                });
            });
        }
    
        getToken(uuid) {
            return new Promise((resolve, reject) => {
                let key = uuid + ':token';
                this.client.get(key, (err, record) => {
                    if (err) {
                        cosole.log('redis get error: ' + err);
                        reject(err);
                    } else {
                        resolve(record);
                    }
                });
            });
        }
    
        delUuid(uuid) {
            this.client.del(uuid + ':cookie');
            this.client.del(uuid + ':token');
        }
    }
    
    var session = new Session(client);
    
    module.exports =  {
        session: session
    };
    

    上面完成的工作包括:

    1. 建立redis的client
    2. 定义Session的model
    3. 封装hash和string两种数据类型(Cookie和Token)的读写以及删除
    4. 实例化Session并export以供调用(这里的export操作采用了另一种形式)

    四、设计并实现api接口

    现在数据模型的定义已经完成,就可以开始进行业务逻辑的实现。
    根据model的定义,我们知道已实现的数据库操作包括user、token、cookie的更新(包含创建)和查询,由于后两者都是redis的操作,我们就实现user和cookie的操作好了。
    在app.js中,server.use代码块下方、server.listen上方区域添加如下代码:

    var route = require('./route');
    
    // api route
    server.post('/user/update', route.updateUser);
    server.get('/user/get', route.getUser);
    
    server.post('/cookie/update', route.updateCookie);
    server.get('/cookie/get', route.getCookie);
    

    可以看到引用了一个route.js文件,目前还没有该文件,需要新建route.js来实现业务逻辑,代码如下:

    var user = require('./mongo').user;
    var session = require('./redisClient').session; 
    
    // 创建/更新user
    // post的数据通过req.body传递
    // 此处约定使用json格式,bodyParser可解析
    exports.updateUser = function(req, res) {
        // 此处省略数据合法性的检查
        let data = {
            idno: req.body.idno,
            name: req.body.name,
            gender: req.body.gender,
            phone: req.body.phone
        };
    
        // 设置response为utf-8编码
        res.charSet('utf-8');   
        // 设置返回数据格式为json
        res.setHeader('Content-Type', 'application/json');
        user.insertOrUpdateByIdno(data)
            .then(function(record) {
                res.send({code: 0, msg: '创建/更新user成功', result: record});
            }, function(err) {
                console.log(err);
                res.send({code: 1, msg: '创建/更新user失败', result: {}});
            });
    };
    
    // 查询user
    // get的参数直接在url中,queryParser可解析
    exports.getUser = function(req, res) {
        let idno = req.query.idno;
        // 设置response为utf-8编码
        res.charSet('utf-8');   
        // 设置返回数据格式为json
        res.setHeader('Content-Type', 'application/json');
        user.findByIdno(idno)
            .then(function(record) {
                res.send({code: 0, msg: 'user查询成功', result: record});
            }, function(err) {
                console.log(err);
                res.send({code: 1, msg: 'user查询失败', result: {}});
            });
    };
    
    // 设置/更新Cookie
    // post提交的数据,有uuid为更新,没uuid为新建
    exports.updateCookie = function(req, res) {
        let uuid = req.body.uuid ? req.body.uuid : genUuid();
        let cookie = req.body;
        delete cookie.uuid;        // 不论有没有都是true
        res.charSet('utf-8');
        res.setHeader('Content-Type', 'application/json');
        session.updateCookie(uuid, cookie)
            .then(function(r) {
                res.send({code: 0, msg: '更新/新建Cookie成功', 
                    result: {uuid: uuid, data: cookie}});
            }, function(e) {
                console.log(e);
                res.send({code: 1, msg: '更新/新建Cookie成功', result: {}});
            });
    };
    
    // 根据uuid查询Cookie
    exports.getCookie = function(req, res) {
        let uuid = req.query.uuid;
        res.charSet('utf-8');
        res.setHeader('Content-Type', 'application/json');
        session.getCookie(uuid)
            .then(function(record) {
                res.send({code: 0, msg: '获取Cookie成功', result: record});
            }, function(err) {
                console.log(err);
                res.send({code: 1, msg: '获取Cookie失败', result: {}});
            });
    };
    
    function genUuid() {
        return Math.random().toString() + Math.random().toString();
    }
    

    五、接口测试

    在MongoDB和Redis数据库都运行起来的情况下,运行node app.js启动服务器,然后使用Postman来模拟http请求。

    1. 向接口"/user/update"发送post请求
    updateUser.png

    ps:你可能注意到了,这里存储的是格林威治时间。

    1. 通过接口"/user/get"查询上述记录
    getUser.png
    1. 查看此时MongoDB的情况
    MongoDB.png
    1. 向接口"/cookie/update"发送post请求
    updateCookie.png
    1. 通过接口"cookie/get"查询上述记录
    getCookie.png
    1. 此时Redis数据库内
    redis.png

    六、代码repo

    本文中所有代码均在此restify_mongoose_redis_sample

    相关文章

      网友评论

          本文标题:(原创) 一个Restify+Mongoose+Redis的no

          本文链接:https://www.haomeiwen.com/subject/bwqtsxtx.html