1. 访问登录页面
点击顶部区域内的连接,打开登陆页面。
index.html中:
<li class="nav-item ml-3 btn-group-vertical">
<a class="nav-link" th:href="@{/login}">登录</a>
</li>
2. 登录
- 验证账号、密码、验证码;
- 成功时:生成登录凭证,发送给客户端;
- 失败时:跳转回登录页。
登录凭证对应的表:login_ticket
字段:id、user_id、ticket字符串凭证、status当前是否过期、expired过期时间
1> 创建 login_ticket表对应的实体类
在entity目录下,新建类:LoginTicket
//登录凭证对应的表:login_ticket
public class LoginTicket {
private int id;
private int userId;
private String ticket;
private int status;
private Date expired;
//get()、set()
//toString()
}
2> 创建接口 LoginTicketMapper
用来对 login_ticket表 进行增删改查。
在dao目录下新建接口:LoginTicketMapper
//这里的SQL语句没有写mapper对应的xml文件,而是采用了注解的方式
@Mapper
public interface LoginTicketMapper {
//插入数据
@Insert({
"insert into login_ticket (user_id, ticket, status, expired) ",
"values (#{userId},#{ticket},#{status},#{expired})"
})
@Options(useGeneratedKeys = true, keyProperty = "id") //将id属性设为自动生成主键
int insertLoginTicket(LoginTicket loginTicket);
//根据 ticket 字段查询
@Select({
"select id,user_id,ticket,status,expired ",
"from login_ticket where ticket=#{ticket}"
})
LoginTicket selectByTicket(String ticket);
//根据 ticket 更新状态
//这里是动态SQL的写法
//@Update({
// "update login_ticket set status=#{status} where ticket=#{ticket} "
//})
@Update({
"<script>",
"update login_ticket set status=#{status} where ticket=#{ticket} ",
"<if test=\"ticket!=null\">",
"and 1=1 ",
"</if>",
"</script>"
})
int updateStatus(String ticket, int status);
}
在写完SQL方法后,最好先测试一波:
在测试类中编写以下测试方法
//测试插入方法
@Autowired
private LoginTicketMapper loginTicketMapper;
@Test
public void test3(){
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 test4(){
LoginTicket loginTicket = loginTicketMapper.selectByTicket("abc");
System.out.println(loginTicket);
loginTicketMapper.updateStatus("abc",1);
loginTicket = loginTicketMapper.selectByTicket("abc");
System.out.println(loginTicket);
}
3> 登录业务
为业务层操作,在service.UserService类下,创建登录方法:
@Autowired
private LoginTicketMapper loginTicketMapper;
//登录业务方法
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;
}
4> 控制器方法
与前端进行交互。写在 LoginController控制器类下。
根据用户输入的密码、验证码判断是否登录成功。
- 登录成功,返回到首页;
- 登陆失败,返回到登录页面。
若用户选定 【记住我】,则登录信息保存100天。
@Value("${server.servlet.context-path}")
private String contextPath;
@RequestMapping(path = "/login", method = RequestMethod.POST)
public String login(String username, String password, String code, boolean rememberme,
Model model, HttpSession session, HttpServletResponse response){
//先判断验证码
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 ? DEFAULT_EXPIRED_SECONDS : REMEMBER_EXPIRED_SECONDS;
Map<String,Object> map = userService.login(username, password, expiredSeconds);
//成功登录:将登陆凭证存进cookie
if(map.containsKey("ticket")){
//cookie中存的为字符串
Cookie cookie = new Cookie("ticket",map.get("ticket").toString());
cookie.setPath(contextPath); //cookie生效路径为整个项目
cookie.setMaxAge(expiredSeconds); //cookie生效时间
response.addCookie(cookie); //响应时,会将cookie发送到浏览器
return "redirect:/index";
}//失败
else{
model.addAttribute("usernameMsg",map.get("usernameMsg"));
model.addAttribute("passwordMsg",map.get("passwordMsg"));
return "/site/login";
}
}
用户信息存储时间的设置:
在 CommunityConstant接口中添加常量:
//默认状态的登录凭证超时时间:默认存12小时
int DEFAULT_EXPIRED_SECONDS = 3600 * 12;
//记住状态下的登录凭证超时时间:100天
int REMEMBER_EXPIRED_SECONDS = 3600 * 24 * 100;
总结:
登录时,我们根据前端传来的地址调用了 login() 控制器方法,此方法又调用了业务层UserService的 login() 方法,进行一系列业务操作。
3. 退出
- 将登陆凭证修改失效状态;
- 跳转至网站首页。
1> 退出业务
为业务层操作,在service.UserService类下,创建退出方法:
//退出登录:将凭证改为1,即失效状态
public void logout(String ticket){
loginTicketMapper.updateStatus(ticket,1);
}
2> 控制器方法
在 LoginController控制器类下创建:
//退出登录
@RequestMapping(path = "/logout", method = RequestMethod.GET)
public String logout(@CookieValue("ticket") String ticket){
//上面登录时,把ticket放进了cookie
userService.logout(ticket);
return "redirect:/login";
}
3> html页面
<a class="dropdown-item text-center" th:href="@{/logout}">退出登录</a>