建设 网站工作汇报,王也踏青图,网站被挂马 301,163邮箱官方注册入口文章目录 #x1f970;前言#x1f6f8;StringRedisTemplate#x1f339;使用StringRedisTemplate⭐常用的方法 #x1f6f8;为什么我们要使用Redis代替Session进行登录操作#x1f386;具体使用✨编写拦截器✨配置拦截器#x1f33a;基于Redis实现发送手机验证码操作前言StringRedisTemplate使用StringRedisTemplate⭐常用的方法 为什么我们要使用Redis代替Session进行登录操作具体使用✨编写拦截器✨配置拦截器基于Redis实现发送手机验证码操作总体思路具体步骤 基于Redis实现短信登录并注册的操作总体思路具体步骤 前言
使用 Redis 进行登录适用于以下情况
分布式系统 当系统需要支持多个节点的分布式部署时使用 Redis 存储登录信息能够更好地支持多节点间的共享和同步确保用户的登录状态能够在整个系统中得到有效的传递和管理。高并发访问 面对大规模的并发访问使用 Redis 可以提供更好的性能表现。Redis 是一个基于内存的高性能 Key-Value 数据库能够更快速地读取和写入数据因此适用于需要处理大量并发请求的场景。灵活的数据结构需求 如果系统需要根据业务需求选择最佳的数据结构并且对存储和操作登录信息有更多的灵活性那么使用 Redis 将会是一个不错的选择。Redis 支持多种数据类型的存储和操作包括字符串、哈希表、列表、集合和有序集合等能够满足不同的业务需求。需要持久化支持 如果系统需要对登录信息进行持久化存储以防止数据丢失Redis 的持久化功能可以很好地满足这一需求。
总的来说使用 Redis 进行登录适用于需要支持分布式部署、面对高并发访问、有灵活的数据结构需求以及需要持久化支持的系统场景。通过合理地利用 Redis 的特性可以更好地满足上述情况下的需求提高系统的可扩展性、性能和稳定性。
虽然 Spring Boot 应用通常是单体应用但是在实际运行中我们也经常会遇到多个实例同时运行的情况这时候就需要使用 Redis 进行分布式 Session 管理。
StringRedisTemplate
StringRedisTemplate是Spring Data Redis提供的一个类它是一个具体的对象用于操作Redis数据库中的字符串类型数据。
StringRedisTemplate封装了Redis的操作并提供了一系列方法来对Redis中的字符串进行读取、写入和删除操作。它是RedisTemplate的一个子类专门用于处理字符串类型的数据。
使用StringRedisTemplate 首先引入依赖引入StringRedisTemplate的依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependency
⭐常用的方法
StringRedisTemplate提供了多个方法来操作Redis中的字符串类型数据。下面是一些常用的方法
opsForValue().set(key, value)将一个字符串类型的值value存储到Redis中并指定键key。opsForValue().get(key)根据键key获取对应的字符串类型的值。opsForValue().increment(key, delta)将键key所对应的值增加deltadelta可以为负数。opsForValue().size(key)获取值的长度。
为什么我们要使用Redis代替Session进行登录操作
集群session存在共享问题会导致数据丢失
保存相同的数据大家互相copy会有内存空间的浪费我们copy数据的时候是需要有一定的时间的会有延迟如果在这个延迟之内如果有人来访问仍然会造成数据不一致的情况 如果我们使用Redis的话。Redis是在tomcat外面的存储如果任意一台tomcat都能访问到Redis可以实现数据共享储存在Redis里面的数据任何tomcat都可以看到使用就不存在数据丢失的问题Redis读写延迟非常低方便进行内存存储Redis是key-value结构 具体使用
✨编写拦截器 RefreshTokenInterceptor.java 在拦截器中配置拦截操作
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;import static com.hmdp.utils.RedisConstants.LOGIN_USER_KEY;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_TTL;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 key LOGIN_USER_KEY token;MapObject, Object userMap stringRedisTemplate.opsForHash().entries(key);// 3.判断用户是否存在if (userMap.isEmpty()) {return true;}// 5.将查询到的hash数据转为UserDTOUserDTO userDTO BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6.存在保存用户信息到 ThreadLocalUserHolder.saveUser(userDTO);// 7.刷新token有效期stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.放行return true;}Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用户UserHolder.removeUser();}
} 刷新token的目的 用户每访问一次这个token就会刷新一次主要用户一直在操作这个token就不会消失 但是如果仅仅拦截的是需要登录的路径用户 访问 不需要登录 的路径 的时候比如首页这个拦截器就不生效此时token就不会刷新这样子过了token的有效期后尽管用户还在访问用户的登录状态却消失了这样肯定不太合理
那么我们就需要在原来的拦截器基础上再加上一个拦截器 LoginInterceptor.java 在拦截器中配置拦截操作
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 {Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.判断是否需要拦截ThreadLocal中是否有用户if (UserHolder.getUser() null) {// 没有需要拦截设置状态码response.setStatus(401);// 拦截return false;}// 有用户则放行return true;}
} ✨配置拦截器
我们上面编写了拦截器我们还需要配置拦截器使这个拦截器生效 MvcConfig.java 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 {Resourceprivate StringRedisTemplate stringRedisTemplate;Overridepublic 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)).addPathPatterns(/**).order(0);}
} 基于Redis实现发送手机验证码操作
总体思路 具体步骤
我们首先引入上面说的依赖然后在application.yml文件或yaml文件中进行配置如下
下面我们编写发送手机验证码的核心代码
Slf4j
Service
public class UserServiceImpl extends ServiceImplUserMapper, User implements IUserService {Resourceprivate StringRedisTemplate stringRedisTemplate;Overridepublic Result sendCode(String phone, HttpSession session) {// 1.校验手机号if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合返回错误信息return Result.fail(手机号格式错误);}// 3.符合生成验证码String code RandomUtil.randomNumbers(6);// 4.保存验证码到 redisstringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);// 5.发送验证码log.debug(发送短信验证码成功验证码{}, code);// 返回okreturn Result.ok();}
}上面代码里面的RegexUtils.isPhoneInvalid(phone)这段代码是什么用法 stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);这段代码有什么用 这段代码的作用是将一个验证码即code存储到Redis中并设置了过期时间为LOGIN_CODE_TTL分钟。以便在一定时间后自动删除该键值对。
基于Redis实现短信登录并注册的操作
总体思路 具体步骤
我们首先引入上面说的依赖并且在application.yml文件或yaml文件中进行配置同上 然后我们来编写核心代码
Slf4j
Service
public class UserServiceImpl extends ServiceImplUserMapper, User implements IUserService {Resourceprivate StringRedisTemplate stringRedisTemplate;Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 1.校验手机号String phone loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {// 2.如果不符合返回错误信息return Result.fail(手机号格式错误);}// 3.从redis获取验证码并校验String cacheCode stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY phone);String code loginForm.getCode();if (cacheCode null || !cacheCode.equals(code)) {// 不一致报错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.保存用户信息到 redis中// 7.1.随机生成token作为登录令牌String token UUID.randomUUID().toString(true);// 7.2.将User对象转为HashMap存储UserDTO userDTO BeanUtil.copyProperties(user, UserDTO.class);MapString, Object userMap BeanUtil.beanToMap(userDTO, new HashMap(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) - fieldValue.toString()));// 7.3.存储String tokenKey LOGIN_USER_KEY token;stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);// 7.4.设置token有效期stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.返回tokenreturn 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;}}User user query().eq(“phone”, phone).one();这段代码使用了mybatisplus相当于select * from tb_user where phone ? 为什么要使用HashMap进行存储 在这段代码中使用HashMap进行存储是为了将用户对象转换成键值对形式便于统一保存到Redis中并且可以方便地进行序列化和反序列化操作。具体来说
便于存储和读取将用户对象转为HashMap后可以方便地通过stringRedisTemplate.opsForHash().putAll()方法一次性将整个用户对象存储到Redis的Hash数据结构中而不需要对用户对象的每个字段分别进行存储。数据结构清晰使用HashMap可以清晰地表示用户对象的各个字段和对应的数值便于管理和维护。方便序列化和反序列化HashMap作为Java中的常用数据结构可以方便地进行序列化将数据转换为字节序列和反序列化将字节序列转换为数据操作便于在存储到Redis或者从Redis中读取时进行数据格式的转换。
总之使用HashMap进行存储能够简化代码逻辑提高数据存储和读取的效率并且方便进行数据结构的转换和管理。 MapString, Object userMap BeanUtil.beanToMap(userDTO, new HashMap(), CopyOptions.create() .setIgnoreNullValue(true) .setFieldValueEditor((fieldName, fieldValue) - fieldValue.toString())); 这段代码为什么要这样写这些参数有什么用 其中beanToMap是一个方法用于将Java对象Bean转换为Map类型的数据结构。
在这段代码中BeanUtil.beanToMap()方法被使用它是一个工具类方法可以通过反射机制将Java对象的属性和对应的值转换为键值对形式并存储到一个Map对象中。
具体来说beanToMap方法接收三个参数 userDTO表示要转换的源对象即需要将其转换为Map的对象。 new HashMap()表示用于存储转换结果的目标HashMap对象这里使用了一个新的空HashMap用于接收转换后的键值对数据。 CopyOptions.create().setIgnoreNullValue(true)这是使用BeanUtil进行对象转换时的配置选项。setIgnoreNullValue(true)表示忽略源对象中值为null的属性不将其放入目标Map中。 .setFieldValueEditor((fieldName, fieldValue) - fieldValue.toString())这个配置项表示对转换过程中的字段值进行编辑处理。在这里它的作用是将字段值转换为字符串类型确保最终存储到Map中的值都是字符串类型。
综合起来这段代码的目的是将UserDTO对象转换为Map类型同时忽略空值属性并确保所有属性值都被转换为字符串类型。这样做的原因可能是为了在存储到Redis中时确保数据的统一性和一致性便于后续从Redis中读取并进行处理。 在技术的道路上我们不断探索、不断前行不断面对挑战、不断突破自我。科技的发展改变着世界而我们作为技术人员也在这个过程中书写着自己的篇章。让我们携手并进共同努力开创美好的未来愿我们在科技的征途上不断奋进创造出更加美好、更加智能的明天