Ruby on Rails 向邮箱发送验证码

92 阅读3分钟

Ruby on Rails 通过邮箱来发送内容为6位随机数的验证码

发邮件

rails提供了发邮件的功能,跟着文档来即可

  1. 创建 Mailer
bin/rails generate mailer User
  1. 指定发送者邮箱
# app/mailers/application_mailer.rb
class ApplicationMailer < ActionMailer::Base
  default from: "chili@x.com"
  layout "mailer"
end
  1. 添加 welcome_email 的方法

该方法将向用户注册的电子邮件地址发送电子邮件

# app/mailers/user_mailer.rb

class UserMailer < ApplicationMailer
      def welcome_email(code)
        @code = code
        mail(to: "apple@x.com", subject: 'hi')
      end
end
  1. 编辑邮箱正文
# views/user_mailer/welcome_email.html.erb

<!DOCTYPE html>
<html>
  <head>
    <meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
  </head>
  <body>
  你正在登录池理记账,验证码为:<code><%= @code %></code>
  </body>
</html>
  1. 配置邮箱参数

在qq邮箱的设置界面开启第三方服务可获取授权码。

由于授权码不可公开,需要将授权码添加至密钥

# config/environments/$RAILS_ENV.rb

  config.action_mailer.raise_delivery_errors = true
  config.action_mailer.perform_caching = false

  config.action_mailer.delivery_method = :smtp
  config.action_mailer.smtp_settings = {
    address:              'smtp.qq.com',
    port:                 587,
    domain:               'smtp.qq.com',
    user_name:            'chili@x.com',   
    password:             Rails.application.credentials.email_password,
    authentication:       'plain',
    enable_starttls_auto: true,
    open_timeout:         10,
    read_timeout:         10
  }
  1. 测试 开启服务
bin/rails c

发送验证码

UserMailer.welcome_email(1234).deliver

控制台不报错,查看邮箱是否收到验证码。

请求接口生成验证码

  1. modal 创建模型 确定字段和限制
bin/rails g model ValidationCode email:string kind:string used_at:datetime

同步数据库

bin/rails db:migrate
  1. router
Rails.application.routes.draw do
      resources :validation_codes, only: [:create]
end
  1. controller

使用SecureRandom生成随机字符串

 bin/rails g controller validation_codes create   

class ValidationCodesController < ApplicationController
  def create
    code = SecureRandom.random_number.to_s[2..7]
    validation_code = ValidationCode.new email: params[:email], kind: 'sign_in', code: code
    if validation_code.save
      head 200
      else
        render json: {
          errors: validation_code.errors
        }
    end
  end
end
  1. 测试
  • 使用curl
curl -X POST http://127.0.0.1:3000/api/v1/validation_codes\?email\='apple@x.com'  -v
  • rspec request
 bin/rails generate rspec:request validation_codes   

# spec/requests/validation_codes_spec.rb

require 'rails_helper'

RSpec.describe "ValidationCodes", type: :request do
  describe "create validationCode" do
    it "send" do
      post '/validation_codes', params: {email: 'apple@x.com'}
      expect(response).to have_http_status(200)
    end
  end
end

验证

bundle exec rspec

将验证码发送到邮箱

  • 找最新的 code
# app/mailers/user_mailer.rb

class UserMailer < ApplicationMailer
      def welcome_email(email)
        validation_code = ValidationCode.order(created_at: :desc).find_by_email(email)
        @code = validation_code.code
        mail(to: email, subject: '池理记账验证码')
      end
end
  • 60s不能重复发验证码并且返回状态码429
# app/controllers/api/v1/validation_codes_controller.rb 

class ValidationCodesController < ApplicationController
  def create
    if ValidationCode.exists?(email: params[:email], kind:'sign_in',created_at: 1.minute.ago..Time.now)
        render status: :too_many_requests
        return
    end
    code = SecureRandom.random_number.to_s[2..7]
    validation_code = ValidationCode.new email: params[:email], kind: 'sign_in', code: code
    if validation_code.save
      UserMailer.welcome_email(validation_code.email)
      render status: 200
    else
      render json: {
        errors: validation_code.errors
      }
    end
  end
end
  • 测试

使用 rspec request

# spec/requests/validation_codes_spec.rb

require 'rails_helper'

RSpec.describe "ValidationCodes", type: :request do
  describe "验证码" do
    it "发送太频繁就会返回 429" do
      post '/api/v1/validation_codes', params: {email: 'chili_sauce@qq.com'}
      expect(response).to have_http_status(200)
      post '/api/v1/validation_codes', params: {email: 'chili_sauce@qq.com'}
      expect(response).to have_http_status(429)
    end
  end
end

验证

rspec

也可通过控制台验证

bin/rails c
validation_code = ValidationCode.new email: 'chili_sauce@qq.com', kind: 'sign_in', code: 1111
validation_code.save
UserMailer.welcome_email('chili_sauce@qq.com').deliver

重构

使用勾子函数 简化 controller

# app/models/validation_code.rb

class ValidationCode < ApplicationRecord
    # email必填
    validates :email, presence: true
    enum kind: {sign_in: 0, reset_password: 1}

    # before_create 只在创建数据库项目时执行
    before_create :generate_code
    after_create :send_email

    def generate_code
        self.code = SecureRandom.random_number.to_s[2..7]
    end
    def send_email
        UserMailer.welcome_email(self.email)
    end
end
# app/controllers/api/v1/validation_codes_controller.rb

class ValidationCodesController < ApplicationController
  def create
    if ValidationCode.exists?(email: params[:email], kind:'sign_in',created_at: 1.minute.ago..Time.now)
      render status: :too_many_requests
      return
    end
    validation_code = ValidationCode.new email: params[:email], kind: 'sign_in'
    if validation_code.save
      render status: 200
    else
      render json: {errors: validation_code.errors}, status: 400
    end
  end
end

如何添加密码?

执行

EDITOR="code --wait" bin/rails credentials:edit

在新文件中写入 email_password: xxx,

通过Rails.application.credentials.email_password 获取对应的值。

参考:guides.rubyonrails.org/action_mail…