Eggjs小试

2018-06-15 admin

前言

这段时间,用Eggjs作为后端服务框架开发了几个项目。项目都很小,但为了进一步了解Eggjs,特意选择了Eggjs作为框架基础开发后端服务。期间也遇到过一些问题和坑,还有几个值得注意的点,下面来讲一下我这段时间开发的总结。


Egg.js 为企业级框架和应用而生 ,我们希望由 Egg.js 孕育出更多上层框架,帮助开发团队和开发人员降低开发和维护成本。

这个是Eggjs文档对_Eggjs_的解释,关于_Eggjs_的详细介绍和使用请点解前面的地址;相对于Egg.js 1.x版本的文档,已经有很大的改进了,很多关键的地方都可以比较完整讲解和带有代表性的实例。

步骤

开始

用的Egg.js版本是2.2.1,对环境有一定的要求,本人用的配置如下:

使用脚手架快速创建项目:

$ npm i egg-init -g
$ egg-init egg-example --type=simple
$ cd egg-example
$ npm i

项目安装完毕,启动项目:

$ npm run dev
$ open localhost:7001

至此,项目顺利建立及启动完毕。

项目结构:(摘自文档)

egg-project
├── package.json
├── app.js (可选)
├── agent.js (可选)
├── app
|   ├── router.js
│   ├── controller
│   |   └── home.js
│   ├── service (可选)
│   |   └── user.js
│   ├── middleware (可选)
│   |   └── response_time.js
│   ├── schedule (可选)
│   |   └── my_task.js
│   ├── public (可选)
│   |   └── reset.css
│   ├── view (可选)
│   |   └── home.tpl
│   └── extend (可选)
│       ├── helper.js (可选)
│       ├── request.js (可选)
│       ├── response.js (可选)
│       ├── context.js (可选)
│       ├── application.js (可选)
│       └── agent.js (可选)
├── config
|   ├── plugin.js
|   ├── config.default.js
│   ├── config.prod.js
|   ├── config.test.js (可选)
|   ├── config.local.js (可选)
|   └── config.unittest.js (可选)
└── test
    ├── middleware
    |   └── response_time.test.js
    └── controller
        └── home.test.js

上述目录也是一个给开发者一个目录创建的指南,但按照文档建立的项目目录结构没有那么全,基本上标注为“可选”的都是初始没有的,在/config目录里也只有plugin.jsconfig.default.js两个文件,其他文件要自己根据需求创建。

建立控制器Controller

初始项目里会有一个示例Controller,在创建一个新的Controller可以参考/app/controller/home.js的示例,一般而言,推荐使用module.exports暴露出一个类或者参数为app返回一个类的函数(文档示例中为箭头函数,其他方式没试过不清楚),类里面包含着这块业务的一些操作,下面在控制器文件目录/app/controller/里新建一个文件名为user.js的控制器文件:

// 继承egg的控制器
const Controller = require('egg').Controller;
class UserController extends Controller {
  async index() {
    const { ctx } = this;
    const { name } = ctx.request.body;
    ctx.body = `hi, ${name}`;
  }
  async getUserById() {
     const { userId } = this.ctx.request.body;
     // 使用业务函数查询用户信息
     const userInfo = await this.service.user.findById(userId);
     this.ctx.body = {
         msgCode: 0,
         message: '成功',
         data: userInfo
     };
  }
}
// 注意:一定要将控制器暴露出去,否则请求的时候会报找不到该controller的错误;
module.exports = UserController;

添加路由

路由代码在/app目录之下,文件名router.js,添加路由的代码如下:

// 参数app为全局应用的对象
module.exports = app => {
    const { router, controller, middleware } = app;
    // 在这里controller相当于app下的controller文件目录,user为user.js,index为控制器类的index方法
    router.get('/', controller.user.index); 
};

编写业务

通常,controller主要处理数据的结构和处理返回的结果,具体的涉及的业务由service业务类方法完成,编写service,在目录/app/service/下建立user.js文件,并编写代码:

// 同样要继承egg的Service类
const Service = require('egg').Service;
class UserService extends Service {
    // 根据用户id查找用户
    async findUserById(id) {
        const mysql = this.app.mysql;
        const result = await mysql.get('users', { id });
        return result;
    }
}
module.exports = UserService;

添加插件

eggjs simple 版本旨在根据业务需求添加eggjs的插件来搭建上层框架。在本人开发过程中,用到的一些插件做简要说明。

