黑马先是总结了从session实现登录,然后是因为如果使用了集群方式的服务器的话,存在集群共享session互相拷贝效率低下的问题,接着引出了速度更快的内存型的kv数据库Redis,
使用Session发送验证码和登录login
举个例子:
原来的发送验证码和登录的例子,直接在session中存验证码6
@Override
public Result sendCode(String phone, HttpSession session) {
if(RegexUtils.isPhoneInvalid(phone)){
return Result.fail("手机号格式错误");
}
//我这里瞎勾八写的验证码是6,图省事
session.setAttribute("code","6");
return Result.ok();
}
从session中获取验证码,然后与从表单输入的验证码相比较,如果一致,那么就是验证码正确,query()方法是查询tb_user中的用户,利用phone字段查询用户,然后如果查询出来用户那么就利用这个电话字段创建用户user对象,如果没查出来,就自动创建新的用户,然后存在UserHolder里面,UserHolder是用静态ThreadLocal存储的User对象。
@Override public Result login(LoginFormDTO loginFormDTO, HttpSession session) {
String phone = loginFormDTO.getPhone();
if(RegexUtils.isPhoneInvalid(phone)){
return Result.fail("手机号格式错误");
}
String code = (String) session.getAttribute("code");
if(code == null || !code.equals("6")){
return Result.fail("验证码错误");
}
User user = query().eq("phone", phone).one();
if(user == null){
user = createUser(phone);
}
session.setAttribute("user",user);
UserHolder.saveUser(user);
return Result.ok();
}
接着如果登录的话,前端界面用户的界面是访问/user/me来返回用户信息,注意这里的me函数一定要返回user,否则黑马点评的界面会又再次跳转到登录界面,非常✓8。黑马点评项目登录有好几次都是因为这个UserHolder的UserDTO为空导致又跳转到登录界面,非常✓8。
@GetMapping("/me")
public Result me(){
// TODO 获取当前登录的用户并返回
UserDTO user = UserHolder.getUser();
log.debug("me:{}", user);
return Result.ok(user);
}
使用Redis存储验证码和Redis的token登录
UserServicelmpl.java,注意,我们使用Redis返回token的时候,Key是token,存储的是UserMap,UserMap是存储了UserDTO信息的,UserDTO存储了用户信息,包括他的phone,因此,token是和电话号码存在一一对应关系的!!我们后续拦截器拦截的时候,获取了Token之后,利用获取到的Token去Redis里面查询的时候就知道是哪个用户了!!!
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Resource private StringRedisTemplate stringRedisTemplate;
@Override
public Result sendCode(String phone, HttpSession session) {
if(RegexUtils.isPhoneInvalid(phone)){
return Result.fail("手机号格式错误");
}
//使用了Redis存储验证码,Key是LOGIN_CODE_KEY,value是6
stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY,"6",RedisConstants.LOGIN_CODE_TTL,
TimeUnit.MINUTES);
log.debug("发送验证码成功");
return Result.ok();
}
@Override public Result login(LoginFormDTO loginFormDTO, HttpSession session) {
String phone = loginFormDTO.getPhone();
if(RegexUtils.isPhoneInvalid(phone)){
return Result.fail("手机号格式错误");
}
//从内存式的Redis数据库中获取验证码,Key是LOGIN_CODE_KEY,value是6
String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY );
String code = loginFormDTO.getCode();
if(code == null || !code.equals(cacheCode)){
return Result.fail("验证码错误");
}
User user = query().eq("phone", phone).one();
if(user == null){
user = createUser(phone);
}
String token = UUID.randomUUID().toString(true);
//不存储User,只存储UserDTO,减轻存储压力,避免存储敏感信息
UserDTO userDTO= BeanUtil.copyProperties(user,UserDTO.class);
//把UserDTO信息转化为HashMap
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
String tokenKey = LOGIN_USER_KEY + token;
//把登录的这个用户以hashMap方式存储到Redis之中,token为Key,取出来的Value才是UserDTO
stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
//设置用户的登录过期时间,防止Redis存储太多用户信息导致内存占用很多
stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);
UserHolder.saveUser(userDTO);
// 返回token
return Result.ok(token);
}
private User createUser(String phone) {
User user = new User();
user.setPhone(phone);
user.setNickName("user"+ RandomUtil.randomString(10));
return user;
}
}
拦截器设置:
拦截器前端
拦截器后端
LoginInterceptor.java
UserServicelmpl.java的@Override public Result login(LoginFormDTO loginFormDTO, HttpSession session)
函数return Result.ok(token);
直接返回了token到前端界面,前端界面再把这个token存储到请求头的’authorization’里面。
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Override // 拦截请求
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("authorization");//根据authorization获取token
if (StrUtil.isBlank(token)) {
response.setStatus(401);
return false;
}
//根据token从Redis里面获取UserMap
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);
//把UserMap从HashMap形式转化为Java Bean。
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
//存储到UserHolder的ThreadLocal里面
UserHolder.saveUser(userDTO);
//刷新过期时间
stringRedisTemplate.expire(LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
// 放行
return true;
}
@Override // 拦截响应
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 移除用户
UserHolder.removeUser();
}
}