随笔 - 147  文章 - 71  trackbacks - 0
<2009年3月>
22232425262728
1234567
891011121314
15161718192021
22232425262728
2930311234

常用链接

留言簿(1)

随笔分类(146)

随笔档案(147)

文章分类(28)

文章档案(28)

喜欢的Blog

搜索

  •  

最新评论

阅读排行榜

评论排行榜

JSP作为后起之秀能够在服务器编程环境中占据一定地位,是和它良好支持一系列业界标准密切相关的。Session就是它提供的基础设施之一。作为一个程序员,你可以不介意具体在客户端是如何实现,就方便的实现简单的基于session的用户管理。

现在对于处理在线用户,有几种不同的处理方法。一种是页面刷新由用户控制,服务器端控制一个超时时间比如30分钟,到了时间之后用户没有动作就被踢出。这种方法的优点是,如果用户忘了退出,可以防止别人恶意操作。缺点是,如果你在做一件很耗时间的事情,超过了这个时间限制,submit的时候可能要再次面临登陆。如果原来的页面又是强制失效的话,就有可能丢失你做的工作。在实现的角度来看,这是最简单的,Server端默认实现的就是这样的模式。

另一种方式是,站点采用框架结构,有一个Frame或者隐藏的iframe在不断刷新,这样你永远不会被踢出,但是服务器端为了判断你是否在线,需要定一个发呆时间,如果超过这个发呆时间你除了这个自动刷新的页面外没有刷新其他页面的话,就认为你已经不在线了。采取这种方式的典型是xici.net。 他的优点是可以可以利用不断的刷新实现一些类似server-push的功能,比如网友之间发送消息。

不管哪一种模式,为了实现浏览当前所有的在线用户,还需要做一些额外的工作。servlet API中没有得到Session列表的API。

可以利用的是Listener. Servlet 2.2和2.3规范在这里略微有一些不一样。2.2中HttpSessionBindingListener可以实现当一个HTTPSession中的Attribute变化的时候通知你的类。而2.3中还引入了HttpSessionAttributeListener.鉴于我使用的环境是Visual age for java 4和JRun server 3.1,他们还不直接支持Servlet 2.3的编程,这里我用的是HttpSessionBindingListener.

    需要做的事情包括做一个新的类来实现HttpSessionBindingListener接口。这个接口有两个方法:
public void valueBound(HttpSessionBindingEvent event),和
public void valueUnbound(HttpSessionBindingEvent event)。
当你执行Session.addAttribute(String,Object)的时候,如果你已经把一个实现了HttpSessionBindingListener接口的类加入为Attribute,Session会通知你的类,调用你的valueBound方法。相反,Session.removeAttribute方法对应的是valueUndound方法。
 1public class HttpSessionBinding implements javax.servlet.http.HttpSessionBindingListener 
 2{
 3    ServletContext application = null;
 4
 5    public HttpSessionBinding(ServletContext application)
 6    {
 7        super();
 8        if (application ==null)
 9            throw new IllegalArgumentException("Null application is not accept.");
10        
11        this.application = application;
12        
13    }

14
15    public void valueBound(javax.servlet.http.HttpSessionBindingEvent e) 
16    
17        Vector activeSessions = (Vector) application.getAttribute("activeSessions");
18        if (activeSessions == null)
19        {
20            activeSessions = new Vector();
21        }

22
23        JDBCUser sessionUser = (JDBCUser)e.getSession().getAttribute("user");
24        if (sessionUser != null)
25        {
26            activeSessions.add(e.getSession());
27        }

28        application.setAttribute("activeSessions",activeSessions);
29    }

30
31    public void valueUnbound(javax.servlet.http.HttpSessionBindingEvent e) 
32    {
33        JDBCUser sessionUser = (JDBCUser)e.getSession().getAttribute("user");
34        if (sessionUser == null)
35        {
36            Vector activeSessions = (Vector) application.getAttribute("activeSessions");
37            if (activeSessions != null)
38            {
39                activeSessions.remove(e.getSession().getId());
40                application.setAttribute("activeSessions",activeSessions);
41            }

42        }

43    }

