前言
经过一段学习时间沉淀,觉得试试使用Node.js后端开发,虽然之前也弄过,但因为时间匆促设计api
风格、数据表等一些东西有各种问题。所以有了这一系列的笔记。
接口设计
现在接口绝大部分都是遵循RESTful 接口设计
,REST 特点倾向于用更加简单轻量的方法设计和实现。所以关于更多信息可以看看RESTful API 设计指南。
后端框架
一开始我是打算利用express
或者koa
框架进行编写,但觉得这俩者设计理念并不符合我的需求,大概挑选几个框架定下hapi,上手体验还算可以,据说还是和Swagger
结合最好的,打算打算尝试一下。
调试技巧
如果是服务端Node.js调试数据比较麻烦,除了在终端查看console.log
输出信息,别无他选。所以对此花了一些时间找到相对而言比较高效的调试方法。
新版本的 Chrome 浏览器和新版本的 Node.js 支持通过一个新的调试协议能互相直接通讯了,就不再需要 node-inspector 了。
- Node.js 6.3+
- Chrome 55+
调试步骤:
- 运行
node -- inspect app.js
- 打开 Chrome 浏览器,在地址栏输入
chrome://inspect
,可以在Remote Target
中找到所需要定位的Node.js
服务目标,选择进入chrome-devtools
。 - 在
Sources
页签下的Filesystem
,通过Add folder to workspace
来添加当前要断点调试的 JavaScript 源代码。 - 选择需要增加断点的 JavaScript 代码,打上断点标记,执行 Node.js 服务,查看断点流程。
项目初始化
无论做什么项目,首先要重注于项目工程化设计
:
├── config # 项目配置目录
| ├── index.js # 配置项目中的配置信息
├── models # 数据库 model
├── node_modules # node.js 的依赖目录
├── plugins # 插件目录
├── routes # 路由目录
│ ├── hello-world.js # 测试接口 hello-world
├── utils # 工具类相关目录
├── app.js # 项目入口文件
├── package.json # JS 项目工程依赖库
├── readme.md # 项目工程如何被使用的说明手册...
项目工程化设计原则:
单业务模块化:
大多数的内务需求,都最终可以被归纳进单个模块,模块与模块间具有物理文件独立性,能更好地便于多人的项目团队人员同时维护代码。模块二百行原则:
二百行是个虚数泛指,但也在大量的项目实践后发现,大多数单文件的模块代码,在良好的设计规划下,往往很难突破两百行的数目,而两百行内的代码的阅读与维护,也都是一个心理压力较小的量级。
正所谓合久必分,单个模块的代码量大量增长,可以考虑分离部分代码到外部文件来管理,通过模块 require 的方式形成关联,以保持各模块代码的相对精简与可维护性。同类模块分组化:
分组在本文中一般指形成一个文件目录,来对一些功能相似的业务模块形成管理,比如常见的路由模块,会有大量不同的基于 resource 的资源路由,被分离在单个模块文件中,最终汇总在 router 的目录。再比如定义数据库表结构映射关系的 model 模型类模块,最终也往往会习惯汇总到一个 model 的目录中,形成统一管理。配置文件分离:
系统中往往会存在一些诸如服务启动的端口号、服务名称、数据库连接配置、服务启动的性能参数域值配置等等,这些参数的业务逻辑往往离散在系统中的各个独立模块中,将其抽取在一个统一的类似 config.js 的配置文件中进行整体管理,可以避免日后打地鼠式的在模块中四处找寻,以及修改编辑疏漏。
使用 Swagger 和 Joi
之前做过PHP接口项目,使用的是接口描述是:Swagger。说起来Swagger并不是文档什么,它是一种与语言无关的接口描述,目标是为 REST APIs 定义一个标准的,帮助在看不到具体源码的情况下能发现和理解各种服务的功能。并通过 swagger-ui 的网页输出,来形成一套美观简洁的可视化文档。而且Hapi是完美支持Swagger接口描述,没有之一。
Joi 是一种验证模块,其 API 因其丰富的功能,使得验证数据结构与数值的合规,变得格外容易。
安装如下:
npm install inert --save # Swagger 静态服务插件
npm install vision --save #Swagger 模板渲染插件
npm install hapi-swagger --save
在项目plugins/hapi-swagger.js
写入:
const inert = require('inert');
const vision = require('vision');
const hapiSwagger = require('hapi-swagger');
const swaggerOptions = {
info: {
title: 'Test API Documentation',
version: '1.0',
},
};
module.exports = [
inert,
vision,
{
plugin: hapiSwagger,
options: swaggerOptions
}
]
然后在app.js
挂载swagger
插件配置:
// app.js
const env = require('env2')('./.env');
const Hapi = require('hapi');
const config = require('./config');
const routesHelloHapi = require('./routes/hello-hapi')
// 引入自定义的 hapi-swagger 插件配置
const pluginHapiSwagger = require('./plugins/hapi-swagger');
const server = new Hapi.Server({
port: process.env.port,
host: process.env.host
});
const init = async () => {
await server.register([
// 为系统使用 hapi-swagger
...pluginHapiSwagger,
]);
server.route([
// 创建一个简单的hello hapi接口
...routesHelloHapi,
]);
// 启动服务
try {
await server.start();
console.log('Server running at: ' + server.info.uri);
} catch(err) {
console.log(err);
}
};
init();
在hello-hapi.js
编写接口描述:
module.exports = [
{
method: 'GET',
path: '/',
handler: (request, h) => {
return 'hello world'
},
config: {
tags: ['api'],
description: 'hello测试接口',
notes: '接口说明',
}
}
]
为 REST 接口添加 Swagger 标记,在路由配置中的 config 字段增加 tags:['api'] 即可将路由暴露为 Swagger 文档,第二个参数选填,可以将接口进行一定的 group 分组管理,接口将被折叠如对应的 group 分类。config 中的 description 字段则用于 Swagger 描述接口的内容,亦可作为一部分代码的功能注释来用。
访问 http://127.0.0.1:3000/documentation ,查看暴露接口的 Swagger 文档。
Joi校验数据验证,例如:
// routes/hello-hapi.js
const Joi = require('joi');
module.exports = [
{
method: 'POST',
path: '/user/' + id,
handler: (request, h) => {
return 'hello world'
},
config: {
tags: ['api'],
description: 'hello测试接口',
notes: '接口说明',
validate: {
params: {
orderId: Joi.string().required(),
}
}
}
}
]
正如上面所示,如果id
没有的直接访问/user
则会报:
{
"error": "Bad Request",
"message": "此处会有明确的字段验证错误描述",
"statusCode": 400
}
更多文档请查看:
Sequelize
Sequelize 是 Node.js 生态中一款知名的基于 promise
数据库 ORM
插件,提供了大量常用数据库增删改查的函数式 API,以帮助在实际开发中,大量减少书写冗长的基础数据库查询语句。Sequelize
支持的数据库有:PostgreSQL
,MySQL
,MariaDB
,SQLite
和 MSSQL
。在使用不同的数据库时候,需要额外安装不同的对应数据库连接驱动,例如使用MySQL需要依赖插件mysql2
。
环境下进行sequelize初始化npm install --save sequelize
# 根据所用数据库选择依赖:
npm install --save pg pg-hstore # PostgreSQL
npm install --save mysql2 # MySQL、MariaDB
npm install --save sqlite3 # SQLite
npm install --save tedious # MSSQL
npm install --save-dev sequelize-cli # Sequelize命令行界面
node_modules\.bin\sequelize init # window 环境下进行sequelize初始化
执行发现当前目录下生成几个文件夹:
├── config # 项目配置目录
| ├── config.json # 数据库连接的配置
├── models # 数据库 model
| ├── index.js # 数据库连接的样板代码
├── migrations # 数据迁移的目录
├── seeders # 数据填充的目录
剩下内容可以参考Sequelize文档。
使用JWT实现身份验证
JWT 全称 JSON Web Token,是为了方便在各系统之间安全地传送 JSON 对象格式的信息,而采用的一个开发标准,基于 RFC 7519 定义。服务器在接收到 JWT 之后,可以验证它的合法性,用户登录与否的身份验证便是 JWT 的使用场景之一。更多详细信息可以参考JSON Web Token 入门教程这篇文章。
实际的项目应用场景中,JWT 的身份验证流程大致如下:
- 用户使用用户名密码、或第三方授权登录后,请求应用服务器;
- 服务器验证用户信息是否合法;
- 对通过验证的用户,签发一个包涵用户 ID、其他少量用户信息(比如用户角色)以及失效时间的 JWT token;
- 客户端存储 JWT token,并在调用需要身份验证的接口服务时,带上这个 JWT token 值;
- 服务器验证 JWT token 的签发合法性,时效性,验证通过后,返回业务数据。
jsonwebtoken 是 Node.js 生态里用于签发与校验 JWT 的流行插件:
npm install -save jsonwebtoken
hapi-auth-jwt2 插件,来赋予系统中的部分接口,需要用户登录授权后才能访问的能力:
npm install -save hapi-auth-jwt
先大概写到这里,有时间写个比较好一点的方案再分享出来!