快速的利用 Express 框架实现一个 Rustfull 接口的后端 Server

快速的利用 Express 框架实现一个 Rustfull 接口的后端 Server

附:基于 Nodejs 的服务器后端的框架用的比较多的是 Koa : github/kaojs/koa 但本文这里介绍的基于 Express 框架。

附录: 前端开发,和一般开发 (非官方说法) 有三个阶段:

  • 基于代码 Code 的 log 打印阶段调试开发
  • 基于 IDE,如(Vscode) 的代码调试,断点阶段。
  • 基于测试用例 (TDD) 驱动开发的测试驱动开发阶段,针对前端的或 nodejs 的项目,推荐使用Facebook 开源的 Jest 包做单元测试和测试驱动。

REST 介绍

  • REST是Representational State Transfer的缩写。它是Web标准架构和HTTP协议。REST架构风格描述了Roy Fielding在博士论文中最初表达的六个限制,并将RESTful风格的基础定义为:

    • 统一的界面
    • 无状态
    • 可缓存
    • 客户端服务器
    • 分层系统
    • 按需编码

RESTful 的应用程序使用HTTP执行四种操作CRUD(C:create,R:read,U:update,D:delete)。Create和Update用来提交数据,get来读取数据,delete用来移除数据。RESTful由基本URL,URL,媒体类型等组成。

使用的工具

  • Node.js
  • MySqL
  • Redis
  • Ticker (定时器)
  • Postman (测试接口)

构建工程

注:如果你的需求是基于 express 建立一个 Web 项目,建议可以使用官方的推荐框架生成器,通过应用生成器工具 express-generator 可以快速创建一个应用的骨架。具体可以参见:http://www.expressjs.com.cn/starter/generator.html

  • 进入你的工程目录,打开你的终端并且进行以下步骤:

首先做下面的准备工作。

  • npx license mit 通过license包下载对应的协议npx gitignore node使用gitignore包自动的从Github仓库中下载相关文件
  • npx covgen使用covgen包生成一份贡献者契约,这会让你的项目更受贡献者的欢迎。
  1. 创建一个 pcxProject 文件夹
mkdir pcxProject
  1. 转入你刚刚创建的目录
cd pcxProject
  1. 创建package.json文件
npm init
  • Package.json文件告诉了npm必要的信息,让npm可以识别项目,同时处理项目的依赖。

  • npm初始化将会提示你输入一些必要信息,比如说app名称、描述、版本、作者、关键字以及你是否相应看见你喜欢的。

你将会看到类似于下面这样的结果:

快速的利用 Express 框架实现一个 Rustfull 接口的后端 Server_第1张图片

  • 输入yes,按下enter键来完成package.json文件的创建。
  1. 创建server.js文件
touch server.js
  • 在这个server中,我们将会写下创建我们server的代码。创建一个api文件夹mkdir api
  1. 在这个api文件夹中,创建三个独立的models、routes、以及controllers文件夹
mkdir api/controllers api/models api/routes
  1. 在api/controller文件夹中创建 Controller.js文件,在routes文件中创建 Routes.js 文件,在models文件夹中创建 Model.js 文件
  • 我们的文件目录应该像下面这样:

快速的利用 Express 框架实现一个 Rustfull 接口的后端 Server_第2张图片

  • 其中,config 文件下面主要是程序启动之前的初始化操作。如定义的初始化数据在 Express Server 启动之前的初始化操作,或者一些数据库连接的初始化。

Express Server 安装及开发

  • 现在来安装express和nodmon,express用来创建server,nodmon帮助我们追踪我们应用程序的改变,它会监视文件的改变并且自动重启server。
npm install --save-dev nodemon
npm install express --save
  • 一旦成功安装之后,你的package.json文件将会被修改,添加两个新的依赖:
  1. 打开 package.json 文件,并且把这个任务添加到文件中 。

"start" : "nodemon server.js"

  1. 打开 server.js 文件,启动 Express 可以采用如下的代码:
var express = require('express'),
  app = express(),
  port = process.env.PORT || 3000;

app.listen(port);

console.log('todo list RESTful API server started on: ' + port);
  1. 在你的终端中运行 npm run start,启动你的server,你将会看到如下 Log.

todo list RESTful API server started on: 3000

安装数据库

mongoDB
  • 首先,我们要安装mongoose

yarn add mongoose

  • Mongoose 是我们用来与 MongoDB 数据库交互的工具。安装之后,打开 Model.js 文件,输入以下代码并且保存:
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;


var TaskSchema = new Schema({
  name: {
    type: String,
    Required: 'Kindly enter the name of the task'
  },
  Created_date: {
    type: Date,
    default: Date.now
  },
  status: {
    type: [{
      type: String,
      enum: ['pending', 'ongoing', 'completed']
    }],
    default: ['pending']
  }
});

