Redis——某马点评day01——短信登录

2023-12-03 05:15

本文主要是介绍Redis——某马点评day01——短信登录,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

项目介绍

导入黑马点评项目

项目架构

 

 

基于Session实现登录

基本流程

实现发送短信验证码功能

 controller层中

    /*** 发送手机验证码*/@PostMapping("code")public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {//  发送短信验证码并保存验证码return userService.sendCode(phone,session);}

Service层中

真的发送的话要接入阿里云或腾讯云的短信发送功能,这里假装发送成功。

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Overridepublic Result sendCode(String phone, HttpSession session) {//1.校验手机号if(RegexUtils.isPhoneInvalid(phone)){//2.如果不符合,返回错误信息return Result.fail("手机号格式错误!");}//3.符合,生成验证码String code = RandomUtil.randomNumbers(6);//4.保存验证码session.setAttribute("code",code);//5.发送验证码log.debug("发送短信验证码成功,验证码:{}",code);return Result.ok();}
}

再次点击发送就可以看见如下:

 

实现验证码登录和注册功能

Controller层中

    /*** 登录功能* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码*/@PostMapping("/login")public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){// 实现登录功能return userService.login(loginForm,session);}

Service层中

    @Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {//1.验证手机号String phone=loginForm.getPhone();if(RegexUtils.isPhoneInvalid(phone)){//1.1不符合,返回报错return Result.fail("手机号格式错误!");}//2.校验验证码Object cacheCode = session.getAttribute("code");String code = loginForm.getCode();if(cacheCode==null||!cacheCode.toString().equals(code)){//3.不一致,报错return Result.fail("验证码错误");}//4.一致,根据手机号查询用户 select * from tb_user where phone=?User user = query().eq("phone", phone).one();//5.判断用户是否存在if(user==null){//6.不存在,创建新用户并保存user=   createUserWithPhone(phone);}//7.存在,保存用户信息到session中session.setAttribute("user",user);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;}

这里的session之所以能够获取到发送验证码请求时存入的验证码是因为这是在同一个会话当中,session会被返回给前端,下一次请求也会携带同一个session过来。所以才有之前保存的信息在里面。

实现登录校验拦截器

 

 接口多了之后就要拦截器进行统一校验,然后为了传递用户信息给后序的业务,需要将用户信息存入Threadlocal里面的.Threadlocal是每个请求线程的一个独立保存空间。

在拦截器的最后一个方法中,清空thread local的信息,第一可以做到退出登录,第二可以防止内存泄露

新建一个拦截器

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取sessionHttpSession session = request.getSession();//2.获取session中的用户Object user = session.getAttribute("user");//3.判断用户是否存在if(user==null){//4.不存在,拦截response.setStatus(401);return false;}//5.存在,保存用户信息到ThreadLocalUserHolder.saveUser((UserDTO) user);//6.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//移除用户UserHolder.removeUser();}
}

 新建配置项

将上面的拦截器投入使用,并且指定不拦截的请求。

@Configuration
public class MvcConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/shop-type/**","/upload/**","/blog/hot","/user/code" ,"/user/login");}
}

登录校验接口

    @GetMapping("/me")public Result me(){// 获取当前登录的用户并返回UserDTO user= UserHolder.getUser();return Result.ok(user);}

隐藏用户敏感信息

在前面代码里返回给前端的是用户完整信息,这是不合理的,所以改成如下。

在session存的时候就只存部分信息。然后上面登录校验逻辑因为黑马资料的问题已经是隐藏之后的了的,所以只改这里一个地方即可。

    @Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {........//7.存在,保存用户信息到session中session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));return Result.ok();}

集群的session共享问题

使用Redis解决Session共享问题。 

基于Redis实现共享session登录

业务流程

基于redis实现短信登录

发送短信验证码部分的代码修改:

生成验证码之后将验证码存入Redis

    @Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result sendCode(String phone, HttpSession session) {//1.校验手机号...//3.符合,生成验证码...//4.保存验证码到RedisstringRedisTemplate.opsForValue().set("login:code:"+phone,code,2, TimeUnit.MINUTES);//5.发送验证码...}

短信验证码登录,注册功能部分的代码修改:

    @Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {//1.验证手机号...//  2.从redis获取 校验验证码String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY+phone);...//4.一致,根据手机号查询用户 select * from tb_user where phone=?...//5.判断用户是否存在...//7.存在,保存用户信息到Redis中//7.1.随机生成token,作为登录令牌String token = UUID.randomUUID().toString(true);//7.2将User对象转为Hash存储UserDTO userDTO=BeanUtil.copyProperties(user, UserDTO.class);Map<String, Object> userMap = BeanUtil.beanToMap(userDTO);//7.3存储String tokenKey="login::token:"+token;stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);//7.4设置token有效期stringRedisTemplate.expire(tokenKey,30,TimeUnit.MINUTES);//8.返回tokenreturn Result.ok(token);}

登录拦截器代码修改


public class LoginInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate=stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取请求头中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {//4.不存在,拦截response.setStatus(401);return false;}//2.基于token获取redis中的用户String tokenKey=RedisConstants.LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);//3.判断用户是否存在if(userMap.isEmpty()){//4.不存在,拦截response.setStatus(401);return false;}//5.将查询到的Hash数据转为UserDTO对象UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//6.存在,保存用户信息到ThreadLocalUserHolder.saveUser( userDTO);//7.刷新token有效期stringRedisTemplate.expire(tokenKey,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);//8.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//移除用户...}
}

因为这里不能用注解注入stringRedisTemplate,要在MVC配置器哪里进行注入

@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns("/shop/**","/shop-type/**","/upload/**","/blog/hot","/user/code" ,"/user/login");}
}

出现Long转String的报错 

在登录逻辑里面将对象转为Map时做多点工作

        //7.2将User对象转为Hash存储UserDTO userDTO=BeanUtil.copyProperties(user, UserDTO.class);Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));

 可以看见对应东西

 

解决登录状态刷新问题

登录状态的刷新只会在访问需要拦截的请求时才刷新,如果是不需要拦截的请求就不会刷新.

为了拦截所有请求,这里新加一个拦截器

新增刷新拦截器:

public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate=stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.获取请求头中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {return true;}//2.基于token获取redis中的用户String tokenKey=RedisConstants.LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(tokenKey);//3.判断用户是否存在if(userMap.isEmpty()){return true;}//5.将查询到的Hash数据转为UserDTO对象UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//6.存在,保存用户信息到ThreadLocalUserHolder.saveUser( userDTO);//7.刷新token有效期stringRedisTemplate.expire(tokenKey,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);//8.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//移除用户UserHolder.removeUser();}
}

 修改原本的登录拦截器

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.判断是否需要拦截(ThreadLocal中是否有用户)if(UserHolder.getUser()==null){//没有,需要拦截response.setStatus(401);return false;}return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//移除用户UserHolder.removeUser();}
}

MVC配置类当中

需要添加order保证刷新拦截器先执行。

@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {//登录拦截器registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/shop-type/**","/upload/**","/blog/hot","/user/code" ,"/user/login").order(1);//刷新拦截器registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);}
}

这篇关于Redis——某马点评day01——短信登录的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/448192

相关文章

一文带你搞懂Redis Stream的6种消息处理模式

《一文带你搞懂RedisStream的6种消息处理模式》Redis5.0版本引入的Stream数据类型,为Redis生态带来了强大而灵活的消息队列功能,本文将为大家详细介绍RedisStream的6... 目录1. 简单消费模式(Simple Consumption)基本概念核心命令实现示例使用场景优缺点2

Redis中6种缓存更新策略详解

《Redis中6种缓存更新策略详解》Redis作为一款高性能的内存数据库,已经成为缓存层的首选解决方案,然而,使用缓存时最大的挑战在于保证缓存数据与底层数据源的一致性,本文将介绍Redis中6种缓存更... 目录引言策略一:Cache-Aside(旁路缓存)策略工作原理代码示例优缺点分析适用场景策略二:Re

redis中使用lua脚本的原理与基本使用详解

《redis中使用lua脚本的原理与基本使用详解》在Redis中使用Lua脚本可以实现原子性操作、减少网络开销以及提高执行效率,下面小编就来和大家详细介绍一下在redis中使用lua脚本的原理... 目录Redis 执行 Lua 脚本的原理基本使用方法使用EVAL命令执行 Lua 脚本使用EVALSHA命令

Redis 热 key 和大 key 问题小结

《Redis热key和大key问题小结》:本文主要介绍Redis热key和大key问题小结,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、什么是 Redis 热 key?热 key(Hot Key)定义: 热 key 常见表现:热 key 的风险:二、

C#使用StackExchange.Redis实现分布式锁的两种方式介绍

《C#使用StackExchange.Redis实现分布式锁的两种方式介绍》分布式锁在集群的架构中发挥着重要的作用,:本文主要介绍C#使用StackExchange.Redis实现分布式锁的... 目录自定义分布式锁获取锁释放锁自动续期StackExchange.Redis分布式锁获取锁释放锁自动续期分布式

SpringBoot基于配置实现短信服务策略的动态切换

《SpringBoot基于配置实现短信服务策略的动态切换》这篇文章主要为大家详细介绍了SpringBoot在接入多个短信服务商(如阿里云、腾讯云、华为云)后,如何根据配置或环境切换使用不同的服务商,需... 目录目标功能示例配置(application.yml)配置类绑定短信发送策略接口示例:阿里云 & 腾

Redis Pipeline(管道) 详解

《RedisPipeline(管道)详解》Pipeline管道是Redis提供的一种批量执行命令的机制,通过将多个命令一次性发送到服务器并统一接收响应,减少网络往返次数(RTT),显著提升执行效率... 目录Redis Pipeline 详解1. Pipeline 的核心概念2. 工作原理与性能提升3. 核

redis过期key的删除策略介绍

《redis过期key的删除策略介绍》:本文主要介绍redis过期key的删除策略,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录第一种策略:被动删除第二种策略:定期删除第三种策略:强制删除关于big key的清理UNLINK命令FLUSHALL/FLUSHDB命

Redis消息队列实现异步秒杀功能

《Redis消息队列实现异步秒杀功能》在高并发场景下,为了提高秒杀业务的性能,可将部分工作交给Redis处理,并通过异步方式执行,Redis提供了多种数据结构来实现消息队列,总结三种,本文详细介绍Re... 目录1 Redis消息队列1.1 List 结构1.2 Pub/Sub 模式1.3 Stream 结

SpringBoot中配置Redis连接池的完整指南

《SpringBoot中配置Redis连接池的完整指南》这篇文章主要为大家详细介绍了SpringBoot中配置Redis连接池的完整指南,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以... 目录一、添加依赖二、配置 Redis 连接池三、测试 Redis 操作四、完整示例代码(一)pom.