路由

  • config/routes 里添加 resources :sessions, only: [:create, :destroy] 只需要这两个 API
  • 手动创建 app/modal/session.rb,因为 session 并不需要存在数据库中,内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Session
    include ActiveModel::Model // 不能像 user 那样继承 ActiveRecord,但是我们又需要用到 Rails 提供的 class 里面的便利方法,所以这里需要引入模块 ActiveModel
    attr_accessor :email, :password

    validates_presence_of :email, :password
    validates_format_of :email, with: /.+@.+/, if: :email
    validates_length_of :password, minimum: 6, on: [:create], if: :password

    end

Controller

  • 使用命令创建 controller ,bin/rails g controller sessions,追加两个方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class SessionsController < ApplicationController
    def create
    session = Session.new create_params
    session.validate
    render_resource session
    end
    def destroy

    end
    def create_params
    params.permit(:email, :password)
    end
    end

追加一个自定义的验证,验证邮箱是否存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Session
include ActiveModel::Model
attr_accessor :email, :password, :user

validates_presence_of :email, :password
validate :check_email_present, if: :email
validates_format_of :email, with: /.+@.+/, if: :email
validates_length_of :password, minimum: 6, if: :password
validate :check_email_password_match, if: Proc.new {|s| s.email.present? and s.password.present?}

def check_email_present
user ||= User.find_by_email email
if user.nil?
errors.add :email, :not_present
end
end

def check_email_password_match
user ||= User.find_by_email email
if user and not user.authenticate(password)
errors.add :password, :not_match
end
end
end

attr_accessor :xxx 做了啥

    1. @xxx
    1. def xxx 获取 @xxx 的值
    1. def xxx= 赋值给 @xxx
为了避免出错,完善一下上面的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Session
include ActiveModel::Model
attr_accessor :email, :password, :user

validates_presence_of :email, :password
validate :check_email_present, if: :email
validates_format_of :email, with: /.+@.+/, if: :email
validates_length_of :password, minimum: 6, if: :password
validate :check_email_password_match, if: Proc.new {|s| s.email.present? and s.password.present?}

def check_email_present
@user ||= User.find_by_email email
if @user.nil?
errors.add :email, :not_present
end
end

def check_email_password_match
@user ||= User.find_by_email email
if @user and not @user.authenticate(password)
errors.add :password, :not_match
end
end
end

增加 session 中间件

config/application.rb
https://edgeguides.rubyonrails.org/api_app.html

1
2
3
config.session_store :cookie_store, key: '_monery_session_id'
config.middleware.use ActionDispatch::Cookies
config.middleware.use config.session_store, config.session_options

增加 session

1
2
3
4
5
6
def create
s = Session.new create_params
s.validate
render_resource s
session[:current_user_id] = s.user.id
end

再给 user 增加一个根据 session_id 获取当前用户的路由
config/routes

1
get '/current_user_info', to: 'user#current_user_info'

app/controllers/users_controller.rb

1
2
3
4
5
def current_user_info
@user_id = session[:current_user_id]
@user = User.find_by_id @user_id
render_resource @user
end

记住密码

切记不可把用户密码直接存在 localStorage
Rails 官方没有提供最佳实践
因为做法各不相同
思路

  1. 当用户记住密码时,带上相关字段发给服务器
  2. 服务器接受以后,下发一个随机数(r)并存在数据库和 session
  3. 当过了一段时间,session 过期以后,只剩下 r 被带上发送给服务器
  4. 服务器检查 r 是否有效且正确(比如记住密码7天有效),有效中就直接派发一个新的 session,
  5. 而数据库需要在 user 表中多增加两个字段 login_token 和 login_token_expired_at 即可
  6. 但是如果需要做多个设备的记住密码,上面方案就不够用了,因为新设备登陆,会覆盖数据库,所以我们就需要新增一张 user 的关联表,用来记录多个 login_token

三句话实现注销功能

config/routes,由于 Rails 定义的 destory 方法需要传一个 id,所以我们需要重新定义一个方法

1
delete 'sessions', to: 'sessions#destroy'
1
2
3
4
def destroy
session[:current_user_id] = nil
head 200
end