阳光从树叶细缝中,露出了笑容,温暖了我的美梦

0%

统计当前网站在线用户信息的简单实例

“本例基于Web基础的servlet展开,虽然较简单,但对Web的三大组件进行了结合。 ”

主要实现三个功能

  • 提供用户登录界面,提供注销功能。
  • 显示当前网站的所有登录用户信息
  • 管理员可以踢除指定的登录用户

友情提示: Web的三大组件都需要在web.xml中配置哦!

登录界面 login.jsp

这里比较简单,假设什么人都能进入,不涉及数据库

1
2
3
4
<form action="${pageContext.request.contextPath }/LoginServlet" method="post">
用户名:<input type="text" name="username" /><br>
<input type="submit" value="提交"/>
</form>

处理登录请求 LoginServlet

由于什么人都能进入,所有我们省去了校验用户名等操作,即假设登录成功。我们将拿到的用户数据放到session域中。核心代码如下:

1
2
3
4
5
6
7
String username = request.getParameter("username");
HttpSession session = request.getSession(true);
session.setAttribute("user", username);
// 把登录用户的IP地址存放到session中
session.setAttribute("ip", request.getRemoteHost());
// 跳转到用户主页
response.sendRedirect(request.getContextPath() + "/index.jsp");

此时我们已经将用户的数据存入到了session域当中去了。但如果我们的用户名是中文的很有可能出现乱码。这里我们需要添加一个解决post乱码的代码。
在最前面加上:request.setCharacterEncoding("utf-8");很显然,这句代码是复用的,每一个servlet都需要它的支持,这时候我们想到另一种一劳永逸的方法,就是添加一个过滤器,对其进行过滤,解决代码复写问题,详细代码见post/get乱码过滤器解决方案

监听器 OnLineListener

由于我们需要得到所有在线用户的信息,就需要监听user属性名称的增加。响应的接口的介绍参见HttpSessionAttributeLisener
下面是该监听器的具体代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class OnLineListener implements HttpSessionAttributeListener {
/**
* 当用户登录成功后,会执行session.setAttribute("user", username); 该方法用于监听用户的user属性的添加
*/

@Override
public void attributeAdded(HttpSessionBindingEvent event) {
// 得到属性名
String name = event.getName();
ServletContext context = event.getSession().getServletContext();
//如果取出的属性名是user的话,即拿到用户名
if ("user".equals(name)) {
// 同步的代码块,为了避免多个登录用户同时操作onLine数据时引发的并发问题
synchronized (OnLineListener.class) {
// 1 把当前登录的session对象封装到map集合中
// 1.1 先从context域中获取session数据
Map<String, HttpSession> onLine = (Map<String, HttpSession>) context
.getAttribute("onLine");
// 1.2 如果网站第一个登录用户,onLine为null,这时新建一个map集合
if (onLine == null) {
onLine = new HashMap<String, HttpSession>();
}
// 1.3把当前用户的session存入map集合
HttpSession session = event.getSession();
onLine.put(session.getId(), session);
// 2 把封装好的map保存到context域中
context.setAttribute("onLine", onLine);
}
}
}

@Override
public void attributeReplaced(HttpSessionBindingEvent event) {

}
//这部分代码可先不看,阅读之后内容回头再看
@Override
public void attributeRemoved(HttpSessionBindingEvent event) {
String name = event.getName();
ServletContext context = event.getSession().getServletContext();
String sessionId = event.getSession().getId();
if ("user".equals(name)) {
// 1 获取context域中的map集合
Map<String, HttpSession> onLine = (Map<String, HttpSession>) context
.getAttribute("onLine");
// 2 移除对应的session对象
onLine.remove(sessionId);
// 3 把修改后的map保存到context域中
context.setAttribute("onLine", onLine);
}
}
}

web.xml中的配置

1
2
3
<listener>
<listener-class>cn.ninja.web.OnLineListener</listener-class>
</listener>

注意: 这里的编程技巧,onLine是先取再判断是否为空,空则说明是第一个用户,在新建一个Map。这样的写法完美的避免了每一次进入监听器都会重新建立一个Map的错误。并且,这里选择将Map放入context域中,其原因是context都能取得到,而session虽然也能取得到,但其每一个session对应一个用户使其并不合适放所有用户的集合。

网站主页 index.jsp

主要包括显示用户名,安全退出功能,查看在线登录用户功能

1
2
3
欢迎回来,${sessionScope.user },
<a href="${pageContext.request.contextPath }/LogoutServlet">【安全退出】</a><br/>
<a href="${pageContext.request.contextPath }/GetOnLineServlet">【查看在线登录用户】</a>

处理查看在线用户请求 GetOnLineServlet

我们想要获得在线用户的登录名,ip地址,登录时间,最后访问时间。但是,我们之前获得的Map没办法直接取到这些值。所以我们这里先定义一个JavaBean取名为OnLineBean,来封装这些属性:

1
2
3
4
5
6
private String sessionId;// session对象的id,它的作用我们之后再讲
private String name;// 登录名
private String ip;// ip
private String loginTime;// 登录时间
private String lastTime;// 最后访问时间
//getter setter方法

