《前端之路》--- 重溫 Egg.js

《前端之路》--- 重溫 Egg.js

在 nodejs 發展日益健壯和穩定的狀況下,咱們在平常的開發中使用 node 已是一件很是常規的事情了,那麼對於咱們必要的掌握一個服務端框架仍是很是有必要的。下面咱們就開始吧。css

1、基礎功能

1.一、目錄結構

在瞭解目錄結構以前,咱們須要對 mvc 的編程模式,進行一個理解。html

  • M =》 Model(模型) 是處理應用程序數據邏輯的部分,主要是和數據庫打交道。
  • V =》 View(視圖) 是做爲視圖展現使用,若是沒有這部分的需求的化, view 這一個層面的內容是能夠被省略的。
  • C =》 Controller(控制器) 是應用程序中處理與用戶交互的部分,一般是爲了 視圖須要的數據作準備,並向 Model 發送數據(get、post) 等
  • S =》 Service(服務) 在實際應用中,Controller 通常不會本身產出數據,也不會包含複雜的邏輯,複雜的過程應抽象爲業務邏輯層 Service

可是在一些 mvc 框架的論壇中,也有一部分的聲音是說但願,業務層面的架構是一個 Thin Controller Fat Model and no need Service 那麼在這樣的一個出發點上來將的話,咱們能夠在後面實際的業務中去不斷嘗試。前端

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

