3 个钩子覆盖状态转换生命周期:Workflow 的 on_entry、on_exit、on_transition
【免费下载链接】uBlockuBlock Origin - An efficient blocker for Chromium and Firefox. Fast and lean.项目地址: https://gitcode.com/GitHub_Trending/ub/uBlock
Ruby 状态机 Workflow 把钩子函数挂在状态转换的关键节点上:on_entry 进状态、on_exit 离状态、on_transition 跟全程,3 个钩子就能覆盖一次状态转换的完整生命周期。
🔍 先看全景:一次状态转换里发生了什么
先别急着写钩子。把 pending → paid 这样一次转换拆成 5 个站点,顺序是固定的:
钩子执行顺序表
| 顺序 | 站点 | 发生时机 | 一句话说明 |
|---|---|---|---|
| 1 | before_transition | 转换开始前 | 做校验,拦下就终止,转换不会发生 |
| 2 | on_transition | 转换进行中 | 不关心具体状态,只记录从哪到哪 |
| 3 | on_exit | 离开旧状态时 | 收尾:释放资源、存状态快照 |
| 4 | on_entry | 进入新状态时 | 初始化:设默认值、发通知 |
| 5 | after_transition | 转换完成后 | 写日志、埋点 |
可以把状态对象想象成流水线上的工单:每过一个工位就被处理一次。on_exit 和 on_entry 分别挂在旧、新两个状态的工位上,on_transition 则是架在整条线之上的摄像头,每张工单经过都拍一帧。这个顺序在 gem 源码lib/workflow.rbL110–L129 中有明确实现,也是 Ruby 状态机钩子执行顺序的标准答案。
所以:5 个工位的先后是固定的,先记住这张顺序表,再谈每个钩子具体干什么。
🎯 三个钩子的职责边界:一张表讲清楚
| 钩子 | 触发时机 | 典型职责 | 参数签名 | 一句比喻 |
|---|---|---|---|---|
| on_entry | 进入新状态时(状态级) | 初始化数据、发通知、启动后续流程 | (prior_state, event, *args) | 入口卡:到新工位先打卡、领物料 |
| on_exit | 离开旧状态前(状态级) | 资源释放、离开校验、状态快照 | (new_state, event, *args) | 出口卡:离岗时清点工具、签退 |
| on_transition | 转换进行中(工作流级) | 全局审计、转换跟踪、耗时统计 | (from, to, event, *args) | 摄像头:每张工单经过都留一帧 |
on_entry 和 on_exit 挂在状态上,只在特定状态生效;on_transition 挂在工作流上,定义一次,所有转换通用。选哪个,先问一句:这段逻辑只属于某个状态,还是对所有转换都成立?
所以:状态专属逻辑放 on_entry / on_exit,跨状态通用逻辑放 on_transition。
📋 参数速查表:三个钩子各收什么
| 钩子 | 参数 | 含义 |
|---|---|---|
| on_entry | prior_state | 转换前的旧状态 |
| on_entry | triggering_event | 触发本次转换的事件 |
| on_entry | *args | 事件方法附带的额外参数 |
| on_exit | new_state | 即将进入的新状态 |
| on_exit | triggering_event | 触发本次转换的事件 |
| on_exit | *args | 事件方法附带的额外参数 |
| on_transition | from | 源状态 |
| on_transition | to | 目标状态 |
| on_transition | event | 触发本次转换的事件 |
| on_transition | *args | 事件方法附带的额外参数 |
on_entry 用法示例里最常见的组合是 prior_state + *args:既知道工单从哪来,又拿得到业务参数。这些签名定义在lib/workflow.rbL214–L232。
所以:三个钩子的参数结构几乎一致,差别只在"另一个状态"指的是旧状态(prior_state)还是新状态(new_state)。
📦 贯穿案例:给订单系统装上钩子
订单钩子案例
4 个状态的订单,3 个钩子各就各位:
class Order include Workflow workflow do initial_state :pending state :pending, on_exit: ->(s, e) { verify_stock! } # 出口卡 state :paid, on_entry: ->(s, e) { charge_card! } # 入口卡 state :shipped, on_entry: ->(s, e) { notify_warehouse! } state :cancelled, on_entry: ->(s, e) { refund! } # ... pay / ship / cancel 等事件声明略 on_transition { |from, to, ev, *args| AuditLog.record(from, to, ev, args) } # 摄像头 end endpending 的出口卡在离开前确认库存,paid 的入口卡完成扣款,cancelled 的入口卡触发退款。审计日志不写进任何一个状态,统一由 on_transition 兜底——以后新增 shipped → returned 之类的转换,审计代码一行不用动。
所以:钩子之间互不侵入,新增转换不需要改旧钩子。
✅ 避坑检查清单
- ✅ 一个钩子只干一件事:扣款和发通知拆开写,排查时不会两件事纠缠在一起。
- ✅ 业务参数走 *args 显式传入:钩子里不用翻全局对象,也方便单独测试。
- ✅ 钩子保持幂等:消息可能重投,写日志、发通知前先判重。
- ✅ 校验放在转换发生前:在 before 阶段拦下,就不会出现改了一半的状态。
- ⚠️ 别在 on_entry 里再触发状态转换:会递归钻进流水线,调用栈容易失控。
- ⚠️ 别依赖 on_exit 留下的临时变量给 on_entry 用:两者之间状态正在切换,顺序一变就埋雷。
- ⚠️ 耗时操作别塞进 before_transition:它每次转换都要跑,会拖慢整条流水线。
- ⚠️ on_transition 只放审计和日志:业务分支逻辑还是放回具体状态里。
所以:✅ 是写法习惯,⚠️ 是容易写炸的地方,动手前过一遍能省不少排查时间。
🚀 上手建议
- 先跑一次真实转换,把 5 个工位的执行顺序亲眼确认一遍,再往里填业务逻辑。
- 想看调度细节,翻 gem 源码
lib/workflow.rb:L110–L129 是转换主流程,L214–L232 是钩子参数拼装。 - 状态变多以后,把 on_transition 的审计输出接到日志系统,比在每个状态里手写日志省心。
所以:先挑一条业务状态流试点,钩子用熟了,再往复杂场景上加。
【免费下载链接】uBlockuBlock Origin - An efficient blocker for Chromium and Firefox. Fast and lean.项目地址: https://gitcode.com/GitHub_Trending/ub/uBlock
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考