Login Engine是非常好用的一个登录engine,不过也有个缺点,它把用户信息缓存在session里。如果用户每次修改完自己的资料,都把session更新的话,自然是不会有什么数据不同步的问题。不过试想这样一种情况:
1、用户A登录;用户A的信息将保存在session[:user]里。
2、管理员操作用户A,修改用户A的资料并保存。
3、用户A刷新页面。
如果显示用户资料是从session[:user]读取的话,显然用户A看到的是老的资料。
正确的做法是管理员修改用户资料以后,把用户session里的内容也更新,当然这个实施起来有些困难,目前看来无法由用户ID获得对应的session。
有朋友说session里不应该缓存用户信息,而应只保存用户ID。这是正确的,这样可以解决上面的问题,不过带来的问题是每次都要从数据库查询。
如果每次刷新页面都从数据库重新读取用户信息,对性能影响是很大的。试想一下用户正在浏览一个论坛的帖子列表,这个页面可能所有用户看起来都是一样的,唯一不一样的地方是上面用户信息的显示。由于大部分内容都一样,可以使用缓存加快浏览速度。不过却由于session里只保存了用户ID,不得不读取数据库来获得用户信息,这样就把速度又拖慢了。
所以应该把用户信息缓存起来,但要保证它能及时更新。方法自己做一个缓存管理器,能根据用户ID得到用户信息,也能随时更新它。
学着ActionController::Caching做了一个UserManager,它可以根据线程配置来自动开关互斥器:
(/vender/plugins/login_engine/lib/login_engine/user_management.rb)
module UserManagement
#
:nodoc:
class UnthreadedUserManager
#
:nodoc:
def initialize
#
:nodoc:
@users
=
{}
end
def get(user_id)
@users
[user_id]
end
def set(user_id
,
user)
@users
[user_id]
=
user
end
end
module ThreadSafety
#
:nodoc:
def get(user_id)
#
:nodoc:
@mutex
.
synchronize { super }
end
def set(user_id
,
user)
#
:nodoc:
@mutex
.
synchronize { super }
end
end
class UserManager
<
UnthreadedUserManager
def initialize
super
if
ActionController
::
Base
.
allow_concurrency
@mutex
=
Mutex
.
new
UserManager
.
send
(
:
include
,
ThreadSafety)
end
end
end
@
@user_manager
=
UserManagement
::
UserManager
.
new
def set_current_user(user)
return
session[
:
user_id]
=
nil
if
user
.
nil
?
session[
:
user_id]
=
user
.
id
cache_user(user)
end
def current_user
get_user(session[
:
user_id])
end
def cache_user(user)
return
if
user
.
nil
?
@
@user_manager
.
set(user
.
id
,
user)
end
def get_user(user_id)
@
@user_manager
.
get(user_id)
end
end
修改(/verdor/plugins/login_engine/lib/login_engine.rb):
#.
require 'login_engine/user_management'
module LoginEngine
include UserManagement
#.
end 加入上面加粗的2行。
修改(/verdor/plugins/login_engine/lib/login_engine/authenticated_system.rb),把session[:user]替换为session[:user_id]。
修改(/verdor/plugins/login_engine/app/controllers/user_controller.rb):
def login
return if generate_blank
@user = User.new(params[:user])
if user = User.authenticate(params[:user][:login], params[:user][:password])
user.logged_in_at = Time.now
user.save
set_current_user(user)
flash[:notice] = "Login successful"
redirect_to_stored_or_default :action => 'home'
else
@login = params[:user][:login]
flash.now[:warning] = 'Login unsuccessful'
end
end
def logout
set_current_user(nil)
redirect_to :action => 'login'
end
def get_user_to_act_on
@user = current_user
end
简单测试:
require 'login_engine'
class ApplicationController < ActionController::Base
include LoginEngine
helper :user
model :user
before_filter :login_required
end
class ShowController < ApplicationController
def show
render_text "User name: #{current_user.first_name}"
end
end
class AdminController < ApplicationController
def edit
user = User.find(params[:id])
user.update_attributes(:first_name => params[:name])
cache_user(user)
render_text "User name: #{user.first_name}"
end
end
一个简单的模拟:
1、用户A从IE登录,访问/show/show,将显示用户的名字。
2、管理员从FF登录,访问/show/show,将显示管理员名字。
3、管理员访问/show/show/2?name=hello,其中2是用户A的ID。这将把用户A的名字修改为hello。
4、用户A刷新页面,可以看到显示的用户名字已经发生变化。
以上过程说这个修改已经达到目的。实现这个功能并不难,主要是为了保留Login Engine原有的功能不变。
修改后的代码:
www.cppblog.com/Files/cpunion/login_engine.rar