SkillAgentSearch skills...

Gracejs

A Nodejs BFF framework, build with koa2(基于koa2的标准前后端分离框架)

Install / Use

/learn @xiongwilee/Gracejs
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<p align="center"><img width="30%" src="http://img002.qufenqi.com/products/0e/64/0e648f3e91454d406b3ac8a2941ad15f.png" /></p>

Gracejs

Gracejs(又称:koa-grace v2) 是全新的基于koa v2.x的MVC+RESTful架构的前后端分离框架。

NPM version Build Status

koa-grace v1.x版本请移步: https://github.com/xiongwilee/Gracejs/tree/v1.0.4

一、简介

Gracejs是koa-grace的升级版,也可以叫koa-grace v2。

主要特性包括:

  1. 支持MVC架构,可以更便捷地生成服务端路由;
  2. 标准的RESTful架构,支持后端接口异步并发,页面性能更优;
  3. 一套Node环境经服务服务多个站点应用,部署更简单;
  4. 优雅的MOCK功能,开发环境模拟数据更流畅;
  5. 完美支持async/await及generator语法,随心所欲;
  6. 更灵活的前端构建选型,想用什么就用什么,随你所愿。

相比于koa-grace v1(以下简称:koa-grace):Gracejs完美支持koa v2,同时做了优化虚拟host匹配和路由匹配的性能、还完善了部分测试用例等诸多升级。当然,如果你正在使用koa-grace也不用担心,我们会把Gracejs中除了支持koa2的性能和功能特性移植到koa-grace的相应中间件中。

这里不再介绍“前后端分离”、“RESTful”、“MVC”等概念,有兴趣可参考前端团队基于koajs的前后端分离实践一文。

Gracejs及前后端分离问题交流群:

  • 微信交流群: 添加微信 xiongwilee 后(备注:城市-职业-姓名)拉你到“Gracejs及前后端分离交流”微信群
  • QQ交流群:368463457 (此群将弃用,可联系添加微信 xiongwilee 拉到微信群)

二、快速开始

