用户登录功能
先分析一下思路:当用户输入用户名和密码将数据提交给后台数据库进行查询,如果存在对应的用户名和密码则表示登录成功,登录成功之后跳转到系统的主页。
1.登录-持久层
规划需要执行的SQL语句
依据用户提交的用户名来做select查询
select * from t_user where username=? and password=?这种不太好,这种相当于在查询用户名时直接判断了用户和密码是否一致了,如果持久层把判断做了那业务层就没事干了,所以这里我们只查询用户名,判断用户名和密码是否一致交给业务层做。
这个功能模块已经被开发完成(UserMapper接口的findByUsername方法),省略。
2.登录-业务层
2.1规划异常
1.用户名对应的密码错误,即密码匹配的异常,起名PasswordNotMatchException,这个是运行时异常
2.用户名没有被找到的异常,起名UsernameNotFoundException,这个也是运行时异常
继承ServiceException 类,并重写里面的方法
2.2设计接口和抽象方法及实现
1.在IUserService接口中编写抽象方法login(String username,String password)login(User user)也是可以的.
/**
* 用户登录功能
* @param username 用户名
* @param password 用户密码
* @return 当前匹配的用户数据,如果没有则返回null
*/
User login(String username,String password);
2.在抽象类UserServiceImpl中实现该抽象方法
@Override
public User login(String username, String password) {
//根据用户名称来查询用户的数据是否存在,不存在则抛出异常
User result = userMapper.findByUsername(username);
if (result == null) {
throw new UsernameNotFoundException("用户数据不存在");
}
/**
* 检测用户的密码是否匹配:
* 1.先获取数据库中加密之后的密码
* 2.和用户传递过来的密码进行比较
* 2.1先获取盐值
* 2.2将获取的用户密码按照相同的md5算法加密
*/
String oldPassword = result.getPassword();
String salt = result.getSalt();
String newMd5Password = getMD5Password(password, salt);
if (!newMd5Password.equals(oldPassword)) {
throw new PasswordNotMatchException("用户密码错误");
}
//判断is_delete字段的值是否为1,为1表示被标记为删除
if (result.getIsDelete() == 1) {
throw new UsernameNotFoundException("用户数据不存在");
}
//方法login返回的用户数据是为了辅助其他页面做数据展示使用(只会用到uid,username,avatar)
//所以可以new一个新的user只赋这三个变量的值,这样使层与层之间传输时数据体量变小,后台层与
// 层之间传输时数据量越小性能越高,前端也是的,数据量小了前端响应速度就变快了
User user = new User();
user.setUid(result.getUid());
user.setUsername(result.getUsername());
user.setAvatar(result.getAvatar());
return user;
}
3.登录-控制层
3.1处理异常
在BaseController 层统一进行异常处理。业务层抛出的异常需要在统一异常处理类中进行统一的捕获和处理,如果该异常类型已经在统一异常类中曾经处理过则不需要重复添加。
else if (e instanceof UsernameNotFoundException) {
result.setState(4001);
result.setMessage("用户数据不存在的异常");
} else if (e instanceof PasswordNotMatchException) {
result.setState(4002);
result.setMessage("用户名密码错误的异常");
}
3.2处理请求
在UserController类中编写处理请求的方法.编写完成后启动主服务验证一下
@RequestMapping("login")
public JsonResult<User> login(String username,String password) {
User data = userService.login(username, password);
return new JsonResult<User>(OK,data);
}
5.优化
5.1 用session存储和获取用户数据
1.在父类中封装两个方法:获取uid和获取username对应的两个方法(用户头像暂不考虑,将来封装到cookie中来使用)
/**
* 获取session对象中的uid
* @param session session对象
* @return 当前登录的用户uid的值
*/
public final Integer getUidFromSession(HttpSession session) {
//getAttribute返回的是Object对象,需要转换为字符串再转换为包装类
return Integer.valueOf(session.getAttribute("uid").toString());
}
public final String getUsernameFromSession(HttpSession session) {
return session.getAttribute("username").toString();
}
2.把首次登录所获取的用户数据转移到session对象:
@RequestMapping("login")
public JsonResult<User> login(String username, String password, HttpSession session) {
User data = userService.login(username, password);
//向session对象中完成数据的绑定(这个session是全局的,项目的任何位置都可以访问)
session.setAttribute("uid",data.getUid());
session.setAttribute("username",data.getUsername());
//测试能否正常获取session中存储的数据
System.out.println(getUidFromSession(session));
System.out.println(getUsernameFromSession(session));
return new JsonResult<User>(OK,data);
}
5.2 拦截器
1.所以想要使用拦截器就要定义一个类并使其实现HandlerInterceptor接口,在store下建包interceptor,包下建类LoginInterceptor并编写代码:
/**定义一个拦截器*/
public class LoginInterceptor implements HandlerInterceptor {
/**
*检测全局session对象中是否有uid数据,如果有则放行,如果没有重定向到登录页面
* @param request 请求对象
* @param response 响应对象
* @param handler 处理器(把url和Controller映射到一块)
* @return 返回值为true放行当前请求,反之拦截当前请求
* @throws Exception
*/
@Override
//在DispatcherServlet调用所有处理请求的方法前被自动调用执行的方法
//springboot会自动把请求对象给到request,响应对象给到response,适配器给到handler
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
//通过HttpServletRequest对象来获取session对象
Object obj = request.getSession().getAttribute("uid");
if (obj == null) { //说明用户没有登录过系统,则重定向到login.html页面
//不能用相对路径,因为这里是要告诉前端访问的新页面是在哪个目录下的新
//页面,但前面的localhost:8080可以省略,因为在同一个项目下
response.sendRedirect("/web/login.html");
//结束后续的调用
return false;
}
//放行这个请求
return true;
}
//在ModelAndView对象返回给DispatcherServlet之后被自动调用的方法
// @Override
// public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// }
//在整个请求所有关联的资源被执行完毕后所执行的方法
// @Override
// public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// }
}
2.注册过滤器:
注册过滤器的技术:借助WebMvcConfigure接口将用户定义的拦截器进行注册.所以想要注册过滤器需要定义一个类使其实现WebMvcConfigure接口并在其内部添加黑名单(在用户登录的状态下才可以访问的页面资源)和白名单(哪些资源可以在不登录的情况下访问:①register.html②login.html③index.html④/users/reg⑤/users/login⑥静态资源):
/**拦截器的注册*/
@Configuration //自动加载当前的类并进行拦截器的注册,如果没有@Configuration就相当于没有写类LoginInterceptorConfigure
public class LoginInterceptorConfigure implements WebMvcConfigurer {
@Override
//配置拦截器
public void addInterceptors(InterceptorRegistry registry) {
//1.创建自定义的拦截器对象
HandlerInterceptor interceptor = new LoginInterceptor();
//2.配置白名单并存放在一个List集合
List<String> patterns = new ArrayList<>();
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");
//registry.addInterceptor(interceptor);完成拦截
// 器的注册,后面的addPathPatterns表示拦截哪些url
//这里的参数/**表示所有请求,再后面的excludePathPatterns表
// 示有哪些是白名单,且参数是列表
registry.addInterceptor(interceptor)
.addPathPatterns("/**")
.excludePathPatterns(patterns);
}
}