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.shnenglu.com/Files/cpunion/login_engine.rar