注意:请确保你的运行环境中Nodejs的版本至少是v7.6.0 (或者你也可以考虑支持 Nodejs v4.x+ 的koa-grace v1.x

安装

执行命令:

$ git clone https://github.com/xiongwilee/Gracejs.git
$ cd Gracejs && npm install

运行

然后,执行命令:

$ npm run dev

然后访问:http://127.0.0.1:3000 就可以看到示例了!

三、案例说明

这里参考 https://github.com/xiongwilee/Gracejs 中app/demo目录下的示例,详解Gracejs的MVC+RESTful架构的实现。

此前也有文章简单介绍过Gracejs的实现( https://github.com/xiongwilee/Gracejs/wiki ),但考虑到Gracejs的差异性,这里再从目录结构MVC模型实现proxy机制这三个关键点做一些比较详细的说明。

目录结构

Gracejs与koa-grace v1.x版本的目录结构完全一致:

.
├── controller
│   ├── data.js
│   ├── defaultCtrl.js
│   └── home.js
├── static
│   ├── css
│   ├── image
│   └── js
└── views
    └── home.html

其中:

  • controller用以存放路由及控制器文件
  • static用以存放静态文件
  • views用以存放模板文件

注意,这个目录结构是生产环境代码的标准目录结构。在开发环境里你可以任意调整你的目录结构,只要保证编译之后的产出文件以这个路径输出即可

如果你对这一点仍有疑问,可以参考: 前端构建-Boilerplate

MVC模型实现

为了满足更多的使用场景,在Gracejs中加入了简单的Mongo数据库的功能。

但准确的说,前后端的分离的Nodejs框架都是VC架构,并没有Model层。因为前后端分离框架不应该有任何数据库、SESSION存储的职能

mvc

如上图,具体流程如下:

  • 第一步,Nodejs server(也就是Gracejs服务)监听到用户请求;
  • 第二步,Gracejs的各个中间件(Middlewares)对请求上下文进行处理;
  • 第三步,根据当前请求的path和method,进入对应的Controller;
  • 第四步,通过http请求以proxy的模式向后端获取数据;
  • 第五步,拼接数据,渲染模板。

这里的第四步,proxy机制,就是Gracejs实现前后端分离的核心部分。

proxy机制

以实现一个电商应用下的“个人中心”页面为例。假设这个页面的首屏包括:用户基本信息模块、商品及订单模块、消息通知模块。

后端完成服务化架构之后,这三个模块可以解耦,拆分成三个HTTP API接口。这时候就可以通过Gracejs的this.proxy方法,去后端异步并发获取三个接口的数据。

如下图:

proxy

这样有几个好处:

  1. 在Nodejs层(服务端)异步并发向后端(服务端)获取数据,可以使HTTP走内网,性能更优;
  2. 后端的接口可以同时提供给客户端,实现接口给Web+APP复用,后端开发成本更低;
  3. 在Nodejs层获取数据后,直接交给页面,不管前端用什么技术栈,可以使首屏体验更佳。

那么,这么做是不是就完美了呢?肯定不是:

  1. 后端接口在外网开放之后,如何保证接口安全性?
  2. 如果当前页面请求是GET方法,但我想POST到后端怎么办?
  3. 我想在Controller层重置post参数怎么办?
  4. 后端接口设置cookie如何带给浏览器?
  5. 经过一层Nodejs的代理之后,如何保证SESSION状态不丢失?
  6. 如果当前请求是一个file文件流,又该怎么办呢? ...

好消息是,这些问题在proxy中间件中都考虑过了。这里不再一一讲解,有兴趣可以看koa-grace-proxy的源码:https://github.com/xiongwilee/Gracejs/middleware/proxy 。

四、详细使用手册

在看详细使用手册之前,建议先看一下Gracejs的主文件源码:https://github.com/xiongwilee/Gracejs/blob/master/src/app.js 。

这里不再浪费篇幅贴代码了,其实想说明的就是:Gracejs是一个个关键中间件的集合

所有中间件都在middleware目录下,配置由config/main.*.js管理。

关于配置文件:

  1. 配置文件extend的权重为:config/env.json(环境变量) > config/server.json(文件配置) > config/main.*.js > config.js;
  2. 配置生成后保存在Gracejs下的全局作用域global.config里,方便读取。

下面介绍几个关键中间件的作用和使用方法。

vhost——多站点配置

vhost在这里可以理解为,一个Gracejs server服务于几个站点。Gracejs支持通过hosthost+一级path两种方式的映射。所谓的隐射,其实就是一个域名(或者一个域名+一级path)对应一个应用,一个应用对应一个目录。

注意:考虑到正则的性能问题,vhost不会考虑正则映射

参考config/main.development.js,可以这么配置vhost:

// vhost配置
vhost: {
  '127.0.0.1':'demo',
  '127.0.0.1/test':'demo_test',
  'localhost':'blog',
}

其中,demo,demo_test,blog分别对应app/下的三个目录。当然你也可以指定目录路径,在配置文件中修改path.project配置即可:

// 路径相关的配置
path: {
  // project
  project: './app/'
}

router——路由及控制器

Gracejs中生成路由的方法非常简单,以自带的demo模块为例,进入demo模块的controller目录:app/demo/controller

文件目录如下:

controller
├── data.js
├── defaultCtrl.js
└── home.js

1、 文件路径即路由

router中间件会找到模块中所有以.js结尾的文件,根据文件路径和module.exports生成路由。

例如,demo模块中的home.js文件:

exports.index = async function () {
  await this.bindDefault();
  await this.render('home', {
    title: 'Hello , Grace!'
  });
}
exports.hello = function(){
  this.body = 'hello world!'
}

则生成/home/index/home/home/hello的路由。需要说明几点:

  1. 如果路由是以/index结尾的话,Gracejs会"赠送"一个去掉/index的同样路由;
  2. 如果当前文件是一个依赖,仅仅被其他文件引用;则在文件中配置exports.__controller__ = false,该文件就不会生成路由了;参考defaultCtrl.js
  3. 这里的控制器函数可以是await/asyncgenerator函数,也可以是一个普通的函数;Gracejs中推荐使用await/async
  4. 这里的路由文件包裹在一个目录里也是可以的,可以参考:app/blog中的controller文件;
  5. 如果当前文件路由就是一个独立的控制器,则module.exports返回一个任意函数即可。

最后,如果用户访问的路由查找不到,router会默认查找/error/404路由,如果有则渲染error/404页(不会重定向到error/404),如果没有则返回404。

2、 路由文件使用说明

将demo模块中的home.js扩展一下:

exports.index = async function () {
    ...
}
exports.index.__method__ = 'get';
exports.index.__regular__ = null;

另外,需要说明以下几点:

  • 如果需要配置dashboard/post/list请求为DELETE方法,则post.js中声明 exports.list.__method__ = 'delete'即可(不声明默认注入get及post方法);
  • 如果要配置更灵活的路由,则中声明exports.list.__regular__ = '/:id';即可,然后在控制器中通过this.params.id获取ID值,更多相关配置请参看:koa-router#named-routes
  • 需要注意的是:如果__regular__配置为正则表达式的话,则会生成当前控制器默认路由及正则可匹配的路由

当然,如果路由文件中的所有控制器方法都是post方法,您可以在控制器文件最底部加入:module.exports.__method__ = 'post'即可,__regular__的配置同理。

注意:一般情况这里不需要额外的配置,为了保证代码美观,没有特殊使用场景的话就不要写__method____regular__配置。

3、 控制器

将demo模块中的home.js的index方法再扩展一下:

exports.index = async function () {
  // 绑定默认控制器方法
  await this.bindDefault();
  // 获取数据
  await this.proxy(...)
  // 渲染目标引擎
  await this.render('home', {
    title: 'Hello , Grace!'
  });
}

它就是一个标准的控制器(controller)了。这个控制器的作用域就是当前koa的context,你可以任意使用koa的context的任意方法。

几个关键context属性的使用说明如下:

koa自带:

更多koa自带context属性,请查看koajs官网:http://koajs.com/

context属性 | 类型 | 说明 ---------- | ---- | ------------------ this.request.href | String | 当前页面完整URL,也可以简写为this.href this.request.query | object | get参数,也可以简写为this.query this.response.set | function | 设置response头信息,也可以简写为this.set this.cookies.set | function | 设置cookie,参考:cookies this.cookies.get | function | 获取cookie,参考:cookies

Gracejs注入:

context属性 | 类型 | 中间件 | 说明 ---------- | ---- | ----- | ------------------ this.bindDefault | function | router | 公共控制器,相当于require('app/*/controller/defaultCtrl.js') this.defaultCtrlData | object | views | 模板渲染的全局变量,类似于 Koa this.state this.request.body | object | body | post参数,可以直接在this.request.body中获取到post参数 this.render | function | views | 模板引擎渲染方法,请参看: 模板引擎- Template engine this.mongo | function | mongo | 数据库操作方法,请参看: 数据库 - Database this.mongoMap | function | mongo | 并行数据库多操作方法,请参看: 数据库 - Database this.proxy | function | proxy | RESTful数据请求方法,请参看:数据代理 this.fetch | function | proxy | 从服务器导出文件方法,请参看: 请求代理 this.backData | Object | proxy | 默认以Obejct格式存储this.proxy后端返回的JSON数据 this.upload | function | xload | 文件上传方法,请参看: 文件上传下载 this.download | function | xload | 文件下载方法,请参看: 文件上传下载

4、控制器中异步函数的写法

在控制器中,如果还有其他的异步方法,可以通过Promise来实现。例如:

exports.main = async function() {
  await ((test) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => { resolve(test) }, 3000)
    });
  })('测试')
}

