1.登录
1.1 验证账号,密码,验证码
生成登录凭证 是 一个key
先来查看一下 “登录凭证”是什么样子的
//查询的依据是ticket,因为ticket是凭证,是核心数据,最终是把ticket字符串发送给浏览器报存。LoginTicket是返回值
查看一下备注,expied 表示什么时候过期
先写一个实体类LoginTicket以封装表中的数据,
实体类就是这样
然后再写增删改查相关逻辑,在DAO下新建接口LoginTicketMapper
之前实现mapper,都是在mapper文件夹下实现配置文件
现在换一种方式,通过在mapper类LoginTicketMapper中写注解,通过注解声明该方法是什么SQL。代码如下:
package com.nowcoder.community.dao;
import com.nowcoder.community.entity.LoginTicket;
import org.apache.ibatis.annotations.*;
@Mapper
public interface LoginTicketMapper {
@Insert({//insertLoginTicket方法是Insert语句,所以用Insert语句。把诸如user_id,ticket,status,expired等多个字符串拼成一个sql
"insert into login_ticket(user_id,ticket,status,expired) ",//login_ticket千万不能写错。user_id,ticket每个逗号后面都要有一个空格。login_ticket是mysql的一个属性,详见博客
"values(#{userId},#{ticket},#{status},#{expired})"//values是自动生成的
})
@Options(useGeneratedKeys = true, keyProperty = "id")//是否自动生成组件useGeneratedKeys = true,把生成的值注入给id属性
int insertLoginTicket(LoginTicket loginTicket);//插入数据方法。 参数就是之前定义的entity中 loginTicket实体类
@Select({//selectByTicket是查询,所以用Select注解
"select id,user_id,ticket,status,expired ",//expired "和"之间要空格
"from login_ticket where ticket=#{ticket}"//where ticket=#{ticket}"是指查询条件
})
LoginTicket selectByTicket(String ticket);//查询的依据是ticket,因为ticket是凭证,是核心数据,最终是把ticket字符串发送给浏览器报存。LoginTicket是返回值
@Update({//updateStatus方法用Update注解
"<script>",//script表示脚本,意思是这里面是html脚本。下面的语法和html一样
"update login_ticket set status=#{status} where ticket=#{ticket} ",
"<if test=\"ticket!=null\"> ",//if开始 \"表示转义后的双引号
"and 1=1 ",
"</if>",//if闭合
"</script>"//script闭合
})
int updateStatus(String ticket, int status);//修改凭证方法,返回int。退出的时候凭证失效,互联网行业很少删除某项东西,只是改变其状态。把ticket传进来,然后要告诉我要变为什么status
}
接下来在MapperTests测试类中
将写好的loginTicketMapper注入
@Autowired
private LoginTicketMapper loginTicketMapper;
并进行测试:
@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));//setExpired设置过期时间。1000 * 60 * 10单位是毫秒。10min以后到期
loginTicketMapper.insertLoginTicket(loginTicket);//将设置好的loginTicket传入进去
}
运行结果:
/根据Ticket 来查询数据,查完以后直接修改
@Test
public void testSelectLoginTicket() {//根据Ticket 来查询数据,查完以后直接修改
LoginTicket loginTicket = loginTicketMapper.selectByTicket("abc");//根据Ticket为abc来查询数据,得到loginTicket这个对象。
System.out.println(loginTicket);//打印查看有没有问题
loginTicketMapper.updateStatus("abc", 1);//根据Ticket 把状态改为1
loginTicket = loginTicketMapper.selectByTicket("abc");//接着检查一遍,看看数据对不对
System.out.println(loginTicket);
}
运行结果:
下面开发业务层service:
@Autowired
private LoginTicketMapper loginTicketMapper;
下面开始实现登录功能,即追加一个方法:
登录方法
public Map<String, Object> login(String username, String password, int expiredSeconds) {//expiredSeconds过期时间/s
Map<String, Object> map = new HashMap<>();
// 空值处理
if (StringUtils.isBlank(username)) {//调用工具 判断username 是否为空
map.put("usernameMsg", "账号不能为空!");//返回 账号不能为空 这个消息
return map;
}
if (StringUtils.isBlank(password)) {
map.put("passwordMsg", "密码不能为空!");
return map;
}
// 验证账号合法性
User user = userMapper.selectByName(username);//检查库里有没有username
if (user == null) {
map.put("usernameMsg", "该账号不存在!");
return map;
}
// 验证状态
if (user.getStatus() == 0) {//账号注册但没有激活
map.put("usernameMsg", "该账号未激活!");
return map;
}
// 验证密码
password = CommunityUtil.md5(password + user.getSalt());//把传入的“明文密码+盐值” 按照相同的规则md5 进行加密,得到加密后的password
if (!user.getPassword().equals(password)) {//判断一下此加密后的密码 和我们 库里的是否一样
map.put("passwordMsg", "密码不正确!");
return map;
}
// 生成登录凭证
LoginTicket loginTicket = new LoginTicket();//创建LoginTicket 这个实体
loginTicket.setUserId(user.getId());
loginTicket.setTicket(CommunityUtil.generateUUID());//凭证 是一个随机生成的字符串
loginTicket.setStatus(0);//设置有效状态0
loginTicket.setExpired(new Date(System.currentTimeMillis() + expiredSeconds * 1000));//设置过期时间 currentTimeMillis当前时间往后推移 expiredSeconds * 1000秒
loginTicketMapper.insertLoginTicket(loginTicket);
map.put("ticket", loginTicket.getTicket());//因为最终要把凭证发给客户端。可以把loginTicket放进去,也可以只发送Ticket
return map;
}
接下来编写表现层的逻辑
得在controller里面写 方法处理页面的请求
因为登录页面 要传入三个值
在LoginController里写 能够处理登录请求的方法:
代码如下:
@RequestMapping(path = "/login", method = RequestMethod.POST)//声明方法访问路径,之前也有访问路径为login,但是两者的请求方式不能一样。比如请求方式都是POST,这样会报错
public String login(String username, String password, String code, boolean rememberme,//方法返回String,方法名login。参数rememberme表示。code表示输入的验证码
Model model, HttpSession session, HttpServletResponse response) {//因为创建cookie所以需要HttpServletResponse
// 检查验证码
String kaptcha = (String) session.getAttribute("kaptcha");//从session中取出验证码
if (StringUtils.isBlank(kaptcha) || StringUtils.isBlank(code) || !kaptcha.equalsIgnoreCase(code)) {//equalsIgnoreCase方法 与equals相比,其忽略大小写的影响。code为空 或者 kaptcha 为空 或者 两者不等 都不对
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")) {//如果map包含了ticket,则说明登录成功了。因为成功了才会存在ticket
Cookie cookie = new Cookie("ticket", map.get("ticket").toString());//把带有ticket的cookie 发送给客户端。map.get("ticket")是对象,.toString()将其变为字符串。
cookie.setPath(contextPath);//设置有效路径
cookie.setMaxAge(expiredSeconds);//设置cookie 有效时间
response.addCookie(cookie);//响应时发送给浏览器
return "redirect:/index";//重新回到首页
} else {
model.addAttribute("usernameMsg", map.get("usernameMsg"));//用户名错误
model.addAttribute("passwordMsg", map.get("passwordMsg"));//密码错误
return "/site/login";//回到登录页面
}
}
其中参数 boolean rememberme 表示:
其中 在“检查账号,密码”部分,
REMEMBER_EXPIRED_SECONDS : DEFAULT_EXPIRED_SECONDS 两个常量
在下面这个接口
定义:
12小时
100天
同样 在“检查账号,密码”部分,
cookie.setPath(contextPath);//设置有效路径
其中contextPath已经定义过
且在之前注入到contextPath
@Value("${server.servlet.context-path}")
private String contextPath;
接下来如何配置这个页面显示
需要在site文件夹下的 login.html下进行编辑
初始页面如下:
故意输错,首先提示验证码错误
验证码正确后,提示:
账号正确后,提示:
密码正确后,登录成功
目前还没有做后续处理,登录成功以后,这些地方应该都要隐去的。
最后处理退出逻辑
点这个退出登录
修改失效状态:
在业务层(Service)UserService中添加:
public void logout(String ticket) {
loginTicketMapper.updateStatus(ticket, 1);//修改凭证为失效
}
然后在Controller里面LoginController,可以处理页面的请求
ticket是在cookie下的
本次课程利用了之前讲过的 验证码以及 cookie