我最终不得不对我的代码进行几处更改。我将在完成后展示我最终得到的结果,然后解释它的作用:
class SessionTimeoutController < ApplicationController
# These are what prevent check_time_until_logout and
# reset_user_clock from resetting users' Timeoutable
# Devise "timers"
prepend_before_action :skip_timeout, only: [:check_time_until_logout, :has_user_timed_out]
def skip_timeout
request.env["devise.skip_trackable"] = true
end
skip_before_filter :authenticate_user!, only: [:has_user_timed_out]
def check_time_until_logout
@time_left = Devise.timeout_in - (Time.now - user_session["last_request_at"]).round
end
def has_user_timed_out
@has_timed_out = (!current_user) or (current_user.timedout? (user_session["last_request_at"]))
end
def reset_user_clock
# Receiving an arbitrary request from a client automatically
# resets the Devise Timeoutable timer.
head :ok
end
end
这些是我所做的更改:
使用env["devise.skip_trackable"]
这是防止 Devise 重置由于不活动而注销用户之前等待多长时间的代码:
prepend_before_action :skip_timeout, only: [:check_time_until_logout, :has_user_timed_out]
def skip_timeout
request.env["devise.skip_trackable"] = true
end
此代码更改 Devise 内部使用的哈希值,以决定是否更新它存储的值以跟踪用户上次活动的时间。具体来说,这是我们正在与之交互的设计代码 (link):
Warden::Manager.after_set_user do |record, warden, options|
scope = options[:scope]
env = warden.request.env
if record && record.respond_to?(:timedout?) && warden.authenticated?(scope) && options[:store] != false
last_request_at = warden.session(scope)['last_request_at']
if record.timedout?(last_request_at) && !env['devise.skip_timeout']
warden.logout(scope)
if record.respond_to?(:expire_auth_token_on_timeout) && record.expire_auth_token_on_timeout
record.reset_authentication_token!
end
throw :warden, :scope => scope, :message => :timeout
end
unless env['devise.skip_trackable']
warden.session(scope)['last_request_at'] = Time.now.utc
end
end
end
(请注意,每次 Rails 处理来自客户端的请求时都会执行此代码。)
接近结尾的这些行对我们来说很有趣:
unless env['devise.skip_trackable']
warden.session(scope)['last_request_at'] = Time.now.utc
end
这是“重置”倒计时的代码,直到用户因不活动而退出。仅当env['devise.skip_trackable'] 不是true 时才会执行,因此我们需要在 Devise 处理用户请求之前更改该值。
为此,我们告诉 Rails 在它做任何其他事情之前更改 env['devise.skip_trackable'] 的值。同样,来自我的最终代码:
prepend_before_action :skip_timeout, only: [:check_time_until_logout, :has_user_timed_out]
def skip_timeout
request.env["devise.skip_trackable"] = true
end
以上所有内容都是我需要更改的内容才能回答我的问题。不过,我还需要进行一些其他更改才能让我的代码按我的意愿工作,所以我也会在这里记录它们。
正确使用Timeoutable
我误读了有关 Timeoutable 模块的文档,因此我的问题中的代码还有其他一些问题。
首先,我的check_time_until_logout 方法将始终返回相同的值。这是我执行的操作的错误版本:
def check_time_until_logout
@time_left = current_user.timeout_in
end
我认为Timeoutable#timeout_in 会返回用户自动注销之前的时间量。相反,它返回 Devise 配置为在注销用户之前等待的时间量。我们需要计算用户离开自己的时间。
要计算这一点,我们需要知道用户上次进行 Devise 识别的活动是什么时候。这段代码,来自我们上面看到的设计源,确定用户上次活动的时间:
last_request_at = warden.session(scope)['last_request_at']
我们需要获得warden.session(scope) 返回的对象的句柄。它看起来像 user_session 哈希,Devise 为我们提供的句柄 current_user 和 user_signed_in? 就是那个对象。
使用user_session 哈希并计算我们自己的剩余时间,check_time_until_logout 方法变为
def check_time_until_logout
@time_left = Devise.timeout_in - (Time.now - user_session["last_request_at"]).round
end
我还误读了Timeoutable#timedout? 的文档。当您输入用户上次活动的时间时,它会检查用户是否超时,不是,当您输入当前时间时。我们需要做的改变很简单:我们需要在user_session 哈希中传递时间,而不是传递Time.now:
def has_user_timed_out
@has_timed_out = (!current_user) or (current_user.timedout? (user_session["last_request_at"]))
end
完成这三项更改后,我的控制器就会按照我的预期行事。