module.exports = mongoose.model('Tasks', TaskSchema);
  • 通过上面的代码,我们在我们的文件导入了mongoose,我们创建了一个模型,说明我们的数据集应该呈现的样子。

  • 正如你所看到的,它指出了数据集(表)应该包含一个名称,它的类型应该是string,还包括了创建日期。它也包含了任务状态,每个任务被创建的时候都有默认值pending。

MySqL
  • 这里使用 promise-mysql 可以避免写回调函数。
yarn add promise-mysql
  • 在程序服务启动之前,在 config 文件加下面添加一个数据库初始化的操作,touch promisePool.js 数据库连接池。
const mysql= require("promise-mysql");

// For Mysql 
const pool = mysql.createPool({
    host: config.DBConfig.DBAddr,
    port: config.DBConfig.Port,
    user: config.DBConfig.UserName,
    password: config.DBConfig.Password,
    database: config.DBConfig.DbName,
    connectionLimit: 10,
});

async function getPool() {
    return pool;
}

async function getConnection() {
    return (await pool).getConnection();
}

  • 在使用的时候,直接可从连接池获取一个连接。

  • 在 config 文件夹的另外一个文件 initServer.js 如其名字一样,提供了初始化的变量的一些工作和导出函数。

  • 之外,在 model.js 中封装了要使用的函数,比如:导出 getAllAddr( )

// For Database
async function getAllAddr(tableName,coinType){
    let connection = await getConnection();
    try {
        let sqlStr = `select * from ${tableName} where cointype='${coinType}';`
        // var sqlStr = sqlString.format('SELECT * FROM walletaddress WHERE cointype= ?',[coinType])
        console.log(sqlStr)
        const result = await connection.query(sqlStr)
        // console.log(result)
        return result;
    } finally {
        connection.release();
    }
}
  • 其他,MySql 获取数据的的接口,如上即可。
Redis
  • Redis 这里采用 async-redis 异步的连接 redis 的库。

  • 在 config/promisePool.js 中加入 Redis 的初始化函数。

const redis = require("async-redis");

// For redis 
const ccIp = config.CCConfig.RedisAddr.split(':',2)[0]
const ccPort = config.CCConfig.RedisAddr.split(':',2)[1]
const client = redis.createClient(ccPort,ccIp,{auth_pass: config.CCConfig.Password})

async function ccInit() {
    await client.on("error", function (err) {
        console.log("Error " + err);
    });
}

var redisDao = function() {};

redisDao.prototype.get = async function (key){
    let resp = await client.get(key);
    console.log('AfterGet:', resp)
    return resp
}

redisDao.prototype.set = async function (key, value){
    await client.set(key, value)
}

redisDao.prototype.del = async function (key){
    await client.del(key, function(err, o) {
        console.log("Del error: " + err);
    })
}

redisDao.prototype.hgetall = async function(key) {
    let resp = await client.hgetall(key);
    // console.log('AfterGetAll:',resp)
    return resp 
}

redisDao.prototype.hmset = async function(Item, key, value) {
    await client.hmset(Item,key,value)
}

redisDao.prototype.hdel = async function(Item, key) {
    console.log("DelHash Item:%s,key:%s", Item, key)
    await client.hdel(Item, key)
}

安装路由

  • 路由决定了应用程序如何响应特定终端的客户端的请求,一个特定的URL(或path)以及特定的HTTP请求(GET、POST等等)。

  • 每个路由都有不同的路由处理方法,当路由匹配时被执行。下面我们将会用不同的方法定义两个基本的路由(“tasks”以及“/tasks/taskId”)。

  • “/tasks”有POST和GET方法,“/tasks/taskId”有GET、PUT和DELETE方法。

  • 正如你所看到的,我们需要controller,每个路由方法都能调用它对应的处理程序。controller 下面可以写业务相关的 Restfull api 的响应处理函数。

use strict';
module.exports = function(app) {
  var todoList = require('../controllers/todoListController');


  // todoList Routes
  app.route('/tasks')
    .get(todoList.list_all_tasks)
    .post(todoList.create_a_task);


  app.route('/tasks/:taskId')
    .get(todoList.read_a_task)
    .put(todoList.update_a_task)
    .delete(todoList.delete_a_task);
};

安装控制器

  • 打开todoListController.js文件,然后进行下一步:

  • 在这个controller中,我们将会写出5个不同的方法:list_all_tasks, create_a_task,read_a_task,update_a_task, delete_a_task。我们将会到处每个方法。

  • 每个方法会用到不同的mongoose方法,例如find、findById、findOneAndUpdate、save 以及 remove。

'use strict';


var mongoose = require('mongoose'),
  Task = mongoose.model('Tasks');

exports.list_all_tasks = function(req, res) {
  Task.find({}, function(err, task) {
    if (err)
      res.send(err);
    res.json(task);
  });
};


