登录功能实现
login.do 是请求
1. 编写前端登录页面:login.jsp
<%--<!DOCTYPE html>--%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>系统登录 - 超市订单管理系统</title>
<link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath }/css/style.css" />
<script type="text/javascript">
/* if(top.location!=self.location){
top.location=self.location;
}*/
</script>
</head>
<body class="login_bg">
<section class="loginBox">
<header class="loginHeader">
<h1>超市订单管理系统</h1>
</header>
<section class="loginCont">
<form class="loginForm" action="${pageContext.request.contextPath }/login.do" name="actionForm" id="actionForm" method="post" >
<div class="info">${error}</div>
<div class="inputbox">
<label for="userCode">用户名:</label>
<input type="text" class="input-text" id="userCode" name="userCode" placeholder="请输入用户名" required/>
</div>
<div class="inputbox">
<label for="userPassword">密码:</label>
<input type="password" id="userPassword" name="userPassword" placeholder="请输入密码" required/>
</div>
<div class="subBtn">
<input type="submit" value="登录"/>
<input type="reset" value="重置"/>
</div>
</form>
</section>
</section>
</body>
</html>
2. 设置欢迎页面,让服务器已启动就跳转到登录页面
配置 web.xml
<!-- 设置欢迎页面-->
<welcome-file-list>
<welcome-file>login.jsp</welcome-file>
</welcome-file-list>
启动 tomcat,看是否打开即成功显示 login.jsp
成功跳出页面,但是登录会显示404未找到,因为后面的还没写!
接下来用户登录后,我们要去判断用户信息和密码是否正确,首先要撰写操作数据库的类,去数据库进行查找
3. 编写Dao层得到用户登录的接口
接口 UserDao:
User getLoginUser(Connection connection, String userCode) throws SQLException;
从数据库中得到要登录的用户,通过用户名利用 sql 进行查询,查询到后将其封装到用户实体类中,封装完成后返回 user
4. 编写Dao层的实现类
UserDaoImpl implements UserDao:
@Override
public User getLoginUser(Connection connection, String userCode) throws SQLException {
PreparedStatement preparedStatement = null;
ResultSet rs = null;
User user = null;
if (connection != null) {
String sql = "select * from smbms_user where userCode=?"; // 查询表里面的所有人,判断条件 userCode 是否 = 登录的用户名
Object[] params = {userCode};
try {
rs = BaseDao.execute(connection, preparedStatement, rs, sql, params); // 执行 sql
if (rs.next()) {
user = new User();
user.setId(rs.getInt("id"));
user.setUserCode(rs.getString("userCode"));
user.setUserName(rs.getString("userName"));
user.setUserPassword(rs.getString("userPassword"));
user.setGender(rs.getInt("gender"));
user.setBirthday(rs.getDate("birthday"));
user.setPhone(rs.getString("phone"));
user.setAddress(rs.getString("address"));
user.setUserRole(rs.getInt("userRole"));
user.setCreatedBy(rs.getInt("createdBy"));
user.setCreationDate(rs.getTimestamp("creationDate"));
user.setModifyBy(rs.getInt("modifyBy"));
user.setModifyDate(rs.getTimestamp("modifyDate"));
}
BaseDao.closeResource(null, preparedStatement, rs);
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return user;
}
5. 编写业务层接口
public User login(String userCode,String password) throws SQLException;
6. 业务层实现类
通过判断密码是否匹配正确,实现用户登录
@Override
public User login(String userCode, String password) {
Connection connection = null;
User user = null;
try {
// System.out.println("UserServiceImpl:"+userCode);
// System.out.println("UserServiceImpl:"+password);
connection = BaseDao.getConnection();
// 通过业务层调用对应的具体数据库操作
user = userDao.getLoginUser(connection, userCode);
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
BaseDao.closeResource(connection, null, null);
}
//进行密码匹配
if (user != null) {
if (!user.getUserPassword().equals(password)) {
user = null;
}
}
return user;
}
在 pom.xml 中加入 junit 依赖,进行单元测试
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
测试是否可以查询到密码,并输出
@Test
public void test() {
UserServiceImpl userService = new UserServiceImpl();
User admin = userService.login("admin", "1234567");
System.out.println(admin.getUserPassword());
}
当账号:admin 和密码:1234567 正确时,输出正确结果
否则,错误
7. 编写 servlet:用于获取前端请求的参数,并调用业务层判断是否存在该用户
先获取页面用户输入的用户名和密码,然后和数据库中的匹配,匹配成功,则将用户信息放入到 session 中,并跳转到主页,如果没有匹配成功,则无法登录,转发回登录页面,提示用户名或者密码错误
package com.uestc.servlet.user;
import com.uestc.pojo.User;
import com.uestc.service.user.UserServiceImpl;
import com.uestc.utils.Constants;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
// Servlet控制层,调用业务层代码
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// System.out.println("LoginServlet---start");
//获取用户名和密码
String userCode = req.getParameter("userCode");
String userPassword = req.getParameter("userPassword");
System.out.println("LoginServlet:"+userCode);
System.out.println("LoginServlet:"+userPassword);
//和数据库中的密码进行比较,调用业务层
UserServiceImpl userService = new UserServiceImpl();
//这里已经把登录的人查出来了
User user = userService.login(userCode, userPassword);
//查到了,则进行登录,将用户信息放到Session中
if (user!=null){
req.getSession().setAttribute(Constants.USER_SESSION, user);
//跳转主页
resp.sendRedirect("jsp/frame.jsp");
}else {
//查无此人,无法登录,转发回登录页面,提示用户名或者密码错误
req.setAttribute("error", "用户名或者密码错误");
req.getRequestDispatcher("login.jsp").forward(req, resp);
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
上述:req.setAttribute(“error”, “用户名或者密码错误”); 的设置来源于 login.jsp 中如下:
Utils 中定义 Constants 类,专门用于存储常理,方便修改
package com.uestc.utils;
public class Constants {
//定义一个用户session常量
public final static String USER_SESSION = "userSession";
}
jsp/frame.jsp 用于请求页面跳转,因此,在 webapp 中新建 jsp 文件,并完善页面 frame.jsp
web.xml 中进行注册
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.threepure.servlet.user.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login.do</url-pattern>
</servlet-mapping>
这个请求的名字 /login.do
是规定好的,和 login.jsp 中某处是一致的如下,如果要改,两边都要改
启动 tomcat,账户输入错误,会显示用户名或密码错误
输入正确后,即可跳转页面
但是由于这些功能都还没有实现,所有点击后为 404
9. 测试访问,保证以上功能可以成功
登录功能优化
登录注销功能实现
1. 编写servlet:用于移除session属性,再返回登录页面
package com.uestc.servlet.user;
import com.uestc.utils.Constants;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//移除用户的 Constants.USER_SESSION
req.getSession().removeAttribute(Constants.USER_SESSION);
//重新返回登录页面
resp.sendRedirect(req.getContextPath()+"/login.jsp"); // req.getContextPath() 为项目目录,但是有时会有后面的请求重复,导致无法找到
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
- 注意:req.getContextPath() 是为了拿到项目的路径,但是有时候会和后面的请求重复,所有要测试
注册 web.xml
<servlet>
<servlet-name>LogoutServlet</servlet-name>
<servlet-class>com.uestc.servlet.user.LogoutServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LogoutServlet</servlet-name>
<url-pattern>/jsp/logout.do</url-pattern>
</servlet-mapping>
注意这里请求为:/jsp/logout.do,那是由于前端页面 head.jsp 的退出系统也是用的 /jsp/logout.do,所以这里注册的请求一定要与前端页面保持一致(前端表示:点击退出系统和退出,自动请求 /jsp/logout.do)
启动 tomcat 并登录显示如下:并点击退出和退出系统请求的是一样的,但是并没有完成注销,退出之后依然可以登录,所以需要进行登录拦截优化
登录拦截优化
1. 为了保证用户注销之后不能再进入主页,需要设置过滤器
package com.uestc.filter;
import com.uestc.pojo.User;
import com.uestc.utils.Constants;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
//判断是否登录的过滤器
public class SysFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 提取 request,response
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
// 过滤器,从 Session 中获取用户
User user = (User) request.getSession().getAttribute(Constants.USER_SESSION);
// 无 Session,既已被注销或者没有登录
if (user == null) {
// 移除后,再次登录跳转到 error.jsp 页面
// System.out.println("没有查到用户");
response.sendRedirect(request.getContextPath() + "/error.jsp");
} else {
filterChain.doFilter(servletRequest, servletResponse); // 继续执行程序,注意是 servletRequest, servletResponse, 不是提取的 request,response
}
}
@Override
public void destroy() {
}
}
2. 注册过滤器
web.xml 中进行注册,jsp 文件下所有页面都要过这个过滤器
<filter>
<filter-name>SysFilter</filter-name>
<filter-class>com.uestc.filter.SysFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SysFilter</filter-name>
<url-pattern>/jsp/*</url-pattern>
</filter-mapping>
启动 tomcat,并登录,然后复制主页的 url 记录:http://localhost:8080/smbms/jsp/frame.jsp,并点击退出系统
在导航栏输入刚刚的访问链接 http://localhost:8080/smbms/jsp/frame.jsp,无法再次访问,成功注销
密码修改
实现增删改查,必须自低向上去编写
1. 导入前端素材
<li><a href="${pageContext.request.contextPath }/jsp/pwdmodify.jsp">密码修改</a></li>
点击密码修改后弹出如下
如果点击密码修改没有出现如下页面,而是 404,说明缺少对应页面:pwdmodify.jsp
pwdmodify.jsp 以及 pwdmodify.js 的配合完成的,及这个密码修改是前端完成的,没有交给后端完成。
pwdmodify.jsp 基本的外形
pwdmodify.js 进行一些逻辑规则
在 pwdmodify.jsp 中,可以找到处理表单的请求为 /jsp/user.do,因此我们要去完善这个请求。
通常访问网页,前端会去请求 servlet,Servlet 调用 service,service 会调用 Dao 层操作数据库,因此写代码从低层开始往上写,边写边思考架构
现在我们需要修改密码,也就是根据用户 id,将数据库中的密码修改了,
1. UserDao 到 UserService 中逐步添加修改密码的方法
先在接口 UserDao 中定义更新密码的接口方法
int updatePwd(Connection connection, int id, String password) throws SQLException;
UserDaoImpl 中实现该更新密码方法,返回 1 说明修改成功
@Override
public int updatePwd(Connection connection, int id, String password) throws SQLException {
PreparedStatement pstm = null;
int execute = 0;
if (connection != null) {
String sql = "update smbms_user set userPassword = ? where id = ?";
Object[] params = {password, id};
try {
execute = BaseDao.execute(connection, pstm, sql, params);
} catch (Exception e) {
throw new RuntimeException(e);
}
BaseDao.closeResource(null, pstm,null); // 只需要关闭 pstm,因为其他都是空
}
return execute;
}
然后业务层接口 UserService 定义修改密码的方法
boolean updatePwd(int id, String pwd);
业务层 UserServiceImpl 实现 updatePwd
@Override
public boolean updatePwd(int id, String pwd) {
Connection connection = null;
boolean flag = false;
try {
connection = BaseDao.getConnection(); // 连接数据库
// 调用 Dao 层修改密码
if (userDao.updatePwd(connection, id, pwd) > 0) {
flag = true; // 成功修改
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
BaseDao.closeResource(connection, null, null); // 关闭资源
}
return flag;
}
servlet 记得实现复用,要提取出方法!
在 dao层 和 service层 自己写映射类和实现类
下面是 servlet 层的主体,可以先注册好 web.xml,因为这个请求是写死了得
<servlet>
<servlet-name>UserServlet</servlet-name>
<servlet-class>com.uestc.servlet.user.UserServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>UserServlet</servlet-name>
<url-pattern>/jsp/user.do</url-pattern>
</servlet-mapping>
如果上述请求改一处,就要把前端页面中所有 /jsp/user.do 的请求都改掉
UserServlet 类,响应密码修改,并调用业务层,业务层调用Dao层,实现底层数据库的用户密码修改(因为 doGet 要实现增删改查,所以将 updatePwd 提取出来,而不是直接写再 doGet 方法内)
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getParameter("method");
if ("savepwd".equals(method) && method != null) {
this.updatePwd(req, resp);
}
}
private void updatePwd(HttpServletRequest req, HttpServletResponse resp) {
// 从 Session 中拿到用户 ID
Object o = req.getSession().getAttribute(Constants.USER_SESSION); // 用户 ID 通过 session 获取
String newpassword = req.getParameter("newpassword");
boolean flag = false;
if (o != null && !StringUtils.isNullOrEmpty(newpassword)) {
UserService userService = new UserServiceImpl();
flag = userService.updatePwd(((User) o).getId(), newpassword); // (User) o 强制类型转换
if (flag) {
//修改密码成功,移除Session
req.setAttribute("message", "修改密码成功,请用新密码重新登录");
req.getSession().removeAttribute(Constants.USER_SESSION);
} else {
req.setAttribute("message", "修改密码失败");
}
} else {
req.setAttribute("message", "新密码有问题");
}
try {
req.getRequestDispatcher("pwdmodify.jsp").forward(req, resp);
} catch (ServletException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
- 代码中 message 与 pwdmodify.jsp 中的一段代码
<div class="info">${message}</div>
是对应的
旧密码是用 AJAX 来进行验证的,所以这里要先将此功能注释掉,在 js 文件夹的 pwdmodify.js 中去掉最下面的如下所示的代码,及把旧密码的判断去掉
否则会导致在密码修改点击保存时,没有反应
没有反应是因为 UserServlet 的 updatePwd 方法,一直走的是 req.getRequestDispatcher(“pwdmodify.jsp”).forward(req, resp); 没有真正执行密码修改逻辑部分(旧密码要使用 AJAX 的方法)
注释掉后,重新启动,输入新密码,就有反应啦
同时可以在页面点击检查,来查看请求是否正确
可以看到正确请求了 pwdmodify.jsp
点击确定即可成功修改,数据库 admin 的密码发生了改变
对于旧密码的判断,一种方法是放入到 UserServlet 的处理中,将旧密码与 session 中的密码进行对比判断,但是效率不高
优化密码修改使用 Ajax(Ajax验证旧密码实现)
1. 阿里巴巴的 fastjson
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.68</version>
</dependency>
先来看 pwdmodify.js 的就旧密码是如何使用 Ajax 的
验证旧密码不需要将接口方法写入到 Dao 层中,因为 session 中有用户的密码,直接使用 Servlet 即可。
pwdmodify.js 的旧密码验证,是键值对的形式,其中:data:{method:“pwdmodify”,oldpassword:oldpassword.val()}, oldpassword.val() 表示是从前端获取,key 为 oldpassword,因此在 UserServlet 类的 pwdmodify 方法中,通过 req.getParameter(“oldpassword”); 来获取 oldpassword
web.xml 注册设置过期时间,为 30 分钟
<!-- 默认 Session 过期时间:真实业务需求-->
<session-config>
<session-timeout>30</session-timeout>
</session-config>
继续看 pwdmodify.js:注意会传入 data 这个数据,data.result 是自己定义的属性
在 pom.xml 中引入阿里巴巴的 fastjson
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.40</version>
</dependency>
通过 UserServlet 类中定义 pwdmodify 方法,实现密码验证
// 验证旧密码, Session 中有用户的密码
private void pwdmodify(HttpServletRequest req, HttpServletResponse resp) {
// 从 Session 内获取用户 ID
Object o = req.getSession().getAttribute(Constants.USER_SESSION);
String oldpassword = req.getParameter("oldpassword");
//万能的Map:结果集
Map<String, String> resultMap = new HashMap<String, String>();
//进行判断
if (o == null) { // o == null 会导致 Session 失效或过期
//1.Session失效了或者Session过期了
resultMap.put("result", "sessionerror"); // 走 pwdmodify.js 中 data.result == "sessionerror" 部分
} else if (StringUtils.isNullOrEmpty(oldpassword)) { //旧密码输入为空
//输入为空
resultMap.put("result", "error"); // 走 pwdmodify.js 中 data.result == "error" 部分
} else {
//获取Session中的用户密码
String userPassword = ((User) o).getUserPassword();
//判断输入的旧密码是否与当前Session中的密码一致
if (oldpassword.equals(userPassword)) {
resultMap.put("result", "true"); // 密码正确,走 data.result == "true" 部分
} else { //旧密码输入不正确
resultMap.put("result", "false");
}
}
try {
// 将上述的一系列判断返回 json 值
resp.setContentType("application/json"); //就像resp.setContentType("text/html")
PrintWriter writer = resp.getWriter(); // 定义流处理
//JSONArray:阿里巴巴的JSON工具类,转换格式
/* resultMap输出出来是{"result","sessionerror" , "result","error"}一对对的键值对 HashMap<K,V>
Json格式={key:value}
* */
writer.write(JSONArray.toJSONString(resultMap));
//最后刷新和关闭流资源
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
以上方法是 AJAX 方式,通过 js 配合实现,是企业常用方式,也可以通过自己写一个类验证,然后在 web.xml 中注册来实现。
启动 tomcat,进行验证,进入密码修改栏目,旧密码出如果输入正确会打勾
如果输入错误会打叉
通过检查网页元素,中的网络查看请求,当打开检查,并在旧密码处进行输入时,会看到如下发送了一次请求,AJAX 是异步的
显示的那条请求分析如下:
后台处理并返回这个响应 {“result”:“false”}
这里有 AJAX 返回的代码中看到的键值对 data:{method:“pwdmodify”,oldpassword:oldpassword.val()},
这里的乱码,需要在 js 里面解决掉,这是来源于 js 的