前言

写这个项目的原因:已经基于PHP写过一个Vue的后台了,点这里可以查看(基于thinkPHP5.1写的Vue后台),然后换个语言写一个类似的后台,[点这里](基于node.js的egg.js框架开发)。目的纯粹是个人兴趣,想在各语言寻找各语言之间有何优势,以便以后工作中需要时有多一种选择。

用egg.js一个重要原因是因为我看中了它的内置多进程和毫秒级定时任务。
内置多进程就不需要我再用pm2了(egg-cluster多进程模块默认是根据服务器有多少核 CPU 启动多少个这样的 worker 进程 ),内置毫秒级定时任务可以快速实现很多骚操作了

假设你已经egg.js结构已经有所了解,不熟悉的东西去官网文档看看

下面是一些我个人对egg.js框架需要注意爬坑地方的理解,以帮助正在爬坑的你
可以直接基于我已经创建好的基础demo开发,里面包含MongoDB/MySQL/Redis等插件

一、egg.js的post请求404

这个坑,我猜上手的人第一步都会遇到
需要禁用安全防范
在config/config.default.js里加入下面这句话
方式一:

  config.security = {
    csrf: { // 安全防范
      enable: false
    },
  };

方式二:
写到用户配置里去

const userConfig = {
    // myAppName: 'egg',
    security: {
      csrf: {
        enable: false,
      },
    },
};

如果想防止前端请求接口跨域在这个文件里加下面的代码(也可以写在userConfig里):

cors: {
  origin: '*',
  allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',
},

二、自定义中间件

控制器和service,路由,模型都没啥好说的,和其他类似的MVC框架一样。
这个框架的中间件实现方式和express稍微有些不一样,官方是这么说的:
egg.js基于Koa这个框架

和 Express 只有 Request 和 Response 两个对象不同,Koa 增加了一个 Context
的对象,作为这次请求的上下文对象(在 Koa 1 中为中间件的 this,在 Koa 2
中作为中间件的第一个参数传入)。我们可以将一次请求相关的上下文都挂载到这个对象上。类似 traceId
这种需要贯穿整个请求(在后续任何一个地方进行其他调用都需要用到)的属性就可以挂载上去。相较于 request 和 response
而言更加符合语义。

同时 Context 上也挂载了 Request 和 Response 两个对象。和 Express
类似,这两个对象都提供了大量的便捷方法辅助开发,例如

get request.query
get request.hostname
set response.body
set response.status

express我也在用,就我个人使用来感受来说,其实区别就是这一点:

我们可以将一次请求相关的上下文都挂载到这个对象上。类似 traceId
这种需要贯穿整个请求(在后续任何一个地方进行其他调用都需要用到)的属性就可以挂载上去

废话不多说了,直接写个demo
在config/config.default.js加上:

  config.middleware = [
    'test', // 中间件test
  ];
  config.test = {
    enable: true, // test中间件开启
    // enable: false, // test中间件如果true为全局开启,如果false时,可以在router里单独为路由配置中间件
  };

新建app/middleware/test.js 文件,middleware目录下的所有js文件要与你定义的中间件名称一样,否则报错。
test.js文件代码:

module.exports = () => { // 中间件test
  return async function (ctx, next) {
    await next();
    // 打印当前时间
    let timeToDate = new Date().toLocaleString();
    console.log(timeToDate);
  }
};

在路由文件router.js里可以单独为指定路由添加中间件

'use strict';

/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
  const {router, controller, middleware} = app;

  //--------------------------------------------------
  // 中间件
  //--------------------------------------------------
  const test = middleware.test();

  router.get('/:id', controller.home.index);
  router.post('/test', test, controller.test.test); // 单独为路由引入中间件
  router.post('/api/posts', controller.post.create);
};

三、使用插件

以MySQL为例:
建议使用egg-sequelize插件,因为它集成了ORM操作,它支持 MySQL、PostgreSQL、SQLite 和 MSSQL 等多个数据源

首先确保你本地/服务器已经有数据库环境了

安装

npm install --save egg-sequelize mysql2

