栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

(仿牛客论坛项目)04 - 登录、退出功能

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

(仿牛客论坛项目)04 - 登录、退出功能

目录
  • 1、访问登录页面
  • 2、登录
    • 2.1 新建 LoginTicket 的实体类
    • 2.2 新建 LoginTicketMapper 层
    • 2.3 编写测试类测试
    • 2.4 UserService 层
    • 2.5 LoginController 层
    • 2.6 修改 login.html 页面
    • 2.7 测试页面
  • 3、退出
    • 3.1 UserService层:
    • 3.2 LoginController 层
    • 3.3 修改index.html页面:
    • 3.4 测试页面


1、访问登录页面
  1. 点击头部的登录按钮,打开登录页面,之前在注册功能的最后顺便已经实现了,(仿牛客论坛项目)02 - 开发注册功能。

2、登录
  • 验证账号、密码、验证码
  • 成功时,生成登录凭证,发放给客户端
  • 失败时,跳转回登录页
2.1 新建 LoginTicket 的实体类
  1. 对应 login_ticket 表:

public class LoginTicket {
    private int id;
    private int userId;
    private String ticket;//给登录的用户一张通行证
    private int status;//0-有效; 1-无效;
    private Date expired;//通行证有效期

    @Override
    public String toString() {
        return "LoginTicket{" +
                " + id +
                ", userId=" + userId +
                ", ticket='" + ticket + ''' +
                ", status=" + status +
                ", expired=" + expired +
                '}';
    }
}
2.2 新建 LoginTicketMapper 层
  1. 这里使用注解方法实现 sql 语句,帮你拼接字符串;
  2. 好处:少写一个文件;缺点:阅读困难,且手写没有提示,例如要实现你插入数据的功能:@insert({"",""});
  3. 注解的方式实现 sql 语句和 mapper 映射类实现 sql 语句,其中的 sql 语句没有变化;
  4. 如果要实现自增主键功能,并将这个值赋给 id :@Options(useGeneratedKeys = true, keyProperty = "id");
  5. 如果要实现动态 sql 拼接,其实还是和 mapper 映射类中差不多的方式,感觉写起来很麻烦。。。;
  6. 一般在字符串后面记得加空格,小心字符串拼接出问题。
@Mapper
public interface LoginTicketMapper {
    @Insert({"insert into login_ticket(user_id,ticket,status,expired)",
            "values(#{userId},#{ticket},#{status},#{expired})"
    })
    @Options(useGeneratedKeys = true, keyProperty = "id")
    //插入用户登录凭证
    int insertLoginTicket(LoginTicket loginTicket);

    @Select({
            "select id,user_id,ticket,status,expired ",
            "from login_ticket where ticket=#{ticket}"
    })
    //根据唯一的ticket查找登录凭证实体类
    LoginTicket selectByTicket(String ticket);

    @Update({
            ""
    })
    //当用户退出登录时,或者用户凭证过期时,根据ticket,修改状态
    int updateStatus(String ticket, int status);
}
2.3 编写测试类测试
@Test
public void testInsertLoginTicket() {
    LoginTicket loginTicket = new LoginTicket();
    loginTicket.setUserId(101);
    loginTicket.setTicket("abc");
    loginTicket.setStatus(0);
    loginTicket.setExpired(new Date(System.currentTimeMillis() + 1000 * 60 * 10));

    loginTicketMapper.insertLoginTicket(loginTicket);
}

@Test
public void testSelectLoginTicket() {
    LoginTicket loginTicket = loginTicketMapper.selectByTicket("abc");
    System.out.println(loginTicket);

    loginTicketMapper.updateStatus("abc", 1);
    loginTicket = loginTicketMapper.selectByTicket("abc");
    System.out.println(loginTicket);
}
  • 插入数据,查找数据以及修改数据都没问题;

2.4 UserService 层
  1. 登录功能,有多种情况,登陆成功、账号不存在、密码错误等等多种情况,所以使用 map 来接收;
  • 空值处理:

    • 账号不能为空
    • 密码不能为空
  • 验证状态:

    • 调用 DAO 层查询用户,账号不存在
    • 账号状态是否未激活
  • 验证密码:

    • 将用户输入的密码使用 MD5 加密算法进行加密得到加密后的密码,将这个密码和从数据库查出来的密码进行比较,如果不一致,代表输入的密码错误
  • 生成登录凭证:

    • 新建一个 LoginTicket 对象,并给它除了 id 值之外的所有数值设值
    • 调用 DAO 层插入登录凭证数据
  • 将生成的 ticket 值放在 map 集合中返回,也就是最终要传给客户端的一个cookie(因为 ticket 这个信息不敏感,一串随机字符而已);

@Override
public Map login(String username, String password, long expiredSeconds) {
    Map map = new HashMap<>();
    //空值处理
    if (StringUtils.isBlank(username)) {
        map.put("usernameMsg", "账号不能为空!");
        return map;
    }
    if (StringUtils.isBlank(password)) {
        map.put("passwordMsg", "密码不能为空!");
        return map;
    }

    // 验证账号
    User user = userMapper.selectByName(username);
    if (user == null) {
        map.put("usernameMsg", "该账号不存在!");
        return map;
    }

    // 验证状态
    if (user.getStatus() == 0) {
        map.put("usernameMsg", "该账号未激活!");
        return map;
    }

    //验证密码
    password = CommunityUtil.md5(password + user.getSalt());
    if (!password.equals(user.getPassword())){
        map.put("passwordMsg", "密码不正确!");
        return map;
    }
    //生成登录凭证
    LoginTicket loginTicket = new LoginTicket();
    loginTicket.setUserId(user.getId());
    loginTicket.setTicket(CommunityUtil.generateUUID());
    loginTicket.setStatus(0);
    loginTicket.setExpired(new Date(System.currentTimeMillis()+ expiredSeconds * 1000));
    loginTicketMapper.insertLoginTicket(loginTicket);

    //将生成的ticket值放在map集合中返回,也就是最终要传给客户端的一个cookie
    map.put("ticket", loginTicket.getTicket());
    return map;
}
2.5 LoginController 层
  1. 实现登录功能
  • 验证码判断(先判断验证码,如果不正确,就不用和数据库进行交互了):

    • session中存放的 kaptcha 值是否为空;
    • 用户输入的 code 值是否为空;
    • 将 session 存放的验证码和 用户传入的 code 进行比较,忽略大小写的情况下是否一致
    • 上述三种情况不正确的情况下,向 model 中存放错误信息,然后返回登陆页面
  • 记录登录凭证时长:

    • 用户可能勾选记住我,或者不勾选记住我,需要判断这个登录凭证时长,在工具类中定义两种不同的存在时长;
int DEFAULT_EXPIRED_SECONDS = 3600 * 12;


int REMEMBER_EXPIRED_SECONDS = 3600 * 24 * 100;
  • 检查账号密码:
    • 调用 UserService 层来进行登录账户密码验证返回一个map,判断map中的所有数据
    • 如果 map 中包含 ticket 表示没有问题,生成cookie,设置有效路径为整个项目,设置有效时长,并传给客户端,返回到首页页面
    • 如果出问题,有可能是用户名错误,密码错误,放到 model 中返回到登录界面提示用户。
@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(String username, String password, String code, boolean rememberme, Model model, HttpSession session,HttpServletResponse response) {
    //先判断验证码,需要从session中取之前我们自己保存的一份
    String kaptcha = (String) session.getAttribute("kaptcha");
    if (StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)) {
        model.addAttribute("codeMsg", "验证码不正确!");
        return "/site/login";
    }
    int expiredSeconds = rememberme ? REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS;
    Map map = userService.login(username, password, expiredSeconds);
    //账号密码是否正确,正确发送cookie,错误反馈错误信息
    if (map.containsKey("ticket")){//代表正确的情况
        Cookie cookie = new Cookie("ticket",(String) map.get("ticket"));
        cookie.setMaxAge(expiredSeconds);
        cookie.setPath(contextPath);
        response.addCookie(cookie);
        return "redirect:/index";
    }else {
        model.addAttribute("usernameMsg", map.get("usernameMsg"));
        model.addAttribute("passwordMsg", map.get("passwordMsg"));
        return "/site/login";
    }
}
2.6 修改 login.html 页面
  1. 将账户、密码、验证码、记住我的勾的name属性进行添加,这个值必须和 Controller 中形参定义的一致,否则对应不上的话,服务器形参获取到的值为空;

  2. 错误信息展现:

    • 用户输入的值还显示在页面上,可以从 request 中取值th:value="${param.username}";
    • 记住我那个勾还和用户原来的选择保持一致:th:checked="${param.rememberme}",这里是boolean值,返回 true 或者 false 也可以用来判断是否勾选;
    • 显示错误信息:th:

注意:

  • 因为我们在 controller 中将 form 表单请求中传过来的值直接定义在了形参上,以常见数据类型 String 等等不会封装在 model 中;
  • 也就是说 model 中可以自动封装形参中的实体类对象,而不能封装常用数据类型,所以我们要从请求中获取;
该账号不存在!
密码长度不能小于8位!
验证码不正确!
忘记密码?
2.7 测试页面
  1. 首次访问登录页面,什么信息都不显示,而且点击刷新验证码可以改变图片;

  1. 输入错误的用户名、错误的密码、错误的验证码查看一下是否会提示错误信息;

  1. 输入正确的查看是否成功(看一下数据库中是否生成了数据凭证)

3、退出
  • 将登录凭证修改为失效状态。
  • 跳转至网站首页。
3.1 UserService层:
  1. 实现退出功能,即修改登录凭证的状态码为 1;
@Override
public void logout(String ticket) {
    loginTicketMapper.updateStatus(ticket,1);
}
3.2 LoginController 层
  1. 要根据客户端的 cookie 中获取 ticket 信息
  2. 调用 UserService 层的 logout 方法修改 ticket 对应的状态码信息,并且返回到登陆页面
@RequestMapping(value = "/logout",method = RequestMethod.GET)
public String logout(@CookieValue("ticket")String ticket){
    userService.logout(ticket);
    return "redirect:/login";
}
3.3 修改index.html页面:
  1. 所有页面服用的头部代码中的退出登录超链接:th:href="@{/logout}"
3.4 测试页面
  1. 查看数据库中的状态码 status 是否变为1

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1006924.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号