以下我们给出GetOnLineServlet的核心代码,用于获得每个属性,并转发到在线用户信息界面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 1从context域中取出map集合
Map<String, HttpSession> onLine = (Map<String, HttpSession>) this
.getServletContext().getAttribute("onLine");
// 2 创建一个新的List集合
List<OnLineBean> list = new ArrayList<OnLineBean>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
// 3遍历map集合
if (onLine != null) {
synchronized (GetOnLineServlet.class) {
for (Entry<String, HttpSession> entry : onLine.entrySet()) {
OnLineBean bean = new OnLineBean();
bean.setSessionId(entry.getKey());
HttpSession session = entry.getValue();
bean.setName((String) session.getAttribute("user"));
bean.setIp((String) session.getAttribute("ip"));
bean.setLoginTime(sdf.format(new Date(session
.getCreationTime())));
bean.setLastTime(sdf.format(new Date(session
.getLastAccessedTime())));
// 把封装好的javabean放入list集合中
list.add(bean);
}
}
}
// 4 把list转发到jsp页面中
request.setAttribute("list", list);
request.getRequestDispatcher("/online.jsp").forward(request, response);

在线用户列表页面 onLine.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<table align="left" border="1" width="600px">
<tr>
<th>编号</th>
<th>登录名</th>
<th>登录时间</th>
<th>最后访问时间</th>
<th>IP</th>
<th>操作</th>
</tr>
<c:forEach items="${requestScope.list }" var="bean" varStatus="varSta">
<tr>
<td>${varSta.count }</td>
<td>${bean.name }</td>
<td>${bean.loginTime }</td>
<td>${bean.lastTime }</td>
<td>${bean.ip }</td>
<td><a href="${pageContext.request.contextPath }/KickOutServlet?sessionId=${bean.sessionId }">移除</a></td>
</tr>
</c:forEach>
</table>

踢人功能请求 KickOutServlet

由onLine.jsp 可知,发生踢人请求时,会传递一个sessionId的参数,这也就是为什么需要把它包装在OnLineBean中的原因。接着我们从Map中获得相应的session对象,并将其信息remove,最后重定向到查看在线用户的网页上来。
这里需要使用之前的那个OnLineListener监听器来删除一些信息。见attributeRemoved方法。

1
2
3
4
5
6
7
8
9
10
11
12
// 1 接收到踢出的id
String sessionId = request.getParameter("sessionId");
// 2 强制注销指定id的用户
Map<String, HttpSession> onLine = (Map<String, HttpSession>) this
.getServletContext().getAttribute("onLine");
// 查找需要注销的session对象
HttpSession session = onLine.get(sessionId);
if (session != null) {
session.removeAttribute("user");// 自动会调用监听器,移除map中的已经注销的用户信息
session.removeAttribute("ip");
}
response.sendRedirect(request.getContextPath() + "/GetOnLineServlet");

这样,我们就可以成功踢人啦~
慢着,如果到此为止的话,我们实现的是相互踢人,这显然是不合理的。应该拥有管理权限的人才能踢人哦。
所以呢,我们这里需要加一个过滤器,将不是管理员的人过滤掉,这里的管理员暂且就定为本地ip啦~

管理员过滤器 SecuryFilter

我们将这个过滤器主要用来对KickOutServlet进行过滤,也就是在执行这个请求前执行过滤器,如果是管理员就放行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class SecuryFilter implements Filter {

@Override
public void destroy() {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
//获得ip地址
String ip = request.getRemoteHost();
response.setContentType("text/html;charset=utf-8");//这句可以放进乱码过滤器
if ("localhost".equals(ip) || "127.0.0.1".equals(ip)) {
// 1管理员
chain.doFilter(request, response);

} else {
// 2非管理员
response.getWriter().write("别想删除了,你不是管理员");
}
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

}

web.xml中对过滤器进行配置

1
2
3
4
5
6
7
8
<filter>
<filter-name>SecuryFilter</filter-name>
<filter-class>cn.ninja.web.SecuryFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SecuryFilter</filter-name>
<url-pattern>/KickOutServlet</url-pattern>
</filter-mapping>

用户的注销功能请求 LogoutServlet

用户注销,同样的会触发removeAttribute对应的监听器,这里即OnLineListener,所以不用再写啦~
最后重定向到登录界面。

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 用户注销,删除保存登录成功时session对象中的数据
*/
HttpSession session = request.getSession(false);
if (session != null) {
session.removeAttribute("user");
session.removeAttribute("ip");
}
/* 这里触发监听器 */

response.sendRedirect(request.getContextPath() + "/login.jsp");
}

到此,全部的功能都已经实现啦~虽然这是个简单的小案例,但涵盖的内容包括了Web的三大组件,以及过滤器监听器,特记录下来,对这块知识做个总结。

“本次案例并没有使用多个ip去测,之后需要学习如何在一台电脑上虚拟多个局域网ip,来访问网站,以实现查看多个用户登录信息的测试”