3 个钩子覆盖状态转换生命周期:Workflow 的 on_entry、on_exit、on_transition
2026/8/30 13:02:45 网站建设 项目流程

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 个站点,顺序是固定的:

钩子执行顺序表

顺序站点发生时机一句话说明
1before_transition转换开始前做校验,拦下就终止,转换不会发生
2on_transition转换进行中不关心具体状态,只记录从哪到哪
3on_exit离开旧状态时收尾:释放资源、存状态快照
4on_entry进入新状态时初始化:设默认值、发通知
5after_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_entryprior_state转换前的旧状态
on_entrytriggering_event触发本次转换的事件
on_entry*args事件方法附带的额外参数
on_exitnew_state即将进入的新状态
on_exittriggering_event触发本次转换的事件
on_exit*args事件方法附带的额外参数
on_transitionfrom源状态
on_transitionto目标状态
on_transitionevent触发本次转换的事件
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 end

pending 的出口卡在离开前确认库存,paid 的入口卡完成扣款,cancelled 的入口卡触发退款。审计日志不写进任何一个状态,统一由 on_transition 兜底——以后新增 shipped → returned 之类的转换,审计代码一行不用动。

所以:钩子之间互不侵入,新增转换不需要改旧钩子。

✅ 避坑检查清单

  • ✅ 一个钩子只干一件事:扣款和发通知拆开写,排查时不会两件事纠缠在一起。
  • ✅ 业务参数走 *args 显式传入:钩子里不用翻全局对象,也方便单独测试。
  • ✅ 钩子保持幂等:消息可能重投,写日志、发通知前先判重。
  • ✅ 校验放在转换发生前:在 before 阶段拦下,就不会出现改了一半的状态。
  • ⚠️ 别在 on_entry 里再触发状态转换:会递归钻进流水线,调用栈容易失控。
  • ⚠️ 别依赖 on_exit 留下的临时变量给 on_entry 用:两者之间状态正在切换,顺序一变就埋雷。
  • ⚠️ 耗时操作别塞进 before_transition:它每次转换都要跑,会拖慢整条流水线。
  • ⚠️ on_transition 只放审计和日志:业务分支逻辑还是放回具体状态里。

所以:✅ 是写法习惯,⚠️ 是容易写炸的地方,动手前过一遍能省不少排查时间。

🚀 上手建议

  1. 先跑一次真实转换,把 5 个工位的执行顺序亲眼确认一遍,再往里填业务逻辑。
  2. 想看调度细节,翻 gem 源码lib/workflow.rb:L110–L129 是转换主流程,L214–L232 是钩子参数拼装。
  3. 状态变多以后,把 on_transition 的审计输出接到日志系统,比在每个状态里手写日志省心。

所以:先挑一条业务状态流试点,钩子用熟了,再往复杂场景上加。

【免费下载链接】uBlockuBlock Origin - An efficient blocker for Chromium and Firefox. Fast and lean.项目地址: https://gitcode.com/GitHub_Trending/ub/uBlock

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询