基于session实现短信登录
原始流程
Redis优化后流程
相关代码
模拟发送短信验证码
// 发送短信验证码并保存验证码
@Override
public Result sendCode(String phone, HttpSession session) {
//1.校验手机号
if (RegexUtils.isPhoneInvalid(phone)){
//2.不符合返回错误信息
return Result.fail("手机号格式错误");
}
//3.符合生成验证码
String code = RandomUtil.randomNumbers(6);
//4.保存验证码到session|现在是保存到redis了,有效期2分钟
//session.setAttribute("code",code);
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);
//5.发送验证码(暂时写个假的)
log.debug("发送验证码成功,{}",code);
//返回ok
return Result.ok();
}
验证码登录、注册
// 登录
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//1.校验手机号
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)){
//不符合返回错误信息
return Result.fail("手机号格式错误");
}
//2.校验验证码|现在是从redis中获取
//Object cacheCode=session.getAttribute("code");
String cacheCode=stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);
String code=loginForm.getCode();
//if (cacheCode==null||!cacheCode.toString().equals(code)){
if (cacheCode==null||!cacheCode.equals(code)){
//3.不一致,报错
return Result.fail("验证码错误");
}
//4.一致,根据手机号查询用户
User user = query().eq("phone", phone).one();
//5.判断用户是否存在
if (user==null){
//6.不存在,创建新用户并保存
user=createUserWithPhone(phone);
}
//7.保存用户信息到session|现在是保存到redis了
//7.1随机生成token,作为登录令牌
String token = UUID.randomUUID().toString(true);
//7.2将user转为Hash存储
UserDTO userDTO=BeanUtil.copyProperties(user,UserDTO.class);
//UserDTO中的id类型导致报错,setIgnoreNullValue忽略空值,setFieldValueEditor字段值修改,只把fieldValue改成String就行
Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),
CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));
//7.3存储用户信息
//session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY+token,userMap);
//7.4设置token有效期
stringRedisTemplate.expire(LOGIN_USER_KEY+token,LOGIN_USER_TTL,TimeUnit.MINUTES);
//返回token
//return Result.ok();
return Result.ok(token);
}
private User createUserWithPhone(String phone) {
//1.创建用户
User user=new User();
user.setPhone(phone);
user.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(10));
//2.保存用户
save(user);
return user;
}
配置拦截器
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//登录拦截器
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/user/code",
"/user/login",
"/blog/hot",
"/shop/**",
"/shop-type/**",
"/upload/**"
).order(1);
//token刷新拦截器
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0); //order设置拦截器执行顺序,值越小越早运行
}
}
登录拦截器
package com.hmdp.utils;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @Description: 拦截器
* @Author: xxx
* @Date: 2022/12/1 15:04
*/
public class LoginInterceptor implements HandlerInterceptor {
// Conhtroller之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.判断是否拦截(ThreadLocal中是否有用户)
if (UserHolder.getUser()==null){
response.setStatus(401);
return false;
}
return true;
}
}
刷新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;
/**
* @Description: 拦截器刷新token
* @Author: xxx
* @Date: 2022/12/1 15:04
*/
public class RefreshTokenInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
// Conhtroller之前
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.获取session|现在是获取请求头中的token
//HttpSession session = request.getSession();
String token=request.getHeader("authorization");
//1.1判空
if (StrUtil.isBlank(token)) {
//不拦截,下一个拦截器进行拦截
//response.setStatus(401);
return true;
}
//2.获取session中的用户|基于token获取redis中的用户
//Object user = session.getAttribute("user");
Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);
//3.判断用户是否存在
if (userMap.isEmpty()){
//4.不存在,不拦截,下一次拦截,返回401
//response.setStatus(401);
return true;
}
//5.将获取到的Hash数据转换为userDTO对象
UserDTO userDTO= BeanUtil.fillBeanWithMap(userMap,new UserDTO(),false);
//6.存在,保存用户信息到ThreadLocal
UserHolder.saveUser(userDTO);
//7.刷新token有效期
stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY+token,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
//8.放行
return true;
}
// 渲染之后
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除用户,避免内存泄漏
UserHolder.removeUser();
}
}