附:基于 Nodejs 的服务器后端的框架用的比较多的是 Koa : github/kaojs/koa 但本文这里介绍的基于 Express 框架。
附录: 前端开发,和一般开发 (非官方说法) 有三个阶段:
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,媒体类型等组成。
注:如果你的需求是基于 express 建立一个 Web 项目,建议可以使用官方的推荐框架生成器,通过应用生成器工具 express-generator 可以快速创建一个应用的骨架。具体可以参见:http://www.expressjs.com.cn/starter/generator.html
首先做下面的准备工作。
mkdir pcxProject
cd pcxProject
npm init
Package.json文件告诉了npm必要的信息,让npm可以识别项目,同时处理项目的依赖。
npm初始化将会提示你输入一些必要信息,比如说app名称、描述、版本、作者、关键字以及你是否相应看见你喜欢的。
你将会看到类似于下面这样的结果:
touch server.js
mkdir api/controllers api/models api/routes
npm install --save-dev nodemon
npm install express --save
"start" : "nodemon server.js"
var express = require('express'),
app = express(),
port = process.env.PORT || 3000;
app.listen(port);
console.log('todo list RESTful API server started on: ' + port);
todo list RESTful API server started on: 3000
yarn add mongoose
'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。
yarn add promise-mysql
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();
}
}
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' });
});
};
这里采用 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
}
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 测试,略。