Egg學習記錄(1)

原文鏈接https://eggjs.org/zh-cn/intro/quickstart.html

Application

Application 是全局應用對象蓖乘,在一個應用中筷笨,只會實例化一個哈恰,在它上面我們可以掛載一些全局的方法和對象

事件

  • server: 該事件一個 worker 進程只會觸發(fā)一次散罕,在 HTTP 服務完成啟動后蹭睡,會將 HTTP server 通過這個事件暴露出來給開發(fā)者车海。
  • error: 運行時有任何的異常被 onerror 插件捕獲后碧浊,都會觸發(fā) error 事件涂邀,將錯誤對象和關聯(lián)的上下文(如果有)暴露給開發(fā)者,可以進行自定義的日志記錄上報等處理箱锐。
  • request 和 response: 應用收到請求和響應請求時比勉,分別會觸發(fā) request 和 response 事件,并將當前請求上下文暴露出來驹止,開發(fā)者可以監(jiān)聽這兩個事件來進行日志記錄浩聋。

獲取方式

Application 對象幾乎可以在編寫應用時的任何一個地方獲取到,下面介紹幾個經(jīng)常用到的獲取方式:
幾乎所有被框架 Loader加載的文件(Controller臊恋,Service赡勘,Schedule 等),都可以 export 一個函數(shù)捞镰,這個函數(shù)會被 Loader 調(diào)用闸与,并使用 app 作為參數(shù):
module.exports = app => {
app.cache = new Cache();
};

Context

Context 是一個請求級別的對象,繼承自 Koa.Context岸售。在每一次收到用戶請求時践樱,框架會實例化一個 Context 對象,這個對象封裝了這次用戶請求的信息凸丸,并提供了許多便捷的方法來獲取請求參數(shù)或者設置響應信息拷邢。框架會將所有的 Service 掛載到 Context 實例上屎慢,一些插件也會將一些其他的方法和對象掛載到它上面(egg-sequelize 會將所有的 model 掛載在 Context 上)瞭稼。

獲取方式

最常見的 Context 實例獲取方式是在 Middleware, Controller 以及 Service 中。Controller 中的獲取方式在上面的例子中已經(jīng)展示過了腻惠,在 Service 中獲取和 Controller 中獲取的方式一樣环肘,在 Middleware 中獲取 Context 實例則和 Koa 框架在中間件中獲取 Context 對象的方式一致。

框架的 Middleware 同時支持 Koa v1 和 Koa v2 兩種不同的中間件寫法集灌,根據(jù)不同的寫法悔雹,獲取 Context 實例的方式也稍有不同:

<pre style="box-sizing: border-box; font: 13.6px/1.45 Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace; margin-top: 0px; margin-bottom: 0px; overflow-wrap: normal; padding: 16px; overflow: auto; background-color: rgb(248, 248, 248); border-radius: 3px; word-break: normal;">// Koa v1
function* middleware(next) {
 // this is instance of Context
 console.log(this.query);
 yield next;
}
// Koa v2
async function middleware(ctx, next) {
 // ctx is instance of Context
 console.log(ctx.query);
}</pre>

除了在請求時可以獲取 Context 實例之外, 在有些非用戶請求的場景下我們需要訪問 service / model 等 Context 實例上的對象欣喧,我們可以通過 Application.createAnonymousContext() 方法創(chuàng)建一個匿名 Context 實例:

// app.js
module.exports = app => {
  app.beforeStart(async () => {
    const ctx = app.createAnonymousContext();
    // preload before app start
    await ctx.service.posts.load();
  });
}

定時任務中的每一個 task 都接受一個 Context 實例作為參數(shù)腌零,以便我們更方便的執(zhí)行一些定時的業(yè)務邏輯:


