用户登录

功能分析

用户进入登陆页面,输入用户名和密码,点击登录按钮,向后台程序发送请求,后台根据用户信息在数据库中进行查找,如果用户名和密码均正确,则会跳转到对应的主页面,即index.html

持久层

规划要执行的SQL语句

分析:一般逻辑是根据用户名查询数据库返回用户记录给业务层,由于密码存储一般是经过加密后的数据,数据库中与用户输入一般不一致,所以密码等其他信息的校验一般放在业务层去进行

1
select * from t_user where username=?;

相关持久层的功能在用户注册模块已经实现

业务层

规划异常

密码错误异常

用户名正确,密码错误引发的异常,PasswordNotMatchException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.bang.store.service.ex;

public class PasswordNotMatchException extends ServiceException{
public PasswordNotMatchException() {
super();
}

public PasswordNotMatchException(String message) {
super(message);
}

public PasswordNotMatchException(String message, Throwable cause) {
super(message, cause);
}

public PasswordNotMatchException(Throwable cause) {
super(cause);
}

protected PasswordNotMatchException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

用户名不存在异常

用户名数据库中不存在所引发的异常,UsernameNotFoundException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.bang.store.service.ex;

public class UsernameNotFoundException extends ServiceException{
public UsernameNotFoundException() {
super();
}

public UsernameNotFoundException(String message) {
super(message);
}

public UsernameNotFoundException(String message, Throwable cause) {
super(message, cause);
}

public UsernameNotFoundException(Throwable cause) {
super(cause);
}

protected UsernameNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

设计接口和抽象方法

直接在IUserService接口中编写登录方法login(String username,String password)

如果登录成功,将用户数据对象需要作为方法返回值返回

状态管理:将相关数据保存在cookie或者session中,可以避免重复度很好的数据多次频繁操作数据库获取

1
2
3
4
5
6
7
 /**
* 用户登录方法
* @param username 用户名
* @param password 用户密码
* @return : 用户对象,因为登陆成功后,各个页面右上角会显示用户信息,需要用到用户对象
*/
User login(String username,String password);

接口实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public User login(String username, String password) {
//根据用户名称查询用户对象
User user = userMapper.findByUsername(username);
//用户对象不存在,抛出用户名不存在异常
if(user==null){
throw new UsernameNotFoundException("用户不存在");
}
//用户密码匹配
if(!user.getPassword().equals(getMD5password(password,user.getSalt()))){
throw new PasswordNotMatchException("用户密码错误");
}
//判断用户是否注销
if(user.getIsDelete()==1){
throw new UsernameNotFoundException("用户不存在");
}
return user;

}

控制层

处理异常

根据业务层抛出异常,在统一异常处理基类中做出对应的处理

1
2
3
4
5
6
7
else if (e instanceof UsernameNotFoundException) {
result.setState(5001);
result.setMessage("用户名不存在异常");
} else if (e instanceof PasswordNotMatchException) {
result.setState(5002);
result.setMessage("用户密码不匹配异常");
}

设计请求

1
2
3
4
请求路径:/user/login
请求方法:POST
请求参数:String username,String password
响应结果:JsonResult<User>

处理请求

UserController类中编写对应的请求处理方法

1
2
3
4
5
@RequestMapping("/login")
public JsonResult<User> login(String username,String password){
User user = userService.login(username, password);
return new JsonResult<User>(OK,"登陆成功",user);
}

前端页面

login.html页面中找到对应表单,在表单中找到登录按钮,将登录按钮与指定时间绑定,按钮点击,向后端指定程序发送请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script>
$("#btn-login").click(function (){
$.ajax({
url: "/user/login"
,type: "POST"
,data: $("#form-login").serialize()
,dataType: "JSON"
,success: function (data){
if(data.state == 200){
alert("登录成功");
//跳转到对应页面
//相对路径指定对应页面位置
location.href="index.html"
}else{
alert("登陆失败 "+data.message);
}
}
,error:function (xmh){
alert("登陆失败"+xmh.status);
}
});
});
</script>

用户会话Session

session对象主要存储在服务器端,可以用于保存服务器的临时数据,其在整个项目中都可以被访问,可以在不同模块之间进行数据共享。

对于用户登录功能,可以将用户当前首次登录输入的信息数据存储在session对象中,供整个会话期间,其他模块共用。

1
2
3
4
//session对象存储数据
session.setAttribute(key,value);
//从session对象获取数据
session.getAttribute(key)

可能在一个项目中,多次用到session对象存储数据或者从session对象中读取数据,为了减少代码冗余,应该将这两个操作封装到函数中

可以封装到工具类,但是由于这两个操作只会在控制层使用,而本项目控制层存在基类,所以将该方法定义在控制层基类BaseController

由于存储操作要视具体情况而定,所以只封装从session对象读取数据功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 /**
* 从session对象中获取当前登录用户的uid
* @param session session对象
* @return uid:当前登录用户的uid
*/
public final Integer getUidFromSession(HttpSession session){
return (Integer) session.getAttribute("uid");
}

/**
* 从session对象中获取当前用户的username
* @param session session对象
* @return username:当前登录用户的username
*/
public final String getUsernameFromSession(HttpSession session){
return (String) session.getAttribute("username");
}

相关位置将数据存取到session对象中

UserController类中的login函数中将相关数据存取到session对象

1
2
3
4
5
6
7
8
@RequestMapping("/login")
public JsonResult<User> login(String username, String password, HttpSession session){
User user = userService.login(username, password);
//数据存取到session对象
session.setAttribute("uid",user.getUid());
session.setAttribute("username",user.getUsername());
return new JsonResult<User>(OK,"登陆成功",user);
}

用户登录拦截器

拦截器

会首先将用户所有请求统一拦截到拦截器中进行处理,所以可以到拦截器中自定义过滤规则,达到拦截请求、过滤响应的目的

比如:对于当前项目(商城系统),访问其他页面会全部拦截到拦截器,判断用户是否登录,如果没有则统一会打开login.html用户登录页面,打开其他页面,可以使用重定向或者请求转发技术来完成

推荐使用重定向技术,如果两个模块不在同一个服务器上,转发可能会出现错误

SpringBoot中如何拦截器的使用

SpringMVC提供了一个HandleInterceptor接口,用于表示拦截器

使用步骤

