Egg.js 基本使用

前言

咱們在上一篇文章Egg.js 源碼分析-項目啓動, 已經簡單的分析了Eggjs 的啓動機制, 以及其相應的實現原理,Eggjs 就是針對一系列的約定俗成的規則,在項目啓動時,自動加載對應文件夾下面的文件,進行項目的初始化,咱們能夠參考官網給出的目錄結構,去對咱們的項目進行規範,包括文件結構規範, 代碼邏輯分層規範,從而達到整個項目的規範。javascript

之因此有這樣的一個 目錄結構 ,其實仍是針對於咱們上一篇文章 Egg.js 源碼分析-項目啓動 分析所得,在項目啓動時,會加載以下的配置, 下面代碼後面加了備註,只標註了咱們應用對應的文件名稱(沒有標註eggjs 對應的文件名稱)

loadConfig() {
    // your-project-name/config/plugin.js
    this.loadPlugin();
    // your-project-name/config/config.default.js 
    // your-project-name/config/`config.${this.serverEnv}`.js 
    super.loadConfig();
  }
複製代碼
load() {
    // app > plugin > core
    this.loadApplicationExtend();
    this.loadRequestExtend();
    this.loadResponseExtend();
    this.loadContextExtend();
    this.loadHelperExtend();
    // your_project_name/app.js
    this.loadCustomApp();
    // your_project_name/app/service/**.js
    this.loadService();
    this.loadMiddleware();
    // your_project_name/app/controller/**.js 
    this.loadController();
    // your_project_name/ app/router.js
    this.loadRouter(); // Dependent on controllers
  }
複製代碼

從上面能夠知道咱們應用的一個大體的結構,咱們下面就來一一從頭開始建立一個項目,深刻了解Eggjs 的使用方式(規範)html

初始化項目

咱們利用egg-init 的手腳架egg-init 先初始化一個項目, 咱們能夠運行以下命令(一直沒怎麼接觸APP 開發, 因此最近想研究下react-native ,因此建立了一個react-native-learning-server項目):java

$ npm i egg-init -g
$ egg-init react-native-learning-server --type=simple
$ cd react-native-learning-server
$ npm i
$ npm run dev
複製代碼

瀏覽器會打開默認端口:http://localhost:7001, 頁面會顯示hi, egg, 說明咱們項目建立成功.react

設置路由

咱們如今假設咱們想作一個相似掘金同樣的APP, 咱們能夠有四個大菜單 Mine , Find , Message, Homegit

咱們簡單的設計幾個API:github

Mine:mongodb

API Method 描敘
/users GET 獲取全部的用戶
/user/:id GET 獲取指定用戶信息
/user POST 添加用戶
/user/:id PUT 編輯用戶
/user/:id DELETE 刪除用戶

Message:數據庫

API Method 描敘
/messages/:userId GET 獲取用戶全部的信息
/message POST 發送信息
/message/:id DELETE 刪除指定信息

Find:npm

API Method 描敘
/search/:keyword GET 根據關鍵字,查詢信息

Home:json

API Method 描敘
/hot GET 查詢最熱信息
/heart GET 查詢關注的熱點

咱們先只設計如上幾個簡單的API(咱們這篇文章,只是想經過一個僞業務來實現Egg的一些使用方式, 重點是Eggjs 的使用)

上面咱們已經初始化了項目, 咱們如今編輯app/router.js去設計路由,其代碼以下:

module.exports = app => {
  const { router, controller } = app;
  router.get('/', controller.home.index);
  // User
  router.get('/users', controller.user.findAll);
  router.get('/user/:id', controller.user.findOne);
  router.post('/user', controller.user.add);
  router.put('/user/:id', controller.user.update);
  router.del('/user/:id', controller.user.delete);
  // Message
  router.get('/messages/:userId', controller.message.findByUserId);
  router.post('/message', controller.message.add);
  router.del('/messages/:id', controller.message.delete);
  // Find
  router.get('/search/:keyword', controller.search.find);
  // Home
  router.get('/hot', controller.home.findHot);
  router.get('/heart', controller.home.findHeart);
};

複製代碼

咱們先無論Controller的實現, 上面咱們就已經實現了咱們的路由了, 可是咱們發現一個問題,就是當項目愈來愈大,那這個router.js 會愈來愈大,也會愈來愈難以維護,因此咱們能夠作以下的調整:

  1. 在app下面建立一個文件夾routers, 而後建立四個文件,分別爲: user.js, message.js, search.js, home.js,
  2. 而後將router.js 中的路由進行分割,不一樣的路由都分割到對應的文件下.
  3. 在router.js 中去引用每一個單獨的路由 拆分後的router.js 以下:
'use strict';
const userRouter = require('./routers/user');
const messageRouter = require('./routers/message');
const homeRouter = require('./routers/home');
const searchRouter = require('./routers/search');
module.exports = app => {
  const { router, controller } = app;
  router.get('/', controller.home.index);
  userRouter(app);
  messageRouter(app);
  homeRouter(app);
  searchRouter(app);
};
複製代碼

其routers/user.js 代碼以下:

'use strict';

module.exports = app => {
  const { router, controller } = app;
  router.get('/users', controller.user.findAll);
  router.get('/user/:id', controller.user.findOne);
  router.post('/user', controller.user.add);
  router.put('/user/:id', controller.user.update);
  router.del('/user/:id', controller.user.delete); 
};

複製代碼

通過如上的拆分, router.js 的代碼變得整潔, 並且相應的路由變得更加容易維護.

設置控制層(Controller)

上面咱們已經開發(配置)好了Router, 可是Router 的回調函數,都指向的是app 的controller下面的對象, 如:controller.user.findAll 咱們在上一章節已經分析了這個路徑怎麼來的: controller 是app(應用)的一個屬性對象, eggjs 會在啓動的時候調用this.loadController(); 方法,去加載整個應用app/controller文件下的全部的js 文件, 會將文件名做爲屬性名稱,掛載在app.controller 對象上, 而後將對應js 文件export(暴露)出來的全部的方法有掛在在文件名稱爲屬性的對象上,而後就能夠經過controller.user.findAll這樣的方式來引用Controller 下面的方法了。

有了這個思路,咱們就能夠很清晰的去維護咱們控制層了,下面是咱們一個home的範例:

'use strict';

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

class HomeController extends Controller {
  async index() {
    this.ctx.body = 'hi , egg.';
  }
  async findHot() {
    this.ctx.body = this.ctx.request.url;
  }
  async findHeart() {
    this.ctx.body = this.ctx.request.url;
  }
}

module.exports = HomeController;

複製代碼

設置Service

咱們已經開發好了Router 和Controller , 可是在咱們的controller 中,都是靜態的內容, 一個項目咱們須要跟數據庫交互,咱們通常將跟DB 交互的內容,都放在Service 層,下面咱們就來開發咱們的service.

咱們首先在app目錄下面,建立一個service 目錄, 而且建立 user.js, message.js, search.js, home.js,文件, 咱們先不鏈接真實的數據庫, 建立的service 以下(home.js):

'use strict';

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

class HomeService extends Service {
  async index() {
    return 'hi, egg';
  }
  findHot() {
    const hotArticle = [
      {
        title: 'Title 0001',
        desc: 'This is hot article 0001',
      },
      {
        title: 'Title 0002',
        desc: 'This is hot article 0002',
      },
    ];
    return hotArticle;
  }
  findHeart() {
    const heartArticle = [
      {
        title: 'Title 0001',
        desc: 'This is heart article 0001',
      },
      {
        title: 'Title 0002',
        desc: 'This is heart article 0002',
      },
    ];
    return heartArticle;
  }
}

module.exports = HomeService;
複製代碼

咱們接下來修改Controller文件:

'use strict';

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

class HomeController extends Controller {
  async index() {
    this.ctx.body = this.service.home.index();
  }
  async findHot() {
    this.ctx.body = this.service.home.findHot();
  }
  async findHeart() {
    this.ctx.body = this.service.home.findHeart();
  }
}

module.exports = HomeController;

複製代碼

咱們調用service方法如: this.service.home.index();, 跟controller 原理相似。

到此位置,咱們項目的基本框架,已經搭建完成,咱們如今能夠思考先,咱們怎麼鏈接數據庫。

鏈接DB

咱們的數據庫咱們選擇用MongoDB, 因此,咱們能夠選擇用egg-mongoose 插件,咱們能夠按照文檔進行操做: 首先安裝插件:

$ npm i egg-mongoose --save

由於egg-mongoose 是做爲Eggjs 的一個插件,因此咱們要配置這個插件,咱們如今app/config/plugin.js中配置插件:

'use strict';
module.exports = {
  // enable plugins 
  mongoose: {
    enable: true,
    package: 'egg-mongoose',
  },
};
複製代碼

接下來,咱們要配置MongoDB 的鏈接, 咱們修改app/config/config.default.js

'use strict';

module.exports = appInfo => {
  const config = exports = {};
  // use for cookie sign key, should change to your own and keep security
  config.keys = appInfo.name + '_1541735701381_1116';
  // add your config here
  config.middleware = [];
  config.cluster = {
    listen: {
      path: '',
      port: 7001,
      hostname: '',
    },
  };
  config.mongoose = {
    client: {
      url: 'mongodb://127.0.0.1/react-native-demo',
      options: {},
    },
  };
  return config;
};

複製代碼

接下來咱們須要給MongoDB 配置Model,咱們在app 目錄下面,建立一個model 文件夾, 而且建立user.js, 代碼以下:

'use strict';
// {app_root}/app/model/user.js
module.exports = app => {
  const mongoose = app.mongoose;
  const Schema = mongoose.Schema;

  const UserSchema = new Schema({
    userName: { type: String },
    password: { type: String },
  });

  return mongoose.model('User', UserSchema);
};
複製代碼

而後咱們接下來,修改Service 來真正的鏈接數據庫,修改service/user.js 代碼以下:

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

class UserService extends Service {
  async findAll() {
    return await this.ctx.model.User.find();
  }
}

module.exports = UserService;

複製代碼

鏈接數據庫的真個流程完成了,咱們能夠打開http://localhost:7001/users, 頁面就會顯示從MongoDB 數據庫裏面查詢的全部的數據。

總結

  1. 安裝插件: $ npm i egg-mongoose --save
  2. 配置插件: app/config/plugin.js中配置插件
  3. 配置鏈接: 咱們修改app/config/config.default.js,添加egg-mongoose鏈接信息
  4. 建立Model: 咱們在{app_root}/app/model/user.js 下建立Model.
  5. 修改Service: 修改Servcie 的代碼,操做MongoDB, await this.ctx.model.User.find();

問題

  1. 在上一篇文章Egg.js 源碼分析-項目啓動中,咱們並無分析到,eggjs 會加載model 文件夾下面的文件.那這裏的model 目錄下的文件是何時加載上的?
  2. config.default.js 中,配置mongoose 的鏈接信息,掛載在config.mongoose上, mongoose這個名稱是不是固定的, 能夠修改爲其餘的?
  3. app/config/plugin.js中,配置mongoose的插件信息, mongoose 的屬性名稱是不是固定的, 能夠修改爲其餘的?

答案

  1. 在eggjs 中,並無實現加載model 的功能,可是egg-mongoose這個插件實現了這個功能,其代碼以下:
function loadModelToApp(app) {
  const dir = path.join(app.config.baseDir, 'app/model');
  app.loader.loadToApp(dir, 'model', {
    inject: app,
    caseStyle: 'upper',
    filter(model) {
      return typeof model === 'function' && model.prototype instanceof app.mongoose.Model;
    },
  });
}

複製代碼
  1. 必須叫作mongoose這個名稱,由於在egg-mongoose這個插件中,會直接去讀取應用中app.config.mongoose 的配置const { client, clients, url, options, defaultDB, customPromise, loadModel } = app.config.mongoose;,因此這個規則是egg-mongoose插件制定的。
  2. plugin.js的配置名稱, 不是固定的, 能夠隨意,由於其真正重要的配置是: package: 'egg-mongoose',指明這個pulgin 用的是那個具體的包。

初始化數據

在一個項目上線的時候,咱們常常須要準備一些初始化數據, 好比用戶數據,咱們通常會建立一個超級管理員的賬號, 這個賬號,是不須要用戶註冊的,因此咱們能夠在項目初始化的時候用腳本生成,咱們按照以下步驟進行操做:

  1. 修改app/model/user.js添加isMaster屬性,以下:
'use strict';
// {app_root}/app/model/user.js
module.exports = app => {
  const mongoose = app.mongoose;
  const Schema = mongoose.Schema;

  const UserSchema = new Schema({
    userName: { type: String, required: true },
    password: { type: String, required: true },
    isMaster: { type: Boolean, default: false, required: true },
  });

  return mongoose.model('User', UserSchema);
};

複製代碼
  1. {app_root}/app目錄下,建立一個data的文件夾,而後建立一個user.json, 其內容以下:
[
    {
        "userName": "admin",
        "password": "admin",
        "isMaster": true
    }
]
複製代碼
  1. 由於須要在項目啓動的時候,去初始化數據,因此咱們在{app_root}目錄下,添加一個app.js(this.loadCustomApp()),代碼以下:
'use strict';
// app.js
module.exports = app => {
  app.beforeStart(async () => {
    if (app.config.initData) {
      const initUsers = require('./app/data/user.json');
      const ctx = app.createAnonymousContext();
      ctx.model.User.create(initUsers, err => {
        if (err) {
          app.coreLogger.console.warn('[egg-app-beforeStart] init user data fail %s', err);
        }
      });
    }
  });
};
複製代碼

咱們在配置文件中添加了一個initData 的開關用來表示是否須要初始化數據,由於初始化數據,通常就是第一次須要(這個配置,應該做爲運行腳本命令的參數傳遞,這樣更易於維護,並且不用每次都去該config.default.js 的代碼)

總結

按照上面的操做,咱們基本完成了一個項目的基本骨架,咱們只須要在上面搭積木就能夠了,並且瞭解了Eggjs 的基本使用。源碼

相關文章
相關標籤/搜索