<pre style="box-sizing: border-box; font: 13.6px/1.45 Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace; margin-top: 0px; margin-bottom: 0px; overflow-wrap: normal; padding: 16px; overflow: auto; background-color: rgb(248, 248, 248); border-radius: 3px; word-break: normal;">// app/schedule/refresh.js
exports.task = async ctx => {
 await ctx.service.posts.refresh();
};</pre>

Request & Response

Request 是一個請求級別的對象,繼承自 Koa.Request唆阿。封裝了 Node.js 原生的 HTTP Request 對象益涧,提供了一系列輔助方法獲取 HTTP 請求常用參數(shù)。

Response 是一個請求級別的對象驯鳖,繼承自 Koa.Response闲询。封裝了 Node.js 原生的 HTTP Response 對象久免,提供了一系列輔助方法設置 HTTP 響應。

獲取方式

可以在 Context 的實例上獲取到當前請求的 Request(ctx.request) 和 Response(ctx.response) 實例嘹裂。


<pre style="box-sizing: border-box; font: 13.6px/1.45 Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace; margin-top: 0px; margin-bottom: 0px; overflow-wrap: normal; padding: 16px; overflow: auto; background-color: rgb(248, 248, 248); border-radius: 3px; word-break: normal;">// app/controller/user.js
class UserController extends Controller {
 async fetch() {
 const { app, ctx } = this;
 const id = ctx.request.query.id;
 ctx.response.body = app.cache.get(id);
 }
}
</pre>

  • Koa 會在 Context 上代理一部分 Request 和 Response 上的方法和屬性,參見 Koa.Context摔握。
  • 如上面例子中的 ctx.request.query.idctx.query.id 是等價的寄狼,ctx.response.body=ctx.body= 是等價的。
  • 需要注意的是氨淌,獲取 POST 的 body 應該使用 ctx.request.body泊愧,而不是 ctx.body

Controller

框架提供了一個 Controller 基類盛正,并推薦所有的 Controller 都繼承于該基類實現(xiàn)删咱。這個 Controller 基類有下列屬性:

  • ctx - 當前請求的 Context 實例。
  • app - 應用的 Application 實例豪筝。
  • config - 應用的配置痰滋。
  • service - 應用所有的 service
  • logger - 為當前 controller 封裝的 logger 對象续崖。

在 Controller 文件中敲街,可以通過兩種方式來引用 Controller 基類:


<pre style="box-sizing: border-box; font: 13.6px/1.45 Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace; margin-top: 0px; margin-bottom: 0px; overflow-wrap: normal; padding: 16px; overflow: auto; background-color: rgb(248, 248, 248); border-radius: 3px; word-break: normal;">// app/controller/user.js

// 從 egg 上獲取(推薦)
const Controller = require('egg').Controller;
class UserController extends Controller {
 // implement
}
module.exports = UserController;

// 從 app 實例上獲取
module.exports = app => {
 return class UserController extends app.Controller {
 // implement
 };
};</pre>

Middleware

Koa 的中間件和 Express 不同严望,Koa 選擇了洋蔥圈模型多艇。

  • 中間件洋蔥圖:

image

前面的章節(jié)中,我們介紹了 Egg 是基于 Koa 實現(xiàn)的像吻,所以 Egg 的中間件形式和 Koa 的中間件形式是一樣的峻黍,都是基于洋蔥圈模型。每次我們編寫一個中間件拨匆,就相當于在洋蔥外面包了一層姆涩。
image

配置

一般來說中間件也會有自己的配置。在框架中惭每,一個完整的中間件是包含了配置處理的阵面。我們約定一個中間件是一個放置在 app/middleware 目錄下的單獨文件,它需要 exports 一個普通的 function洪鸭,接受兩個參數(shù):

options: 中間件的配置項样刷,框架會將 app.config[${middlewareName}] 傳遞進來。
app: 當前應用 Application 的實例览爵。

在應用中使用中間件

在應用中置鼻,我們可以完全通過配置來加載自定義的中間件,并決定它們的順序蜓竹。