目錄結構java

  • app/router.js 用於配置 URL 路由規則
  • app/controller/** 用於解析用戶的輸入,處理後返回相應的結果
  • app/service/** 用於編寫業務邏輯層
  • app/middleware/** 用於編寫中間件
  • app/public/** 用於放置靜態資源
  • app/extend/** 用於框架的擴展
  • config/config.{env}.js 用於編寫配置文件
  • config/plugin.js 用於配置須要加載的插件
  • test/** 用於單元測試
  • app.js 和 agent.js 用於自定義啓動時的初始化工做

1.二、內置對象

咱們在看 egg 的內置對象的時候,咱們先看下 koa 的內置對象,application、context、request、response。

  • 再對比下 egg 的內置對象: 包括從 Koa 繼承而來的 4 個對象(Application, Context, Request, Response) 以及框架擴展的一些對象(Controller, Service, Helper, Config, Logger)
1.2.一、Application

Application 是一個全局對象,上面掛載了這個框架經常使用到的 全局方法和對象。node

1.2.二、Context

Context 是一個請求級別的對象,最多見的 Context 實例獲取方式是在 Middleware, Controller 以及 Service 中,例如:git

// controller
class HomeController extends Controller {
  async index() {
    const { ctx } = this;
    ctx.body = 'hi, egg, ss';
  }
}
1.2.三、Request

Request 是一個請求級別的對象,繼承自 Koa.Request。封裝了 Node.js 原生的 HTTP Request 對象,提供了一系列輔助方法獲取 HTTP 請求經常使用參數。github

// 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);
  }
}

// 可是若是 須要注意的是,獲取 POST 的 body 應該使用 ctx.request.body,而不是 ctx.body. 因此咱們在獲取 請求對應參數的時候均採用 ctx.request.query 或者 ctx.request.body 的方式.
1.2.四、Response

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

1.2.五、Controller

框架提供了一個 Controller 基類,這個基類包括了一些經常使用的屬性: ctx、app、config、service、logger 等編程

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

class TestController extends Controller {
    async index() {
        const { ctx, app, config, service, logger } = this;
        ctx.body = 'test';
    }
}

module.exports = TestController;
1.2.六、Service

框架提供了一個 Service 基類, Service 基類的屬性和 Controller 基類屬性一致,訪問方式也相似

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

class TestService extends Service {
    index() {
        const { ctx } = this;
        return ctx.request.query;
    }
}

module.exports = TestService;
1.2.七、Helper

Helper 用來提供一些實用的 utility 函數 ( 工具函數 ) ,能夠在 Context 的實例上獲取到當前請求的 Helper(ctx.helper) 實例。 其中咱們還能夠自定義 helper 方法,以下:

// Demo
// app/extend/helper.js
module.exports = {
    upperCase(str) {
        return str.toUpperCase();
    }
};
1.2.八、Config

咱們能夠經過 app.config 從 Application 實例上獲取到 config 對象,也能夠在 Controller, Service, Helper 的實例上經過 this.config 獲取到 config 對象。 被建立出來的原則就是但願,配置和代碼分離的原則。

1.2.九、Logger

日誌系統包含了 四大層面的 日誌對象, 分別是 App Logger、App CoreLogger、Context Logger、Context CoreLogger、Controller Logger & Service Logger

這些從 app、context 、logger、service 等各個層面都提供對應的 debug 方法,這些方法中包括

  • logger.debug()
  • logger.info()
  • logger.warn()
  • logger.error()

後需在開發中使用到,再具體介紹每一部分的功能。

1.三、運行環境

經過 config/env 文件指定對應的環境,可是通常推薦使用 構建工具來生成這個文件,那麼咱們看下這個文件裏面的內容是什麼樣子的

//  config/env
prod
// 對,就是這麼簡單。

在 Koa 中咱們經過 app.env 來進行環境判斷,app.env 默認的值是 process.env.NODE_ENV。可是在 Egg(和基於 Egg 的框架)中,配置統一都放置在 app.config 上,因此咱們須要經過 app.config.env 來區分環境,app.env 再也不使用。

1.四、配置

框架提供了強大且可擴展的配置功能,能夠自動合併應用、插件、框架的配置,按順序覆蓋,且能夠根據環境維護不一樣的配置。合併後的配置可直接從 app.config 獲取。

配置文件:

  • config.default.js ---默認配置文件,通常也做爲開發環境使用
  • config.prod.js --- 生產環境配置文件,會覆蓋默認配置文件的同名配置
  • config.unittest.js --- 單元測試, 測試環境
  • config.local.js --- 本地開發環境,額外於 默認配置。

1.五、中間件

這裏簡單介紹下,如何編寫 中間件 和如何使用 中間件。

1.5.一、如何編寫中間件

從文件夾規則來講,咱們編寫的本身的中間件通常都會放在 app/middleware/ xxx.js 具體的 Demo 以下:

// egg 的中間件
// 基於 洋蔥圈模型
// 和 koa 的中間件的構成差很少,只不過 egg 在外面包裹了一層
module.exports = options => {
    return async function toLowerCase(ctx, next) {
        await next();
        const result = ctx.request.query;
        ctx.body = result;
    };
};
// koa 的中間件
function log(ctx) {
    console.log(ctx.method, ctx.header.host);
}

module.exports = function() {
    return async function(ctx, next) {
        next();
        await log(ctx);
    };
};
1.5.二、如何使用中間件

在開始問如何使用中間件的時候,咱們要清除一個問題就是咱們每每在何時須要用到中間件、怎麼用的問題,下面就簡單介紹下。

  • 在應用中使用中間件
// config.default.js
module.exports = {
  // 配置須要的中間件,數組順序即爲中間件的加載順序
  middleware: [ 'gzip' ],

  // 配置 gzip 中間件的配置
  gzip: {
    threshold: 1024, // 小於 1k 的響應體不壓縮
  },
};
  • 在框架和插件中使用中間件
// app.js
module.exports = app => {
  // 在中間件最前面統計請求時間
  app.config.coreMiddleware.unshift('report');
};

// app/middleware/report.js
module.exports = () => {
  return async function (ctx, next) {
    const startTime = Date.now();
    await next();
    // 上報請求時間
    reportTime(Date.now() - startTime);
  }
};

//  核心方法是 app.config.coreMiddleware.unshift('report')
  • router 中使用中間件
module.exports = app => {
  const gzip = app.middleware.gzip({ threshold: 1024 });
  app.router.get('/needgzip', gzip, app.controller.handler);
};
  • 框架自己自帶中間件
// 修改一些初始化的配置
// config/config.default.js
module.exports = {
  bodyParser: {
    jsonLimit: '10mb',
  },
};
1.5.三、中間件的通用配置
  • enable 是否開啓使用中間件
  • match 設置只有符合某些規則的請求才會通過這個中間件
  • ignore 設置符合某些規則的請求不通過這個中間件
module.exports = {
  bodyParser: {
    enable: fasle   // 不開啓使用 bodyParser 中間件
  }, 
  gzip: {
    match: '/static'   // 只壓縮 /static 目錄下的文件 
  },
  apiAgent: {
    ignore: /^\/api/   // 只用 /api 的請求路徑纔不會使用  apiAgent 中間件   
  }
};

1.六、路由

Router 主要用來描述請求 URL 和具體承擔執行動做的 Controller 的對應關係, 框架約定了 app/router.js 文件用於統一全部路由規則

// demo
module.exports = app => {
    const { router, controller } = app;
    router.get('/', controller.home.index);
    router.get('/news', controller.news.list);
    router.get('/user/:id', controller.test.user);
};

router 提供的訪問方法

  • router.head - HEAD
  • router.options - OPTIONS
  • router.get - GET
  • router.put - PUT
  • router.post - POST
  • router.patch - PATCH
  • router.delete - DELETE
  • router.redirect --- 這裏重點提示下 redirect 重定向,能夠對 URL 進行重定向處理,好比咱們最常用的能夠把用戶訪問的根目錄路由到某個主頁。

router 有哪些使用方式?
主要列舉2中方式:

  • 最簡單的一種,直接 router.method('/url', controller)
router.verb('path-match', app.controller.action);
  • 最複雜的一種方式
router.verb('router-name', 'path-match', middleware1, ..., middlewareN, app.controller.action);

// router-name 是指路徑別名
// middleware 是指 中間件
// path-match 是指 路由 url

如何使用 RESTful 風格的 URL 定義

// 定義
// router.js
router.resources('posts', '/api/posts', controller.posts);
// controller/posts.js
exports.index = async ctx => {
    ctx.body = {
        success: true,
        data: [1, 2, 34]
    };
};

訪問 http://127.0.0.1:7001/api/posts 便可

這裏須要仔細一點,查看關於 restful 的 使用方法,須要對 restful 有一個基本對理解。

2、總結

其實整篇文章,寫到這裏已經初步對於 egg 已經有了一個大概的瞭解了,那麼後續我猜應該還會有對應的實際的寫項目的文章吧(但願有時間和經從來寫)

GitHub 地址:(歡迎 star 、歡迎推薦 : )
《前端之路》 - 重溫 EggJS

相關文章
相關標籤/搜索