Egg.js

简介

Egg.js 为企业级框架和应用而生,帮助开发团队和开发人员降低开发和维护成本。

专注于提供 Web 开发的核心功能和一套灵活可扩展的插件机制,不会做出技术选型,因为固定的技术选型会使框架的扩展性变差,无法满足各种定制需求。

Egg 的插件机制有很高的可扩展性,一个插件只做一件事,Egg 通过框架聚合这些插件,并根据自己的业务场景定制配置,这样应用的开发成本就变得很低。

Egg 奉行『约定优于配置』,按照一套统一的约定进行应用开发,Egg 有很高的扩展性,可以按照团队的约定定制框架。使用 Loader 可以让框架根据不同环境定义默认配置,还可以覆盖 Egg 的默认约定。

特性

  • 提供基于 Egg 定制上层框架的能力
  • 高度可扩展的插件机制
  • 内置多进程管理
  • 基于 Koa 开发,性能优异
  • 框架稳定,测试覆盖率高
  • 渐进式开发(根据设备的支持情况来提供更多功能,提供离线能力,推送通知,甚至原生应用的外观和速度,以及对资源进行本地缓存。)

Egg.js 与 Koa

Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。

学习指南:koa_笔记

Context

和 Express 只有 Request 和 Response 两个对象不同,Koa 增加了一个 Context 的对象,作为这次请求的上下文对象(在 Koa 1 中为中间件的 this,在 Koa 2 中作为中间件的第一个参数传入)。我们可以将一次请求相关的上下文都挂载到这个对象上。(在后续任何一个地方进行其他调用都需要用到)的属性就可以挂载上去。相较于 request 和 response 而言更加符合语义。

同时 Context 上也挂载了 Request 和 Response 两个对象。和 Express 类似,这两个对象都提供了大量的便捷方法辅助开发,例如

  • get request.query
  • get request.hostname
  • set response.body
  • set response.status

Egg 继承于 Koa

如上述,Koa 是一个非常优秀的框架,然而对于企业级应用来说,它还比较基础。

而 Egg 选择了 Koa 作为其基础框架,在它的模型基础上,进一步对它进行了一些增强。

插件

众所周知,在 Express 和 Koa 中,经常会引入许许多多的中间件来提供各种各样的功能,而 Egg 提供了一个更加强大的插件机制,让这些独立领域的功能模块可以更加容易编写。

一个插件可以包含

  • extend:扩展基础对象的上下文,提供各种工具类、属性。
  • middleware:增加一个或多个中间件,提供请求的前置、后置处理逻辑。
  • config:配置各个环境下插件自身的默认配置项。

一个独立领域下的插件实现,可以在代码维护性非常高的情况下实现非常完善的功能,而插件也支持配置各个环境下的默认(最佳)配置,让我们使用插件的时候几乎可以不需要修改配置项。

Egg 与 Koa 的版本关系

Egg 1.x

  • 应用开发者可以选择 async function(Node.js 8.x+) 或者 generator function(Node.js 6.x+)进行编写。

Egg 2.x

  • 只支持 Node.js 8 及以上的版本。

快速入门

环境准备

  • 操作系统:支持 macOS,Linux,Windows
  • 运行环境:建议选择Node.js稳定版本,最低要求 8.x。

快速初始化

npm i -g egg-init
egg-init egg-demo --type=simple //--type=simple可以去掉然后自己配置
cd egg-demo
npm i

启动项目:

npm run dev
浏览器打开:localhost:7001

逐步搭建

通常你可以通过上面的方式,快速选择适合对应业务模型的脚手架,快速启动 Egg.js 项目的开发。

现在我们需要自己手动一步步的搭建一个项目。

注意:实际项目中,我们推荐使用上一节的脚手架直接初始化。

初始化项目

先来初始化下目录结构:

mkdir egg-example
cd egg-example
npm init
npm i egg --save
npm i egg-bin --save-dev

添加 npm scriptspackage.json

{
"name": "egg-example",
"scripts": {
"dev": "egg-bin dev"//npm run dev
}
}

编写控制器

// ./app/controller/home.js
const Controller = require('egg').Controller;
class HomeController extends Controller {
async index() {
this.ctx.body = 'Hello world';//这个内容就可以显示在body上面
}
}
module.exports = HomeController;//把我创建的这个类默认暴露出去

配置路由:

// ./app/router.js
module.exports = app => {//app参数里面包含了很多东西
const { router, controller } = app;//我们从中结构出controller文件夹中的内容
router.get('/', controller.home.index);//会找到home.js中默认暴露的类的index方法
};

加一个配置文件:

// ./config/config.default.js
exports.keys = '此处改为你自己的 Cookie 安全字符串';//自定义例如'abc1234'必须填写

现在可以启动应用来体验下

npm run dev
打开浏览器:localhost:7001

注意:

  • Controller 有 classexports 两种编写方式,本文示范的是前者。exports不推荐使用是为了兼容,可以自行到官方文档查看
  • Config 也有 module.exportsexports 的写法。
  • 开发期默认开启了 development 插件,修改后端代码后,会自动重启,无需再次npm run dev

扩展

在基于 Egg 的框架或者应用中,我们可以通过定义 app/extend/{application,context,request,response}.js

这里表示可以创建application,context,request,response四个js文件

来扩展 Koa 中对应的四个对象的原型,通过这个功能,我们可以快速的增加更多的辅助方法,例如我们在 app/extend/context.js 中写入下列代码:

// ./app/extend/context.js
module.exports = {
get isIOS() {//get表示通过这个isIOS得到什么记得添加
const iosReg = /iphone|ipad|ipod/i;//正则
return iosReg.test(this.get('user-agent'));
//User Agent显示使用的浏览器类型及版本、操作系统及版本、浏览器内核、等信息的标识。
},
};

在 Controller 中,我们就可以使用到刚才定义的这个便捷属性了:

// ./app/controller/home.js
const Controller = require('egg').Controller;//从egg上引入控制器
class HomeController extends Controller {//声明一个类并从constroller继承
async index() {//声明一个函数
this.ctx.body = this.ctx.isIOS
? '你的操作系统是IOS.'
: '你的操作系统不是IOS.';
}
}
module.exports = HomeController;//把这个类默认暴露出去

静态资源

Egg 内置了static插件static 插件默认映射 app/public/ 目录,我们把静态资源都放到 app/public 目录即可:

模板渲染

框架并不强制你使用某种模板引擎,只是约定了 View 插件开发规范,开发者可以引入不同的插件来实现差异化定制。

更多用法参见 View,在本例中,我们使用 Nunjucks 来渲染,先安装对应的插件 egg-view-nunjucks

npm i egg-view-nunjucks --save

开启插件:

// ./config/plugin.js
exports.nunjucks = {
enable: true,//使用
package: 'egg-view-nunjucks'//使用什么插件
};
// ./config/config.default.js
exports.keys = '此处改为你自己的 Cookie 安全字符串';
// 添加 view 配置
exports.view = {
defaultViewEngine: 'nunjucks',//默认视图引擎
mapping: {//.tpl结尾的文件
'.tpl': 'nunjucks',
},
};

为列表页编写模板文件,一般放置在 ./app/view 目录下

<!-- ./app/view/news/list.tpl -->
<!-- {% %} 来当做模板,与现有的html标记混用。和php干的事情是一样的。-->
<html>
<head>
<title>Hacker News</title>
<link rel="stylesheet" href="/public/css/news.css" />
</head>
<body>
<ul class="news-view view">
{% for item in list %}<!-- 这里可以直接拿到dataList中的list但无法拿到dataList -->
<li class="item">
<a href="{{ item.url }}">{{ item.title }}</a>
</li>
{% endfor %}<!-- 结束for循环 -->
</ul>
</body>
</html>

添加 Controller 和 Router

// ./app/controller/news.js
const Controller = require('egg').Controller;
class NewsController extends Controller {
async list() {
const dataList = {
list: [
{ id: 1, title: 'this is news 1', url: '/news/1' },
{ id: 2, title: 'this is news 2', url: '/news/2' }
]
};
await this.ctx.render('news/list.tpl', dataList);
//渲染list.tpl文件且把dataList的内容传过去
}
} module.exports = NewsController;
// ./app/router.js
module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
router.get('/news', controller.news.list);
};

启动浏览器,访问 http://localhost:7001/news 即可看到渲染后的页面。

编写 service

在实际应用中,Controller 一般不会自己产出数据,也不会包含复杂的逻辑,复杂的过程应该交给业务逻辑层 Service

我们来添加一个 Service 抓取页面的数据 ,如下:这里只是demo写法,根据实际情况再改变

// ./app/service/news.js
const Service = require('egg').Service;
class NewsService extends Service {
async list(page) {//得到传来的页码参数
// 读取配置拿到API接口
const { serverUrl } = this.config.news; //使用内置的curl发出请求拿回数据
//结构出来result,在这里给他改名叫做idList
const { result: idList } = await this.ctx.curl(`${serverUrl}/topstories.json`, {
data: {//携带信息
orderBy: '"$key"',//根据什么排序
startAt: `${page}`,//起始页
endAt: `${page+1}`,//结束页
},
dataType: 'json',//需要返回的数据类型
}); // 获取详细信息
const newsList = await Promise.all(
//获取对象的所有键名
Object.keys(idList).map(key => {
const url = `${serverUrl}/item/${idList[key]}.json`;
return this.ctx.curl(url, { dataType: 'json' });//再去请求每一个的详细信息
})
);
return newsList.map(res => res.data);//把每一条的数据的data返回出去。
}
}
module.exports = NewsService;