exports.create_a_task = function(req, res) {
  var new_task = new Task(req.body);
  new_task.save(function(err, task) {
    if (err)
      res.send(err);
    res.json(task);
  });
};


exports.read_a_task = function(req, res) {
  Task.findById(req.params.taskId, function(err, task) {
    if (err)
      res.send(err);
    res.json(task);
  });
};


exports.update_a_task = function(req, res) {
  Task.findOneAndUpdate(req.params.taskId, req.body, {new: true}, function(err, task) {
    if (err)
      res.send(err);
    res.json(task);
  });
};


exports.delete_a_task = function(req, res) {


  Task.remove({
    _id: req.params.taskId
  }, function(err, task) {
    if (err)
      res.send(err);
    res.json({ message: 'Task successfully deleted' });
  });
};
schedule 定时器任务
  • 这里采用 while(true) + sleep 实现定时器,此处有两个定时器,两个 while(true) 但是考虑到 nodejs 的单线程异步的特性,当遇到需要同步的函数或阻塞的操作或 IO 操作的时候,如网络请求的操作时,nodejs 便会切换到其他地方去执行。除非是,await promise 操作才会等待。

  • 在scheduleTask 文件夹下面的 task.js 里面放入如下内容:

const schedule = require('./schedule')

async function startQueryBlock() {
    try {
        schedule.ParseBlockTicker()
        schedule.HeartBeatTicker()
    } catch (error) {
        console.error(error);
    }
}

module.exports = {
    startQueryBlock
}
  • 在具体的 Schedule 中完成特定的定时任务的操作。(如下)
const ParseBlockTicker = async () => {
    while(true) {
        await sleep(2000)  // Todo ****    
        // console.log('scheduleCronstyle:' + new Date());
        const block = await Server.ServerData.api.rpc.chain.getBlock();
        const lastHeight = parseInt(block.block.header.blockNumber.words[0]) 
        if(lastHeight <= Server.ServerData.currentHeight) {
            continue
        } else {
            while(Server.ServerData.currentHeight < lastHeight) {
                Server.ServerData.currentHeight ++  // Todo 
                if(Server.ServerData.currentHeight % 100 == 0) {
                    console.log("[PCX] update node block info: node height is %d,mgr height is %d",lastHeight, Server.ServerData.currentHeight)
                }
                while(true) {
                    err = await syncTo()
                    if( err != null) {
                        // if err happen, sleep 5 second retry
                        await sleep(5000);
                        console.log("[PCX] fail to parse block and will try again. height : %d, err : %s", Server.ServerData.currentHeight, err);
                        continue
                    }
                    break
                }
                await PromisePool.redisDao.set(Server.ServerData.PCX + 'HEIGHT', Server.ServerData.currentHeight)
            }
        }
    }
}

const HeartBeatTicker = async () => {
    while(true) {
        await sleep(30000)
        // console.log('heartBeatTicker:' + new Date());
        let nodeStatus = new Server.RespParam.NodeStatusReq()
        nodeStatus.msgid = Server.RespParam.GetMsgId()
        nodeStatus.coinType = Server.ServerData.PCX

        const block = await Server.ServerData.api.rpc.chain.getBlock();
        if(!block.block) {
            nodeStatus.message = Server.ServerData.PCX + Server.ServerData.NodeOfflineMessage
            console.log("[PCX] TRON node error, Report it!")
            let err = await reportSyncNodeStatus(nodeStatus)
            if(err != null) {
                console.log("reportNodeStatus failed")
            } else {
                console.log("reportNodeStatus successful.")
            }
        } else {
            nodeStatus.message = Server.ServerData.PCX + Server.ServerData.NodeAliveMessage
        }
    }
}

整合

  • 安装 bodyParser, 使用 bodyParser 在处理之前,先在中间层解析请求中的body,在req.body属性。它暴露了各种工厂来创建中间件。如果没有body来解析或者空对象({}),中间件将会使用解析的req.body属性来填充。

  • 关于主程序 Server.js 中可根据也业务调整如下:


var ServerInit = require("./config/initServer")
var express = require('express');
bodyParser = require('body-parser');
const parseBlock = require('./parseBlock/parseBlock')


function startExpress() {
    app = express(),
    port = process.env.PORT ||  ServerInit.ServerData.HttpRestPort;

    app.use(bodyParser.urlencoded({ extended: true }));
    app.use(bodyParser.json());

    var routes = require('./api/routes/routes');
    routes(app);    
    app.use(function(req, res) {
        res.status(404).send({url: req.originalUrl + ' not found'})
      });

    app.listen(port);
    console.log("Server has starting")
}

async function main() {
    await ServerInit.InitServer();
    startExpress();
    // 启动定时任务
    parseBlock.startScheduleTask(); 
}

main();

通过Postman测试

用 Postman 测试,略。

你可能感兴趣的:(Nodejs,及,Web,前端,nodejs,restfull)