如果我們需要加載上面的 gzip 中間件箕母,在 config.default.js 中加入下面的配置就完成了中間件的開啟和配置:

module.exports = {
  // 配置需要的中間件储藐,數(shù)組順序即為中間件的加載順序
  middleware: [ 'gzip' ],

  // 配置 gzip 中間件的配置
  gzip: {
    threshold: 1024, // 小于 1k 的響應體不壓縮
  },
};

router 中使用中間件

以上兩種方式配置的中間件是全局的,會處理每一次請求嘶是。 如果你只想針對單個路由生效钙勃,可以直接在 app/router.js 中實例化和掛載,如下:

<pre style="box-sizing: border-box; font: 13.6px/1.45 Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace; margin-top: 0px; margin-bottom: 0px; overflow-wrap: normal; padding: 16px; overflow: auto; background-color: rgb(248, 248, 248); border-radius: 3px; word-break: normal;">module.exports = app => {
 const gzip = app.middleware.gzip({ threshold: 1024 });
 app.router.get('/needgzip', gzip, app.controller.handler);
};
</pre>

框架默認中間件

除了應用層加載中間件之外聂喇,框架自身和其他的插件也會加載許多中間件辖源。所有的這些自帶中間件的配置項都通過在配置中修改中間件同名配置項進行修改,例如框架自帶的中間件中有一個 bodyParser 中間件(框架的加載器會將文件名中的各種分隔符都修改成駝峰形式的變量名)希太,我們想要修改 bodyParser 的配置克饶,只需要在 config/config.default.js 中編寫


<pre style="box-sizing: border-box; font: 13.6px/1.45 Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace; margin-top: 0px; margin-bottom: 0px; overflow-wrap: normal; padding: 16px; overflow: auto; background-color: rgb(248, 248, 248); border-radius: 3px; word-break: normal;">module.exports = {
 bodyParser: {
 jsonLimit: '10mb',
 },
};</pre>

通用配置

無論是應用層加載的中間件還是框架自帶中間件,都支持幾個通用的配置項:

  • enable:控制中間件是否開啟誊辉。
  • match:設置只有符合某些規(guī)則的請求才會經(jīng)過這個中間件矾湃。
  • ignore:設置符合某些規(guī)則的請求不經(jīng)過這個中間件。

enable

如果我們的應用并不需要默認的 bodyParser 中間件來進行請求體的解析堕澄,此時我們可以通過配置 enable 為 false 來關閉它


<pre style="box-sizing: border-box; font: 13.6px/1.45 Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace; margin-top: 0px; margin-bottom: 0px; overflow-wrap: normal; padding: 16px; overflow: auto; background-color: rgb(248, 248, 248); border-radius: 3px; word-break: normal;">module.exports = {
 bodyParser: {
 enable: false,
 },
};</pre>

match 和 ignore

match 和 ignore 支持的參數(shù)都一樣邀跃,只是作用完全相反,match 和 ignore 不允許同時配置蛙紫。

如果我們想讓 gzip 只針對 /static 前綴開頭的 url 請求開啟坞嘀,我們可以配置 match 選項


<pre style="box-sizing: border-box; font: 13.6px/1.45 Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace; margin-top: 0px; margin-bottom: 0px; overflow-wrap: normal; padding: 16px; overflow: auto; background-color: rgb(248, 248, 248); border-radius: 3px; word-break: normal;">module.exports = {
 gzip: {
 match: '/static',
 },
};
</pre>

match 和 ignore 支持多種類型的配置方式

  1. 字符串:當參數(shù)為字符串類型時,配置的是一個 url 的路徑前綴惊来,所有以配置的字符串作為前綴的 url 都會匹配上丽涩。 當然,你也可以直接使用字符串數(shù)組裁蚁。
  2. 正則:當參數(shù)為正則時矢渊,直接匹配滿足正則驗證的 url 的路徑。
  3. 函數(shù):當參數(shù)為一個函數(shù)時枉证,會將請求上下文傳遞給這個函數(shù)矮男,最終取函數(shù)返回的結果(true/false)來判斷是否匹配。

