2.5 开发登录、退出功能

访问登录页面

  • 点击顶部区域内的链接,打开登录页面

登录

  • 验证账号、密码、验证码
  • 成功时,生成登录凭证,发送给客户端
  • 失败时,跳转回登录页

退出

  • 将登录凭证修改为失效状态
  • 跳转至网页首页

为了维护用户的登录状态,创建一张login_ticket表

这张表的功能就类似于session,用户登录成功后,服务度就需生成一个ticket,然后返回给浏览器。

CREATE TABLE `login_ticket` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_id` int NOT NULL,
  `ticket` varchar(45) NOT NULL,
  `status` int DEFAULT '0' COMMENT '0-有效; 1-无效;',
  `expired` timestamp NOT NULL,
  PRIMARY KEY (`id`),
  KEY `index_ticket` (`ticket`(20))
) ENGINE=InnoDB AUTO_INCREMENT=46 DEFAULT CHARSET=utf8

创建实体类LoginTicket

整张表的核心是ticket,也就是围绕着ticket来处理的,最后需要把ticket发送给浏览器让其保存。用户下次登录时,把ticket传递给服务端,服务端根据ticket从数据库中查询到userId,进而查询到是哪个用户在登录。

package com.newcoder.community.entity;
import java.util.Date;
public class LoginTicket {
    private int id;
    private int userId;
    private String ticket;
    private int status;
    private Date expired;
 
}

LoginTicketMapper

  • 可以在配置文件中写sql语句,也可以通过注解的方式直接在mapper接口上写代码
  • keyProperty用于指定自动生成后的主键应该注入给哪个属性? 
package com.newcoder.community.dao;

import com.newcoder.community.entity.LoginTicket;
import org.apache.ibatis.annotations.*;

@Mapper
@Deprecated
public interface LoginTicketMapper {
    @Insert({
            "insert into login_ticket(user_id, ticket, status, expired) ",
            "values(#{userId}, #{ticket}, #{status}, #{expired})"
    })
    @Options(useGeneratedKeys = true, keyProperty = "id") //将生成的主键注入给Bean中的哪个属性
    int insertLoginTicket(LoginTicket loginTicket);

    @Select({
            "select id, user_id, ticket, status, expired",
            "from login_ticket where ticket = #{ticket}"
    })
    LoginTicket selectByTicket(String ticket);


    //用户失效的处理方式:通常不会直接修改数据,而是修改状态值就Ok
    @Update({
            "update login_ticket set status = #{status} where ticket = #{ticket}"
    })
    int updateStatus(String ticket, int status);
}

UserService

    @Autowired
    private UserMapper userMapper;
    public  Map<String, Object> login(String username, String password, int 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(!user.getPassword().equals(password)){
            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);

        //最终返回的结果需要把凭证放进去
        map.put("ticket", loginTicket.getTicket());

        return map;
    }

LoginController.

在CommunityConstant接口中额外增加两个变量

    //默认状态的登录凭证的超时时间
    int DEFAULT_EXPIRED_SECONDS = 3600 * 12;

    //记住状态的登录凭证超时时间
    int REMEMBER_EXPIRED_SECONDS = 3600 * 24 * 100;

因为之前生成的验证码都是放在session里的,所以方法的参数里需要用到session. 

    @RequestMapping(path="/login",method = RequestMethod.POST)
    public String login(String username, String password, String code, boolean rememberme,
                        Model model, /*HttpSession session,*/ HttpServletResponse response
                        ){
        //1 检查验证码
        String kaptcha = (String) session.getAttribute("kaptcha");
        String kaptcha = null;
       
        //kaptcha是服务端生成的验证码,code是用户输入的验证码,需要判断二者是否相同
        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);
        if(map.containsKey("ticket")){
            Cookie cookie = new Cookie("ticket", map.get("ticket").toString());
            cookie.setPath(contextPath);//设置cookie生效的路径(应该是包含在整个项目之内)
            //设置cookie的有效时间
            cookie.setMaxAge(expiredSeconds);
            response.addCookie(cookie);
            return "redirect:/index";//重定向到首页
        }
        else{
            model.addAttribute("usernameMsg", map.get("usernameMsg"));
            model.addAttribute("passwordMsg", map.get("passwordMsg"));
            return "/site/login";
        }
    }

页面显示层

处理页面回显时的逻辑,(也就是账户密码输入错误时,应该保留原有输入值的处理方案)

  • 在SpringMVC的处理方法中,如果参数类型是不是基本数据类型,则会自动装入至Model对象中。
  • 而对于基本的数据类型和String类,"${param.username}":请求中的参数会自动放入request中,可以直接在model中获取值,也可以直接利用params.username的值,相当于是调用了request.getParameter("username")的方式。
<!-- 内容 -->
		<div class="main">
			<div class="container pl-5 pr-5 pt-3 pb-3 mt-3 mb-3">
				<h3 class="text-center text-info border-bottom pb-3">登&nbsp;&nbsp;录</h3>
				<form class="mt-5" method="post" th:action="@{/login}">
					<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':''}|"
								   th:value="${param.username}"
								   id="username" name="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':''}|"
								   th:value="${param.password}"
								   id="password" name="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':''}|" id="verifycode" name="code" 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" id="remember-me" checked="checked" name="rememberme"
									th:checked="${param.rememberme}">
							<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>				
			</div>
		</div>

退出:把凭证改为失效的状态并跳转至首页

UserService:传入ticket,然后改变数据库中对应的状态即可

    public void logout(String ticket){
        loginTicketMapper.updateStatus(ticket, 1);
    }

LoginController:需要得到从浏览器发送过来的cookie值,利用@CookieValue()注解实现,然后重定向至登录页面(默认是get请求)

    @RequestMapping(path="/logout",method = RequestMethod.GET)
    public String logout(@CookieValue("ticket") String ticket){
        userService.logout(ticket);
        //重定向默认就是get请求
        return "redirect:/login";
    }

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值