黑马先是总结了从session实现登录,然后是因为如果使用了集群方式的服务器的话,存在集群共享session互相拷贝效率低下的问题,接着引出了速度更快的内存型的kv数据库Redis,
使用Session发送验证码和登录login
举个例子:
原来的发送验证码和登录的例子,直接在session中存验证码6
@Overridepublic 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;@Overridepublic Result sendCode(String phone, HttpSession session) {if(RegexUtils.isPhoneInvalid(phone)){return Result.fail("手机号格式错误");}//使用了Redis存储验证码,Key是LOGIN_CODE_KEY,value是6stringRedisTemplate.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是6String 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信息转化为HashMapMap<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才是UserDTOstringRedisTemplate.opsForHash().putAll(tokenKey,userMap);//设置用户的登录过期时间,防止Redis存储太多用户信息导致内存占用很多stringRedisTemplate.expire(tokenKey,LOGIN_USER_TTL,TimeUnit.MINUTES);UserHolder.saveUser(userDTO);
// 返回tokenreturn 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获取tokenif (StrUtil.isBlank(token)) {response.setStatus(401);return false;}//根据token从Redis里面获取UserMapMap<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();}
}