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


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=" + 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 映射类中差不多的方式,感觉写起来很麻烦。。。<if test=\"ticket!=null\">
  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({
            "<script>",
            "update login_ticket set status=#{status} where ticket=#{ticket} ",
            "<if test=\"ticket!=null\"> ",
            "and 1=1 ",
            "</if>",
            "</script>"
    })
    //当用户退出登录时,或者用户凭证过期时,根据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<String, Object> login(String username, String password, long expiredSeconds) {
    Map<String, Object> 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 中存放错误信息,然后返回登陆页面
  • 记录登录凭证时长:

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

/**
 * 记住状态的登录凭证超时时间,100天
 */
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<String, Object> 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:class="|form-control ${usernameMsg!=null?'is-invalid':''}|"

注意:

  • 因为我们在 controller 中将 form 表单请求中传过来的值直接定义在了形参上,以常见数据类型 String 等等不会封装在 model 中;
  • 也就是说 model 中可以自动封装形参中的实体类对象,而不能封装常用数据类型,所以我们要从请求中获取;
<form class="mt-5" th:action="@{/login}" method="post">
   <div class="form-group row">
       <label for="username" class="col-sm-2 col-form-label text-right">账号:</label>
       <div class="col-sm-10">
           <input type="text" th:class="|form-control ${usernameMsg!=null?'is-invalid':''}|"
                  name="username" th:value="${param.username}"
                  id="username" placeholder="请输入您的账号!" required>
           <div class="invalid-feedback" th:text="${usernameMsg}">
               该账号不存在!
           </div>
       </div>
   </div>
   <div class="form-group row mt-4">
       <label for="password" class="col-sm-2 col-form-label text-right">密码:</label>
       <div class="col-sm-10">
           <input type="password" th:class="|form-control ${passwordMsg!=null?'is-invalid':''}|"
                  name="password" th:value="${param.password}"
                  id="password" placeholder="请输入您的密码!" required>
           <div class="invalid-feedback" th:text="${passwordMsg}">
               密码长度不能小于8位!
           </div>
       </div>
   </div>
   <div class="form-group row mt-4">
       <label for="verifycode" class="col-sm-2 col-form-label text-right">验证码:</label>
       <div class="col-sm-6">
           <input type="text" th:class="|form-control ${codeMsg!=null?'is-invalid':''}|"
                  name="code"
                  id="verifycode" placeholder="请输入验证码!">
           <div class="invalid-feedback" th:text="${codeMsg}">
               验证码不正确!
           </div>
       </div>
       <div class="col-sm-4">
           <img th:src="@{/kaptcha}" id="kaptcha" style="width:100px;height:40px;" class="mr-2"/>
           <a href="javascript:refresh_kaptcha();" class="font-size-12 align-bottom">刷新验证码</a>
       </div>
   </div>
   <div class="form-group row mt-4">
       <div class="col-sm-2"></div>
       <div class="col-sm-10">
           <input type="checkbox" name="rememberme"
                  id="remember-me" th:checked="${param.remenberme}">
           <label class="form-check-label" for="remember-me">记住我</label>
           <a href="forget.html" class="text-danger float-right">忘记密码?</a>
       </div>
   </div>
   <div class="form-group row mt-4">
       <div class="col-sm-2"></div>
       <div class="col-sm-10 text-center">
           <button type="submit" class="btn btn-info text-white form-control">立即登录</button>
       </div>
   </div>
</form>

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

在这里插入图片描述

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Kafka是一个高性能的分布式消息队列系统,可以实现高吞吐量、低延迟的消息传递。它支持点对点和发布-订阅两种消息传递模式。在仿牛客项目中使用Kafka可以实现消息的异步处理和分布式架构。 使用Kafka的第一步是创建一个主题(topic),主题既是消息的类别,也是消息在Kafka中的存储位置。可以使用命令行工具kafka-topics.bat来创建主题。例如,可以使用以下命令来创建一个名为test的主题: bin\windows\kafka-topics.bat --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic test 上述命令中,--bootstrap-server参数指定了Kafka服务器的地址和端口,--replication-factor参数指定了主题的副本数,--partitions参数指定了主题的分区数。创建主题后,可以向主题中发送消息,并由消费者进行消费。 要列出已经存在的主题,可以使用以下命令: kafka-topics.bat --list --bootstrap-server localhost:9092 需要注意的是,以上命令中的localhost:9092是Kafka服务器的地址和端口,根据实际情况进行修改。 总结起来,在仿牛客项目中使用Kafka,首先需要创建一个主题,然后可以使用相关命令行工具进行消息的发送和消费。这样可以实现消息的异步处理和分布式架构。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [仿牛客论坛项目学习记录——5 Kafka 构建TB级异步消息系统](https://blog.csdn.net/dadayangpei/article/details/127173098)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值