在 config/plugin.js 中引入 egg-sequelize 插件

  module.exports = {
  	// 使用egg-sequelize mysql2
	sequelize: {
	  enable: true,
	  package: 'egg-sequelize',
	}
}

在 config/config.default.js 中编写 sequelize 配置

方式一:

 module.exports = appInfo => {
   const config = exports = {};
   // MySQL配置
   config.sequelize = {
     dialect: 'mysql',
     host: '127.0.0.1',
     port: 3306,
     username: 'root',
     password: 'xxx',
     database: 'xxx',
   };
   return {
     ...config
   };
}

方式二:

const userConfig = {
    // myAppName: 'egg',
    // 所有的插件配置都可以写在这里
    sequelize: {
      sync: true, // whether sync when app init
      dialect: 'mysql',
      host: '127.0.0.1',
      port: '3306',
      database: 'egg_sequelize_dev',
      username: 'xxx',
      password: 'xxx',
    }
}

四、使用构造函数

这个框架如果不使用构造函数,你会发现会经常使用到定义如下代码

const { ctx } = this;

如果我们在类文件里加上构造函数,就不需要再写重复代码了

  // 构造函数
  constructor(ctx) {
    super(ctx); // 如果需要在构造函数做一些处理,一定要有这句话,才能保证后面 `this.ctx`的使用。
    // 就可以直接通过 this.ctx 获取 ctx 了
    // 还可以直接通过 this.app 获取 app 了
  }

这个是我在service/user.js里写的

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

class UserService extends Service {
  // 构造函数
  constructor(ctx) {
    super(ctx); // 如果需要在构造函数做一些处理,一定要有这句话,才能保证后面 `this.ctx`的使用。
    // 就可以直接通过 this.ctx 获取 ctx 了
    // 还可以直接通过 this.app 获取 app 了
  }

  /**
   * 通过id获取用户信息
   * @param id
   * @return {Promise<{}>}
   */
  async getUserById(id) {
    // const { ctx } = this;
    let userInfo = {};
    try {
      userInfo = await this.ctx.model.User.findAll({
        where: { id },
        // 查询操作的时候,加入这个参数可以直接拿到对象类型的查询结果,否则还需要通过方法调用解析
        raw: true,
        attributes:["id","name","age"], //返回的指定字段
      });
    } catch (err) {
      this.ctx.logger.error(err);
    }
    return userInfo;
  }

  // 根据id更新数据
  async update(id) {
    console.log(id)
    let result = {};
    try {
      result = await this.ctx.model.User.update({
        age: 20
      }, {
        where: {
          id: id
        }
      });
    } catch (err) {
      this.ctx.logger.error(err);
    }
    return result;
  }
}

module.exports = UserService;

会发现下面不再需要定义ctx,app等这些了,直接用this.app,this.ctx即可

五、定时任务

框架自身就支持毫秒级定时任务也是我非常看重的一点

定时任务写在 schedule/task.js里

module.exports = {
  schedule: {
    interval: '1s', // 1s间隔,默认单位是ms,比如只写个1,就变成1毫秒执行一次
    type: 'all', // 指定所有的 worker 都需要执行
  },
  async task(ctx) {
    console.log(new Date()) // 打印当前时间
  },
};

六、框架扩展

可以加一些类似助手函数之类的方法
框架本身内置支持:
Application
Context
Request
Response
Helper
扩展文件都放在app/extend/xxx.js

举例:app/extend/helper.js

module.exports = {
  // 字符串转整形
  parseInt(string) {
    if (typeof string === 'number') return string;
    if (!string) return string;
    return parseInt(string) || 0;
  }
}

使用方式:

async destroy() {
   const {ctx} = this;
   const {params, service, helper} = ctx;
   const id = helper.parseInt(params.id); // 使用方式
   await service.wbUrl.destroy(id);
   ctx.status = 200;
   ctx.body = {
       code: 0,
       success: true,
   };
}

之前爬坑的时候没有记录,有的地方我已经爬完了却想不起来哪些需要记录。后面我会把我使用egg.js遇到的问题都记录下来,如果有幸我们在这里相遇,也请把你的问题抛出来,我们一起研究解决

Logo

前往低代码交流专区

更多推荐