<pre style="box-sizing: border-box; font: 13.6px/1.45 Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace; margin-top: 0px; margin-bottom: 0px; overflow-wrap: normal; padding: 16px; overflow: auto; background-color: rgb(248, 248, 248); border-radius: 3px; word-break: normal;">module.exports = {
 gzip: {
 match(ctx) {
 // 只有 ios 設備才開啟
 const reg = /iphone|ipad|ipod/i;
 return reg.test(ctx.get('user-agent'));
 },
 },
};
</pre>

Router

Router 主要用來描述請求 URL 和具體承擔執(zhí)行動作的 Controller 的對應關系室谚, 框架約定了 app/router.js 文件用于統(tǒng)一所有路由規(guī)則毡鉴。

通過統(tǒng)一的配置,我們可以避免路由規(guī)則邏輯散落在多個地方秒赤,從而出現(xiàn)未知的沖突猪瞬,集中在一起我們可以更方便的來查看全局的路由規(guī)則。

app/router.js 里面定義 URL 路由規(guī)則
// app/router.js
module.exports = app => {
  const { router, controller } = app;
  router.get('/user/:id', controller.user.info);
};
app/controller 目錄下面實現(xiàn) Controller
// app/controller/user.js
class UserController extends Controller {
  async info() {
    const { ctx } = this;
    ctx.body = {
      name: `hello ${ctx.params.id}`,
    };
  }
}

RESTful 風格的 URL 定義

如果想通過 RESTful 的方式來定義路由入篮, 我們提供了 app.resources('routerName', 'pathMatch', controller) 快速在一個路徑上生成 CRUD 路由結構陈瘦。


<pre style="box-sizing: border-box; font: 13.6px/1.45 Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace; margin-top: 0px; margin-bottom: 0px; overflow-wrap: normal; padding: 16px; overflow: auto; background-color: rgb(248, 248, 248); border-radius: 3px; word-break: normal;">// app/router.js
module.exports = app => {
 const { router, controller } = app;
 router.resources('posts', '/api/posts', controller.posts);
 router.resources('users', '/api/v1/users', controller.v1.users); // app/controller/v1/users.js
};
</pre>

上面代碼就在 /posts 路徑上部署了一組 CRUD 路徑結構,對應的 Controller 為 app/controller/posts.js 接下來潮售, 你只需要在 posts.js 里面實現(xiàn)對應的函數(shù)就可以了痊项。

Method Path Route Name Controller.Action
GET /posts posts app.controllers.posts.index
GET /posts/new new_post app.controllers.posts.new
GET /posts/:id post app.controllers.posts.show
GET /posts/:id/edit edit_post app.controllers.posts.edit
POST /posts posts app.controllers.posts.create
PUT /posts/:id post app.controllers.posts.update
DELETE /posts/:id post app.controllers.posts.destroy

<pre style="box-sizing: border-box; font: 13.6px/1.45 Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace; margin-top: 0px; margin-bottom: 0px; overflow-wrap: normal; padding: 16px; overflow: auto; background-color: rgb(248, 248, 248); border-radius: 3px; word-break: normal;">// app/controller/posts.js
exports.index = async () => {};

exports.new = async () => {};

exports.create = async () => {};

exports.show = async () => {};

exports.edit = async () => {};

exports.update = async () => {};

exports.destroy = async () => {};
</pre>

如果我們不需要其中的某幾個方法锅风,可以不用在 posts.js 里面實現(xiàn),這樣對應 URL 路徑也不會注冊到 Router鞍泉。

參數(shù)獲取

Query String 方式

<pre style="box-sizing: border-box; font: 13.6px/1.45 Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace; margin-top: 0px; margin-bottom: 0px; overflow-wrap: normal; padding: 16px; overflow: auto; background-color: rgb(248, 248, 248); border-radius: 3px; word-break: normal;">// app/router.js
module.exports = app => {
 app.router.get('/search', app.controller.search.index);
};

