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