1.分析
发送验证码:
@Override
public Result sendCode(String phone, HttpSession session) {
//1.校验手机号
if (RegexUtils.isPhoneInvalid(phone)) {
//手机号格式错误,返回错误信息
return Result.fail("手机号格式错误,请重新输入");
}
//生成验证码
String code = RandomUtil.randomNumbers(6);
//保存验证码到redis,key为手机号,value为code,有效期2分钟
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);
//发送验证码
log.debug("发送验证码{}",code);
//返回信息
return Result.ok();
}
登录验证
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
//验证手机号
String phone = loginForm.getPhone();
if (RegexUtils.isPhoneInvalid(phone)){
return Result.fail("手机号格式错误");
}
// 验证验证码
String code = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);
if (code == null || !code.toString().equals(loginForm.getCode()) ){
//验证码过期
return Result.fail("验证码错误");
}
//根据手机号创建用户
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(User::getPhone,phone);
User one = getOne(lambdaQueryWrapper);
/*QueryWrapper<User> lambdaQueryWrapper = new QueryWrapper<>();
lambdaQueryWrapper.eq("phone",phone);
User one = getOne(lambdaQueryWrapper);*/
/*User one = query().eq("phone", phone).one();*/
//判断用户是否存在
if (one == null) {
//用户不存在,创建用户
one =createUserWithPhone(phone);
}
//保存用户到redis
UserDTO userDTO = BeanUtil.copyProperties(one, UserDTO.class);
//生成随机id当作key
String token = UUID.randomUUID().toString(true);
//将user对象转为hashmap存储
Map<String, Object> stringObjectMap = BeanUtil.beanToMap(userDTO);
//存储到redis
String tokenkey = LOGIN_USER_KEY + token;
stringRedisTemplate.opsForHash().putAll(tokenkey,stringObjectMap);
//设置有效期
stringRedisTemplate.expire(tokenkey,LOGIN_USER_TTL,TimeUnit.MINUTES);
//返回token
return Result.ok(token);
}
在拦截器中实现redis的时间刷新
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");
if (token == null) {
//用户不存在,返回状态码401并拦截
response.setStatus(401);
return false;
}
//获得redis中的用户信息
Map<Object, Object> userMapper = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);
//判断用户是否存在
if (userMapper.isEmpty()) {
//用户不存在,返回状态码401并拦截
response.setStatus(401);
return false;
}
//将mapper对象转换为userdto
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMapper, new UserDTO(), false);
//用户存在,保存用户信息到threadlocal
UserHolder.saveUser(userDTO);
//刷新redis有效期
stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token,LOGIN_USER_TTL, TimeUnit.MINUTES);
//放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除user,防止用户信息泄露
UserHolder.removeUser();
}
因为拦截器为交给spring管理,所以需要在mvc的拦截器中进行注入StringRedisTemplate
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
);
}
到目前为止登录的弊端(项目需要优化的地方):
拦截器只拦截了需要访问的功能,当用户访问不需要登录的功能时redis依旧会过期
解决方法:创建一个新的拦截器,拦截一切路径,并此拦截器只进行刷新redis时间后直接放行,需要验证是否登陆的功能放在原拦截器中实现
优化后的代码:
拦截所有请求的拦截器(刷新token的拦截器)
package com.hmdp.utils;
import cn.hutool.core.bean.BeanUtil;
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;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_TTL;
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) throws Exception {
//获取请求头中的信息
String token = request.getHeader("authorization");
if (token == null) {
return true;
}
//获得redis中的用户信息
Map<Object, Object> userMapper = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);
//判断用户是否存在
if (userMapper.isEmpty()) {
return true;
}
//将mapper对象转换为userdto
UserDTO userDTO = BeanUtil.fillBeanWithMap(userMapper, new UserDTO(), false);
//用户存在,保存用户信息到threadlocal
UserHolder.saveUser(userDTO);
//刷新redis有效期
stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token,LOGIN_USER_TTL, TimeUnit.MINUTES);
//放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//移除user,防止用户信息泄露
UserHolder.removeUser();
}
}
拦截需要登录的拦截器
package com.hmdp.utils;
import cn.hutool.core.bean.BeanUtil;
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.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_TTL;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//判断用户是否存在
if (UserHolder.getUser() == null) {
//不存在用户信息,
//设置状态码401
response.setStatus(401);
//拦截
return false;
}
//放行
return true;
}
}
mvc拦截器配置
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;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//登录拦截器
registry.addInterceptor(new LoginInterceptor()).excludePathPatterns(
"/shop/**",
"/voucher/**",
"/shop-type/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
).order(1);
//刷新token拦截器
registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).order(0);
}
}
整体思路