44}

假设其中的JDBCUser类是一个任意User类。在执行用户登录时,把User类和HttpSessionBinding类都加入到Session中去。这样,每次用户登录后,在application中的attribute "activeSessions"这个vector中都会增加一条记录。每当session超时,valueUnbound被触发,在这个vector中删去将要被超时的session。
 1public void login()
 2throws ACLException,SQLException,IOException
 3{
 4    /* get JDBC User Class */
 5    if (user != null)
 6    {
 7        logout();
 8    }

 9    {
10        // if session time out, or user didn't login, save the target url temporary.
11
12        JDBCUserFactory uf = new JDBCUserFactory();
13
14        if ( (this.request.getParameter("userID")==null
15            || (this.request.getParameter("password")==null)  )
16        {
17            throw new ACLException("Please input a valid userName and password.");
18        }

19        
20        JDBCUser user = 
21            (JDBCUser) uf.UserLogin(
22                this.request.getParameter("userID"),
23                this.request.getParameter("password") );
24        user.touchLoginTime();
25        this.session.setAttribute("user",user);
26        this.session.setAttribute("BindingNotify",new HttpSessionBinding(application));
27    }

28}

这两个函数位于一个HttpSessionManager类中.这个类引用了jsp里面的application全局对象。这个类的其他代码和本文无关且相当长,我就不贴出来了。

下面来看看jsp里面怎么用。
假设一个登录用的表单被提交到doLogin.jsp, 表单中包含UserName和password域。

节选部分片段:
 1<%
 2    HttpSessionManager hsm = new HttpSessionManager(application,request,response);
 3    try
 4    {
 5        hsm.login();
 6    }

 7    catch ( UserNotFoundException e)
 8    {
 9        response.sendRedirect("InsufficientPrivilege.jsp?detail=User%20does%20not%20exist.");
10        return;
11    }

12    catch (    InvalidPasswordException e2)
13    {    
14        response.sendRedirect("InsufficientPrivilege.jsp?detail=Invalid%20Password");
15        return;
16    }

17    catch ( Exception e3)
18    {
19    %>    Error:<%=e3.toString() %><br>
20        Press <a href="login.jsp">Here</a> to relogin.
21<%        return;
22    }

23    response.sendRedirect("index.jsp");
24%>

再来看看现在我们怎么得到一个当前在线的用户列表。
 1<body bgcolor="#FFFFFF">
 2<table cellspacing="0" cellpadding="0" width="100%">
 3
 4    <tr >
 5      <td style="width:24px">SessionId
 6      </td>
 7      <td style="width:80px" >User
 8      </td>
 9      <td style="width:80px" >Login Time
10      </td>
11      <td style="width:80px" >Last access Time
12      </td>
13    </tr>
14<%
15    Vector activeSessions = (Vector) application.getAttribute("activeSessions");
16    if (activeSessions == null)
17    {
18        activeSessions = new Vector();
19        application.setAttribute("activeSessions",activeSessions);
20    }
21    
22    Iterator it = activeSessions.iterator();
23    while (it.hasNext())
24    {
25        HttpSession sess = (HttpSession)it.next();
26        JDBCUser sessionUser = (JDBCUser)sess.getAttribute("user");
27        String userId = (sessionUser!=null)?sessionUser.getUserID():"None";
28
%>
29    <tr>
30          <td nowrap=''><%= sess.getId() %></td>
31          <td nowrap=''><%= userId %></td>
32          <td nowrap=''>
33        <%=  BeaconDate.getInstance( new java.util.Date(sess.getCreationTime())).getDateTimeString()%></td>
34          <td class="<%= stl %>3" nowrap=''>
35        <%=  BeaconDate.getInstance( new java.util.Date(sess.getLastAccessedTime())).getDateTimeString()%></td>
36    </tr>
37<%
38    }
39
%>
40</table>
41</body>

以上的代码从application中取出activeSessions,并且显示出具体的时间。其中BeaconDate类假设为格式化时间的类。

这样,我们得到了一个察看在线用户的列表的框架。至于在线用户列表分页等功能,与本文无关,不予讨论。
这是一个非刷新模型的例子,依赖于session的超时机制。我的同事sonymusic指出很多时候由于各个厂商思想的不同,这有可能是不可信赖的。考虑到这种需求,需要在每个页面刷新的时候都判断当前用户距离上次使用的时间是否超过某一个预定时间值。这实质上就是自己实现session超时。如果需要实现刷新模型,就必须使用这种每个页面进行刷新判断的方法。

附:关闭浏览器窗口,释放session的问题
在主页中添加以下代码:
 1<script type="text/javascript">
 2function MM_callJS(jsStr) {
 3    return eval(jsStr);
 4}

 5    
 6function removeline() {
 7    if (event.clientX < 0 && event.clientY < 0{
 8        document.write('<iframe width="100" height="100" src="logout.jsp"></iframe><OBJECT classid=CLSID:8856F961-340A-11D0-A96B-00C04FD705A2 height=0  id=WebBrowser width=0></OBJECT>');
 9        document.all.WebBrowser.ExecWB(451);
10    }

11}

12
</script>
13
14<frameset id="frame1" onUnload="MM_callJS('removeline()')">
代码解释:
if(event.clientX<0&&event.clientY<0)判断浏览器是关闭还是刷新,因为刷新也会调用onunload。
document.all.WebBrowser.ExecWB(45,1);是无提示的关闭浏览器。
classid=CLSID:8856F961-340A-11D0-A96B-00C04FD705A2
这个是调用不弹出对话框的方法,实际是调用系统的方法如下
document.all.WebBrowser.ExecWB(45,1);
因为在javascript当中不能调用java方法,所以选择另外写一个logout.jsp文件用于调用invalidate()方法。
注意:session在调用invalidate()方法前要removeAttribute掉相关的name。



限制用户重复登录
OnlineUserBindingListener.java
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

public class OnlineUserBindingListener implements HttpSessionBindingListener {
         String username;
        
public OnlineUserBindingListener(){
        }
        
        
public OnlineUserBindingListener(String username){
            
this.username=username;
        }
        
public void valueBound(HttpSessionBindingEvent event) {
            HttpSession session 
= event.getSession();
            ServletContext application 
= session.getServletContext();
            
// 把用户名放入在线列表
            List onlineUserList = (List) application.getAttribute("onlineUserList");
            
// 第一次使用前,需要初始化
            if (onlineUserList == null) {
                onlineUserList 
= new ArrayList();
                
            }
            onlineUserList.add(
this.username);
            application.setAttribute(
"onlineUserList", onlineUserList);
        }

        
public void valueUnbound(HttpSessionBindingEvent event) {
            HttpSession session 
= event.getSession();
            ServletContext application 
= session.getServletContext();

            
// 从在线列表中删除用户名
            List onlineUserList = (List) application.getAttribute("onlineUserList");
            onlineUserList.remove(
this.username);
            SimpleDateFormat sdf 
= new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            System.out.println(
this.username + "退出系统。");
            System.out.println(
"退出时间"+sdf.format(new Date()));
        }
    
}
登陆验证
OnlineUserBindingListener online = new OnlineUserBindingListener();
List list 
= (List) application.getAttribute("onlineUserList");
boolean flag = false;
if (list != null) {
    
for (int i = 0; i < list.size(); i++) {
        String param 
= (String) list.get(i);
        
if (param.equals(username)) {
            flag 
= true;
            
break;
        }
    }
}
if (flag) {
    out.println(
"<script language='javascript'>alert('该用户正在使用中!');"
        
+ "parent.location.href='login.jsp';</script>");
else {
    session.setAttribute(
"onlineUserBindingListener",
        
new OnlineUserBindingListener(username));
}
退出系统
// 从在线列表中删除用户名
List onlineUserList = (List) application.getAttribute("onlineUserList");
onlineUserList.remove(session.getAttribute(
"username"));
session.invalidate();

posted on 2009-03-19 20:45 飞翔天使 阅读(806) 评论(0)  编辑  收藏 所属分类: JSP

只有注册用户登录后才能发表评论。


网站导航: