在Rails中用Sidekiq实现按用户自定义时间发消息及任务管理
嘿,这个需求我之前帮朋友落地过类似的,核心就是把每个用户的本地时间转成UTC来调度Sidekiq任务,再加上修改偏好时的旧任务清理逻辑,一步步来很清晰👇
实现步骤拆解
1. 先给User模型加偏好配置字段
首先得把用户的通知偏好存在数据库里,比如本地提醒时间、时区、通知方式。先生成迁移文件:
rails generate migration AddNotificationPreferencesToUsers preferred_time:string timezone:string notification_method:string rails db:migrate
然后在User模型里补充验证和核心方法:
class User < ApplicationRecord # 验证字段格式,避免无效值 validates_format_of :preferred_time, with: /\A\d{2}:\d{2}\z/, allow_blank: false validates_inclusion_of :timezone, in: ActiveSupport::TimeZone.all.map(&:name), allow_blank: false validates_inclusion_of :notification_method, in: ['email', 'text', 'chatbot'], allow_blank: false # 计算下次通知的UTC时间(Sidekiq调度依赖UTC) def next_notification_time # 把当前用户时区的日期,改成他偏好的时间点 local_target_time = Time.now.in_time_zone(timezone).change( hour: preferred_time.split(':')[0].to_i, min: preferred_time.split(':')[1].to_i ) # 如果今天的时间已经过了,就顺延到明天 local_target_time += 1.day if local_target_time < Time.now.in_time_zone(timezone) # 转成UTC时间返回 local_target_time.utc end # 清理这个用户已有的待执行通知任务 def clear_existing_notification_jobs Sidekiq::ScheduledSet.new.each do |job| # 精准匹配属于该用户的NotificationWorker任务 if job.klass == 'NotificationWorker' && job.args[0] == id job.delete end end end # 调度下一次的通知任务 def schedule_next_notification clear_existing_notification_jobs next_time = next_notification_time NotificationWorker.perform_at(next_time, id) end # 当用户修改偏好时,自动重新调度任务 after_save :schedule_next_notification, if: -> { preferred_time_changed? || timezone_changed? || notification_method_changed? } end
2. 写Sidekiq Worker处理通知逻辑
创建app/workers/notification_worker.rb,负责实际发送通知并循环调度下一次任务:
class NotificationWorker include Sidekiq::Worker def perform(user_id) user = User.find_by(id: user_id) return unless user # 用户不存在就直接终止,避免报错 # 根据用户选择的方式发送通知 case user.notification_method when 'email' # 替换成你的邮件发送逻辑,比如调用Mailer UserMailer.daily_alert(user).deliver_now when 'text' # 短信发送逻辑,比如对接Twilio/阿里云短信 SmsService.send(user.phone, "你的每日提醒来了!") when 'chatbot' # 聊天机器人通知,比如Slack/企业微信机器人 ChatbotService.notify(user.chat_id, "每日提醒:今天也要加油哦!") end # 任务执行完后,自动调度下一次的通知(实现每日循环) user.schedule_next_notification end end
3. 初始化现有用户的任务
如果是已有用户,需要批量给他们调度第一次任务,写个Rake任务:
创建lib/tasks/notifications.rake:
namespace :notifications do desc "初始化所有用户的每日通知任务" task schedule_initial: :environment do User.find_each do |user| # 只给有完整配置的用户调度 next unless user.preferred_time.present? && user.timezone.present? && user.notification_method.present? user.schedule_next_notification end puts "✅ 所有用户的初始通知任务已调度完成!" end end
运行初始化命令:
rake notifications:schedule_initial
4. 关键细节提醒
- 时区转换:一定要把用户的本地时间转成UTC,Sidekiq的定时任务是基于UTC时区的,直接用本地时间调度会出现时间偏差。
- 任务去重:用户修改偏好时必须先清理旧任务,不然会出现同一个用户收到多条重复通知的情况。
- 异常容错:Worker里加了
User.find_by而非User.find,避免用户被删除后任务报错中断。
这样整个流程就跑通了:用户设置偏好后自动调度任务,每天按他的本地时间发送通知,修改偏好时旧任务会被清理,重新调度新的任务。
内容的提问来源于stack exchange,提问作者user402516
相关产品推荐
相关产品推荐

