Egg.js 为企业级框架和应用而生,帮助开发团队和开发人员下降开发和维护成本。javascript
专一于提供 Web 开发的核心功能和一套灵活可扩展的插件机制,不会作出技术选型,由于固定的技术选型会使框架的扩展性变差,没法知足各类定制需求。php
Egg 的插件机制有很高的可扩展性,一个插件只作一件事,Egg 经过框架聚合这些插件,并根据本身的业务场景定制配置,这样应用的开发成本就变得很低。css
Egg 奉行『约定优于配置』,按照一套统一的约定进行应用开发,Egg 有很高的扩展性,能够按照团队的约定定制框架。使用 Loader 可让框架根据不一样环境定义默认配置,还能够覆盖 Egg 的默认约定。html
Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。
学习指南:koa_笔记java
和 Express 只有 Request 和 Response 两个对象不一样,Koa 增长了一个 Context 的对象,做为此次请求的上下文对象(在 Koa 1 中为中间件的 this
,在 Koa 2 中做为中间件的第一个参数传入)。咱们能够将一次请求相关的上下文都挂载到这个对象上。(在后续任何一个地方进行其余调用都须要用到)的属性就能够挂载上去。相较于 request 和 response 而言更加符合语义。ios
同时 Context 上也挂载了 Request 和 Response 两个对象。和 Express 相似,这两个对象都提供了大量的便捷方法辅助开发,例如git
get request.query
get request.hostname
set response.body
set response.status
如上述,Koa 是一个很是优秀的框架,然而对于企业级应用来讲,它还比较基础。github
而 Egg 选择了 Koa 做为其基础框架,在它的模型基础上,进一步对它进行了一些加强。web
众所周知,在 Express 和 Koa 中,常常会引入许许多多的中间件来提供各类各样的功能,而 Egg 提供了一个更增强大的插件机制,让这些独立领域的功能模块能够更加容易编写。npm
一个插件能够包含
一个独立领域下的插件实现,能够在代码维护性很是高的状况下实现很是完善的功能,而插件也支持配置各个环境下的默认(最佳)配置,让咱们使用插件的时候几乎能够不须要修改配置项。
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 scripts
到 package.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
注意:
class
和 exports
两种编写方式,本文示范的是前者。exports不推荐使用是为了兼容,能够自行到官方文档查看module.exports
和 exports
的写法。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 便可看到渲染后的页面。
在实际应用中,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.js
, config.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