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 的基本使用。源码

相关文章
相关标签/搜索