Redis实现短信登录解决状态登录刷新的问题
Redis实现短信登录
获取验证码控制层
/*** 发送手机验证码*/@PostMapping("/code")public Result sendCode(@RequestParam("phone") String phone) {// TODO 发送短信验证码并保存验证码return userService.sendCode(phone);}
获取验证码服务层
Result sendCode(String phone);@Overridepublic Result sendCode(String phone) {//1.校验手机号if (RegexUtils.isPhoneInvalid(phone)) {//2.如果不符合,返回错误信息return Result.fail("手机号格式错误");}//3.符合,生成验证码String code = RandomUtil.randomNumbers(6); // //4.保存验证码到session,用于用户登录时,将输入的验证码与服务端存入的验证码比对 // session.setAttribute("code",code);//4.保存验证码到redisstringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);//5.发送验证码 真正上线项目使用阿里云的发送验证码log.debug("发送短信验证码成功,验证码:{}",code);//返回return Result.ok();}
登录控制层
/*** 登录功能* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码*/@PostMapping("/login")public Result login(@RequestBody LoginFormDTO loginForm){// TODO 实现登录功能return userService.login(loginForm);}
登录服务层
Result login(LoginFormDTO loginForm);@Overridepublic Result login(LoginFormDTO loginForm) {//校验手机号String phone = loginForm.getPhone();if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("手机号格式错误");} // //校验验证码 // Object cacheCode = session.getAttribute("code");// 从redis获取验证码并校验String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);if (cacheCode == null || !cacheCode.equals(loginForm.getCode())) {//不一致,报错return Result.fail("验证码错误");}//一致,根据手机号查询用户QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("phone", phone);User user = userMapper.selectOne(queryWrapper);//判断用户是否存在if(user == null){//不存在,创建新用户并保存 调用该类下面的方法user = createUserWithPhone(phone);} // //保存用户信息到session // session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));//7保存用户信息到redis中//7.1 随机生成token,作为登录令牌String token = UUID.randomUUID().toString(true);//7.2 将User对象转为HashMap存储UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,new HashMap<>(),CopyOptions.create() //转换为map时,将userDTO中的Long转为String,否则会出现Long转String错误.setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldVlaue) ->fieldVlaue.toString()));//7.3 存储stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token,userMap);//7.4设置token有效期,putAll不能同时设置有效期stringRedisTemplate.expire(LOGIN_USER_KEY + token,LOGIN_USER_TTL,TimeUnit.MINUTES);//返回tokenreturn Result.ok(token) ;}private User createUserWithPhone(String phone) {User user = new User();user.setPhone(phone);user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));userMapper.insert(user);return user;}
手机号、邮箱、密码、验证码校验工具类
public abstract class RegexPatterns {/*** 手机号正则*/public static final String PHONE_REGEX = "^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$";/*** 邮箱正则*/public static final String EMAIL_REGEX = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";/*** 密码正则。4~32位的字母、数字、下划线*/public static final String PASSWORD_REGEX = "^\\w{4,32}$";/*** 验证码正则, 6位数字或字母*/public static final String VERIFY_CODE_REGEX = "^[a-zA-Z\\d]{6}$";}public class RegexUtils {/*** 是否是无效手机格式* @param phone 要校验的手机号* @return true:符合,false:不符合*/public static boolean isPhoneInvalid(String phone){return mismatch(phone, RegexPatterns.PHONE_REGEX);}/*** 是否是无效邮箱格式* @param email 要校验的邮箱* @return true:符合,false:不符合*/public static boolean isEmailInvalid(String email){return mismatch(email, RegexPatterns.EMAIL_REGEX);}/*** 是否是无效验证码格式* @param code 要校验的验证码* @return true:符合,false:不符合*/public static boolean isCodeInvalid(String code){return mismatch(code, RegexPatterns.VERIFY_CODE_REGEX);}// 校验是否不符合正则格式private static boolean mismatch(String str, String regex){if (StrUtil.isBlank(str)) {return true;}return !str.matches(regex);} }
RedisConstants工具类
public static final String LOGIN_CODE_KEY = "login:code:";public static final Long LOGIN_CODE_TTL = 2L;public static final String LOGIN_USER_KEY = "login:token:";public static final Long LOGIN_USER_TTL = 36000L;public static final String USER_NICK_NAME_PREFIX = "user_";
User与UserDTO实体类
@Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("tb_user") public class User implements Serializable {private static final long serialVersionUID = 1L;/*** 主键*/@TableId(value = "id", type = IdType.AUTO)private Long id;/*** 手机号码*/private String phone;/*** 密码,加密存储*/private String password;/*** 昵称,默认是随机字符*/private String nickName;/*** 用户头像*/private String icon = "";/*** 创建时间*/private LocalDateTime createTime;/*** 更新时间*/private LocalDateTime updateTime;}@Data public class UserDTO {private Long id;private String nickName;private String icon; }
解决状态登录刷新的问题
配置拦截器
@Configuration public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/user/code","/user/login","/blog/hot","/shop/**","/voucher/**","/upload/**","/shop-type/**").order(1);registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**").order(0);//order值越小,优先级越高或者按默认顺序执行} }登录拦截器
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//判断是否需要拦截(ThreadLocal中是否有用户)if(UserHolder.getUser() == null){//没有,需要拦截,设置状态码response.setStatus(401);//拦截return false;}//有用户,放行return true;}}刷新token拦截器
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 { // //获取session // HttpSession session = request.getSession();//获取请求头中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {return true;} // //获取session中用户 // Object user = session.getAttribute("user");//基于TOKEN获取redis中的用户Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);//判断用户是否存在if(userMap.isEmpty()) {return true;}//将Map转回为对象UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//存在,保存用户信息到ThreadLocalUserHolder.saveUser(userDTO);//刷新token的有效期stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY,RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);//放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();} }ThreadLocal工具类
public class UserHolder {private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();public static void saveUser(UserDTO user){tl.set(user);}public static UserDTO getUser(){return tl.get();}public static void removeUser(){tl.remove();} }
相关文章:
Redis实现短信登录解决状态登录刷新的问题
Redis实现短信登录 获取验证码控制层 /*** 发送手机验证码*/PostMapping("/code")public Result sendCode(RequestParam("phone") String phone) {// TODO 发送短信验证码并保存验证码return userService.sendCode(phone);} 获取验证码服务层 Result sendC…...
33. java快速排序
1. 前言 排序算法是数据结构中最基础的算法,快速排序则是面试中最常见的排序算法。无论是校招面试还是社招面试,快速排序算法的出现频率远高于其他算法,而且经常会要求候选人白板手写实现算法。快速排序算法的核心是分治处理,重点是分析时间复杂度。 2. 快速排序算法 面试…...
普通二叉搜索树的模拟实现【C++】
二叉搜素树简单介绍 二叉搜索树又称二叉排序树,是具有以下性质的二叉树: 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大于根节点的值 它的左右子树也分别为二叉搜索树 注意…...
unity 介绍Visual Scripting Scene Variables
Visual Scripting中的场景变量是指在Unity中使用可视化脚本时,能够在不同场景间传递和存储数据的变量。这些变量可以用来跟踪游戏状态、玩家信息或其他动态数据,允许开发者在不编写代码的情况下创建复杂的游戏逻辑。 场景变量的优势包括: 1…...
linux服务器部署filebeat
# 下载filebeat curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.17.23-linux-x86_64.tar.gz # 解压 tar xzvf filebeat-7.17.23-linux-x86_64.tar.gz# 所在位置(自定义) /opt/filebeat-7.17.23-linux-x86_64/filebeat.ym…...
个人获取Wiley 、ScienceDirect、SpringerLink三个数据库文献的方法
在同学们的求助文献中经常出现Wiley 、ScienceDirect、SpringerLink这三个数据库文献。本文下面就讲解一下个人如何不用求助他人自己搞定这三个数据库文献下载的方法。 个人下载文献首先要先获取数据库资源,小编平时下载文献是通过科研工具——文献党下载器获取的数…...
Java五子棋
目录 一:案例要求: 二:代码: 三:结果: 一:案例要求: 实现一个控制台下五子棋的程序。用一个二维数组模拟一个15*15路的五子棋棋盘,把每个元素赋值位“┼”可以画出棋…...
【从0开始自动驾驶】用python做一个简单的自动驾驶仿真可视化界面
【从0开始自动驾驶】用python做一个简单的自动驾驶仿真可视化界面 废话几句废话不多说,直接上源码目录结构init.pysimulator.pysimple_simulator_app.pyvehicle_config.json 废话几句 自动驾驶开发离不开仿真软件成品仿真软件种类多https://zhuanlan.zhihu.com/p/3…...
一拖二快充线:单接与双接的多场景应用
在当代社会,随着智能手机等电子设备的普及,充电问题成为了人们关注的焦点。一拖二快充线作为一种创新的充电解决方案,因其便捷性与高效性而受到广泛关注。本文将深入探讨一拖二快充线的定义、原理以及在单接与双接手机场景下的应用࿰…...
接口自动化测试概述
目录 1 接口自动化测试简介 1.1 什么是接口 1.2 什么是接口测试 1.3 为什么要做接口测试 1.4 什么是接口测试自动化 1.5 为什么要做接口测试自动化 2 接口自动化测试规范 2.1 文档准备 2.1.1 需求文档 2.1.2 接口文档 2.1.3 UI 交互图 2.1.4 数据表设计文档 2.2 明…...
Fingerprint.js:精准用户识别的浏览器指纹技术
在数字化时代,用户识别成为互联网服务中不可或缺的一环。随着隐私保护意识的增强,传统的用户识别方法如Cookies和本地存储面临着越来越多的挑战。而Fingerprint.js作为一种创新的浏览器指纹技术,以其高效、隐私友好的特性,逐渐在个…...
Gson将对象转换为JSON(学习笔记)
JSON有两种表示结构,对象和数组。对象结构以"{"大括号开始,以"}"大括号结束。中间部分由0或多个以”,"分隔的”key(关键字)/value(值)"对构成,关键字和值之间以":"分隔,语法结…...
什么是IPv6
目前国内的网络正在快速的向IPv6升级中,从网络基础设施如运营商骨干网、城域网,到互联网服务商如各类云服务,以及各类终端设备厂商如手机、电脑、路由器、交换机等。目前运营商提供的IPv6线路主要分为支持前缀授权和不支持前缀授权两种。 说…...
python画图|放大和缩小图像
在较多的画图场景中,需要对图像进行局部放大,掌握相关方法非常有用,因此我们很有必要一起学习 【1】官网教程 首先是进入官网教程,找到学习资料: https://matplotlib.org/stable/gallery/subplots_axes_and_figures…...
Mac优化清理工具CleanMyMac X 4.15.6 for mac中文版
CleanMyMac X 4.15.6 for mac中文版下载是一款功能更加强大的系统优化清理工具,软件只需两个简单步骤就可以把系统里那些乱七八糟的无用文件统统清理掉,节省宝贵的磁盘空间。CleanMyMac X 4.15.6 for mac 软件与最新macOS系统更加兼容,流畅地…...
资质申请中常见的错误有哪些?
在申请建筑资质的过程中,企业可能会犯一些常见的错误,以下是一些需要避免的错误: 1. 资料准备不充分: 申请资质需要提交大量的资料,包括企业法人资料、财务报表、业绩证明等。资料不齐全或不准确都可能导致申请失败。…...
基于单片机的多路温度检测系统
**单片机设计介绍,基于单片机CAN总线的多路温度检测系统设计 文章目录 前言概要功能设计设计思路 软件设计效果图 程序设计程序 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探…...
面试题:通过栈实现队列
题目描述: 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty): 实现 MyQueue 类: void push(int x) 将元素 x 推到队列的末尾int pop() 从队列的开头移除并返回元素i…...
网络战时代的端点安全演变
在恶意网络行为者与对手在世界各地展开网络战争的日常战争中,端点安全(中世纪诗人可能会称其为“守卫大门的警惕哨兵”)当然是我们的互联数字世界的大门。 端点安全类似于我们今天称之为现代企业的数字有机体的免疫系统,可以将…...
雷池 WAF 如何配置才能正确获取到源 IP
经常有大哥反馈说雷池攻击日志里显示的 IP 有问题。 这里我来讲一下为什么一些情况下雷池显示的攻击 IP 会有问题。 问题说明 默认情况下,雷池会通过 HTTP 连接的 Socket 套接字读取客户端 IP。在雷池作为最外层网管设备的时候这没有问题,雷池获取到的…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...
STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
IP如何挑?2025年海外专线IP如何购买?
你花了时间和预算买了IP,结果IP质量不佳,项目效率低下不说,还可能带来莫名的网络问题,是不是太闹心了?尤其是在面对海外专线IP时,到底怎么才能买到适合自己的呢?所以,挑IP绝对是个技…...
【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
