文章目录
学习
黑马点评
项目整理总结:
https://www.bilibili.com/video/BV1cr4y1671t/?vd_source=5f3396d3af2c3945929d54c786f289e5
官方命令文档:https://redis.io/commands/
分布式Session
流程为:用户先发送验证码,将验证码和发送验证码的ip存入redis。用户去登录会去校验用户提交的手机号和验证码,是否一致,如果一致,则根据手机号查询用户信息,不存在则新建,最后将用户数据保存到redis,并且生成token(token中携带用户信息)返回给用户,当我们校验用户是否登录时,会去携带着token进行访问,从token中解析出用户信息,拿用户id从redis中取出对应的value,判断是否存在这个数据,如果没有则拦截,如果存在则将其保存到threadLocal中,并且放行。
1.获取短信验证码
@PostMapping("/code")
public Result sendPhoneCode(@RequestParam("phone") String phone, HttpServletRequest request) {
// 1.校验手机号(是否为空、是否合法)
if (phone == null || "".equals(phone)) {
return Result.fail("手机号格式错误!");
}
// 2.生成一个发送短信的随机数
String code = (int) ((Math.random() * 9 + 1) * 100000) + "";
// 3.调用发送短信的接口
boolean sendSMS = smsService.sendSMS(phone, code);
// boolean sendSMS = true;
//短信发送成功
if (sendSMS) {
// 获取用户IP,防止用户在60s更换手机或者刷新应用重新发送
String userIp = IpUtils.getIpAddr(request);
Boolean flag = redisTemplate.opsForValue().setIfAbsent("login:sms:limit:" + userIp, code, 60, TimeUnit.SECONDS);
if (!flag) {
return Result.fail("同一ip禁止重复发送手机验证码");
}
// 把生成的验证码放入到redis缓存中,用于后续的验证
redisTemplate.opsForValue().set("login:sms:" + phone, code, 60, TimeUnit.SECONDS);
// 发送短信成功
return Result.ok();
} else {
return Result.fail("手机验证码发送失败");
}
}
2.登录
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm) {
// 1.校验手机号
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机号格式错误!");
}
// 2.校验验证码
String cacheCode = redisTemplate.opsForValue().get("login:sms:" + phone);
if (cacheCode == null) {
return Result.fail("验证码已失效!");
}
if (!cacheCode.equalsIgnoreCase(loginForm.getCode())) {
return Result.fail("输入的验证码有误!");
}
// 3.数据库中根据手机号查询用户
User user = userMapper.selectOne(
new LambdaQueryWrapper<User>()
.eq(User::getPhone, phone)
);
// 4.判断用户是否存在
if (user == null) {
//不存在,则创建
user = new User();
user.setPhone(phone);
userMapper.insert(user);
}
// 5.保存用户信息到redis中
// 5.1: 随机生成token,作为登录令牌(token中包含用户id)
String token = UUID.randomUUID().toString();
// 5.2: 登录成功以后,把用户信息放入到缓存中,只要是为了后续业务的需要,登录有效期1小时
redisTemplate.opsForValue().set("login:user:" + user.getId(), JSON.toJSONString(user), 60 * 60 * 1000L, TimeUnit.MINUTES);
// 5.3: 而且短信码一定注册成功其实也没有任何用处,会占用redis内存空间,所有删除掉
redisTemplate.delete("login:sms:" + phone);
return Result.ok(token);
}
3.登录拦截器(做登录拦截及token续期)
public class RefreshTokenInterceptor implements HandlerInterceptor {
private StringRedisTemplate redisTemplate;
public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
this.redisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1.获取请求头中的token
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)) {
return false;
}
// 解析出token中的用户id
String id = "1010";
// 2.基于TOKEN获取redis中的用户
String json = redisTemplate.opsForValue().get("login:user:" + id);
// 3.判断用户是否存在
if (json == null) {
//没有用户信息表示未登录/登录过期
return false;
}
// 5.将查询到的hash数据转为UserDTO
User user = JSON.parseObject(json, User.class);
// 6.存在,保存用户信息到 ThreadLocal
UserHolder.saveUser(user);
// 7.刷新token有效期
redisTemplate.expire("login:user:" + id, 60 * 60 * 1000L, TimeUnit.MINUTES);
// 8.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 移除用户
UserHolder.removeUser();
}
}
4.webmvc配置
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// token刷新的拦截器
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).excludePathPatterns(
"/user/code",
"/user/login"
).order(0);
}
}