JSP实例
需求
实现用户登录与退出登录功能,要求一个用户只能在一处登录。 完成对用户表的 CRUD 操作。
使用技术: JSP、Servlet、Filter、Listener、JDBC、MySQL
实现项目
创建数据库
CREATE TABLE `user3` ( `userid` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(30) DEFAULT NULL, `userpwd` varchar(30) DEFAULT NULL, `usersex` varchar(2) DEFAULT NULL, `phonenumber` varchar(30) DEFAULT NULL, `qqnumber` varchar(20) DEFAULT NULL, PRIMARY KEY (`userid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
搭建项目
创建项目
添加jar包
添加的jar包:
添加 jstl 标签库的约束文件
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
创建包
创建 POJO用户类
package com.bjsxt.pojo;
public class Users{
private int userid;
private String username;
private String userpwd;
private String usersex;
private String phonenumber;
private String qqnumber;
public Users(int userid, String username, String userpwd, String usersex, String phonenumber, String qqnumber) {
this.userid = userid;
this.username = username;
this.userpwd = userpwd;
this.usersex = usersex;
this.phonenumber = phonenumber;
this.qqnumber = qqnumber;
}
public Users(){
super();
}
@Override
public String toString() {
return "Users{" +
"userid=" + userid +
", username='" + username + '\'' +
", userpwd='" + userpwd + '\'' +
", usersex='" + usersex + '\'' +
", phonenumber='" + phonenumber + '\'' +
", qqnumber='" + qqnumber + '\'' +
'}';
}
public int getUserid() {
return userid;
}
public void setUserid(int userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUserpwd() {
return userpwd;
}
public void setUserpwd(String userpwd) {
this.userpwd = userpwd;
}
public String getUsersex() {
return usersex;
}
public void setUsersex(String usersex) {
this.usersex = usersex;
}
public String getPhonenumber() {
return phonenumber;
}
public void setPhonenumber(String phonenumber) {
this.phonenumber = phonenumber;
}
public String getQqnumber() {
return qqnumber;
}
public void setQqnumber(String qqnumber) {
this.qqnumber = qqnumber;
}
}
创建 JDBC 工具类
db.properties:
这个db文件,要放到src的目录下
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3307/test?useUnicode=true&charac terEncoding=utf8
jdbc.username=root
jdbc.password=1234567
创建JdbcUtils工具类
放到commons文件夹内:
package com.bjsxt.commons;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ResourceBundle;
public class JdbcUtils {
private static String driver;
private static String url;
private static String username;
private static String password;
static {
try{
ResourceBundle bundle = ResourceBundle.getBundle("db");
driver = bundle.getString("jdbc.driver");
url = bundle.getString("jdbc.url");
username = bundle.getString("jdbc.username");
password = bundle.getString("jdbc.password");
Class.forName(driver);
}catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获取连接方法
public static Connection getConnection(){
Connection conn = null;
try {
conn = DriverManager.getConnection(url,username,password);
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
//关闭连接
public static void closeConnection(Connection conn){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
注意:
在实践的过程内,出现了报错:at java.lang.Class.forName()
本次问题是因为mysql的jar包出现了问题
解决方法:
点击跳转链接
实现业务
用户登录
导入登录页面的js,css以及图片文件夹
创建登录页面
创建名为login.jsp的登录页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>欢迎登录后台管理系统</title>
<link href="css/style.css" rel="stylesheet" type="text/css" />
<script language="JavaScript" src="js/jquery.js"></script>
<script src="js/cloud.js" type="text/javascript"></script>
<script language="javascript">
$(function(){
$('.loginbox').css({'position':'absolute','left':($(window).width()-692)/2});
$(window).resize(function(){
$('.loginbox').css({'position':'absolute','left':($(window).width()-692)/2});
})
});
</script>
</head>
<body style="background-color:#1c77ac; background-image:url(images/light.png); background-repeat:no-repeat; background-position:center top; overflow:hidden;">
<div id="mainBody">
<div id="cloud1" class="cloud"></div>
<div id="cloud2" class="cloud"></div>
</div>
<div class="logintop">
<ul>
<li><a href="#">回首页</a></li>
<li><a href="#">帮助</a></li>
<li><a href="#">关于</a></li>
</ul>
</div>
<div class="loginbody">
<div class="loginbox loginbox2">
<form action="login.do" method="post">
<ul>
<li><input name="username" type="text" class="loginuser" value="admin" onclick="JavaScript:this.value=''"/></li>
<li><input name="userpwd" type="text" class="loginpwd" value="密码" onclick="JavaScript:this.value=''"/></li>
<li class="yzm">
<span><input name="" type="text" value="验证码" onclick="JavaScript:this.value=''"/></span><cite>X3D5S</cite>
</li>
<li><input name="" type="submit" class="loginbtn" value="登录" onclick="javascript:window.location='main.html'" /></li>
</ul>
</form>
</div>
</div>
</body>
</html>
创建登录业的务持久层
在dao包内创建一个接口UserLoginDao
package com.bjsxt.dao;
import com.bjsxt.pojo.Users;
public interface UserLoginDao {
public Users selectUsersByUserNameAndUserPwd(String username, String userpwd);
}
在dao包下的impl文件夹下,创建一个实现接口UserLoginDao的实现类 – UserLoginDaoImpl类
package com.bjsxt.dao.impl;
import com.bjsxt.commons.JdbcUtils;
import com.bjsxt.dao.UserLoginDao;
import com.bjsxt.pojo.Users;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* 对于用户登录的数据库查询
*/
public class UserLoginDaoImpl implements UserLoginDao {
@Override
public Users selectUsersByUserNameAndUserPwd(String username, String userpwd) {
Users user = null;
Connection conn = null;
try {
conn = JdbcUtils.getConnection();
PreparedStatement ps = conn.prepareStatement("select * from user3 where username = ? and userpwd = ?");
ps.setString(1,username);
ps.setString(2,userpwd);
ResultSet resultSet = ps.executeQuery();
while (resultSet.next()){
user = new Users();
user.setUsersex(resultSet.getString("usersex"));
user.setUserpwd(resultSet.getString("userpwd"));
user.setUsername(resultSet.getString("username"));
user.setUserid(resultSet.getInt("userid"));
user.setPhonenumber(resultSet.getString("phonenumber"));
user.setQqnumber(resultSet.getString("qqnumber"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
}
return user;
}
}
创建登录页的业务层
创建自定义异常的类,用于对于出现的异常进行自定义处理
package com.bjsxt.service;
/**
* 异常包,用于自定义异常
*/
public class UserNotFoundException extends RuntimeException{
public UserNotFoundException() {
}
public UserNotFoundException(String message) {
super(message);
}
public UserNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
创建一个接口:
package com.bjsxt.service;
import com.bjsxt.pojo.Users;
public interface UserLoginService {
Users userLogin(String username, String userpwd);
}
创建接口的实现类,用于实现登录页的用户登录检测部分
package com.bjsxt.service.impl;
import com.bjsxt.dao.UserLoginDao;
import com.bjsxt.dao.impl.UserLoginDaoImpl;
import com.bjsxt.pojo.Users;
import com.bjsxt.service.UserLoginService;
import com.bjsxt.service.UserNotFoundException;
/**
* 用户登录业务
*/
public class UserLoginServiceImpl implements UserLoginService {
/**
* 用户登录
*/
@Override
public Users userLogin(String username, String userpwd) {
/**实例化之前创建的持久层,让他获取到我们的用户名和密码去数据库内进行查询*/
//创建持久层的对象
UserLoginDao userLoginDao = new UserLoginDaoImpl();
//将用户输入的用户名和密码交给持久层,和数据库进行比对
Users users = userLoginDao.selectUsersByUserNameAndUserPwd(username, userpwd);
//对返回的数据进行判断
if(users == null){
//使用自定义的异常处理方式对异常进行处理
throw new UserNotFoundException("用户名或密码有误!");
}
return users;
}
}
创建登录业务的servlet 实现简单的用户登录
创建Servlet
package com.bjsxt.web.servlet;
import com.bjsxt.commons.Constants;
import com.bjsxt.pojo.Users;
import com.bjsxt.service.UserLoginService;
import com.bjsxt.service.UserNotFoundException;
import com.bjsxt.service.impl.UserLoginServiceImpl;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* 处理用户登录请求
* 因为我们在login.jsp页面内请求的是login.do,
* 所以我们在这里创建的WebServlet就是login.do
*/
@WebServlet("/login.do")
public class UserLoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入login.do");
//当用户在from表单内点击提交请求后,会给我们发送过来两个数据:username userpwd
String username = req.getParameter("username");
String userpwd = req.getParameter("userpwd");
String code = req.getParameter("code");
System.out.println("获取到用户名和密码:"+username+" == "+userpwd+" == "+code);
try{
//创建业务层对象
UserLoginService userLoginService = new UserLoginServiceImpl();
Users users = userLoginService.userLogin(username, userpwd);
System.out.println("从业务层获取到的users:"+users);
//如果业务层找到了和登录用户匹配的密码,那么我们的登录时成功的
//我们就可以建立客户端与服务端的会话状态
//将查到的users用户放到session对象内,为了以后不会出现对每一个session的key进行修改的情况
//我们将key值封装到一个抽象类内,需要时进行调用
HttpSession session = req.getSession();
session.setAttribute(Constants.USER_SESSION_KEY,users);
//使用重定向方式去跳转到首页
resp.sendRedirect("main.jsp");
}catch(UserNotFoundException e){
//将传递的值在main.jsp页面内进行尝试显示
req.setAttribute("msg",e.getMessage());
req.getRequestDispatcher("login.jsp").forward(req,resp);
}catch (Exception e){
e.printStackTrace();
//基于重定向的跳转,直接跳转到一个报错的页面
resp.sendRedirect("error.jsp");
}
}
}
添加登录后的首页:main.jsp
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2021/2/28 0028
Time: 3:50
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>信息管理系统界面</title>
</head>
<frameset rows="*,31" cols="*" frameborder="no" border="0" framespacing="0">
<frameset cols="187,*" frameborder="no" border="0" framespacing="0">
<frame src="left.jsp" name="leftFrame" scrolling="No" noresize="noresize" id="leftFrame" title="leftFrame" />
<frame src="index.jsp" name="rightFrame" id="rightFrame" title="rightFrame" />
</frameset>
<frame src="footer.jsp" name="bottomFrame" scrolling="No" noresize="noresize" id="bottomFrame" title="bottomFrame" />
</frameset>
<noframes><body>
</body></noframes>
</html>
其他导入的页面有:
error.jsp 报错页面:
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2021/2/28 0028
Time: 3:55
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
出错了,请于管理员联系。。。
</body>
</html>
footer.jsp
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2021/2/28 0028
Time: 3:54
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>无标题文档</title>
<link href="css/style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="footer">
<span></span>
<i></i>
</div>
</body>
</html>
index.jsp:
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2021/2/26 0026
Time: 18:28
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>无标题文档</title>
<link href="css/style.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="js/jquery.js"></script>
</head>
<body>
<div class="place">
<span>位置:</span>
<ul class="placeul">
<li><a href="#">首页</a></li>
</ul>
</div>
<div class="mainindex">
<div class="welinfo">
<span><img src="images/sun.png" alt="天气" /></span>
<b></b>
</div>
</div>
</body>
</html>
left.jsp
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2021/2/28 0028
Time: 3:52
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>无标题文档</title>
<link href="css/style.css" rel="stylesheet" type="text/css" />
<script language="JavaScript" src="js/jquery.js"></script>
<script type="text/javascript">
$(function(){
//导航切换
$(".menuson .header").click(function(){
var $parent = $(this).parent();
$(".menuson>li.active").not($parent).removeClass("active open").find('.sub-menus').hide();
$parent.addClass("active");
if(!!$(this).next('.sub-menus').size()){
if($parent.hasClass("open")){
$parent.removeClass("open").find('.sub-menus').hide();
}else{
$parent.addClass("open").find('.sub-menus').show();
}
}
});
// 三级菜单点击
$('.sub-menus li').click(function(e) {
$(".sub-menus li.active").removeClass("active")
$(this).addClass("active");
});
$('.title').click(function(){
var $ul = $(this).next('ul');
$('dd').find('.menuson').slideUp();
if($ul.is(':visible')){
$(this).next('.menuson').slideUp();
}else{
$(this).next('.menuson').slideDown();
}
});
})
</script>
</head>
<body style="background:#f0f9fd;">
<div class="lefttop"><span></span>导航菜单</div>
<dl class="leftmenu">
<dd>
<div class="title"><span><img src="images/leftico03.png" /></span>用户管理</div>
<ul class="menuson">
<li><cite></cite><a href="usermanager/addUser.jsp" target="rightFrame">添加用户</a><i></i></li>
<li><cite></cite><a href="usermanager/findUser.jsp" target="rightFrame">查询用户</a><i></i></li>
<li><cite></cite><a href="logout.do" target="rightFrame">退出登录</a><i></i></li>
</ul>
</dd>
</dl>
</body>
</html>
我们 输入 http://localhost:8080/webdemo/login.jsp 进入登录页面
输入错误的名称和密码
会给出报错提示
输入正确的用户名(oldliu) 和密码(123)
用户名和密码我们在数据库内进行设置
创建登录业务的Filter
我们实现的用户登录,在一个浏览器上登录之后,进入的main.jsp页面,在其他浏览器上也可以进行访问,这很明显是不允许的
这里缺乏一个对于客户端浏览器是否登录的状态的判断,这里依赖于一个filter去实现
实现思路:
当客户端浏览器再一次去请求服务端的资源时,我添加的过滤器要先拦截你的请求,查看当前的客户端浏览器是否是登录的状态,如果是登录的状态,那允许访问,如果不是则让其跳回登录页面。
Filter如何知道当前的客户端浏览器是否是一个登录状态呢?
如果用户登录成功,那么我们将用户的信息放到了客户端浏览器的session内,在客户端浏览器内获取到对应的session,再从session内取user对象,如果可以获取到这个user对象,那么也就说明这个浏览器已经被登录过,那么允许访问资源。
拦截检查的是动态访问资源,如.do .jsp 而不是静态资源 ,如:.css**
代码:
package com.bjsxt.web.filter;
import com.bjsxt.commons.Constants;
import com.bjsxt.pojo.Users;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* 判断当前客户端浏览器是否登录的Filter
*/
//对.do和.jsp的请求进行拦截
@WebFilter(urlPatterns = {"*.do","*.jsp"})
public class UserLoginFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//获取用户请求的url,看看是否是login.jsp或者login.do
HttpServletRequest request = (HttpServletRequest)servletRequest;
String uri = request.getRequestURI();
//判断当前请求的是否为login.jsp或者login.do,如果请求的是用户登录的资源那么需要放行。
//!= -1说明找到了
if(uri.indexOf("login.jsp") != -1 || uri.indexOf("login.do") != -1|| uri.indexOf("validateCode.do") != -1){
filterChain.doFilter(servletRequest, servletResponse);
}else{
HttpSession session = request.getSession();
//从session对象内获取到users对象 返回的是一个Object对象,需要进行强转
Users users = (Users) session.getAttribute(Constants.USER_SESSION_KEY);
if(users != null){
filterChain.doFilter(servletRequest, servletResponse);
}else{
request.setAttribute(Constants.REQUEST_MSG,"不登录不好使!");
request.getRequestDispatcher("login.jsp").forward(servletRequest, servletResponse);
}
}
}
@Override
public void destroy() {
}
}
利用之前在Constants.java内构建的抽象类
public abstract class Constants {
public static String USER_SESSION_KEY="users";
public static String REQUEST_MSG="msg";
public static String VALIDATE_CODE_KEY="code";
}
对浏览器发来的请求进行判断
当不在一个浏览器登录时
左上角给出报错提示
实现用户只可以在一处登录
实现功能:
当前用户登录之后,在另一个浏览器上登录时,会将原来的用户登录状态挤掉
分析:
我们可以将每一个客户端浏览器登录后对应的Httpsession对象放到servletContext对象内进行缓存,放到vlaue的位置上,key使用的是用户的userid
例如我们在谷歌浏览器内使用oldliu进行登录,我们在servletContext对象的缓存的key查到oldliu的id。我们再次登录时,要先去servletContext对象去找,我们在IE浏览器内使用的也是oldliu登录,我们先去根据userid在servletContext对象内进行查找,看是否可以找到Httpsession,如果找到,那么说明登录过,我们要先把Httpsession对象销毁,在将IE浏览器端的session放到被销毁的Httpsession对象的位置上,原来的servletContext对象的Httpsession已经被销毁,想要在谷歌浏览器上再次请求时,已经获取不到原来对应的Httpsession,我们就让他回到登录页面。
这个业务需要在页面跳转之前完成
我们在UserLoginServlet.java内进行补充代码
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入login.do");
//当用户在from表单内点击提交请求后,会给我们发送过来两个数据:username userpwd
String username = req.getParameter("username");
String userpwd = req.getParameter("userpwd");
String code = req.getParameter("code");
System.out.println("获取到用户名和密码:"+username+" == "+userpwd+" == "+code);
try{
//创建业务层对象
UserLoginService userLoginService = new UserLoginServiceImpl();
Users users = userLoginService.userLogin(username, userpwd);
System.out.println("从业务层获取到的users:"+users);
//如果业务层找到了和登录用户匹配的密码,那么我们的登录时成功的
//我们就可以建立客户端与服务端的会话状态
//将查到的users用户放到session对象内,为了以后不会出现对每一个session的key进行修改的情况
//我们将key值封装到一个抽象类内,需要时进行调用
HttpSession session = req.getSession();
session.setAttribute(Constants.USER_SESSION_KEY,users);
/**
* 实现用户只可以在一处登录的效果
*/
ServletContext servletContext = this.getServletContext();
//users.getUserid()+"" :添加一个”“,将其转为一个字符串
HttpSession temp = (HttpSession) servletContext.getAttribute(users.getUserid()+"");
if (temp!=null){ //如果找到,说明这个账号已经登录过
//在servletContext对象内先找到它,将其销毁
servletContext.removeAttribute(users.getUserid()+"");
temp.invalidate();
}
//将新的浏览器的HttpSession对象放到被销毁的Httpsession对象的位置上
servletContext.setAttribute(users.getUserid()+"",session);
//使用重定向方式去跳转到首页
resp.sendRedirect("main.jsp");
}catch(UserNotFoundException e){
//将传递的值在main.jsp页面内进行尝试显示
req.setAttribute("msg",e.getMessage());
req.getRequestDispatcher("login.jsp").forward(req,resp);
}catch (Exception e){
e.printStackTrace();
//基于重定向的跳转,直接跳转到一个报错的页面
resp.sendRedirect("error.jsp");
}
}
关键代码:
/**
* 实现用户只可以在一处登录的效果
*/
ServletContext servletContext = this.getServletContext();
//users.getUserid()+"" :添加一个”“,将其转为一个字符串
HttpSession temp = (HttpSession) servletContext.getAttribute(users.getUserid()+"");
if (temp!=null){ //如果找到,说明这个账号已经登录过
//在servletContext对象内先找到它,将其销毁
servletContext.removeAttribute(users.getUserid()+"");
temp.invalidate();
}
//将新的浏览器的HttpSession对象放到被销毁的Httpsession对象的位置上
servletContext.setAttribute(users.getUserid()+"",session);
这样,业务功能被实现
我们完善一下jsp页面
left.jsp点击跳转的页面:
<dd>
<div class="title"><span><img src="images/leftico03.png" /></span>用户管理</div>
<ul class="menuson">
<li><cite></cite><a href="usermanager/addUser.jsp" target="rightFrame">添加用户</a><i></i></li>
<li><cite></cite><a href="usermanager/findUser.jsp" target="rightFrame">查询用户</a><i></i></li>
<li><cite></cite><a href="logout.do" target="rightFrame">退出登录</a><i></i></li>
</ul>
</dd>
addUser.jsp:
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2021/2/28 0028
Time: 18:02
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>无标题文档</title>
<link href="../css/style.css" rel="stylesheet" type="text/css" />
<link href="../css/style.css" rel="stylesheet" type="text/css" />
<link href="../css/select.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="place">
<span>位置:</span>
<ul class="placeul">
<li><a href="#">用户管理</a></li>
<li><a href="#">添加用户</a></li>
</ul>
</div>
<div class="formbody">
<div class="formtitle"><span>基本信息</span></div>
<form action="../userManager.do" method="post">
<input type="hidden" name="flag" value="addUser"/>
<ul class="forminfo">
<li>
<label>用户名</label>
<input name="username" type="text" class="dfinput" /></li>
<li>
<li>
<label>用户密码</label>
<input name="userpwd" type="text" class="dfinput" /><i></i></li>
<li>
<label>性别</label><cite>
<input name="usersex" type="radio" value="1" checked="checked" />男
<input name="usersex" type="radio" value="0" />女
</li>
<li>
<label>联系方式</label>
<input name="phonenumber" type="text" class="dfinput" />
</li>
<li>
<label>QQ号</label>
<input name="qqnumber" type="text" class="dfinput" />
</li>
<li>
<label> </label>
<input type="submit" class="btn" value="确认保存" />
</li>
</ul>
</form>
</div>
</body>
</html>
findUser.jsp:
<%--
Created by IntelliJ IDEA.
User: Administrator
Date: 2021/2/28 0028
Time: 18:02
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>无标题文档</title>
<link href="../css/style.css" rel="stylesheet" type="text/css" />
<link href="../css/select.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="place">
<span>位置:</span>
<ul class="placeul">
<li><a href="#">用户管理</a></li>
<li><a href="#">查询用户</a></li>
</ul>
</div>
<div class="rightinfo">
<form action="../userManager.do" method="post">
<input type="hidden" name="flag" value="findUser"/>
<ul class="prosearch">
<li>
<label>查询:</label>
<i>用户名</i>
<a>
<input name="username" type="text" class="scinput" />
</a>
</li>
<li>
<label>性别:</label>
<input name="usersex" type="radio" value="1" checked="checked" /> 男
<input name="usersex" type="radio" value="0" /> 女
</li>
<li>
<label>手机号:</label>
<a>
<input name="phonenumber" type="text" class="scinput" />
</a>
</li>
<li>
<label>QQ号:</label>
<a>
<input name="qqnumber" type="text" class="scinput" />
</a>
</li>
<a>
<input type="submit" class="sure" value="查询"/>
</a>
</ul>
</form>
</body>
</html>
出现这种问题,是因为没有跳回到上一个请求路径,在当前的显示区域内显示登陆页面
我们在main.jsp页面内添加部分代码:
if (window.parent.length>0){
//让他们的父窗口发送请求
window.parent.location="login.jsp";
}
这样,就实现了用户只可以在一处浏览器登录的效果
解决HttpSession超时销毁时的异常问题
问题产生的原因:
在谷歌浏览器登录过一次后,再次在其他浏览器登录,由于在谷歌浏览器登录时,session会被缓存到servletContext对象内,当设定的时间到达,我们的tomcat会销毁这个客户端谷歌浏览器对应的session对象,但在servletCOntext对象内仍然还缓存着谷歌浏览器的session对象,说明缓存的就是一个已经被销毁的httpSession对象。
因为servletCOntext对象内仍然还缓存着谷歌浏览器的session对象,所以在IE浏览器内仍然可以通过servletContext对象的userid获取到客户端浏览器对应的session,查到存在session对象,我们就要按照用户只可以登录一次来进行销毁,但是这个session已经被销毁了,我们对于一个已经被销毁的session再次进行销毁时,就会抛出异常。
解决思路:
我们可以创建一个httpSession的生命周期监听器,当session被销毁之前会触发这个监听方法,在这个监听方法内,我们获取到servletContext对象,从servletContext对象内,将对应的session删除掉。
这样我们用户在其他浏览器内登录,也不会在servletContext内获取到已经因为超时被删掉的session对象。
在web.xml设置一个定时:
<!--添加一个超时时间的配置,添加一分钟的时间限制-->
<session-config>
<session-timeout>1</session-timeout>
</session-config>
在启动登录页面一分钟以后,我们再次登录,会报一个异常:
java.lang.IllegalStateException: invalidate: Session already invalidated
at org.apache.catalina.session.StandardSession.invalidate(StandardSession.java:1288)
at org.apache.catalina.session.StandardSessionFacade.invalidate(StandardSessionFacade.java:176)
at com.bjsxt.web.servlet.UserLoginServlet.doPost(UserLoginServlet.java:61)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:648)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at com.bjsxt.web.filter.UserLoginFilter.doFilter(UserLoginFilter.java:31)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:668)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:223)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1517)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1474)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:744)
在listener文件夹内创建文件:
package com.bjsxt.web.listener;
import com.bjsxt.commons.Constants;
import com.bjsxt.pojo.Users;
import javax.servlet.ServletContext;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
/**
* 解决HttpSession被反复销毁的问题
*/
@WebListener
public class HttpSessionLifecycleListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent se) {
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
//获取ServletContext对象,然后将所对应的HttpSession删除掉
HttpSession session = se.getSession();
ServletContext servletContext = session.getServletContext();
Users users = (Users) session.getAttribute(Constants.USER_SESSION_KEY);
servletContext.removeAttribute(users.getUserid()+"");
}
}
这样,异常就会消除
添加验证码功能
在commons文件夹下创建一个生成验证码和干扰线的文件:
package com.bjsxt.commons;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/validateCode.do")
public class ValidateCodeServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 在内存中创建图象
int width = 70, height = 45;
BufferedImage image = new BufferedImage(width, height,
BufferedImage.TYPE_INT_RGB);
// 获取图形上下文
Graphics g = image.getGraphics();
// 生成随机类
Random random = new Random();
// 设定背景色
g.setColor(getRandColor(200, 250));
g.fillRect(0, 0, width, height);
// 设定字体
g.setFont(new Font("Times New Roman", Font.PLAIN, 20));
// 随机产生155条干扰线,使图象中的认证码不易被其它程序探测到
g.setColor(getRandColor(160, 200));
for (int i = 0; i < 155; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x, y, x + xl, y + yl);
}
// 取随机产生的认证码(4位数字)
String sRand = "";
for (int i = 0; i < 4; i++) {
String rand = String.valueOf(random.nextInt(10));
sRand += rand;
// 将认证码显示到图象中
g.setColor(new Color(20 + random.nextInt(110), 20 + random
.nextInt(110), 20 + random.nextInt(110)));
// 调用函数出来的颜色相同,可能是因为种子太接近,所以只能直接生成
g.drawString(rand, 13 * i + 6, 16);
}
// 图象生效
g.dispose();
try {
ImageIO.write(image, "JPEG", response.getOutputStream());
} catch (Exception e) {
System.out.println("验证码图片产生出现错误:" + e.toString());
}
//保存验证码到Session
request.getSession().setAttribute("randStr", sRand);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
/*
* 给定范围获得随机颜色
*/
private Color getRandColor(int fc, int bc) {
Random random = new Random();
if (fc > 255)
fc = 255;
if (bc > 255)
bc = 255;
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
}
在login.jsp文件内的验证码区域,进行修改:
<li class="yzm">
<span><input name="code" type="text" value="验证码" onclick="JavaScript:this.value=''"/></span><cite><img id="code" src="validateCode.do" onclick="change1()"></cite>
</li>
在login.jsp文件上面的js区域,添加一个change1()方法
$(function(){
$('.loginbox').css({'position':'absolute','left':($(window).width()-692)/2});
$(window).resize(function(){
$('.loginbox').css({'position':'absolute','left':($(window).width()-692)/2});
});
});
//实现点击图片动态的更新验证码
function change1(){
$("#code").attr("src","validateCode.do?"+Math.random());
}
在抽象类内的属性:
public static String VALIDATE_CODE_KEY="code";
在我们的servlet类 ValidateCodeServlet.java内,有代码将验证码保存到Session:
//保存验证码到Session
request.getSession().setAttribute(Constants.VALIDATE_CODE_KEY, sRand);
我们将用于于处理用户登录请求的UserLoginServlet.java重新进行有关验证码的修改
修改前:
package com.bjsxt.web.servlet;
import com.bjsxt.commons.Constants;
import com.bjsxt.pojo.Users;
import com.bjsxt.service.UserLoginService;
import com.bjsxt.service.UserNotFoundException;
import com.bjsxt.service.impl.UserLoginServiceImpl;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* 处理用户登录请求
* 因为我们在login.jsp页面内请求的是login.do,
* 所以我们在这里创建的WebServlet就是login.do
*/
@WebServlet("/login.do")
public class UserLoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入login.do");
//当用户在from表单内点击提交请求后,会给我们发送过来两个数据:username userpwd
String username = req.getParameter("username");
String userpwd = req.getParameter("userpwd");
String code = req.getParameter("code");
System.out.println("获取到用户名和密码:"+username+" == "+userpwd+" == "+code);
try{
//创建业务层对象
UserLoginService userLoginService = new UserLoginServiceImpl();
Users users = userLoginService.userLogin(username, userpwd);
System.out.println("从业务层获取到的users:"+users);
//如果业务层找到了和登录用户匹配的密码,那么我们的登录时成功的
//我们就可以建立客户端与服务端的会话状态
//将查到的users用户放到session对象内,为了以后不会出现对每一个session的key进行修改的情况
//我们将key值封装到一个抽象类内,需要时进行调用
HttpSession session = req.getSession();
session.setAttribute(Constants.USER_SESSION_KEY,users);
/**
* 实现用户只可以在一处登录的效果
*/
ServletContext servletContext = this.getServletContext();
//users.getUserid()+"" :添加一个”“,将其转为一个字符串
HttpSession temp = (HttpSession) servletContext.getAttribute(users.getUserid()+"");
if (temp!=null){ //如果找到,说明这个账号已经登录过
//在servletContext对象内先找到它,将其销毁
servletContext.removeAttribute(users.getUserid()+"");
temp.invalidate();
}
//将新的浏览器的HttpSession对象放到被销毁的Httpsession对象的位置上
servletContext.setAttribute(users.getUserid()+"",session);
//使用重定向方式去跳转到首页
resp.sendRedirect("main.jsp");
}catch(UserNotFoundException e){
//将传递的值在main.jsp页面内进行尝试显示
req.setAttribute("msg",e.getMessage());
req.getRequestDispatcher("login.jsp").forward(req,resp);
}catch (Exception e){
e.printStackTrace();
//基于重定向的跳转,直接跳转到一个报错的页面
resp.sendRedirect("error.jsp");
}
}
}
修改后:
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("进入login.do");
//当用户在from表单内点击提交请求后,会给我们发送过来两个数据:username userpwd
String username = req.getParameter("username");
String userpwd = req.getParameter("userpwd");
String code = req.getParameter("code");
System.out.println("获取到用户名和密码:"+username+" == "+userpwd+" == "+code);
try{
HttpSession session = req.getSession();
String codeTemp = (String)session.getAttribute(Constants.VALIDATE_CODE_KEY);
//输入的验证码和生成的验证码一样时
if (codeTemp.equals(code)){
//创建业务层对象
UserLoginService userLoginService = new UserLoginServiceImpl();
Users users = userLoginService.userLogin(username, userpwd);
System.out.println("从业务层获取到的users:"+users);
//如果业务层找到了和登录用户匹配的密码,那么我们的登录时成功的
//我们就可以建立客户端与服务端的会话状态
//将查到的users用户放到session对象内,为了以后不会出现对每一个session的key进行修改的情况
//我们将key值封装到一个抽象类内,需要时进行调用
session.setAttribute(Constants.USER_SESSION_KEY,users);
/**
* 实现用户只可以在一处登录的效果
*/
ServletContext servletContext = this.getServletContext();
//users.getUserid()+"" :添加一个”“,将其转为一个字符串
HttpSession temp = (HttpSession) servletContext.getAttribute(users.getUserid()+"");
if (temp!=null){ //如果找到,说明这个账号已经登录过
//在servletContext对象内先找到它,将其销毁
servletContext.removeAttribute(users.getUserid()+"");
temp.invalidate();
}
//将新的浏览器的HttpSession对象放到被销毁的Httpsession对象的位置上
servletContext.setAttribute(users.getUserid()+"",session);
//使用重定向方式去跳转到首页
resp.sendRedirect("main.jsp");
}else{ //输入的验证码不对
req.setAttribute(Constants.REQUEST_MSG,"验证码有误,请重新输入");
req.getRequestDispatcher("login.jsp").forward(req,resp);
}
}catch(UserNotFoundException e){
//将传递的值在main.jsp页面内进行尝试显示
req.setAttribute("msg",e.getMessage());
req.getRequestDispatcher("login.jsp").forward(req,resp);
}catch (Exception e){
e.printStackTrace();
//基于重定向的跳转,直接跳转到一个报错的页面
resp.sendRedirect("error.jsp");
}
}
加入了对于验证码是否匹配的检测
也实现了点击图片,更换验证码的效果
用户退出登录
在left.jsp页面的退出登录:
<li><cite></cite><a href="logout.do" target="rightFrame">退出登录</a><i></i></li>
在servlet文件夹内,创建LogoutServlet文件
package com.bjsxt.web.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* 用户退出登录
*/
@WebServlet("/logout.do")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
session.invalidate();
resp.sendRedirect("login.jsp");
}
}
这样,我们点击退出登录,就会返回到登录页