学习要善于做笔记,学完长时间不用,学会的知识又被遗忘了,本文是学习张老师redis课程记录,感兴趣的小伙伴可以去B站看原视频,本章节主要实现redis代替session实现登录功能
主要流程图
登录后,后端返回token给前端,前端拿到token后,将token存储到sessionStorage中,在前端添加拦截器,对所有后端请求添加请求头,将token携带到后端验证登录信息。
发送短信验证码,并将验证码存储到redis中org.springframework.boot spring-boot-starter-data-redisorg.apache.commons commons-pool2org.springframework.boot spring-boot-starter-webmysql mysql-connector-javaruntime 5.1.47 org.projectlombok lomboktrue org.springframework.boot spring-boot-starter-testtest com.baomidou mybatis-plus-boot-starter3.4.3 cn.hutool hutool-all5.7.17 com.github.qcloudsms qcloudsms1.0.6
public Result sendCode(String phone, HttpSession session) {
//校验手机号
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机号格式错误!");
}
//生成验证码
String code = RandomUtil.randomNumbers(6);
//保存数据到redis
stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone,
code, RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);
//发送验证码,APPID,APPKEY,TEMPLATEID,SMSSING这个四个值都是申请腾讯云控制台上获取,获取路径见下图
try {
String[] params = {code, Long.toString(RedisConstants.LOGIN_CODE_TTL)};
SmsSingleSender sender = new SmsSingleSender(RedisConstants.APPID, RedisConstants.APPKEY);
SmsSingleSenderResult result = sender.sendWithParam("86", phone, RedisConstants.TEMPLATEID, params,
RedisConstants.SMSSING, "", "");
} catch (HTTPException e) {
e.getStackTrace();
} catch (IOException e) {
e.getStackTrace();
}
log.debug("发送验证码成功,验证码:{}", code);
return Result.ok();
}
登录功能
用户信息存储到redis有效期设置的为30分钟。
public Result log(LoginFormDTO loginForm, HttpSession session) {
//获取手机号
String phone = loginForm.getPhone();
//校验手机号
if (RegexUtils.isPhoneInvalid(phone)) {
return Result.fail("手机号格式错误!");
}
//根据手机号查询用户信息
User user = query().eq("phone", phone).one();
//判断用户是否存在
if (user == null) {
//不存在,创建用户并保存
createUserWithphone(phone);
}
//生成随机的token,作为登录的令牌,不带‘-’
String token = UUID.randomUUID().toString(true);
//将User转换成hashmap存储,此处使用的是hutool自动转换工具
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
Map userMap1 = BeanUtil.beanToMap(userDTO);
//将User转换成hashmap存储,此处使用的是hutool自动转换换成map,
// setIgnoreNullValue(true)忽略对象中为空的值。
// setFieldValueEditor((a, b) -> b.toString()))将传入的userDTO对象中的值转换成String类型
Map userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
CopyOptions.create().setIgnoreNullValue(true)
.setFieldValueEditor((a, b) -> b.toString()));
String tokenkey = RedisConstants.LOGIN_USER_KEY + token;
//将用户信息以token为键存入redis
stringRedisTemplate.opsForHash().putAll(tokenkey, userMap);
//设置token有效期为30分钟
stringRedisTemplate.expire(tokenkey, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
//返回token
return Result.ok(token);
}
第一个拦截器,拦截所有请求
package com.hmdp.utils;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class RefreshTokenInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
//获取请求头中的token
String token = request.getHeader("auth");
//如果token为空直接放行
if (StrUtil.isBlank(token)) {
return true;
}
String tokenkey = RedisConstants.LOGIN_CODE_KEY + token;
//根据token获取用户信息
Map
第二个拦截器
package com.hmdp.utils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
//判断是否需要拦截(ThreadLocal中是否有用户)
if (UserHolder.getUser()==null){
response.setStatus(401);
//拦截
return false;
}
//放行
return true;
}
}
配置拦截的路径,及拦截器执行顺序
package com.hmdp.config;
import com.hmdp.utils.LoginInterceptor;
import com.hmdp.utils.RefreshTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
public void addInterceptors(InterceptorRegistry registry) {
//第二个登录拦截器
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/user/login",
"user/code",
"upload/**"
).order(1); // order 值越大执行的优先级越低
//第一个刷新token的拦截器,/** 拦截所有路径
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);
}
}
该实现是通过拦截器来实现登录有效期的刷新,如使用的用户较固定,如公司内部使用系统入OA等,登录信息也可不设置有效期,每天晚上通过定时任务来清理redis中的登录信息,具体清理时间也可根据业务要求来。



