Redis可以进行如下操作
主要进行如下操作:
@Override
public Result sendCode(String phone, HttpSession session) {
// 1.校验手机号
if (RegexUtils.isPhoneInvalid(phone))
{
// 2.不符合就返回错误信息
return Result.fail("手机号格式错误!");
}
// 3. 符合,生成验证码--一般长度为6位 用一个随机数默认向手机号发送短信
String code = RandomUtil.randomNumbers(6);
// 4.将验证码保存到session中
session.setAttribute("code",code);
// 5.发送验证码--aliyun的第三方库
log.debug("验证码发送成功,验证码:"+code);
// 5.返回ok
return Result.ok();
}
登录
主要进行如下操作:
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
String phone = loginForm.getPhone();
// 1.校验手机号
if (RegexUtils.isPhoneInvalid(phone)) {
// 不符合就返回错误信息
return Result.fail("手机号格式错误!");
}
// 2.继手机号之后,校验验证码
String code = loginForm.getCode();
String cacheCode = (String) session.getAttribute("code");
if (cacheCode == null || RegexUtils.isCodeInvalid(code) || cacheCode == code) {
// 3.不一致,报错
return Result.fail("验证码错误!");
}
// 4.一致,根据手机号查询用户
User user = query().eq("phone", phone).one();
// 5.判断用户是否存在
if (user == null){
// 6.不存在,创建新用户并保存
user = createUserWithPhone(phone);
}
session.setAttribute("user",user);
// 每一个session都有一个SessionID
return Result.ok();
}
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;
}
定义拦截器
主要进行如下操作:
public class LoginInterceptor implements HandlerInterceptor {
// 1. 前置拦截器
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 获取session
HttpSession session = request.getSession();
// 2. 获取session中的用户
Object user = session.getAttribute("user");
// 3. 判断用户是否存在
if (user == null){
// 4. 不存在,拦截,返回401代码
response.setStatus(401);
return false;
}
// 5. 存在,保存用户信息到 ThreadLocal
UserHolder.saveUser((User) user);
// 6. 放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
配置拦截器
主要进行如下操作:
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 将定义的LoginInterceptor登录拦截器添加进去
// 并排除一些不需要拦截的请求
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(
"/shop/**",
"/shop-type/**",
"/voucher/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
);
}
}
session实现共享所面临的问题:
Redis代替session需要考虑的问题
主要进行如下操作:
@Autowired
private StringRedisTemplate stringRedisTemplate;
//引入ObjectMapperJSON处理类
@Override
public Result sendCode(String phone, HttpSession session) {
// 1.校验手机号
if (RegexUtils.isPhoneInvalid(phone))
{
// 2.不符合就返回错误信息
return Result.fail("手机号格式错误!");
}
// 3. 符合,生成验证码--一般长度为6位 用一个随机数默认向手机号发送短信
String code = RandomUtil.randomNumbers(6);
// 4.将验证码保存到Redis中 --- 一般加个前缀即能保证唯一性,又能指明当前key-value的功能
// 5.设置验证码的key的有效期 LOGIN_CODE_KEY:前缀 LOGIN_CODE_TTL:有效期时间
stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY+phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);
// 6.发送验证码--aliyun的第三方库
log.debug("验证码发送成功,验证码:"+code);
// 7.返回ok
return Result.ok();
}
主要操作分为以下几点:
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
String phone = loginForm.getPhone();
// 1.校验手机号
if (RegexUtils.isPhoneInvalid(phone)) {
// 不符合就返回错误信息
return Result.fail("手机号格式错误!");
}
// TODO 2.从redis获取到验证码并进行验证
String code = loginForm.getCode();
String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);
if (cacheCode == null || RegexUtils.isCodeInvalid(code) || cacheCode == code) {
// 3.不一致,报错
return Result.fail("验证码错误!");
}
// 4.一致,根据手机号查询用户
User user = query().eq("phone", phone).one();
// 5.判断用户是否存在
if (user == null){
// 6.不存在,创建新用户并保存
user = createUserWithPhone(phone);
}
// TODO 7. 保存用户信息到redis,问题进行保存的只能是用户的部分信息,不能包括完整信息,用户的敏感数据必须被隐藏。UserDTD类只包含数据的部分信息
// 7.1、用随机token 作为登录令牌
String token = UUID.randomUUID().toString(true);
// 7.2、将Java对象转换为Hash进行存储
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
Map<String, Object> map = BeanUtil.beanToMap(userDTO);
String key = LOGIN_USER_KEY+token;
map.put("id",userDTO.getId().toString());
stringRedisTemplate.opsForHash().putAll(key,map);
// 7.3、设置token有效期 30分钟 CACHE_SHOP_TTL常量
stringRedisTemplate.expire(key,CACHE_SHOP_TTL,TimeUnit.MINUTES);
return Result.ok(token);
}
拦截器配置
主要获得请求头携带的token(登录动作的返回值,在前端作为数据存储在请求头中),方便后续的操作。主要操作:
public class LoginInterceptor implements HandlerInterceptor {
private StringRedisTemplate stringRedisTemplate;
public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
// 1. 前置拦截器
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 获取请求头中的token
String token = request.getHeader("authorization");
if (StrUtil.isBlank(token)){
// 不存在,拦截,返回401代码
response.setStatus(401);
return false;
}
// 2. 基于token获取reids中的用户
Map<Object, Object> map = stringRedisTemplate.opsForHash().entries(LOGIN_USER_KEY + token);
// 3. 判断是否是空值
if (map.isEmpty()){
// 4. 不存在,拦截,返回401代码
response.setStatus(401);
return false;
}
// 5. 将查询到的hash数据转为UserDTD对象
UserDTO userDTO = BeanUtil.fillBeanWithMap(map, new UserDTO(), false);
// 6. 存在,保存用户信息到 ThreadLocal
UserHolder.saveUser(userDTO);
// 7. 刷新token的有效期
String key = LOGIN_USER_KEY + token;
stringRedisTemplate.expire(key,30, TimeUnit.MINUTES);
// 8. 放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
注入拦截器
将定义的LoginInterceptor登录拦截器注入到spring中,需要进行以下操作:
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 排除一些不需要拦截的请求
registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))
.excludePathPatterns(
"/shop/**",
"/shop-type/**",
"/voucher/**",
"/upload/**",
"/blog/hot",
"/user/code",
"/user/login"
);
}
}