Gem Devise async send email

bajiudongfeng · 发布于 2017年04月09日 · 最后由 bajiudongfeng 回复于 2017年04月12日 · 381 次阅读
14935

1. 解决方案

  • 在user这个model 下添加如下代码
# Override Devise to send mails with async
def send_devise_notification(notification, *args)
  devise_mailer.send(notification, self, *args).deliver_later
end

默认的话使用devise内部内置的发送邮件的方法,也可以进行覆盖,自定义邮件发送.但是要注意修改devise.rb文件:

# Configure the class responsible to send e-mails.
# config.mailer = 'Devise::Mailer'

  • 后台进行邮件处理的可以选择Resque,sidekiq等,此处使用sidekiq 在application.rb文件中加入如下代码:

config.active_job.queue_adapter = :sidekiq

  • devise 默认的邮件发送使用的redis队列名称是 mailers,因此要在sidekiq.yml文件中加入此队列.

- [mailers, 3]

2. 运行结果

development.log 文件:

[ActiveJob] Enqueued ActionMailer::DeliveryJob (Job ID: 6a5e3f3f-bccf-4fc7-89e0-f16f63918aa0) to Sidekiq(mailers) with arguments: "Devise::Mailer", "reset_password_instructions", "deliver_now", #<0x00000005839500 gid:>>, "CpfoqSDN3z6zvA9yiUsL", {}

sidekiq.log 文件:

2017-04-09T08:53:57.511Z 24351 TID-y44z8 ActionMailer::DeliveryJob JID-fede8f1837ccca19b5dbf262 INFO: start
2017-04-09T08:53:58.493Z 24351 TID-y44z8 ActionMailer::DeliveryJob JID-fede8f1837ccca19b5dbf262 INFO: done: 0.983 sec

3. 源码探究

  • 打印 send_devise_notification(notification, *args) 参数信息:

I, [2017-04-09T17:00:56.308408 #21573] INFO -- : notification: reset_password_instructions
I, [2017-04-09T17:00:56.308512 #21573] INFO -- : args: ["hmNJj3Gszwvswd2VjxQL", {}]
I, [2017-04-09T17:00:56.498526 #21573] INFO -- : devise_mailer: Devise::Mailer Class

因此可知 类: Devise::Mailer 方法: reset_password_instructions

  • 源码文件路径: devise/app/mailers/devise/mailer.rb
if defined?(ActionMailer)
  class Devise::Mailer < Devise.parent_mailer.constantize
    include Devise::Mailers::Helpers

    def confirmation_instructions(record, token, opts={})
      @token = token
      devise_mail(record, :confirmation_instructions, opts)
    end

    def reset_password_instructions(record, token, opts={})
      @token = token
      devise_mail(record, :reset_password_instructions, opts)
    end

    def unlock_instructions(record, token, opts={})
      @token = token
      devise_mail(record, :unlock_instructions, opts)
    end

    def email_changed(record, opts={})
      devise_mail(record, :email_changed, opts)
    end

    def password_change(record, opts={})
      devise_mail(record, :password_change, opts)
    end
  end
end

因此:devise_mailer会根据不同的参数调用不同的方法.

  • Devise.parent_mailer.constantize 解析:
# devise/lib/devise.rb
module Devise
  # The parent mailer all Devise mailers inherit from.
  # Defaults to ActionMailer::Base. This should be set early
  # in the initialization process and should be set to a string.
  mattr_accessor :parent_mailer
  @@parent_mailer = "ActionMailer::Base"
end

  • 发送邮件操作进一步解析

发送邮件的方法 devise_mail 定义在 module Devise::Mailers::Helpers中,这是发送邮件的具体实现.

# 发送邮件 record 就是User记录, action 是 :reset_password_instructions
def devise_mail(record, action, opts = {}, &block)
  initialize_from_record(record)
  mail headers_for(action, opts), &block
end

 # 一些初始化的工作
def initialize_from_record(record)
  @scope_name = Devise::Mapping.find_scope!(record)
  @resource   = instance_variable_set("@#{devise_mapping.name}", record)
end

# 设置一些头信息
def headers_for(action, opts)
  headers = {
    subject: subject_for(action),
    to: resource.email,
    from: mailer_sender(devise_mapping),
    reply_to: mailer_reply_to(devise_mapping),
    template_path: template_paths,
    template_name: action
  }.merge(opts)

  @email = headers[:to]
  headers
end

headers 的结果类似:

headers: {:subject=>"重置密码信息", :to=>"14172@qq.com", :from=>"infoabc@qq.com", :reply_to=>"infoabc@qq.com", :template_path=>["devise/mailer"], :template_name=>:reset_password_instructions}

共收到 2 条回复
1107

其实关键就是

def devise_mail(record, action, opts = {}, &block)
  initialize_from_record(record)
  mail headers_for(action, opts), &block
end

最后调用的是 mail 方法,是 ActionMailer 的东西(Devise 啰嗦那么多就是不想和项目的 Mailer 代码绑定),然后提出了 send_devise_notification(notification, *args) 接口,所以核心逻辑就是帮Devise 去用 ActiveJob 对 Mailer 的强化为发送邮件异步化...

14935

@jasl 是啊,devise 把发送邮件相关的方法都实现好了,而要异步发送就需要加入后台队列.因此这个方法就出现了.

# Override Devise to send mails with async
def send_devise_notification(notification, *args)
  devise_mailer.send(notification, self, *args).deliver_later
end
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册