快速的利用 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包生成一份贡献者契约,这会让你的项目更受贡献者的欢迎。
- 创建一个 pcxProject 文件夹
mkdir pcxProject
- 转入你刚刚创建的目录
cd pcxProject
- 创建package.json文件
npm init
Package.json文件告诉了npm必要的信息,让npm可以识别项目,同时处理项目的依赖。
npm初始化将会提示你输入一些必要信息,比如说app名称、描述、版本、作者、关键字以及你是否相应看见你喜欢的。
你将会看到类似于下面这样的结果:
- 输入yes,按下enter键来完成package.json文件的创建。
- 创建server.js文件
touch server.js
- 在这个server中,我们将会写下创建我们server的代码。创建一个api文件夹mkdir api
- 在这个api文件夹中,创建三个独立的models、routes、以及controllers文件夹
mkdir api/controllers api/models api/routes
- 在api/controller文件夹中创建 Controller.js文件,在routes文件中创建 Routes.js 文件,在models文件夹中创建 Model.js 文件
- 我们的文件目录应该像下面这样:
- 其中,config 文件下面主要是程序启动之前的初始化操作。如定义的初始化数据在 Express Server 启动之前的初始化操作,或者一些数据库连接的初始化。
Express Server 安装及开发
- 现在来安装express和nodmon,express用来创建server,nodmon帮助我们追踪我们应用程序的改变,它会监视文件的改变并且自动重启server。
npm install --save-dev nodemon
npm install express --save
- 一旦成功安装之后,你的package.json文件将会被修改,添加两个新的依赖:
- 打开 package.json 文件,并且把这个任务添加到文件中 。
"start" : "nodemon server.js"
- 打开 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);
- 在你的终端中运行 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 Routesapp.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 retryawait 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.PCXconst block = await Server.ServerData.api.rpc.chain.getBlock();if(!block.block) {nodeStatus.message = Server.ServerData.PCX + Server.ServerData.NodeOfflineMessageconsole.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 测试,略。
快速的利用 Express 框架实现一个 Rustfull 接口的后端 Server相关推荐
- 如何利用laragon框架制作一个简单的应用?
如何利用laragon框架制作一个简单的应用? 一.搭建环境 1. 安装Laragon 1.1 打开安装包用的语言 选择自己习惯用的语言 1.2 选择安装地址 1.3 选择Next,开始install ...
- [Web]如何利用Boostrap框架搭建一个还可以的静态网站(五_子页_脱发指南)
文章目录 返回总结 整体效果 组件 人物介绍块 内容介绍块 代码 独属CSS HairLossGuide.css html HairLossGuide.html 返回总结 如何利用Boostrap框架 ...
- 通过python利用Django框架搭建一个属于自己的免费网站(已更新)
大家好,我是天空之城,今天给大家带来,通过python利用Django框架搭建一个属于自己的免费网站. 以下是网上找到的免费教程,https://djangogirlstaipei.gitbooks. ...
- [Web]如何利用Boostrap框架搭建一个还可以的静态网站(六_子页)
文章目录 返回总结 整体效果 组件 vedio题目 vedio博主介绍 vedio vedio用户留言 电子烟评测块 辩论块 代码 html ElectronicCigarettes.html 返回总 ...
- [Web]如何利用Boostrap框架搭建一个还可以的静态网站(四_子页_戒烟小说)
文章目录 返回总结 整体效果 思路 组件 轮播图 排行榜 轮播广告语 广告 空行 小说块 代码 独属css SmokeNote_Style.css html SmokeNote.html 返回总结 如 ...
- [Web]如何利用Boostrap框架搭建一个还可以的静态网站(三_主页)
文章目录 返回总结 整体效果 思路 各个组件代码 导航栏 LOGO 滚动时间 标题线 图词模块 页脚 代码 独属css文件 indexCss1.css html代码 index.html 返回总结 如 ...
- 遗传编程(Genetic Programming)学习笔记(三):利用DEAP框架创建一个GP表达式
DEAP框架 Python 的 DEAP库是一个进化算法框架,可以帮助我们快速实现各种进化算法,例如遗传算法(GA).粒子群算法(PSO).遗传编程(GP).分布估计算法(EDA).多目标优化算法 ...
- 快速上手用Taro框架搭建一个微信小程序
1.安装node版本管理工具https://www.runoob.com/nodejs/nodejs-install-setup.html 2.打开终端 3.打开终端后在命令行输入npm instal ...
- 利用wojilu框架仿一个网站的全过程(Step by Step利用wojilu框架开发网站系列二 附源码)...
被仿的网站和仿照后的网站 被仿的网站-易读 仿照后的网站-我读 仿这个网站的目的:为了实践一下新学习的轻量级asp.net web开发框架[我记录] ,该网站比较简单,适合入门. 实现章节 章节部分, ...
最新文章
- Android TextVeiw 在java代码中添加空格的方法
- conda不是内部或外部命令
- oracle 用mybatis生成主键
- 性能测试-Jmeter
- VC++绘制铁路道岔
- java中自定义输入数字格式_Java 创建并使用自定义数字格式、35;###、####.#####和语言环境...
- 华科PAMI黑科技,方向任意目标检测新算法
- 我的docker随笔28:基于容器的升级方案实验
- 重置系统_【刹车系统】丰田锐志刹车系统重置记忆
- Python初学手记----在window系统中安装环境
- hibernate笔记--组合主键映射方法
- Java核心技术 卷1基础知识 原书第10版.pdf
- IE下判断IE版本的语句...[if lte IE 8]……[endif]
- 03 - Linux下安装软件的三种方式
- 虚拟机VMware16安装教程
- 各种梯度算法总结 + Total Variation
- 移动硬盘里面装linux系统,在移动硬盘上安装Ubuntu和其他linux系统-随身携带操作系统...
- 高质量代码的几大标准
- 浅析爱心代码的绘制思路——python实现
- 打开桌面计算机不显示文件夹,Win10系统怎么让此电脑中的文件夹不显示?
热门文章
- linux运维、架构之路-HAProxy反向代理
- Python之路 day1 基础1 变量 for while 用户输入
- 2016-5-31 问题及解决
- [codevs1262] 不要把球传我 数论+组合数学
- [转]Flex与.NET互操作(三):基于WebService的数据访问(下)
- AJAX ControlToolkit学习日志-Tabs(27)
- Hexo+GitHub 快速搭建个人博客(一)---- 基本部署
- Spring Boot 单例模式中依赖注入问题
- 一个注册为输入法的木马分析
- C++ 使用模板需要注意的事情