nocobase/docs/zh-CN/development/server/events.md
2022-11-06 14:07:53 +08:00

4.7 KiB

事件

NocoBase 在应用、插件、数据库的生命周期中提供了非常多的事件监听,这些方法只有在触发了事件之后才会执行。

如何添加事件监听?

事件的注册一般放于 afterAdd 或 beforeLoad 中

export class MyPlugin extends Plugin {
  // 插件添加进来之后,有没有激活都执行 afterAdd()
  afterAdd() {
    this.app.on();
    this.db.on();
  }

  // 只有插件激活之后才会执行 beforeLoad()
  beforeLoad() {
    this.app.on();
    this.db.on();
  }
}

db.on

数据库相关事件与 Collection 配置、Repository 的 CRUD 相关,包括:

  • 'beforeSync' / 'afterSync'
  • 'beforeValidate' / 'afterValidate'
  • 'beforeCreate' / 'afterCreate'
  • 'beforeUpdate' / 'afterUpdate'
  • 'beforeSave' / 'afterSave'
  • 'beforeDestroy' / 'afterDestroy'
  • 'afterCreateWithAssociations'
  • 'afterUpdateWithAssociations'
  • 'afterSaveWithAssociations'
  • 'beforeDefineCollection'
  • 'afterDefineCollection'
  • 'beforeRemoveCollection' / 'afterRemoveCollection

更多详情参考 Database API

app.on()

app 的事件与应用的生命周期相关,相关事件有:

  • 'beforeLoad' / 'afterLoad'
  • 'beforeInstall' / 'afterInstall'
  • 'beforeUpgrade' / 'afterUpgrade'
  • 'beforeStart' / 'afterStart'
  • 'beforeStop' / 'afterStop'
  • 'beforeDestroy' / 'afterDestroy'

更多详情参考 Application API

示例

我们继续以简单的在线商店来举例,相关的数据表建模可以回顾 数据表和字段 部分的示例。

创建订单后减商品库存

通常我们的商品和订单是不同的数据表。客户在下单以后把商品的库存减掉可以解决超卖的问题。这时候我们可以针对创建订单这个数据操作定义相应的事件,在这个时机一并解决库存修改的问题:

class ShopPlugin extends Plugin {
  beforeLoad() {
    this.db.on('orders.afterCreate', async (order, options) => {
      const product = await order.getProduct({
        transaction: options.transaction
      });

      await product.update({
        inventory: product.inventory - order.quantity
      }, {
        transaction: options.transaction
      });
    });
  }
}

因为默认 Sequelize 的事件中就携带事务等信息,所以我们可以直接使用 transaction 以保证两个数据操作都在同一事务中进行。

同样的,也可以在创建发货记录后修改订单状态为已发货:

class ShopPlugin extends Plugin {
  load() {
    this.db.on('deliveries.afterCreate', async (delivery, options) => {
      const orderRepo = this.db.getRepository('orders');
      await orderRepo.update({
        filterByTk: delivery.orderId,
        value: {
          status: 2
        }
        transaction: options.transaction
      });
    });
  }
}

随应用同时存在的定时任务

在不考虑使用工作流插件等复杂情况下,我们也可以通过应用级的事件实现一个简单的定时任务机制,且可以与应用的进程绑定,退出后就停止。比如我们希望定时扫描所有订单,超过签收时间后自动签收:

class ShopPlugin extends Plugin {
  timer = null;
  orderReceiveExpires = 86400 * 7;

  checkOrder = async () => {
    const expiredDate = new Date(Date.now() - this.orderReceiveExpires);
    const deliveryRepo = this.db.getRepository('deliveries');
    const expiredDeliveries = await deliveryRepo.find({
      fields: ['id', 'orderId'],
      filter: {
        status: 0,
        createdAt: {
          $lt: expiredDate
        }
      }
    });
    await deliveryRepo.update({
      filter: {
        id: expiredDeliveries.map(item => item.get('id')),
      },
      values: {
        status: 1
      }
    });
    const orderRepo = this.db.getRepository('orders');
    const [updated] = await orderRepo.update({
      filter: {
        status: 2,
        id: expiredDeliveries.map(item => item.get('orderId'))
      },
      values: {
        status: 3
      }
    });

    console.log('%d orders expired', updated);
  };

  load() {
    this.app.on('beforeStart', () => {
      // 每分钟执行一次
      this.timer = setInterval(this.checkOrder, 1000 * 60);
    });

    this.app.on('beforeStop', () => {
      clearInterval(this.timer);
      this.timer = null;
    });
  }
}

小结

通过上面的示例,我们基本了解了事件的作用和可以用于扩展的方式:

  • 数据库相关的事件
  • 应用相关的事件

本章涉及的示例代码整合在对应的包 packages/samples/shop-events 中,可以直接在本地运行,查看效果。