proxy——数据代理

Gracejs支持两种数据代理场景:

  1. 单纯的数据代理,任意请求到后端接口,然后返回json数据(也包括文件流请求到后端,后端返回json数据);
  2. 文件代理,请求后端接口,返回一个文件(例如验证码图片);

下面逐一介绍两种代理模式的使用方法。

1、 数据代理

数据代理可以在控制器中使用this.proxy方法:

this.proxy(object|string,[opt])
使用方法

this.proxy 方法返回的是一个Promise,所以这里你可以根据当前Controller的类型使用async/await或者Generator实现异步并发。例如:

async/await:

exports.demo = async function () {
  await this.proxy({ /* ... */ })
}

Generator:

exports.demo = function * () {
  yield this.proxy({ /* ... */ })
}

为了使语法更简便,可以在执行this.proxy之后,直接在上下文中的backData字段中获取到数据。例如:

exports.demo = async function () {
  await this.proxy({
    userInfo:'github:post:user/login/oauth/access_token?client_id=****',
    otherInfo:'github:other/info?test=test',
  })
  
  console.log(this.backData);
  /**
   *  {
   *    userInfo : {...},
   *    otherInfo : {...}
   *  }
   */
}

Generator方法亦然。

此外,如果要获取proxy的请求头信息,你可以在proxy方法返回的内容中获取到,例如:

exports.demo = async function (){
  let res = await this.proxy({
    userInfo:'github:post:user/login/oauth/access_token?client_id=****',
    otherInfo:'github:other/info?test=test',
  });
  
  console.log(res);
  /**
   *  {
   *    userInfo : {
   *      statusCode: {...} // 返回http status code
   *      request: {...}    // 请求体
   *      headers: {...}    // 响应头信息
   *      body: {...}       // 未处理的response body
   *    },
   *    otherInfo : {...}
   *  }
   */
}
使用场景一:多个数据请求的代理

可以发现,上文的案例就是多个数据同时请求的代理方案,这里也就是异步并发获取数据的实现。使用this.proxy方法实现多个数据异步并发请求非常简单:

exports.demo = async function (){
  await this.proxy({
    userInfo:'github:post:user/login/oauth/access_token?cli

Related Skills

View on GitHub
GitHub Stars1.4k
CategoryDevelopment
Updated10d ago
Forks237

Languages

JavaScript

Security Score

100/100

Audited on Mar 10, 2026

No findings