前言

经过一段学习时间沉淀,觉得试试使用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+

调试步骤:

  1. 运行 node -- inspect app.js
  2. 打开 Chrome 浏览器,在地址栏输入 chrome://inspect ,可以在 Remote Target 中找到所需要定位的 Node.js 服务目标,选择进入 chrome-devtools
  3. Sources 页签下的 Filesystem ,通过 Add folder to workspace 来添加当前要断点调试的 JavaScript 源代码。
  4. 选择需要增加断点的 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 因其丰富的功能,使得验证数据结构与数值的合规,变得格外容易。

hapi-swagger

安装如下:

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 支持的数据库有:PostgreSQLMySQLMariaDBSQLiteMSSQL。在使用不同的数据库时候,需要额外安装不同的对应数据库连接驱动,例如使用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

先大概写到这里,有时间写个比较好一点的方案再分享出来!

其他

项目源码