  1. 自定义一个类,实现HandleInterceptor接口,项目一般会将所有拦截器统一放在一个目录下,本项目中统一放在com.bang.store.interceptor包下

    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
    package com.bang.store.interceptor;

    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;

    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;

    /**
    * 定义用户登录拦截器
    */
    public class LoginInterceptor implements HandlerInterceptor {
    //在所有请求处理方法之前被调用执行,即在请求到达Dispatcher中央处理器之前
    //拦截器的重点关注位置

    /**
    * 功能:检测session对象中是否含有uid数据,如果有则放行请求,否则重定向到用户登录界面
    * @param request 请求对象
    * @param response 响应对象
    * @param handler 处理器
    * @return 如果返回值为true,正常放行;如果为false,则表示拦截,不放行,
    * @throws Exception
    */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    //获取session对象
    HttpSession session = request.getSession();
    //获取uid数据
    Object uid = session.getAttribute("uid");
    if(uid==null){ //表明用户没有登录
    response.sendRedirect("/web/login.html"); //重定向到登录页面
    return false; //拦截请求
    }
    return true; //用户登录后则直接放行
    }

    //请求执行后被调用
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    //所有关联操作完成之后调用
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
    }
  2. 注册拦截器

    添加白名单与黑名单,即当前拦截器只对那些请求起作用、对哪些请求不起作用

    比如:注册、登录页面不能够被拦截,否则任何界面都无法进入,造成死循环

  3. 拦截器注册操作

    借助WebMVCConfigure接口,可以将用户定义的拦截器进行注册,才能使拦截器生效

    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
    package com.bang.store.configure;

    import com.bang.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;

    //注解,让SpringBoot识别
    @Configuration
    public class LoginInterceptorConfigure implements WebMvcConfigurer {

    /**
    * 添加拦截器
    * @param registry
    */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    //实例化自定义拦截器
    HandlerInterceptor loginInterceptor = new LoginInterceptor();
    //配置白名单
    List<String> whiteList = new ArrayList<String>();
    whiteList.add("/web/login.html");
    whiteList.add("/web/register.html");
    whiteList.add("/web/product.html");
    whiteList.add("/index.html");
    //注册拦截器
    registry.addInterceptor(loginInterceptor)
    .addPathPatterns("/web/**") //配置黑名单,即拦截器要拦截的路径
    .excludePathPatterns(whiteList); //配置白名单,即拦截器不拦截的路径
    }
    }