// app/controller/search.js
exports.index = async ctx => {
 ctx.body = `search: ${ctx.query.name}`;
};

// curl http://127.0.0.1:7001/search?name=egg
</pre>

參數(shù)命名方式


<pre style="box-sizing: border-box; font: 13.6px/1.45 Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace; margin-top: 0px; margin-bottom: 0px; overflow-wrap: normal; padding: 16px; overflow: auto; background-color: rgb(248, 248, 248); border-radius: 3px; word-break: normal;">// app/router.js
module.exports = app => {
 app.router.get('/user/:id/:name', app.controller.user.info);
};

// app/controller/user.js
exports.info = async ctx => {
 ctx.body = `user: ${ctx.params.id}, ${ctx.params.name}`;
};

// curl http://127.0.0.1:7001/user/123/xiaoming
</pre>

復雜參數(shù)的獲取

路由里面也支持定義正則皱埠,可以更加靈活的獲取參數(shù):


<pre style="box-sizing: border-box; font: 13.6px/1.45 Consolas, &quot;Liberation Mono&quot;, Menlo, Courier, monospace; margin-top: 0px; margin-bottom: 0px; overflow-wrap: normal; padding: 16px; overflow: auto; background-color: rgb(248, 248, 248); border-radius: 3px; word-break: normal;">// app/router.js
module.exports = app => {
 app.router.get(/^\/package\/([\w-.]+\/[\w-.]+)$/, app.controller.package.detail);
};

// app/controller/package.js
exports.detail = async ctx => {
 // 如果請求 URL 被正則匹配, 可以按照捕獲分組的順序咖驮,從 ctx.params 中獲取边器。
 // 按照下面的用戶請求,`ctx.params[0]` 的 內(nèi)容就是 `egg/1.0.0`
 ctx.body = `package:${ctx.params[0]}`;
};

// curl http://127.0.0.1:7001/package/egg/1.0.0</pre>

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末游沿,一起剝皮案震驚了整個濱河市饰抒,隨后出現(xiàn)的幾起案子肮砾,更是在濱河造成了極大的恐慌诀黍,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仗处,死亡現(xiàn)場離奇詭異眯勾,居然都是意外死亡,警方通過查閱死者的電腦和手機婆誓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進店門吃环,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人洋幻,你說我怎么就攤上這事郁轻。” “怎么了文留?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵好唯,是天一觀的道長。 經(jīng)常有香客問我燥翅,道長骑篙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任森书,我火速辦了婚禮靶端,結果婚禮上,老公的妹妹穿的比我還像新娘凛膏。我一直安慰自己杨名,他們只是感情好,可當我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布猖毫。 她就那樣靜靜地躺著镣煮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鄙麦。 梳的紋絲不亂的頭發(fā)上典唇,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天镊折,我揣著相機與錄音,去河邊找鬼介衔。 笑死恨胚,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的炎咖。 我是一名探鬼主播赃泡,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼乘盼!你這毒婦竟也來了升熊?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤绸栅,失蹤者是張志新(化名)和其女友劉穎级野,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體粹胯,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡蓖柔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了风纠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片况鸣。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖竹观,靈堂內(nèi)的尸體忽然破棺而出镐捧,到底是詐尸還是另有隱情,我是刑警寧澤臭增,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布懂酱,位于F島的核電站,受9級特大地震影響速址,放射性物質(zhì)發(fā)生泄漏玩焰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一芍锚、第九天 我趴在偏房一處隱蔽的房頂上張望昔园。 院中可真熱鬧,春花似錦并炮、人聲如沸默刚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽荤西。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間邪锌,已是汗流浹背勉躺。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留觅丰,地道東北人饵溅。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像妇萄,于是被迫代替她去往敵國和親蜕企。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容