Web应用开发框架-egg(三)06-基础功能——插件之定义插件、编写插件 & 定时任务 & 自定义启动app

Web应用开发框架-egg(三)06-基础功能——插件之定义插件、编写插件 & 定时任务 & 自定义启动app

插件

插件机制是我们框架的一大特色。它不但可以保证框架核心的足够精简、稳定、高效,还可以促进业务逻辑的复用,生态圈的形成。

  • Koa 已经有了中间件的机制,为啥还要插件呢?
  • 中间件、插件、应用它们之间是什么关系,有什么区别?
为什么要插件

使用 Koa 中间件过程中发现了下面一些问题:

  1. 中间件加载其实是有先后顺序的,但是中间件自身却无法管理这种顺序,只能交给使用者。这样其实非常不友好,一旦顺序不对,结果可能有天壤之别。
  2. 中间件的定位是拦截用户请求,并在它前后做一些事情,例如:鉴权、安全检查、访问日志等等。但实际情况是,有些功能是和请求无关的,例如:定时任务、消息订阅、后台逻辑等等。
  3. 有些功能包含非常复杂的初始化逻辑,需要在应用启动的时候完成。这显然也不适合放到中间件中去实现。
中间件、插件、应用的关系

一个插件其实就是一个『迷你的应用』,和应用(app)几乎一样:

  • 它包含了 Service、中间件、配置、框架扩展等等。
  • 它没有独立的 Router 和 Controller。(插件一般不写业务逻辑)
  • 它没有 plugin.js,只能声明跟其他插件的依赖,而不能决定其他插件的开启与否。
使用插件

插件一般通过 npm 模块的方式进行复用:

npm i egg-mysql --save

然后需要在应用或框架的 config/plugin.js 中声明:

// config/plugin.js
// 使用 mysql 插件
exports.mysql = {
  enable: true,
  package: 'egg-mysql',
};
根据环境配置

同时,我们还支持 plugin.{env}.js 这种模式,会根据运行环境加载插件配置。

// config/plugin.local.js
exports.dev = {
  enable: true,
  package: 'egg-dev',
};
引入
  • packagenpm 方式引入,也是最常见的引入方式
  • path 是绝对路径引入,如应用内部抽了一个插件,但还没达到开源发布独立 npm 的阶段,或者是应用自己覆盖了框架的一些插件
// config/plugin.js
const path = require('path');
exports.mysql = {
  enable: true,
  path: path.join(__dirname, '../lib/plugin/egg-mysql'),
};
如何写一个插件

你可以直接使用 egg-boilerplate-plugin 脚手架来快速上手。

$ mkdir egg-hello && cd egg-hello
$ npm init egg --type=plugin
$ npm i

一个插件其实就是一个『迷你的应用』,下面展示的是一个插件的目录结构,和应用(app)几乎一样。

. egg-hello
├── package.json
├── app.js (可选)
├── agent.js (可选)
├── app
│   ├── extend (可选)|   ├── helper.js (可选)|   ├── request.js (可选)|   ├── response.js (可选)|   ├── context.js (可选)|   ├── application.js (可选)|   └── agent.js (可选)
│   ├── service (可选)
│   └── middleware (可选)
│       └── mw.js
├── config
|   ├── config.default.js
│   ├── config.prod.js
|   ├── config.test.js (可选)
|   ├── config.local.js (可选)
|   └── config.unittest.js (可选)
└── test
    └── middleware
        └── mw.test.js
  1. 插件没有独立的 router 和 controller。这主要出于几点考虑:
  • 路由一般和应用强绑定的,不具备通用性。
  • 一个应用可能依赖很多个插件,如果插件支持路由可能导致路由冲突。
  • 如果确实有统一路由的需求,可以考虑在插件里通过中间件来实现。
  1. 插件需要在 package.json 中的 eggPlugin 节点指定插件特有的信息:
  • {String} name - 插件名(必须配置),具有唯一性,配置依赖关系时会指定依赖插件的 name。
  • {Array} dependencies - 当前插件强依赖的插件列表(如果依赖的插件没找到,应用启动失败)。
  • {Array} optionalDependencies - 当前插件的可选依赖插件列表(如果依赖的插件未开启,只会 warning,不会影响应用启动)。
  • {Array} env - 只有在指定运行环境才能开启,具体有哪些环境可以参考运行环境。此配置是可选的,一般情况下都不需要配置。
{
  "name": "egg-rpc",
  "eggPlugin": {
    "name": "rpc",
    "dependencies": [ "registry" ],
    "optionalDependencies": [ "vip" ],
    "env": [ "local", "test", "unittest", "prod" ]
  }
}
插件能做什么?
  • 扩展内置对象的接口
    • app/extend/request.js - 扩展 Koa#Request 类
    • app/extend/response.js - 扩展 Koa#Response 类
    • app/extend/context.js - 扩展 Koa#Context 类
    • app/extend/helper.js - 扩展 Helper 类
    • app/extend/application.js - 扩展 Application 类
    • app/extend/agent.js - 扩展 Agent 类
  • 插入自定义中间件
  • 在应用启动时做一些初始化工作
  • 设置定时任务
定时任务

会有许多场景需要执行一些定时任务,例如:

  1. 定时上报应用状态。
  2. 定时从远程接口更新本地缓存。
  3. 定时进行文件切割、临时文件删除。

框架提供了一套机制来让定时任务的编写和维护更加优雅。

编写定时任务

所有的定时任务都统一存放在 app/schedule 目录下,每一个文件都是一个独立的定时任务,可以配置定时任务的属性和要执行的方法。

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