插件安装:

$ npm i --save egg-pluginName

在文件/config/plugin.js添加配置:

exports.pluginName = {
  enable: true,
  package: 'egg-pluginName',
};

需要插件初始化配置的情况下,修改/config/config.default.js

config.pluginName = {
    // 配置项
};

egg-mysql

因使用mysql数据库,需要一个nodejsmysql的操作库,基于eggjs选择了egg-mysql ,操作文档点击这里。基本的数据库增删查改都能操作,写起来还挺方便,但是有个需求,编写某个接口,返回当前用户某一段时间的数据,这就比较蛋疼了,找了很久,就连egg-mysql封装的库ali-rds查看了源码也找不到这类方法,无奈之下,只能通过组织原生的mysql查询语句去动态拼凑,虽然不推荐,不过如果找到更好的方法,还是愿意改写的。

查询指定日期数据的mysql相关参考资料:

egg-redis

redis相当于基于内存的一个微型数据库,其存取速度非常快,代码执行的时候几乎感觉不到阻塞,这里使用egg-redis作为项目对redis的操作库,文档点击这里。文档说明解析了基本的存取和设置操作,对于较为复杂的操作只能通过查看redis官方文档,对应的命令小写即为方法名: redis命令DECR,对应的方法使用:

// /app/controller/user.js
const value = await this.app.redis.decr(keyName);

扩展内置对象

添加过几个插件之后,发现其中的源代码都是以扩展内置对象的方式去挂载相关的库或者插件的。

ctx

文档原文:“一般来说属性的计算在同一次请求中只需要进行一次,那么一定要实现缓存,否则在同一次请求中多次访问属性时会计算多次,这样会降低应用性能。

推荐的方式是使用 Symbol + Getter 的模式。”

const jwt = require('jsonwebtoken');
const JWT = Symbol('Context#jwt');
module.exports = {
  get jwt() {
    if (!this[JWT]) {
      this[JWT] = jwt;
    }
    return this[JWT];
  }
};

