当用户输入用户名和密码将数据提交给后台数据库进行查询,如果存在对应的用户名和密码,则表示登录成功,登录成功之后跳转到系统的主页,就是index.html页面,跳转在前端的jQuery来完成。
1、登录持久层
1.1规划需要执行的SQL语句:依据用户提交的用户名和密码做select查询,密码的比较在业务层执行。
select * from t_user where username=?
说明:如果在分析过程发现某个功能模块已经被开发完成,所以就可以省略当前的开发步骤,这个分析过程不能够省略。
1.2接口设计和抽象方法:也不用重复开发。
1.3单元测试也是无需单独执行。
2、登录业务层
2.1 规划相关的异常:
(1)用户名对应的密码错误,密码匹配失败的异常:PasswordNotMatchException异常,也属于运行时异常,也属于业务层异常。
(2)用户名没有被找到,抛出异常:UsernameNotFoundException,也属于运行时异常,也属于业务层异常。
(3)异常的编写:
1、业务层异常需要继承ServiceException异常类;
2、在具体的异常类中定义构造方法(可以使用快捷键生成,5个构造方法)
2.2 设计业务层接口和抽象方法:
(1)(关于用户模块之前已经编写出来)直接在IUserService接口中编写抽象方法,login(String username,String password);将当前登录成功的用户数据以当前用户对象的形式进行返回。状态管理:可以将数据保存在cookie或者session中,可以避免重复度很高的数据多次频繁操作数据进行获取(用户名、用户id--存放在session中,用户头像保存在cookie中)。
(2)需要在实现类中实现父接口中的抽象方法。
(3)在测试类中测试业务层登录的方法是否可以执行通过。
2.3 抽象方法的实现:
@Override public User login(String username, String password) { //1、根据用户的名称来查询用户的数据是否存在 User result = userMapper.findByUsername(username); if (result == null){ throw new UserNotFoundException("用户数据不存在!"); } //2、检测用户的密码是否匹配 //(1)先获取到数据库中的加密之后的密码;(2)然后和用户传递过来的密码进行比较; //在(2)步骤中不能直接比较: String oldPassword = result.getPassword(); //首先获取盐值即上一次注册时自动生成的盐值, String salt = result.getSalt(); //然后将用户的密码按照相同的md5算法的规则进行加密 String newMd5Password = getMD5Password(password,salt); //3、将密码进行比较 if (newMd5Password.equals(oldPassword)){ throw new PasswordNotMatchException("用户密码错误!"); } //判断is_delete字段的值是否为1表示被标记为删除 if (result.getIsDelete() == 1){ throw new UserNotFoundException("用户数据不存在!"); } //重新new一个user对象,此处只封装三个数据。这样做法使得层与层之间数据传输的体量变小,提升了性能。 User user = new User(); user.setUid(result.getUid()); user.setUsername(result.getUsername()); user.setAvatar(result.getAvatar()); //如果找到将当前用户的数据返回,而返回的数据是为了给其他页面展示数据用的(uid,username,avatar // 因为用户登录成功一般会显示id、用户名和头像等信息) return user; }
3、登录控制层
1、处理异常:业务层抛出的异常是什么?需要在统一的异常处理类中进行统一的捕获和处理;如果曾经抛出的异常类型已经在统一异常处理类中曾经处理过,则不需要重复添加;
else if (e instanceof UserNotFoundException) { result.setState(5001); result.setMessage("用户数据不存在的异常!"); }else if (e instanceof PasswordNotMatchException) { result.setState(5002); result.setMessage("用户名的密码错误的异常!"); }
2、设计请求:
(1)请求路径:/users/login
(2)请求方式:POST
(3)请求数据:String name, String password,HttpSession session
(4)响应结果:JsonResult<User>;
3、处理请求:在UserController类中编写处理请求的方法。
@RequestMapping("login") public JsonResult<User> login(String username, String password){ User data = userService.login(username,password); return new JsonResult<User>(OK,data); }
4、登录前端页面
(1)在login.html页面中依据前面所设置的请求来发送ajax请求。
(2)访问页面进行用户的登录操作。
用户登录会话session:
session对象作用主要存储在服务器端,可以用于保存服务器的临时数据的对象,所保存的数据可以在整个项目中都可以通过访问来获取,把session中的数据看作一个共享的数据。首次登录的时候所获取到的用户的数据,转移到session对象即可。session.getAttrbute("key")可以将获取session中的数据这种行为进行封装,封装在BaseController类中。
1、封装session对象中数据的获取(只要登录成功了,获取的是当前用户的所有session数据,可以封装在父类中),数据的设置(当用户登录成功后进行数据的设置,设置到全局的session对象中)。
2、在父类中封装两个数据:获取uid和获取用户的username对应的两个方法。用户头像暂时不考虑,将来封装在cookie中来使用。
//因为并不知道是哪个session对象,需要设置一个session对象(需要知道从哪个session对象中获取uid) protected final Integer getuidFromSession(HttpSession session){ //返回当前登录的用户uid值 //session中保存的可以是任何数据类型,cookie中只能是String类型 //将串转化为整数使用Integer.valueOf()方法 return Integer.valueOf(session.getAttribute("uid") .toString()); } //获取当前登录用户的username //在实现类中重写父类中的toString()方法,不是句柄信息的输出. protected final String getUsernameFromSession(HttpSession session){ return session.getAttribute("username").toString(); }
3、需要在登录的方法中将数据封装在session对象中。服务器本身自动创建有session对象,已经是一个全局的session对象。SpringBoot直接使用session对象,要求是直接将HttpSession类型的对象作为请求处理方法的参数,会自动将全局的session对象注入到请求处理方法的session形参上。
拦截器:可以将所有的请求统一拦截到拦截器中 ,可以在拦截器中来定义过滤的规则,如果不满足系统设置的过滤规则,统一的处理是重新去打开login.html页面(重定向和转发),推荐使用重定向。
在SpringBoot项目中做拦截器的定义和使用。SpringBoot是依靠SpringMVC来完成的。SpringMVC提供了以恶HanderInterceptor接口,用于表示定义一个拦截器。首先自定义类,然后再让类实现这个接口。
(1)首先自定义一个类,在这个类实现这个HandlerInterceptor接口。
源码解析:
public interface HandlerInterceptor { //在调用所有处理请求的方法之前被自动调用执行的方法 default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } //在ModelAndView对象返回之后被调用的方法 default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } //在整个请求所有关联的资源被执行完毕最后所执行的方法 default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } }
//定义一个拦截器 public class LoginInterceptor implements HandlerInterceptor { /* 检测全局session对象中是否有uid数据,如果有则放行, 如果没有则重定向到登录 request:请求对象 response:响应对象 handler:处理器(url+Controller:映射) return:如果返回值为true表示放行当前的请求,如果为false则表示拦截当前的请求; * */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //HttpServletRequest对象来获取session对象 Object obj = request.getSession().getAttribute("uid"); if(obj == null){//说明用户没有登录过系统,则重定向到login.html response.sendRedirect("/web/login.html"); //结束后续的调用 return false; } //请求放行 return true; } }
2、注册过滤器:添加白名单(哪些资源可以在不登陆的情况下访问:login.html,register.html\login\reg\index.html\product.html)、添加黑名单(在用户登录的状态下才可以访问的页面资源)。
3、注册过滤器的技术(怎么注册过滤器):借助vcConfigure接口,可以将用户定义的拦截器进行注册,才可以保证拦截器能够生效和使用。定义一个类:然后让这个类实现WebMvcCOnfigure接口。由于是配置信息(配置类),因此建议放在项目的config包结构下。
//将自定义的拦截器进行注册
default void addInterceptors(InterceptorRegistry registry) { }
4、提示重定向次数过多,login.html页面无法打开,将浏览器cookie清除,再将浏览器设置为初始设置。