框架提供了内置的 HttpClient 来方便开发者使用 HTTP 请求。

this.ctx.curl(api地址:string,配置:json)

然后稍微修改下之前的 Controller:

// ./app/controller/news.js
const Controller = require('egg').Controller; class NewsController extends Controller {
async list() {
const ctx = this.ctx;
const page = ctx.query.page || 1;//获取用户url里面的page值没有就返回1
const newsList = await ctx.service.news.list(page);//传过去,声明newsList接受return回来的数据
await ctx.render('news/list.tpl', { list: newsList });//把list传过去,他的数据是newsList
}
}
module.exports = NewsController;

还需增加 app/service/news.js 中读取到的配置:

// ./config/config.default.js
// 添加 news 的配置项
exports.news = {
serverUrl: 'API接口地址'
};

编写扩展

如果时间的数据是 UnixTime 格式的,我们希望显示为便于阅读的格式。框架提供了一种快速扩展的方式,只需在 app/extend 目录下提供扩展脚本即可,具体参见扩展。在这里,我们可以使用 View 插件支持的 Helper 来实现:

npm i moment --save

// ./app/extend/helper.js
const moment = require('moment');
exports.relativeTime = time => moment(new Date(time * 1000)).fromNow();

在模板里面使用:

<!-- ./app/view/news/list.tpl -->
<html>
<head>
<title>Hacker News</title>
<link rel="stylesheet" href="/public/css/news.css" />
</head>
<body>
<ul class="news-view view">
{% for item in list %}
<li class="item">
<a href="{{ item.url }}">{{ item.title }}</a>
</li>
{% endfor %}
{{ helper.relativeTime(item.time) }}<!--通过这样的方法使用 -->
</ul>
</body>
</html>

编写中间件

假设我们的新闻站点,禁止百度爬虫访问。可以通过 Middleware 判断 User-Agent,如下:

// ./app/middleware/robot.js
// options === app.config.robot //这里的robot是这个文件的名字
module.exports = (options, app) => {
return async function robotMiddleware(ctx, next) {
const source = ctx.get('user-agent') || '';
const match = options.ua.some(ua => ua.test(source));
//some()会让ua数组中的每一项去执行()里面的函数,如果有一个元素满足条件,则表达式返回true , 剩余的元素不会再执行检测。
if (match) {
ctx.status = 403;
ctx.message = 'Go away, robot.';//别用中文
} else {
await next();//放行
}
}
};
// ./config/config.default.js
// 添加中间件
exports.middleware = [
'robot'
];
// 添加配置
exports.robot = {
ua: [
/Baiduspider/i,
]
};

现在可以使用 curl http://localhost:7001/news -A "Baiduspider" 看看效果。

如果你是window用户在CMD下是无法执行此命令的,推荐你安装Git Bash运行此命令

配置文件

写业务的时候,不可避免的需要有配置文件,框架提供了强大的配置合并管理功能:

  • 支持按环境变量加载不同的配置文件,如 config.local.jsconfig.prod.js 等等。
  • 应用/插件/框架都可以配置自己的配置文件,框架将按顺序合并加载。
  • 具体合并逻辑可参见配置文件
// ./config/config.default.js
// 这里是默认值
exports.robot = {
ua: [
/curl/i,
/Baiduspider/i,
],
};
// ./config/config.local.js
// 仅在开发模式下读取,将覆盖默认值
exports.robot = {
ua: [
/Baiduspider/i,
],
};
// ./app/service/some.js
const Service = require('egg').Service; class SomeService extends Service {
async list() {
const rule = this.config.robot.ua;// /Baiduspider/i,
}
}
module.exports = SomeService;

单元测试

单元测试非常重要,框架也提供了 egg-bin 来帮开发者无痛的编写测试。

测试文件应该放在项目根目录下的 test 目录下,并以 test.js 为后缀名,即 ./test/**/*.test.js

**表示任何文件夹

*表示任何文件

npm i egg-mock --save-dev

然后配置依赖和 npm scripts配置:

