10

用户订阅包含最新视频的电子邮件,但他们还设置了何时收到这些电子邮件。

Subscription(user_id, frequency, day, time, time_zone)

user_id  |  frequency  |  day    |  time   |  time_zone
1        |  daily      |  null   |  16:00  |  GMT
2        |  weekly     |  friday |  11:00  |  UTC
3        |  weekly     |  monday |  18:00  |  EST

我们如何才能在用户在他们的时区选择的确切时间和频率发送电子邮件而不会搞砸(例如发送双重电子邮件或错过时间)

唯一的频率是每天和每周,如果每天,则该天为空。

我为此使用redis作为数据库,让我知道如何以正确的方式做到这一点!

4

6 回答 6

2

我将使用resque-scheduler gem扩展 fmendez 的答案。

首先,让我们创建发送电子邮件的工作人员

class SubscriptionWorker
  def self.perform(subscription_id)
    subscription = Subscription.find subscription_id

    # ....
    # handle sending emails here
    # ....

    # Make sure that you don't have duplicate workers
    Resque.remove_delayed(SubscriptionWorker, subscription_id)        

    # this actually calls this same worker but sets it up to work on the next
    # sending time of the subscription.  next_sending_time is a method that
    # we implement in the subscription model.
    Resque.enqueue_at(subscription.next_sending_time, SubscriptionWorker, subscription_id)
  end
end

在您的订阅模型中,添加一种next_sending_time方法来计算下一次应发送电子邮件的时间。

# subscription.rb
def next_sending_time
  parsed_time = Time.parse("#{time} #{time_zone}") + 1.day

  if frequency == 'daily'
    parsed_time
  else
    # this adds a day until the date matches the day in the subscription
    while parsed_time.strftime("%A").downcase != day.downcase
      parsed_time += 1.day
    end
  end
end
于 2013-02-23T09:31:35.393 回答
1

这更像是一个系统级别的问题,而不是特定于 ruby​​ 的问题。

首先,将您所有的时间都存储为 GMT。时区不是真实的,除了作为偏移视图中时间的首选项设置(在用户表中)。进行数学运算的坐标系应该是一致的。

然后每个频率对应一个 cronjob 设置:每天、星期一、星期二等。实际上,从表中的数据来看,您不需要两列,除非您看到这正在发生变化。

然后,当 cron 触发时,使用调度程序(如 linux AT)来处理电子邮件何时发出。这更像是一个系统级别的调度问题,或者至少,我更相信系统可以处理这个问题。它需要处理系统重启的情况,包括服务和应用程序。

然而,需要注意的是,如果您要发送大量邮件,您真的不能保证邮件会根据偏好发送。实际上,您可能不得不将其限制为避免被转储/阻止(也就是说,1000 条/消息分布在一个小时内)。不同的网络会有不同的使用门槛。

所以基本上:

cron -> at -> 发送邮件

于 2013-02-28T18:03:17.727 回答
1

我过去曾delayed_job用于类似的任务。可能您可以使用与resque. 本质上,您必须在当前作业结束时安排下一个作业。

class Subscription < ActiveRecord::Base
  after_create :send_email        
  def send_email 
    # do stuff and then schedule the next run
  ensure
    send_email
  end
  handle_asynchronously :send_email, :run_at => Proc.new{|s| s.deliver_at }

  def daily? (frequency == "daily");end
  def max_attempts 1;end

  def time_sec
    hour,min=time.split(":").map(&:to_i)
    hour.hours + min.minutes
  end

  def days_sec
    day.nil? ? 0 : Time::DAYS_INTO_WEEK[day.to_sym].days
  end

  def interval_start_time
    time = Time.now.in_time_zone(time_zone)
    daily? ?  time.beginning_of_day : time.beginning_of_week
  end

  def deliver_at
    run_at = interval_start_time + days_sec + time_sec
    if time.past?
      run_at = daily? ? run_at.tomorrow : 1.week.from_now(run_at)
    end
    run_at
  end        
end

重新安排注意事项

更新代码以处理循环终止。您可以通过添加一个boolean名为activetrue默认设置为)的列来处理此问题。要禁用订阅,请将列设置为 false。

  def send_email
    return unless active?
    # do stuff and then schedule the next run
  ensure
    send_email if active?
  end

max_attempts作业的 设置为 1。否则您将淹没队列。在上述解决方案中,send_email将尝试一次的作业。

于 2013-02-23T02:39:26.420 回答
0

您可以使用DelayedJobResque Gems,它们将计划任务的每个实例表示为数据库表中的一行。有一些方法可以触发、安排和从日历中撤回任务。

于 2013-02-22T18:47:52.620 回答
0

我认为您可以为此目的利用此 gem:https ://github.com/bvandenbos/resque-scheduler

问候,

于 2013-02-19T18:21:05.337 回答
0

我刚刚为我的项目实现了这个,我发现一个非常简单的方法是使用Whenever Gem(在这里找到https://github.com/javan/whenever)。

要开始,请转到您的应用并输入

gem 'whenever'

然后使用:

wheneverize .

在终端。这将在您的配置文件夹中创建一个 schedule.rb 文件。

你把你的规则放在你的 schedule.rb 中(如下所示)并让它们调用某些方法——例如,我调用模型方法 DataProviderUser.cron 它将运行我在那里的任何代码。

创建此文件后,要在命令行上启动 cron 作业,请使用:

whenever
whenever -w

whenever -c

停止/清除 cron 作业。

github 上的 gems 文档非常有用,但我建议您将输出设置为您自己的日志文件(如下所示)。希望有帮助:)

在我的 schedule.rb 我有:

set :output, 'log/cron.log'
every :hour do
runner "DataProviderUser.cron('hourly')"
end

every :day do
runner "DataProviderUser.cron('daily')"
end

every '0 0 * * 0' do
runner "DataProviderUser.cron('weekly')"
end

every 14.days do
runner "DataProviderUser.cron('fortnightly')"
end

every :month do
runner "DataProviderUser.cron('monthly')"
end
于 2013-02-24T11:46:39.567 回答