事件

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

如何添加事件监听?

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

1export class MyPlugin extends Plugin {
2  // 插件添加进来之后,有没有激活都执行 afterAdd()
3  afterAdd() {
4    this.app.on();
5    this.db.on();
6  }
7
8  // 只有插件激活之后才会执行 beforeLoad()
9  beforeLoad() {
10    this.app.on();
11    this.db.on();
12  }
13}

db.on

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

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

app.on

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

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

示例

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

创建订单后减商品库存

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

1class ShopPlugin extends Plugin {
2  beforeLoad() {
3    this.db.on('orders.afterCreate', async (order, options) => {
4      const product = await order.getProduct({
5        transaction: options.transaction,
6      });
7
8      await product.update(
9        {
10          inventory: product.inventory - order.quantity,
11        },
12        {
13          transaction: options.transaction,
14        },
15      );
16    });
17  }
18}

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

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

1class ShopPlugin extends Plugin {
2  load() {
3    this.db.on('deliveries.afterCreate', async (delivery, options) => {
4      const orderRepo = this.db.getRepository('orders');
5      await orderRepo.update({
6        filterByTk: delivery.orderId,
7        value: {
8          status: 2
9        }
10        transaction: options.transaction
11      });
12    });
13  }
14}

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

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

1class ShopPlugin extends Plugin {
2  timer = null;
3  orderReceiveExpires = 86400 * 7;
4
5  checkOrder = async () => {
6    const expiredDate = new Date(Date.now() - this.orderReceiveExpires);
7    const deliveryRepo = this.db.getRepository('deliveries');
8    const expiredDeliveries = await deliveryRepo.find({
9      fields: ['id', 'orderId'],
10      filter: {
11        status: 0,
12        createdAt: {
13          $lt: expiredDate,
14        },
15      },
16    });
17    await deliveryRepo.update({
18      filter: {
19        id: expiredDeliveries.map((item) => item.get('id')),
20      },
21      values: {
22        status: 1,
23      },
24    });
25    const orderRepo = this.db.getRepository('orders');
26    const [updated] = await orderRepo.update({
27      filter: {
28        status: 2,
29        id: expiredDeliveries.map((item) => item.get('orderId')),
30      },
31      values: {
32        status: 3,
33      },
34    });
35
36    console.log('%d orders expired', updated);
37  };
38
39  load() {
40    this.app.on('beforeStart', () => {
41      // 每分钟执行一次
42      this.timer = setInterval(this.checkOrder, 1000 * 60);
43    });
44
45    this.app.on('beforeStop', () => {
46      clearInterval(this.timer);
47      this.timer = null;
48    });
49  }
50}

小结

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

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