{
"scripts": {
"test": "egg-bin test",
}
}
// ./test/app/middleware/robot.test.js
const { app, mock, assert } = require('egg-mock/bootstrap');
//他会去找到./app/middleware/robot.js进行测试
describe('test/app/middleware/robot.test.js', () => {
it('阻止机器人爬虫', () => {
return app.httpRequest()
.get('/')
.set('User-Agent', "Baiduspider")
.expect(403);
});
});

执行测试:npm test

EggJs快速入门的更多相关文章

  1. Web Api 入门实战 (快速入门+工具使用+不依赖IIS)

    平台之大势何人能挡? 带着你的Net飞奔吧!:http://www.cnblogs.com/dunitian/p/4822808.html 屁话我也就不多说了,什么简介的也省了,直接简单概括+demo ...

  2. SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=》提升)

     SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=>提升,5个Demo贯彻全篇,感兴趣的玩才是真的学) 官方demo:http://www.asp.net/si ...

  3. 前端开发小白必学技能—非关系数据库又像关系数据库的MongoDB快速入门命令(2)

    今天给大家道个歉,没有及时更新MongoDB快速入门的下篇,最近有点小忙,在此向博友们致歉.下面我将简单地说一下mongdb的一些基本命令以及我们日常开发过程中的一些问题.mongodb可以为我们提供 ...

  4. 【第三篇】ASP.NET MVC快速入门之安全策略(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

  5. 【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

  6. Mybatis框架 的快速入门

    MyBatis 简介 什么是 MyBatis? MyBatis 是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架.MyBatis 消除 了几乎所有的 JDBC 代码和参数的手工设置以及结果 ...

  7. grunt快速入门

    快速入门 Grunt和 Grunt 插件是通过 npm 安装并管理的,npm是 Node.js 的包管理器. Grunt 0.4.x 必须配合Node.js >= 0.8.0版本使用.:奇数版本 ...

  8. 【第一篇】ASP.NET MVC快速入门之数据库操作(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

  9. 【第四篇】ASP.NET MVC快速入门之完整示例(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

随机推荐

  1. java基础 - 形参和实参,值传递和引用传递

    形参和实参 形参:就是形式参数,用于定义方法的时候使用的参数,是用来接收调用者传递的参数的. 形参只有在方法被调用的时候,虚拟机才会分配内存单元,在方法调用结束之后便会释放所分配的内存单元. 因此,形 ...

  2. 浅谈python中selenium库调动webdriver驱动浏览器的实现原理

    最近学web自动化时用到selenium库,感觉很神奇,遂琢磨了一下,写了点心得. 当我们输入以下三行代码并执行时,会发现新打开了一个浏览器窗口并访问了百度首页,然而这是怎么做到的呢? from se ...

  3. 【使用篇二】SpringBoot整合SpringDataJPA(18)

    一.pom.xml添加依赖 <dependencies> <!--web--> <dependency> <groupId>org.springfram ...

  4. 【Unity游戏开发】接入UWA_GOT的iOS版SDK以后无法正常出包

    一.正文 问: RT,最近有看到UWA_GOT工具新增了iOS版本的支持,于是下载了最新的工具包进行了接入测试.是按照文档直接将UWA_GOTv2.0.1_iOS.unitypackage导入进了Un ...

  5. PalletOne调色板跨链的BTC实现

    之前已经讲到了PalletOne调色板跨链以太坊ETH和ERC20的技术原理,接下来我们来讲解PalletOne跨链比特币BTC的技术原理. 一.BTC充币 假如用户A持有一定数量的比特币BTC,他希 ...

  6. eclipse的一些常用快捷键

    掌握了eclipse快捷键功能,能够大大提高开发效率. 这里总结一些eclipse的常用快捷键. 编辑相关快捷键  1. [ALT+/]:此快捷键为用户编辑的好帮手,能为用户提供内容的辅助,不要为记不 ...

  7. javascript ES6 新特性之 解构

    解构的作用是可以快速取得数组或对象当中的元素或属性,而无需使用arr[x]或者obj[key]等传统方式进行赋值 var arr = [1, 2, 3]; //传统方式 var a = arr[0], ...

  8. [算法]实现strStr()

    题目 实现 strStr() 函数. 给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始).如果不存在 ...

  9. 【nodejs原理&源码赏析(6)】深度剖析cluster模块源码与node.js多进程(下)

    目录 一. 引言 二.server.listen方法 三.cluster._getServer( )方法 四.跨进程通讯工具方法Utils 五.act:queryServer消息 六.轮询调度Roun ...

  10. MySQL逻辑控制语句的使用

    一.IF语句     1).   IF(expr1,expr2,expr3) 如果expr1为true则结果为expr2否则为expr3 -->相当于三元运算符                  ...