qiezi的学习园地

AS/C/C++/D/Java/JS/Python/Ruby

  C++博客 :: 首页 :: 新随笔 ::  ::  :: 管理 ::

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
posted on 2006-05-08 21:13 qiezi 阅读(220) 评论(0)  编辑 收藏 引用 所属分类: Ruby