三、用户登录
3.1 用户-登录-持久层
3.1.1 规划需要执行的SQL语句
用户登录功能需要执行的SQL语句是根据用户名查询用户数据,再判断密码是否正确。SQL语句大致是:
SELECT * FROM t_user WHERE username=?
说明:以上SQL语句对应的后台开发已经完成,无需再次开发。
3.2 用户-登录-业务层
3.2.1 规划异常
1.如果用户名不存在则登录失败,抛出com.cy.store.service.ex.UserNotFoundException异常,并从父类生成子类的五个构造方法。
package com.cy.store.service.ex;
/** 用户数据不存在的异常 */
public class UserNotFoundException extends ServiceException {
// Override Methods...
}
2.如果用户的isDelete字段的值为1,则表示当前用户数据被标记为“已删除”,需进行登录失败操作同时抛出UserNotFoundException。
3.如果密码错误则进行登录失败操作,同时抛出com.cy.store.service.ex.PasswordNotMatchException异常。
package com.cy.store.service.ex;
/** 密码验证失败的异常 */
public class PasswordNotMatchException extends ServiceException {
// Override Methods...
}
4.创建以上UserNotFoundException和PasswordNotMatchException异常类,以上异常类应继承自ServiceException类。
3.2.2 接口与抽象方法
在IUserService接口中添加登录功能的抽象方法。
/**
* 用户登录
* @param username 用户名
* @param password 密码
* @return 登录成功的用户数据
*/
User login(String username, String password);
当登录成功后需要获取该用户的id,以便于后续识别该用户的身份,并且还需要获取该用户的用户名、头像等数据,用于显示在软件的界面中,需使用可以封装用于id、用户名和头像的数据的类型来作为登录方法的返回值类型。
3.2.3 实现抽象方法
1.在UserServiceImpl类中添加login(String username, String password)方法并分析业务逻辑。
@Override
public User login(String username, String password) {
// 调用userMapper的findByUsername()方法,根据参数username查询用户数据
// 判断查询结果是否为null
// 是:抛出UserNotFoundException异常
// 判断查询结果中的isDelete是否为1
// 是:抛出UserNotFoundException异常
// 从查询结果中获取盐值
// 调用getMd5Password()方法,将参数password和salt结合起来进行加密
// 判断查询结果中的密码,与以上加密得到的密码是否不一致
// 是:抛出PasswordNotMatchException异常
// 创建新的User对象
// 将查询结果中的uid、username、avatar封装到新的user对象中
// 返回新的user对象
return null;
}
2.login(String username, String password)方法中代码的具体实现。
@Override
public User login(String username, String password) {
// 调用userMapper的findByUsername()方法,根据参数username查询用户数据
User result = userMapper.findByUsername(username);
// 判断查询结果是否为null
if (result == null) {
// 是:抛出UserNotFoundException异常
throw new UserNotFoundException("用户数据不存在的错误");
}
// 判断查询结果中的isDelete是否为1
if (result.getIsDelete() == 1) {
// 是:抛出UserNotFoundException异常
throw new UserNotFoundException("用户数据不存在的错误");
}
// 从查询结果中获取盐值
String salt = result.getSalt();
// 调用getMd5Password()方法,将参数password和salt结合起来进行加密
String md5Password = getMd5Password(password, salt);
// 判断查询结果中的密码,与以上加密得到的密码是否不一致
if (!result.getPassword().equals(md5Password)) {
// 是:抛出PasswordNotMatchException异常
throw new PasswordNotMatchException("密码验证失败的错误");
}
// 创建新的User对象
User user = new User();
// 将查询结果中的uid、username、avatar封装到新的user对象中
user.setUid(result.getUid());
user.setUsername(result.getUsername());
user.setAvatar(result.getAvatar());
// 返回新的user对象
return user;
}
3.完成后在UserServiceTests中编写并完成单元测试。
@Test
public void login() {
try {
String username = "lower";
String password = "123456";
User user = iUserService.login(username, password);
System.out.println("登录成功!" + user);
} catch (ServiceException e) {
System.out.println("登录失败!" + e.getClass().getSimpleName());
System.out.println(e.getMessage());
}
注意:不要使用错误的数据尝试登录,例如早期通过持久层测试新增用户的数据,将这些数据从表中删除。
3.3 用户-登录-控制器
3.3.1 处理异常
处理用户登录功能时,在业务层抛出了UserNotFoundException和PasswordNotMatchException异常,而这两个异常均未被处理过。则应在BaseController类的处理异常的方法中,添加这两个分支进行处理。
@ExceptionHandler(ServiceException.class)
public JsonResult<Void> handleException(Throwable e) {
JsonResult<Void> result = new JsonResult<Void>(e);
if (e instanceof UsernameDuplicateException) {
result.setState(4000);
} else if (e instanceof UserNotFoundException) {
result.setState(4001);
} else if (e instanceof PasswordNotMatchException) {
result.setState(4002);
} else if (e instanceof InsertException) {
result.setState(5000);
}
return result;
}
3.3.2 设计请求
设计用户提交的请求,并设计响应的方式:
请求路径:/users/login
请求参数:String username, String password
请求类型:POST
响应结果:JsonResult<User>
3.3.3 处理请求
1.在UserController类中添加处理登录请求的login(String username, String password)方法。
@RequestMapping("login")
public JsonResult<User> login(String username, String password) {
// 调用业务对象的方法执行登录,并获取返回值
// 将以上返回值和状态码OK封装到响应结果中并返回
return null;
}
2.处理登录请求的login(String username, String password)方法代码具体实现。
@RequestMapping("login")
public JsonResult<User> login(String username, String password) {
// 调用业务对象的方法执行登录,并获取返回值
User data = userService.login(username, password);
// 将以上返回值和状态码OK封装到响应结果中并返回
return new JsonResult<User>(OK, data);
}
4.完成后启动项目,访问http://localhost:8080/users/login?username=Tom&password=1234请求进行登录。
3.4 用户-登录-前端页面
1.在login.html页面中body标签内部的最后,添加script标签用于编写JavaScript程序。
<script type="text/javascript">
$("#btn-login").click(function() {
$.ajax({
url: "/users/login",
type: "POST",
data: $("#form-login").serialize(),
dataType: "json",
success: function(json) {
if (json.state == 200) {
alert("登录成功!");
location.href = "index.html";
} else {
alert("登录失败!" + json.message);
}
}
});
});
</script>
2.完成后启动项目,打开浏览器访问http://localhost:8080/web/login.html页面并进行登录。
四、拦截器
在Spring MVC中拦截请求是通过处理器拦截器HandlerInterceptor来实现的,它拦截的目标是请求的地址。在Spring MVC中定义一个拦截器,需要实现HandlerInterceptor接口。
4.1 HandlerInterceptor
4.1.1 preHandle()方法
该方法将在请求处理之前被调用。SpringMVC中的Interceptor是链式的调用,在一个应用或一个请求中可以同时存在多个Interceptor。每个Interceptor的调用会依据它的声明顺序依次执行,而且最先执行的都是Interceptor中的preHandle()方法,所以可以在这个方法中进行一些前置初始化操作或者是对当前请求的一个预处理,也可以在这个方法中进行一些判断来决定请求是否要继续进行下去。该方法的返回值是布尔值类型,当返回false时,表示请求结束,后续的Interceptor和Controller都不会再执行;当返回值true时,就会继续调用下一个Interceptor的preHandle方法,如果已经是最后一个Interceptor的时,就会调用当前请求的Controller方法。
4.1.2 postHandle()方法
该方法将在当前请求进行处理之后,也就是Controller方法调用之后执行,但是它会在DispatcherServlet进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller处理之后的ModelAndView对象进行操作。postHandle方法被调用的方向跟preHandle是相反的,也就是说先声明的Interceptor的postHandle方法反而会后执行。如果当前Interceptor的preHandle()方法返回值为false,则此方法不会被调用。
4.1.3 afterCompletion()方法
该方法将在整个当前请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行。这个方法的主要作用是用于进行资源清理工作。如果当前Interceptor的preHandle()方法返回值为false,则此方法不会被调用。
4.2 WebMvcConfigurer
在SpringBoot项目中,如果想要自定义一些Interceptor、ViewResolver、MessageConverter,该如何实现呢?在SpringBoot 1.5版本都是靠重写WebMvcConfigurerAdapter类中的方法来添加自定义拦截器、视图解析器、消息转换器等。而在SpringBoot 2.0版本之后,该类被标记为@Deprecated。因此我们只能靠实现WebMvcConfigurer接口来实现。
WebMvcConfigurer接口中的核心方法之一addInterceptors(InterceptorRegistry registry)方法表示添加拦截器。主要用于进行用户登录状态的拦截,日志的拦截等。
- addInterceptor:需要一个实现HandlerInterceptor接口的拦截器实例
- addPathPatterns:用于设置拦截器的过滤路径规则;addPathPatterns("/**")对所有请求都拦截
- excludePathPatterns:用于设置不需要拦截的过滤规则
public interface WebMvcConfigurer {
// ...
default void addInterceptors(InterceptorRegistry registry) {
}
}
4.3 项目添加拦截器功能
1.分析:项目中很多操作都是需要先登录才可以执行的,如果在每个请求处理之前都编写代码检查Session中有没有登录信息,是不现实的。所以应使用拦截器解决该问题。
2.创建拦截器类com.cy.store.interceptor.LoginInterceptor,并实现org.springframework.web.servlet.HandlerInterceptor接口。
package com.cy.store.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** 定义处理器拦截器 */
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (request.getSession().getAttribute("uid") == null) {
response.sendRedirect("/web/login.html");
return false;
}
return true;
}
}
3.创建LoginInterceptorConfigurer拦截器的配置类并实现org.springframework.web.servlet.config.annotation.WebMvcConfigurer接口,配置类需要添加@Configruation注解修饰。
package com.cy.store.config;
import com.cy.store.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.List;
/** 注册处理器拦截器 */
@Configuration
public class LoginInterceptorConfigurer implements WebMvcConfigurer {
/** 拦截器配置 */
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 创建拦截器对象
HandlerInterceptor interceptor = new LoginInterceptor();
// 白名单
List<String> patterns = new ArrayList<String>();
patterns.add("/bootstrap3/**");
patterns.add("/css/**");
patterns.add("/images/**");
patterns.add("/js/**");
patterns.add("/web/register.html");
patterns.add("/web/login.html");
patterns.add("/web/index.html");
patterns.add("/web/product.html");
patterns.add("/users/reg");
patterns.add("/users/login");
patterns.add("/districts/**");
patterns.add("/products/**");
// 通过注册工具添加拦截器
registry.addInterceptor(interceptor).addPathPatterns("/**").excludePathPatterns(patterns);
}
}
4.4会话
1.重新构建login()方法,登录成功后将uid和username存入到HttpSession对象中。
@RequestMapping("login")
public JsonResult<User> login(String username, String password, HttpSession session) {
// 调用业务对象的方法执行登录,并获取返回值
User data = userService.login(username, password);
//登录成功后,将uid和username存入到HttpSession中
session.setAttribute("uid", data.getUid());
session.setAttribute("username", data.getUsername());
// System.out.println("Session中的uid=" + getUidFromSession(session));
// System.out.println("Session中的username=" + getUsernameFromSession(session));
// 将以上返回值和状态码OK封装到响应结果中并返回
return new JsonResult<User>(OK, data);
}
2.在父类BaseController中添加从HttpSession对象中获取uid和username的方法,以便于后续快捷的获取这两个属性的值。
/**
* 从HttpSession对象中获取uid
* @param session HttpSession对象
* @return 当前登录的用户的id
*/
protected final Integer getUidFromSession(HttpSession session) {
return Integer.valueOf(session.getAttribute("uid").toString());
}
/**
* 从HttpSession对象中获取用户名
* @param session HttpSession对象
* @return 当前登录的用户名
*/
protected final String getUsernameFromSession(HttpSession session) {
return session.getAttribute("username").toString();
}
源码获取:点赞+关注+评论666