如何基于Meeting模型的ends_at字段在会议结束时执行函数,并适配会议取消或重排场景?
如何基于Meeting模型的ends_at字段在会议结束时执行函数,并适配会议取消或重排场景?
我之前做项目时也碰到过一模一样的问题,用延迟作业来回删改任务确实搞得代码乱糟糟的——要么每次改时间就得删旧任务发新的,要么一堆任务堆着执行前还要反复校验,太麻烦了。这里分享几个更清爽的解决方案:
方案一:定时轮询(中小项目首选,省心!)
不用纠结每次会议变化都去调整任务,直接跑一个周期性定时任务(比如每分钟执行一次),每次轮询最近刚结束、还没处理过结束事件,并且没被取消的会议。处理完之后给会议加个标记(比如post_processed: true),防止重复执行。
- 优点:逻辑超级简单,不用管任务的增删改,出问题也好排查;
- 缺点:会有一点延迟(取决于你轮询的间隔),如果会议量特别大的话可能有性能损耗,但中小项目完全没问题。
举个伪代码例子(用Sidekiq Cron的话):
class ProcessEndedMeetingsJob include Sidekiq::Job def perform # 筛选出过去1分钟内结束、未处理、未取消的会议 Meeting.where( "ends_at <= ? AND ends_at >= ?", Time.current, 1.minute.ago, post_processed: false, cancelled: false ).find_each do |meeting| # 执行你需要的结束后逻辑 meeting.run_post_end_actions meeting.update(post_processed: true) end end end
方案二:唯一延迟任务(追求精准触发的话用这个)
给每个会议的结束任务加个唯一标识(比如"meeting_end_#{meeting.id}"),每次会议的ends_at或者取消状态变化时,先删掉旧的任务,再重新创建新的延迟任务。很多任务队列(比如Sidekiq、Delayed Job)都支持唯一任务,实在没有的话自己写个检查逻辑也不难。
- 优点:触发时间精准,没有轮询的延迟;
- 缺点:需要处理任务的更新逻辑,但比你之前那种“发一堆任务再挨个校验”要清晰太多。
伪代码示例(Sidekiq场景):
class Meeting < ApplicationRecord # 保存后更新对应的结束任务 after_save :sync_end_job private def sync_end_job # 先清理旧的任务 Sidekiq::Queue.new.each do |job| if job.args == [self.id] && job.item["class"] == "MeetingEndJob" job.delete end end # 只有会议没取消的情况下,才创建新的延迟任务 unless cancelled? MeetingEndJob.set(wait_until: ends_at).perform_async(self.id) end end end # 执行会议结束逻辑的任务 class MeetingEndJob include Sidekiq::Job def perform(meeting_id) meeting = Meeting.find_by(id: meeting_id) # 最后再做一次兜底校验:防止任务创建后会议又被取消/改时间了 return if meeting.nil? || meeting.cancelled? || meeting.ends_at > Time.current meeting.run_post_end_actions end end
方案三:事件驱动调度(复杂系统适配)
如果你的系统已经有事件总线或者专门的调度服务,可以监听Meeting的ends_at变更、取消事件,动态调整调度任务。比如用Rufus Scheduler结合Active Record的回调,实现任务的动态增删,这种方式扩展性最强,但适合已经有一定架构基础的项目。
总结
如果是中小项目,直接用定时轮询就够省心;要是对触发时间的精准度要求高,就选唯一延迟任务方案;复杂系统的话可以考虑事件驱动的方式。
内容来源于stack exchange
相关产品推荐
相关产品推荐

