中国加油
今天疫情突然卷土重来,本来感觉胜利的曙光在即,却又杀了一个回马枪,本人写作能力有限,只能在此为中国加油助威,不给国家添麻烦,该上班上班,下班回家老老实实在家温故而知新,预祝祖国早日战胜疫情。
1.项目介绍
(1)需求:
实现用户登录与退出登录功能,要求一个用户只能在一处登录。
完成对用户表的CRUD操作
(2)使用的技术:
JSP,Servlet,Filter,Listener,JDBC,Mysql
2.预期效果
具体实现:
(1)实现登录操作功能
(2)验证码可实现随机生成功能
(3)如果不登录则不能进入到主界面功能
(4)完成用户表的增删改查功能
效果图如下
3.开始实现功能
(1)创建users表
CREATE TABLE `users` ( `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;
(1)整体目录结构如下
(2)创建好项目后需要导入jstl.jar,Servlet-api.jar,mysql-connector-java.jar这三个jar包
复制到lib目录下后还需要将jar包关联到项目中(项目名右击Build Path====Configure Build Path ==Libraries=Add JARS).
eclipse还需要导入jstl的约束(c.tld),需要在web-xml中配置一下才能在jsp中使用其标签库
以上截图如下
jstl约束引用代码
<jsp-config>
<taglib>
<taglib-uri>http://java.sun.com/jstl/core</taglib-uri>
<taglib-location>/WEB-INF/c.tld</taglib-location>
</taglib>
</jsp-config>
(3)编写db.properties文件
如果不加serverTimezone=UTC我在连接数据库的时候报了一个错误,问了一下度娘解决了。
jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/lc?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
jdbc.username = root
jdbc.password = root
(4)编写JDBCTutils工具类
package com.lc.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 = (Connection) DriverManager.getConnection(url, username, password);
} catch (Exception e) {
e.printStackTrace();
}
return conn;
}
// 关闭连接
public static void closeConnection(Connection conn) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
(5)编写pojo类
package com.lc.pojo;
public class Users {
private int userid;
private String username;
private String userpwd;
private String usersex;
private String phonenumber;
private String 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;
}
}
(6)编写dao层接口
package com.lc.dao;
import com.lc.pojo.Users;
import com.sun.xml.internal.ws.developer.UsesJAXBContext;
public interface UserLoginDao {
//进行登录操作
public Users selectUserByUserNameAndPassword(String username,String userpwd);
}
(7)编写dao层实现类进行数据库查询操作
package com.lc.dao.impl;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import com.lc.commons.JdbcUtils;
import com.lc.dao.UserLoginDao;
import com.lc.pojo.Users;
import com.sun.xml.internal.ws.developer.UsesJAXBContext;
public class UserLoginDaoImpl implements UserLoginDao {
// 进行登录操作
@Override
public Users selectUserByUserNameAndPassword(String username, String userpwd) {
Users user = null;
Connection conn = null;
try {
conn = JdbcUtils.getConnection();
String sql = "select * from users where username = ? and userpwd = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, username);
ps.setString(2, userpwd);
ResultSet rs = ps.executeQuery();
while(rs.next()) {
user = new Users();
user.setUsersex(rs.getString("usersex"));
user.setUserpwd(rs.getString("userpwd"));
user.setUsername(rs.getString("username"));
user.setUserid(rs.getInt("userid"));
user.setPhonenumber(rs.getString("phonenumber"));
user.setQqnumber(rs.getString("qqnumber"));
}
}catch(Exception e) {
e.printStackTrace();
}
return user;
}
(8)编写service接口及其实现类
package com.lc.service;
import com.lc.pojo.Users;
public interface UserService {
Users userLogin(String username,String userpwd);
}
package com.lc.service.impl;
import com.lc.dao.UserLoginDao;
import com.lc.dao.impl.UserLoginDaoImpl;
import com.lc.exception.UserNotFoundException;
import com.lc.pojo.Users;
import com.lc.service.UserService;
public class UserServiceImpl implements UserService {
@Override
public Users userLogin(String username, String userpwd) {
UserLoginDao userLoginDao = new UserLoginDaoImpl();
Users users = userLoginDao.selectUserByUserNameAndPassword(username, userpwd);
if(users == null) {
throw new UserNotFoundException("用户名或者密码错误!");
}
return users;
}
}
(9)在此之前我自己定义了一个异常类,方便执行代码时出现异常检查
package com.lc.exception;
public class UserNotFoundException extends RuntimeException {
public UserNotFoundException() {
}
public UserNotFoundException(String message) {
super(message);
}
public UserNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
(10)编写servlet层代码
package com.lc.web.serlvet;
import java.io.IOException;
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 com.lc.commons.Constants;
import com.lc.exception.UserNotFoundException;
import com.lc.pojo.Users;
import com.lc.service.UserService;
import com.lc.service.impl.UserServiceImpl;
import com.sun.corba.se.pept.transport.ContactInfo;
@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 {
String username = req.getParameter("username");
String userpwd = req.getParameter("userpwd");
String code = req.getParameter("code");
try {
HttpSession session = req.getSession();
UserService userService = new UserServiceImpl();
Users users = userService.userLogin(username, userpwd);
String codeTemp = (String) session.getAttribute(Constants.VALIDATE_CODE_KEY);
if(codeTemp.equals(code)) {
// 建立客户端与服务端对话
session.setAttribute(Constants.USER_SESSION_KEY, users);
//实现用户只能在一个地方进行登录操作
ServletContext context = this.getServletContext();
HttpSession temp = (HttpSession) context.getAttribute(users.getUserid()+"");
if(temp != null) {
context.removeAttribute(users.getUserid()+"");
//该方法用于 主要用于注销 调用该方法 会清空所有已定义的session 而不是清空全部session的值
temp.invalidate();
}
context.setAttribute(users.getUserid()+"", session);
// 使用重定向方式跳转首页
resp.sendRedirect("main.jsp");
}else {
req.setAttribute(Constants.REQUEST_MSG, "验证码错误!");
req.getRequestDispatcher("login.jsp").forward(req, resp);
}
} catch (UserNotFoundException e) {
req.setAttribute("msg", e.getMessage());
req.getRequestDispatcher("login.jsp").forward(req, resp);
} catch (Exception e) {
resp.sendRedirect("error.jsp");
}
}
}
(11)以下是页面代码
<%@ 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">
/* 这行代码解决的问题是响应重定向时到login.jsp会显示到主界面的右侧框中 */
if (window.parent.length > 0) {
window.parent.location = "login.jsp";
}
$(function() {
$('.loginbox').css({
'position' : 'absolute',
'left' : ($(window).width() - 692) / 2
});
$(window).resize(function() {
$('.loginbox').css({
'position' : 'absolute',
'left' : ($(window).width() - 692) / 2
});
})
});
/*点击验证码更换验证码的方法*/
function change(){
$("#code").attr("src","validateCode.do?"+Math.random());
}
</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">
${msg}
<div class="loginbox loginbox2">
<form action="login.do" method="post">
<ul>
<li><input name="username" type="text" class="loginuser"
value="admin" οnclick="JavaScript:this.value=''" /></li>
<li><input name="userpwd" type="text" class="loginpwd"
value="密码" οnclick="JavaScript:this.value=''" /></li>
<li class="yzm">
<span>
<input name="code" type="text" value="验证码" οnclick="JavaScript:this.value=''" /></span><cite><img id = "code" src="validateCode.do" οnclick="change()"></cite>
</li>
<li><input type="submit" class="loginbtn" value="登录"
οnclick="javascript:window.location='main.jsp'" /></li>
</ul>
</form>
</div>
</div>
</body>
</html>
(12)完成验证码功能(从网上找了一个生产验证码的的工具类,拿过来用了一下)
package com.lc.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 = 112, 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(Constants.VALIDATE_CODE_KEY, 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);
}
}
以上就完成了一个简单的登录界面
但是当我们输入/main.jsp可以发现可以直接进入到主界面,这就有点不合理了,
然后就想到了过滤器(过滤器的作用就是拦截和放行)
所以在项目中编写一个filter类实现filter接口重写其方法
package com.lc.web.filter;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import com.lc.commons.Constants;
import com.lc.pojo.Users;
/**
* 实现不登录状态下不能访问主界面功能
* @author Administrator
*
*/
@WebFilter(urlPatterns = {"*.jsp","*.do"})
public class UserLoginFilter implements Filter{
@Override
public void destroy() {
// TODO Auto-generated method stub
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain arg2) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String url = req.getRequestURI();
if(url.indexOf("login.jsp") != -1 || url.indexOf("login.do") != -1 || url.indexOf("validateCode.do") != -1) {
arg2.doFilter(req, response);
}else {
HttpSession session = req.getSession();
Users users = (Users) session.getAttribute(Constants.USER_SESSION_KEY);
if(users != null) {
arg2.doFilter(req, response);
}else {
req.setAttribute(Constants.REQUEST_MSG, "请先进行登录操作!");
req.getRequestDispatcher("login.jsp").forward(req, response);
}
}
}
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
}
}
再次运行项目的时候可以发现在不登录状态下是不能进入到主界面的。
运行项目后咱们在chorme浏览器中进行了登录操作,然后打开IE浏览器再次进行操作,我们会发现用同一个账号在不同的地方可以同时登录,这感觉不太好,应该是在不同地方同时登录时会顶掉先登录的浏览器中的账号。
首先想要完成这个操作,我们会想到登录以后用户信息放在了session作用域中,通过ServletContext对象可以获取到这些信息,我们只需要当在另一个地方进行登录的时候将session中用户的信息删除掉就可以完成这个功能了。
想到这里我们就想到了监听器Listenter(其作用就是监听某个对象的的状态变化的组件)
其中的sessionDestroyed方法就是在销毁对象之前执行的一个方法
代码如下
package com.lc.web.listenter;
import javax.servlet.ServletContext;
import javax.servlet.annotation.WebFilter;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import com.lc.commons.Constants;
import com.lc.pojo.Users;
/**
* 解决当一个浏览器中进行登录操作后session已经销毁,在另一个浏览器登录时报错问题
* @author Administrator
*
*/
@WebListener
public class UserLoginListenter implements HttpSessionListener{
@Override
public void sessionCreated(HttpSessionEvent arg0) {
// TODO Auto-generated method stub
}
@Override
public void sessionDestroyed(HttpSessionEvent se) {
HttpSession session = se.getSession();
ServletContext servletContext = session.getServletContext();
Users users = (Users) servletContext.getAttribute(Constants.USER_SESSION_KEY);
session.removeAttribute(users.getUserid()+"");
}
}
有了监听器的存在对于退出功能也就好实现了直接获取到session后利用其invalidate()方法,将里面的信息进行清除,然后响应重定向到登录界面就很简单了
代码如下
package com.lc.web.serlvet;
import java.io.IOException;
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;
/**
* 退出操作
* @author Administrator
*
*/
@WebServlet("/logout.do")
public class UserLoginOutServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
session.invalidate();
req.getRequestDispatcher("login.jsp").forward(req, resp);
}
}
对于用户表的增删改查感兴趣的同学可以自己写一写,我就不讲这些代码放在这里了,由于最近项目比较紧,开始加班模式了,只能简化一下啦。
对于这个小练习有不同见解的或者我写的有问题的地方希望同学可以提出来,大家一起讨论一下,以加深理解。