class LogSubscription extends Subscription {
  // 通过 schedule 属性来设置定时任务的执行间隔等配置
  static get schedule() {
    return {
      interval: '1s', // 1 分钟间隔
      type: 'worker', // 指定所有的 worker 都需要执行
    };
  }

  // subscribe 是真正定时任务执行时被运行的函数
  async subscribe() {
    console.log('我是定时任务')
  }
}

module.exports = LogSubscription;

另一种写法:

module.exports = {
  schedule: {
    interval: '1s', // 1 分钟间隔
    type: 'all', // 指定所有的 worker 都需要执行
  },
  async task(ctx) {
    console.log('我是定时任务')
  },
};
参数
  • interval
    • 数字类型,单位为毫秒数,例如 5000
    • 字符类型,会通过 ms 转换成毫秒数,例如 5s
  • type
    • worker 类型:每台机器上只有一个 worker 会执行这个定时任务,每次执行定时任务的 worker 的选择是随机的。
    • all 类型:每台机器上的每个 worker 都会执行这个定时任务。
自定义启动

我们常常需要在应用启动期间进行一些初始化工作,等初始化完成后应用才可以启动成功,并开始对外提供服务。

框架提供了统一的入口文件(app.js)进行启动过程自定义,这个文件返回一个 Boot 类,我们可以通过定义 Boot 类中的生命周期方法来执行启动应用过程中的初始化工作。

框架提供了这些 生命周期函数供开发人员处理:

  • 配置文件即将加载,这是最后动态修改配置的时机(configWillLoad
  • 配置文件加载完成(configDidLoad
  • 文件加载完成(didLoad
  • 插件启动完毕(willReady
  • worker 准备就绪(didReady
  • 应用启动完成(serverDidReady
  • 应用即将关闭(beforeClose
// app.js
class AppBootHook {
    constructor(app) {
      this.app = app;
    }
  
    configWillLoad() {
         // 此时 config 文件已经被读取并合并,但是还并未生效
         // 这是应用层修改配置的最后时机
         // 注意:此函数只支持同步调用
        console.log('configWillLoad')
     
  
      // 例如:参数中的密码是加密的,在此处进行解密
    //   this.app.config.mysql.password = decrypt(this.app.config.mysql.password);
    //   // 例如:插入一个中间件到框架的 coreMiddleware 之间
    //   const statusIdx = this.app.config.coreMiddleware.indexOf('status');
    //   this.app.config.coreMiddleware.splice(statusIdx + 1, 0, 'limit');
    }
  
    async didLoad() {
        console.log('didLoad')
      // 所有的配置已经加载完毕
      // 可以用来加载应用自定义的文件,启动自定义的服务
  
      // 例如:创建自定义应用的示例
    //   this.app.queue = new Queue(this.app.config.queue);
    //   await this.app.queue.init();
  
    //   // 例如:加载自定义的目录
    //   this.app.loader.loadToContext(path.join(__dirname, 'app/tasks'), 'tasks', {
    //     fieldClass: 'tasksClasses',
    //   });
    }
  
    async willReady() {
        console.log('willReady')
      // 所有的插件都已启动完毕,但是应用整体还未 ready
      // 可以做一些数据初始化等操作,这些操作成功才会启动应用
  
      // 例如:从数据库加载数据到内存缓存
    //   this.app.cacheData = await this.app.model.query(QUERY_CACHE_SQL);
    }
  
    async didReady() {
        console.log('didReady')
      // 应用已经启动完毕
  
    //   const ctx = await this.app.createAnonymousContext();
    //   await ctx.service.Biz.request();
    }
  
    async serverDidReady() {
        console.log('serverDidReady')
      // http / https server 已启动,开始接受外部请求
      // 此时可以从 app.server 拿到 server 的实例
  
    //   this.app.server.on('timeout', socket => {
    //     // handle socket timeout
    //   });
    }
  }
  
  module.exports = AppBootHook;

注意:在自定义生命周期函数中不建议做太耗时的操作,框架会有启动的超时检测。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
报错"No .egg-info directory found in D:\Users\sf\AppData\Local\Temp\pip-pip-egg-info-c2oddhz8"表示在指定路径下找不到.egg-info目录。这个错误通常出现在使用pip安装第方库时。解决此问题的方法如下: 1. 确保你的pip版本是最新的,可以通过运行命令`pip install --upgrade pip`来更新pip。 2. 如果更新pip后仍然遇到问题,可以尝试手动安装第方库。你可以直接从提供的链接中下载压缩包,链接是:http://mirrors.aliyun.com/pypi/packages/ba/8e/aedef81641c8dca6fd0fb7294de5bed9c45f3397d67fddf755c1042c2642/PyExecJS-1.5.1.tar.gz 。 3. 将下载的压缩包解压到某一文件夹中。你可以将压缩包解压到任何你喜欢的位置,例如你可以将它解压到D:\Users\sf\文件夹下。 4. 打开命令提示符(CMD),并使用`cd`命令进入解压后的文件夹。在CMD中输入`cd D:\Users\sf`,然后按Enter键。 5. 在CMD中输入`python setup.py install`来安装第方库。这将运行setup.py脚本并安装所需的库及其依赖项 。 6. 如果以上步骤仍然不能解决问题,你可以尝试使用其他方法安装该库,例如使用conda等。 通过以上步骤,你应该能够成功解决"No .egg-info directory found"的报错,并顺利安装所需的库。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [成功解决ERROR: No .egg-info directory found in C:\Users\admin\AppData\Local\Temp\pip-pip-egg-info](https://blog.csdn.net/Csy79/article/details/129356797)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Python 报错解决| “No .egg-info directory found in xxx“问题解决方案](https://blog.csdn.net/mjc1321/article/details/129940245)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值