This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Checker和Trigger的编写
Checker
-
什么是Checker(用途)
Checker是一种用于验证和控制操作执行条件的机制。它主要用于:
- 在执行特定操作前进行条件检查
- 确保数据的一致性和完整性
- 实现业务逻辑和规则的验证
- 防止非法或不当的操作
-
Checker的几种类型
根据示例代码,Checker主要有以下几种类型:
a. row: 行级检查器,用于检查单个实体记录是否满足特定条件。
-
{ entity: "spAuction", type: "row", action: "operate", errMsg: "当前拍卖会不可操作", filter: { type: "sync", isOffline: true, iState: { $in: ["preparing", "saling", "paused"], }, }, }
b. logical: 逻辑检查器,可以执行更复杂的逻辑判断。
-
{ entity: "spAuction", type: "logical", action: "watch", checker: (operation, context, option) => { const { filter } = operation; assert(filter); const cnt = context.count( "userRelation", { filter: { userId: context.getCurrentUserId(), relation: { name: "follower", }, spAuction: filter, }, }, {} ); if (cnt instanceof Promise) { return cnt.then((cnt2) => { if (cnt2 > 0) { throw new OakUserException("您已关注"); } }); } if (cnt > 0) { throw new OakUserException("您已关注"); } }, }
c. data: 数据检查器,用于验证操作数据的有效性。
-
{ entity: 'spAuction', type: 'data', action: 'publish', checker: (data, context) => { assert(!(data instanceof Array)); const beginsAt = data.beginsAt; const endAt = data.endAt; const type = data.type; if (!beginsAt) { throw new OakPreConditionUnsetException( "error::spAuction.publish.noBeginsAt", "spAuction" ); } if (type == 'auto') { if (!endAt) { throw new OakPreConditionUnsetException( "error::spAuction.publish.noEndAt", "spAuction" ); } else if (beginsAt >= endAt) { throw new OakPreConditionUnsetException( "error::spAuction.publish.endBeforeBegin", "spAuction" ); } else { const now = Date.now(); if (now >= endAt.valueOf()) { throw new OakPreConditionUnsetException( "error::spAuction.publish.endAtTooEarly", "spAuction" ); } } } } }
-
Checker的编写规则
Checker通常包含以下几个主要部分:
-
entity: 指定检查器适用的实体类型。
-
type: 指定检查器的类型(row, logical, data)。
-
action: 指定检查器适用的操作类型(如create, update, remove等)。
- 也可以是自定义的Action
-
filter: 用于row类型检查器,定义筛选条件。
- 这个属性只在row类型的Checker才存在
-
checker: 用于logical类型检查器,定义具体的检查逻辑。
- 实际上是一个函数,用法和Trigger的fn类似,但是需要注意的是,这里的context相关调用的类型是RuntimeCxt,因为Checker不仅会运行在前端,也会运行在后端,必须检查context的调用的返回值是不是Promise再进行相应的处理,这里请务必安装oak-assistant插件,以获得完整的checker相关检查功能
-
errMsg: 定义检查失败时的错误消息。
-
注意点
a. 异步处理: 许多检查器需要处理异步操作,如数据库查询。确保正确处理Promise对象。
b. 错误处理: 使用适当的异常类型(如OakUserException, OakPreConditionUnsetException)来表示不同类型的错误。
c. 上下文使用: 充分利用context对象来获取必要的信息和执行操作。
d. 条件组合: 有时需要组合多个条件,使用$or, $in等操作符。
e. 性能考虑: 尽量减少不必要的数据库查询,优化检查逻辑。
-
其他重要内容
a. 灵活性: Checker系统允许为同一实体定义多个检查器,以处理不同的操作和场景。
b. 可扩展性: 可以根据需要轻松添加新的检查器或修改现有的检查器。
c. 代码组织: 将所有检查器集中在一个数组中,便于管理和维护。
d. 安全性: Checker在执行关键操作前提供了额外的安全层,有助于防止数据错误和恶意操作。
Trigger
-
什么是Trigger(用途)
Trigger(触发器)是一种在特定事件发生时自动执行的程序。在您的框架中,Trigger用于在实体(如订单)的某些操作(如创建、更新、取消等)发生前后执行一些额外的逻辑。这些逻辑可能包括更新相关实体、发送通知、执行计算等。 Trigger的主要用途包括:
- 自动化业务逻辑
- 维护数据一致性
- 执行附加操作
- 实现复杂的业务规则
-
Trigger的几种类型
根据您的代码,Trigger可以分为以下几种类型: a. 按执行时机分类:
- Before Trigger:在主操作执行之前触发
- After Trigger:在主操作执行之后触发 b. 按触发实体和动作分类:
- 单一动作Trigger:针对特定实体的特定动作触发
- 多动作Trigger:针对特定实体的多个动作触发 c. 按功能分类:
- 更新Trigger:更新相关实体的数据
- 通知Trigger:向订阅者推送消息
- 计算Trigger:执行一些计算或业务逻辑
-
Trigger的编写规则
根据您的代码,Trigger的编写规则如下: a. Trigger定义为一个对象,包含以下属性:
- name: Trigger的名称(描述其功能)
- entity: 触发器所针对的实体
- action: 触发的动作(可以是单个字符串或字符串数组)
- when: 触发时机('before' 或 'after')
- fn: 触发器执行的函数 b. 触发器函数 (fn) 接收三个参数:
- operation: 包含当前操作的相关信息(如filter、data等)
- context: 运行时上下文,提供各种操作方法
- option: 额外的选项参数 c. 触发器函数通常是异步的,返回一个Promise d. 触发器函数可以使用context对象执行各种操作,如查询数据、更新实体等
-
注意点
a. 性能考虑:Trigger可能会影响系统性能,特别是在高频操作上。应谨慎使用,并优化触发器的逻辑。 b. 事务处理:在某些情况下,可能需要使用事务来确保触发器操作的原子性。 c. 错误处理:应该妥善处理触发器中可能出现的错误,以防止影响主要操作。 d. 循环触发:要注意避免触发器之间的循环调用,这可能导致无限循环。 e. 测试:由于触发器可能影响多个相关实体,应该进行全面的测试,包括单元测试和集成测试。 f. 文档化:应该详细记录每个触发器的功能、触发条件和潜在影响,以便于维护和调试。 g. 权限控制:确保触发器在适当的权限下执行,特别是当它们执行敏感操作时。 通过合理使用Trigger,可以实现复杂的业务逻辑,提高系统的自动化程度和数据一致性。但同时也要注意控制复杂度,避免过度使用导致系统难以维护。
下面是context调用的方式、参数声明和返回值的详细文档。
- 不同类型Trigger的编写示例 a. Before Trigger 示例:
{
name: '订单付款前更新付款时间',
entity: 'order',
action: 'payAll',
when: 'before',
fn: async ({ operation }, context, option) => {
const { data } = operation;
data.payAt = Date.now();
return 1;
}
}
b. After Trigger 示例:
{
name: '订单完成后更新完成时间',
entity: 'order',
action: ['receive', 'take'],
when: 'after',
fn: async ({ operation }, context, option) => {
const { filter } = operation;
const finishAt = Date.now();
await context.operate('order', {
id: await generateNewIdAsync(),
action: 'update',
data: { finishAt },
filter: { id: filter.id }
}, {});
return 1;
}
}
c. 多动作Trigger示例:
{
name: '订单状态变化时通知订阅者',
entity: 'order',
action: ['startPaying', 'payAll', 'cancel', 'send', 'receive'],
when: 'after',
fn: async ({ operation }, context, option) => {
const { filter, id } = operation;
if (filter?.id) {
context.saveOperationToEvent(id, `orderStateChange-${filter.id}`);
}
return 1;
}
}
- Context调用方式、参数声明和返回值文档 Context对象提供了多个方法来执行各种操作。以下是一些常用方法的详细文档: a. context.select(entity, query, options) 用途:查询实体数据 参数:
- entity: string - 要查询的实体名称
- query: Object - 查询条件,包含data(要获取的字段)和filter(过滤条件)
- options: Object - 可选参数,如排序、分页等 返回值:Promise - 返回查询结果数组 示例:
const [order] = await context.select('order', {
data: { id: 1, status: 1 },
filter: { id: orderId }
}, {});
b. context.operate(entity, operation, options) 用途:执行实体操作(如创建、更新、删除等) 参数:
- entity: string - 要操作的实体名称
- operation: Object - 操作详情,包含action、data、filter等
- options: Object - 可选参数 返回值:Promise