第一次扩展cxt对象的时候,不明白为何要使用Symbol + Getter的模式,后来基于这个问题,查找资料,发现这种方式更能避免和其他属性名发生冲突,上述代码中,ctxjwt定义为只读方式。在方便维护的同时,生成一个带有命名空间(Context#jwt)字符串描述的Symbol实例数据, 作为ctx的属性,通过只读属性jwt来获取内部的JWT属性。 PS:

ctx.JWT == ctx[JWT] // false

关于Symbol的介绍和使用请参考阮一峰ES6

app

在扩展app对象的时候,遇到个问题,就是如果需要获取ctx怎么办? 查找文档,找到了在扩展app对象时,只需要在函数体里添加一句代码:

const ctx = app.createAnonymousContext();

就可以获取ctx对象,这对于使用其他函数提供了一道桥梁。

编写中间件

eggjs的中间件处理流程遵循koa的洋葱式请求模型 中间价的写法:

module.exports = options => {
    return async function middleWareFunctionName (ctx, next) {
        // 控制器之前业务处理代码
        // ...
        await next();
        //控制器之后业务处理代码
        // ...
    }
}

中间件以返回一个处理业务的函数为主体,函数接收两个参数:ctxnextctx则是请求级别的对象,next()方法可以让请求进入下一个步骤。特别注意的是:在一个控制器中,有对请求到达下一步之前做一些操作的,可以控制next()在代码流程中的位置,其后也可以处理请求之后的操作。

制定定时任务

eggjs写定时任务也是非常简单的,关注于业务代码,加以简单的配置,即可使用定时任务。 下面是一个简单的定时统计业务数据的定时任务:

const Subscription = require('egg').Subscription;

class Statistics extends Subscription {
  // 通过 schedule 属性来设置定时任务的执行间隔等配置
  static get schedule() {
    return {
      cron: '00 59 23 * * *', // 秒 分 时 日 月 年
      // interval: '10s', // 设置时间间隔触发,单位s为秒,ms为毫秒
      type: 'worker', // all 指定所有的 worker 都需要执行, worker 为某一个 worker 执行
    };
  }

  // subscribe 是真正定时任务执行时被运行的函数
  async subscribe() {
   // 定时任务业务代码
   // ...
  }
}

module.exports = Statistics;

在程序启动的时候,就会在配置的指定时机执行相关的业务代码。

配置

csrf的讨论

eggjsv2.x版本之后默认开启了csrf插件,已确保基于cookie存储验证信息的网站信息安全。

csrf能将请求限制在同源网站,即只有拥有“专有令牌”的网站发送请求才会正确响应。此处容易与jwt的作用混淆,可以看看这篇文章

跨域

使用jwt验证

jwt则在认证方式上跟csrf上有所不同,jwt可以在不使用cookie的情况下,以token的方式在前后端交互数据的body里传输,也可以在header里设置相关信息,详细可以参考这篇文章

日志

类的写法

远程机开发部署

文档中,有《应用部署》一文,里面介绍的很详细的。使用egg-script插件启动生产环境中的应用程序。项目生产静默部署,启动使用npm start,停止使用npm stop另,在开发环境里想要使用pm2管理进程后台启动,--watch会不断打印控制台日志,原因不清楚。

生产环境部署

启动命令:

$ npm install --production
$ npm start

停止命令:

$ npm stop

总结

优点

使用eggjs开发企业级应用还是相当方便的,虽然说要根据需求装,但安装和配置步骤非常简单,很多有用的业务配置都能够很方便快速配置好,还可以区分环境,项目结构和调用方式很合理。

不足

工具函数的访问需要自己手动添加扩展

没有写测试,希望下次补上。

原文链接:https://segmentfault.com/a/1190000015296295

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-34094.html

文章标题:Eggjs小试

相关文章
在JavaScript的AngularJS库中进行单元测试的方法
开发者们都一致认为单元测试在开发项目中十分有好处。它们帮助你保证代码的质量,从而确保更稳定的研发,即使需要重构时也更有信心。 ** 测试驱动开发流程图** AngularJS的代码声称其较高的可测性确实是合理的。单单文档中列出端对端的测试...
2017-03-24
javascript截取字符串小结
本文总结介绍了三种使用Javascript截取字符串的方法,同时也总结了两种截取中文字符串的办法,分享一下。 1.substring 方法 定义和用法 substring 方法用于提取字符串中介于两个指定下标之间的字符。 语法 string...
2017-03-23
js获取字符串字节数方法小结
本文实例讲述了js获取字符串字节数的方法。分享给大家供大家参考。具体如下: 大家都知道,获取字符串的长度可用length来获取, 那么获取这段字符串的字节数呢? 英文字母肯定lenght和字节数都一样:都是1 而中文lenght=1,字节数...
2017-03-24
七夕情人节丘比特射箭小游戏
载入jQuery <script src="./jquery-1.11.0.min.js" type="text/javascript"></scrip...
2017-03-29
使用console进行性能测试
对于前端开发人员,在开发过程中经常需要监控某些表达式或变量的值,如果使用用 debugger 会显得过于笨重,最常用的方法是会将值输出到控制台上方便调试。 最常用的语句就是console.log(expression)了。 从早前一道阿里实...
2017-03-23
JS实现生成会变大变小的圆环实例
本文实例讲述了JS实现生成会变大变小的圆环。分享给大家供大家参考。具体如下: 这里使用javascript生成圆环,会变大变小,对于研究如何利用JavaScript生成动画效果,这是个很好的范例。 运行效果如下图所示: 具体代码如下: &...
2017-03-29
js代码实现随机颜色的小方块
下面一段代码就是用js实现的随机颜色的小方块,不多说了代码很简单,直接上代码了。 /**/js注释已删 <!DOCTYPE html> <html> <head> <...
2017-03-27
JavaScript获得指定对象大小的方法
本文实例讲述了JavaScript获得指定对象大小的方法。分享给大家供大家参考。具体如下: function objectSize(the_object) { /* function to validate the existe...
2017-03-27
nodejs实现遍历文件夹并统计文件大小
离开公司已经有2个多个月,之前写了不少工具,但在离开公司的当天,我亲手把一年来的所有积累工具和代码都格式化了。今天想起之前在项目中遇到的一个问题,今天将其记录下来。 我在优化内存的时候,遇到一些图片在加载的时候很难loading出现,发现一...
2017-03-24
JavaScript简单修改窗口大小的方法
本文实例讲述了JavaScript简单修改窗口大小的方法。分享给大家供大家参考。具体如下: function resize_window(){ // move the window to 0,0 (X,Y) wi...
2017-03-29
回到顶部