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。在雷池作为最外层网管设备的时候这没有问题,雷池获取到的…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
面试高频问题
文章目录 🚀 消息队列核心技术揭秘:从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"?性能背后的秘密1.1 顺序写入与零拷贝:性能的双引擎1.2 分区并行:数据的"八车道高速公路"1.3 页缓存与批量处理…...
联邦学习带宽资源分配
带宽资源分配是指在网络中如何合理分配有限的带宽资源,以满足各个通信任务和用户的需求,尤其是在多用户共享带宽的情况下,如何确保各个设备或用户的通信需求得到高效且公平的满足。带宽是网络中的一个重要资源,通常指的是单位时间…...
