Wafer2 Node.js QuickStart 架构分析

2017-12-11 16:35:48 浏览数 (1)

与 Wafer1 的 Node.js Demo 采用 Express 不同,Wafer2 的 Node.js QuickStart 采用了 Koa.js 框架编写,Koa 将整个请求过程看做全异步的操作,使用 Node.js 7.6 开始支持的 async/await 语法,大大简化了整个应用开发的繁琐性,能让我们写出更加好看的异步代码。关于 async/await,这里不过多的介绍,有兴趣的同学可以查看阮一峰的 async/await 教程。

Koa 中间件 - 洋葱模型

Koa 的中间件模式和 Express 不同,Express 的中间件是流式串行结构,当一个中间件调用了 next 函数,逻辑就再也不会回到这个中间件中,这种模型在一些情况下会使得很多我们想要实现的功能变得复杂,比如请求时间计算,我们不得不将请求的开始时间写进 request 对象,而对于全局错误的捕获,我们也只能监听 uncaughtException 事件,这样导致我们的应用架构变得不够清晰。

而 Koa 的洋葱模型巧妙的解决了这个文件,它将所有的中间件(或者处理业务的函数)看成是异步的,next 函数则返回一个 Promise 对象,每一个中间件会包裹住下一个中间件,如同洋葱一样,请求(ctx 对象)从最外层进到最里层,又从最里层再流出来,简化了我们整个应用设计的难度。

response 中间件

同大部分 Node.js 程序一样,应用的入口是 app.js,应用的最开始会先引入一个 response 中间件:

代码语言:txt复制
app.use(response)

这个中间件用来处理整个应用的异常捕获和请求响应结束之后的响应数据封装。

打开 middlewares/response.js 文件,可以看到如下代码:

代码语言:javascript复制
const debug = require('debug')('koa-weapp-demo')

/**
 * 响应处理模块
 */
module.exports = async function (ctx, next) {
    try {
        // 调用下一个 middleware
        await next()

        // 处理响应结果
        // 如果直接写入在 body 中,则不作处理
        // 如果写在 ctx.body 为空,则使用 state 作为响应
        ctx.body = ctx.body ? ctx.body : {
            code: ctx.state.code !== undefined ? ctx.state.code : 0,
            data: ctx.state.data !== undefined ? ctx.state.data : {}
        }
    } catch (e) {
        // catch 住全局的错误信息
        debug('Catch Error: %o', e)

        // 设置状态码为 200 - 服务端错误
        ctx.status = 200

        // 输出详细的错误信息
        ctx.body = {
            code: -1,
            error: e && e.message ? e.message : e.toString()
        }
    }
}

response 中间件对全局做了一个 try...catch 操作,将 next() 的调用错误给 catch 住,防止请求过程中的某一个错误导致整个 Node 程序退出。同时,在请求结束之后,会从 ctx.state 取出 data 和 code 两个字段,封装进 ctx.body 中,ctx.body 就是整个引用最后返回给前端的数据,Koa 会对 ctx.body 进行 JSON 序列化,并在 http header 上加上响应的类型头。

路由

QuickStart 使用的是 koa-router 来处理路由映射,打开 routes/index.js 可以看到,本文件中统一处理所有的路由。其中与用户登录授权有关的两个路由与别的不同,分别使用了 SDK 导出的 authorizationMiddleware 和 validationMiddleware 中间件。

代码语言:txt复制
// --- 登录与授权 Demo --- //

// 登录接口
router.get('/login', authorizationMiddleware, controllers.login)

// 用户信息接口(可以用来验证登录态
router.get('/user', validationMiddleware, controllers.user)

只有通过了这两个中间件的处理,才会执行后面相关的 controllers。

控制器映射

QuickStart 中还有一个值得分享的就是控制器映射,打开 controllers/index.js 文件,可以看到如下代码:

代码语言:javascript复制
const _ = require('lodash')
const fs = require('fs')
const path = require('path')

/**
 * 映射 d 文件夹下的文件为模块
 */
const mapDir = d => {
    const tree = {}

    // 获得当前文件夹下的所有的文件夹和文件
    const [dirs, files] = _(fs.readdirSync(d)).partition(p => fs.statSync(path.join(d, p)).isDirectory())

    // 映射文件夹
    dirs.forEach(dir => {
        tree[dir] = mapDir(path.join(d, dir))
    })

    // 映射文件
    files.forEach(file => {
        if (path.extname(file) === '.js') {
            tree[path.basename(file, '.js')] = require(path.join(d, file))
        }
    })

    return tree
}

// 默认导出当前文件夹下的映射
module.exports = mapDir(path.join(__dirname))

controllers/index.js 是所有控制器默认导出的文件,他遍历了 controllers 文件夹下的所有文件和文件夹,并生成一个对象结构。

例如,如下的文件夹结构:

代码语言:txt复制
── controllers
   ├── user.js
       └── login.js
   └── upload.js

会被映射为如下的 JavaScript 对象:

代码语言:txt复制
{
    user: {
        login: require('./user/login.js')
    },
    upload: require('./upload.js')
}

这样,我们只需引入 controllers/index.js 就可以引入所有的 controllers,创建一个新的文件夹和文件,也可以马上通过 controllers/index.js 来访问到,无需单独 require,让整个应用更加的清晰。

结语

这次关于 Node.js 版本 QuickStart 的代码就分享到这里,欢迎大家使用腾讯云微信小程序解决方案。如果有什么更好的意见或者建议,可以在评论中提出来,一起讨论一下。

0 人点赞