web开发框架之Egg

概述

之前我们介绍过koa了,文本了解下Egg。如官网文档所说:Egg是继承于Koa, 对于企业级应用koa还略显单薄,Egg将koa作为基础框架,在它的模型基础上做了一些增强。

项目脚手架

Egg官网有配套的脚手架,用于快速初始化egg项目

npm init egg --type=simple

关于此脚手架的执行原理,可参照:你不知道的npm init

Egg VS Koa

如官网介绍Egg是以Koa作为基础进行增加开发的,相比Koa,它作了很多约定与限制,使用的时候只需在对应目录撰写对应逻辑即可。

由框架约定的目录:

app/router.js 用于配置 URL 路由规则,具体参见 Router。
app/controller/** 用于解析用户的输入,处理后返回相应的结果,具体参见 Controller。
app/service/** 用于编写业务逻辑层,可选,建议使用,具体参见 Service。
app/middleware/** 用于编写中间件,可选,具体参见 Middleware。
app/public/** 用于放置静态资源,可选,具体参见内置插件 egg-static。
app/extend/** 用于框架的扩展,可选,具体参见框架扩展。
config/config.{env}.js 用于编写配置文件,具体参见配置。
config/plugin.js 用于配置需要加载的插件,具体参见插件。
test/** 用于单元测试,具体参见单元测试。
app.js 和 agent.js 用于自定义启动时的初始化工作,可选,具体参见启动自定义。关于agent.js的作用参见Agent机制。

由内置插件约定的目录:

app/public/** 用于放置静态资源,可选,具体参见内置插件 egg-static。
app/schedule/** 用于定时任务,可选,具体参见定时任务。

引用插件时,安装完毕后,在config/plugin.js 中声明即可:

  mongoose: {
    enable: true,
    package: 'egg-mongoose',
  }

对应配置项在config/config.default.js中编写:

 mongoose: {
  clients: {
    mallDB: {
      url: 'mongodb://localhost:27017/mall',
      options: {},
    },
    mangeDB: {
      url: 'mongodb://localhost:27017/manage',
      options: {},
    },
  },
}

按照官网建议,app/controller下的控制器只对输入输出做处理,业务相关逻辑放到app/service下:

  ├─service
  |  ├─address.js 
  |  ├─goods.js 
  |  ├─order.js
  |  └user.js  

service继承require('egg').Service, 函数接受到参数后,进行相关数据库操作,最终返回执行结果,在控制器中只需要调用即可。

ctx.body = await ctx.service.address.detail(u_name, a_id)

这样做的优点是条理更加清晰,对大型项目来说,是非常有利的。

下面介绍几个自己在使用中发现的几处不同于koa的地方

egg-mongoose

该模块用来实现egg连接mongodb,model定义在app/model文件夹下

  // app/model

  ├─model
  |  ├─address.js 
  |  ├─banner.js 
  |  ├─goods.js 
  |  ├─order.js
  |  └user.js        

app/controller(控制器)下使用方式为:

  ctx.model.address.xxx

打印下ctx.model看运行结果:

{ 
  Address: Model { Address },
  Banner: Model { Banner },
  Goods: Model { Good },
  Order: Model { Order },
  User: Model { User } 
}

可以得知:Addressmodel/address.js文件名(首字母大写),Model模型名称对应的是mongodb集合名称(会自动添加复数s)

egg-jwt

jsonwebtoken使用该模块,需要校验的路由地址,需要在app/router.js中指定,不指定则不校验

  router.get(/^\/index\//, controller.index.index)   // index开头的路由全部指向到同一个模板文件
      .post('/api/user/register', controller.user.register)
      .post('/api/user/login', controller.user.login)
      .post('/api/user/info', jwt, controller.user.info)  //校验token

也可以在中间件中指定哪些接口需要校验token(指定返回内容)

// app/middleware/auth.js 下

module.exports = (options, app) => {
  return async function(ctx, next) {
    const url = ctx.url;
    // 校验所有api下接口
    if (ctx.url.match(/^\/api\//) && !app.config.URL_PASS_LOGIN.includes(url)) {
      const token = (ctx.get('Authorization') || '').replace(/^Bearer /, '');
      if (!token) {
        ctx.response.body = {
          code: 2001,
          message: '未登录,请登录!',
        };
        return;
      }
    }
    // 校验token
    const u_name = await app.jwt.verify(token, app.config.jwt.secret);
    // do something 
    await next();
  }
}

前端发送xmlhttprequest请求时需要指定header

  config.headers['Authorization'] =`Bearer ${token}`

默认token无效时会返回状态码401,需要在拦截器的error中做对应跳转处理

  error => {
    if(error.response.status === 401){
      window.location.replace(`/index/login?from=${encodeURIComponent(window.location.pathname+window.location.search)}`)
      return Promise.reject();
    }
  }
  

也可以在中间件中自定义返回参数。

security

对于post请求,Egg内置了安全插件egg-security,可以临时进行关闭,但是不推荐这么做:

  security: {
    csrf: false,
  }

按照egg-security插件的要求,每次访问时候,会在cookie中写入一个安全校验值csrfToken,前端在请求时带上即可:

首先修改config/default.config.js文件:

security: {
  csrf: {
    headerName: 'x-csrf-token', // 自定义请求头
  },
}

前端修改对应拦截器,加入:

config.headers['x-csrf-token']=getCookie('csrfToken'); // getCookie方法自行定义

总结

Egg扩展性与封装性都比较高,本身设计的初衷就是便于大型项目开发,所以约定与规范相比koa也没那么自由,毕竟二者应用场景不一样,各位按需选用即可。

参考