springboot发送短信验证码,结合redis 实现限制,验证码有效期2分钟,有效期内禁止再次发送,一天内发送超3次限制

news2025/2/22 18:47:56

springboot结合redis发送短信验证码,实现限制发送操作

    • 前言(可忽略)
    • 实现思路
    • 正题
    • 效果图示例
      • 手机号不符合规则校验图
      • 成功发送验证码示例图
      • redis中缓存随机数字验证码,2分钟后失效删除redis缓存图
      • 验证码有效期内 返回禁止重复发送图
      • 验证码24小时内发送达到3次,限制再次发送验证码图
      • idea代码控制台输出日志信息图
      • 接口压测1万次全部success图
    • 使用到的maven依赖
    • redis 缓存Key 统一静态管理类
    • 返回错误信息枚举定义
    • redis缓存util工具类封装
    • service验证码发送接口定义和实现代码
      • 接口定义
      • 接口实现类
    • controller业务接口请求代码
    • 接口请求测试
      • 请求
      • 响应
    • 结尾

前言(可忽略)

好久没有更新过csdn博客了,从2023年到发布这篇文章之前,感觉有1年没有写代码的状态,大多还是和 工作/日常 有关 影响,有点退步了。 现在找回了写代码的那种状态,找回状态后发现还是要多学习,要始终保持着热爱 虚心钻研之心.

工作之余想到了一些功能点,空闲时间自己实现了下,做个记录。 有时间还是得要多写点业务代码,不然一直在退步.
----好了 不多说了,进入文章正题---</b>

实现思路

调用发送短信接口,输入手机号发送短信。

发送请求后,判断手机号码格式是否正确,格式不正确,返回格式错误信息。

格式正确,进行发送短信,发送成功时,redis中缓存该手机号验证码2分钟 同时缓存24小时发送次数默认1。

如验证码2分钟未失效,再次使用相同手机号码发短信时,返回验证码在有效期内请勿重复发送。

2分钟验证码失效后,会自动删除该验证码缓存。可再次对该手机号发送验证码,再次发送成功后,该号码24小时发送次数缓存值+1,此时短信发送成功第二次。

24小时内重复发送短信测试,直到达到3次后。 发送验证码返回24小时发送验证码3次,发送失败,请于1天后重试。

正题

使用springboot框架结合redis发送验证码, 实现限制功能,验证码有效期2分钟,有效期内禁止再次发送,一天内发送超3次限制.

代码中定义短信云平台模板,进行模拟发送真实验证码,可接入阿里云等各种第三方云短信模板,结合各种场景进行验证码后续的业务操作。

在这里插入图片描述

// 模拟阿里云发送短信业务, 模板示例值.  可自行对接
String sendMsg = "【提先森的小站】测试阿里云服务短信发送,此次操作验证码为 {} ,该验证码有效期2分钟,请尽快输入验证.";

使用良好优雅的代码规范实现限制发送验证码功能, 加入统一枚举返回错误类型,统一redis缓存key值管理~


以下开始正文 附上效果图示例和完整代码,希望对看到有需要的朋友有所帮助。
如有帮到您,还希望点赞支持一下yo~


效果图示例

手机号不符合规则校验图

在这里插入图片描述

成功发送验证码示例图

在这里插入图片描述

redis中缓存随机数字验证码,2分钟后失效删除redis缓存图

在这里插入图片描述

验证码有效期内 返回禁止重复发送图

在这里插入图片描述

验证码24小时内发送达到3次,限制再次发送验证码图

在这里插入图片描述

idea代码控制台输出日志信息图

在这里插入图片描述

接口压测1万次全部success图

在这里插入图片描述


在这里插入图片描述

使用到的maven依赖

        <!--redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--hutool 工具使用参考文档:https://hutool.cn/ -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.20</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>  

redis 缓存Key 统一静态管理类

/**
 * @author zxt
 * @version 1.0.0
 * @date 2023年06月20日 11:29:16
 * @describe redis 缓存Key 统一管理
 */
public class RedisKeyConstant {
	// 发送验证码key
    public static final String USER_REGISTER_KEY = "user:register:send_code:{}";
    // 手机号24小时内发送验证码次数key
    public static final String USER_REGISTER_COUNT_KEY = "user:register:day_send_count:{}";


    public static final String USER_INFORMATION_KEY = "user:information:{}";
    public static final Integer USER_INFORMATION_EXPIRED = 3;
    public static final String USER_LOGIN_INFORMATION_KEY = "login:info:{}";
    public static final Integer USER_LOGIN_INFORMATION_EXPIRED = 2;
    public static final String APPLET_TELECOM_TOKEN_KEY = "user:token:{}";
    public static final Integer APPLET_TELECOM_TOKEN_EXPIRED = 1;

}

返回错误信息枚举定义

/**
 * @author zxt
 * @apiNote
 * @date 2024/4/16 10:23
 */
public enum ServiceErrorEnum {

    SUCCESS(0, "OK!"),
    SERVER_ERROR(500, "Internal Server Error"),
    PARAM_FAIL(-1, "Param Fail"),

    REGISTER_RECAPTCHA_INPUT_ERROR(10000, "注册失败, 验证码输入错误..."),
    REGISTER_RECAPTCHA_EXPIRE(10001, "注册失败, 手机号验证码信息不存在..."),
    REGISTER_RECAPTCHA_ISVALID(10002, "验证码在有效期内, 2分钟内请勿重复发送... "),
    REGISTER_CODE_COUNT_ERROR(10003, "24小时内已发送验证码3次,发送已限制 请于1天后重试..."),
    REGISTER_PHONENUMBER_ERROR(10004, "请检查手机号码是否符合规范..."),
    REGISTER_PHONENUMBER_ISREGISTER(10006, "短信发送失败,该手机号已注册..."),
    REGISTER_PHONENUMBER_EXIST(10007, "该手机号已注册,注册失败..."),

    LOGIN_PASSWORD_ERROR(10008, "用户名或密码输入错误,登录失败..."),
    LOGIN_NOREGISTER_ERROR(10009, "手机号未注册,登录失败..."),
    ;

    /**
     * code
     */
    private int code;

    /**
     * message
     */
    private String message;


    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    ServiceErrorEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }
}

redis缓存util工具类封装

/**
 * spring redis 工具类
 *
 * @author zxt
 **/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
@Slf4j
public class RedisCache
{
    @Resource
    public RedisTemplate redisTemplate;

    private static final Long SUCCESS = 1L;
    private static final Integer DEFAULT_EXPIRE_TIME = 30 * 60;

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     * @param timeout 时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }
    
    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 判断 key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public Boolean hasKey(String key)
    {
        return redisTemplate.hasKey(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }

}

service验证码发送接口定义和实现代码

接口定义

package com.tiz.third.sevice;

import com.baomidou.mybatisplus.extension.service.IService;
import com.tiz.third.pojo.User;

import java.util.Map;

/**
 * @author zxt
 * @date 2024-05-24 17:57
 * @describe 注册登录接口业务
 */
public interface ModelService{
    /**
     * 发送验证码
     * @param phoneNumber
     * @return
     */
    public String sendVerificationCode(String phoneNumber);

	/**
     * 注册
     * @param user
     * @return
     */
     //.....更多接口实现已略
}

接口实现类

/**
 * @author zxt
 * @date 2024-05-24 18:01
 * @describe
 */
@Slf4j
@Service
public class ModelServiceImpl implements ModelService {
    @Resource
    private RedisCache redisCache;

    @Override
    public String sendVerificationCode(String phoneNumber) {
        // 模拟阿里云发送短信业务, 模板示例值。 可自行对接
        String sendMsg = "【提先森的小站】测试阿里云服务短信发送,此次操作验证码为 {} ,该验证码有效期2分钟,请尽快输入验证.";

        synchronized (this) {
            boolean phoneIsValid = Validator.isMobile(phoneNumber);
            if (!phoneIsValid) {
                log.error("请确认发送验证码手机号格式是否正确");
                return ServiceErrorEnum.REGISTER_PHONENUMBER_ERROR.getMessage();
            }

            //手机验证码key
            String msgKey = StringUtils.format(RedisKeyConstant.USER_REGISTER_KEY, phoneNumber);
            //手机验证码value
            String msgValue = StringUtils.format(sendMsg, RandomUtil.randomNumbers(4));

            //判断手机验证码key是否存在
            Boolean registerExists = redisCache.hasKey(msgKey);
            if (registerExists) {
                log.error("验证码在有效期内,2分钟内请勿重复发送!");
                return ServiceErrorEnum.REGISTER_RECAPTCHA_ISVALID.getMessage();
            }

            //手机验证码1天内获取次数
            String countKey = StringUtils.format(RedisKeyConstant.USER_REGISTER_COUNT_KEY, phoneNumber);
            //判断验证码1天发送次数key是否存在
            Boolean cacheDayCount = redisCache.hasKey(countKey);

            if (cacheDayCount) {
                Integer count = redisCache.getCacheObject(countKey);
                //发送次数值控制
                Integer newCacheCount = count + 1;
                if (newCacheCount > 3) {
                    log.error("手机号:{} 注册验证码24小时内已发送3次,发送验证码失败,请于24小时后重试!", phoneNumber);
                    return ServiceErrorEnum.REGISTER_CODE_COUNT_ERROR.getMessage();

                }
                log.error("手机号:{} 注册验证码发送成功, 24小时内已发送{}次", phoneNumber, newCacheCount);

                //缓存手机验证码1天发送次数值+1
                redisCache.setCacheObject(countKey, newCacheCount);
            } else {
                //缓存手机验证码1天发送次数值 默认1
                redisCache.setCacheObject(countKey, 1, 1, TimeUnit.DAYS);
                log.error("手机号:{} 注册验证码发送成功, 24小时内已发送{}次", phoneNumber, 1);
            }

            //缓存手机验证码2分钟 key+value
            redisCache.setCacheObject(msgKey, msgValue, 2, TimeUnit.MINUTES);

            return "send success";
        }
    }
}

controller业务接口请求代码

/**
 * @author zxt
 * @date 2024-05-24 18:03
 * @describe
 */
@RequestMapping("/model")
@RestController
@Slf4j
public class ModelController {
    @Resource
    private ModelService modelService;

    /**
     * 发送手机验证码
     * @param phoneNumber
     * @return
     */
    @GetMapping("/send/{phoneNumber}" )
    public AjaxResult sendVerificationCode(@NotBlank(message = "手机号码不能为空") @PathVariable  String phoneNumber) {
        // 可自己代码定义返回值类型,业务逻辑返回string 此处自己处理返回相对应返回类型
        return AjaxResult.success(modelService.sendVerificationCode(phoneNumber));
    }
}

接口请求测试

请求

get请求
localhost:xx/model/send/{手机号}

响应

{
  "msg": "24小时内已发送验证码3次,发送已限制 请于1天后重试...",
  "code": 200,
  "data": null,
  "currentTimeStamp": 1716779182241
}

在这里插入图片描述

结尾

该功能 可用于 > 注册、登录 等各种需要发送手机验证码场景,可防盗刷验证码限流,去redis中拿取手机号对应缓存,判断验证码是否正确,去执行一系列的业务操作。

如对您有帮助,点个赞支持一下,感谢支持~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1701214.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

精通C++ STL(二):string类的模拟实现

目录 string类各函数接口总览 默认成员函数 构造函数 拷贝构造函数 赋值运算符重载函数 析构函数 迭代器相关函数 begin和end 容量和大小相关函数 size和capacity reserve和resize empty 修改字符串相关函数 push_back append operator insert erase clear swap c_str 访…

spring boot 之 结合aop整合日志

AOP 该切面仅用于请求日志记录&#xff0c;若有其他需求&#xff0c;在此基础上扩展即可&#xff0c;不多逼逼&#xff0c;直接上代码。 引入切面依赖 <!-- 切面 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>sp…

云主机选购指南:如何选择适合自己的云主机

一、认识移动云 移动云是中国移动提供的专业云服务品牌&#xff0c;基于移动云计算技术构建。它实现了云网一体化&#xff0c;确保客户享有安全可控的服务。通过充分利用移动云计算能力&#xff0c;打造了N31X资源布局&#xff0c;结合各省级数据中心&#xff0c;通过专线互联…

基于Vue的前端加载中页面动画——弹跳动画Loading组件的设计与实现

基于Vue的前端加载中页面动画——弹跳动画Loading组件的设计与实现 摘要 随着技术的飞速进步&#xff0c;前端开发的复杂性日益提升。传统的开发方式通常将整个系统构建为一个整体&#xff0c;导致即使是微小的改动或功能的增加也可能引起整体逻辑的变动。为了解决这个问题&a…

【蓝桥杯】国赛普及-

题目列表 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) P9420 [蓝桥杯 2023 国 B] 子 2023 / 双子数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include<bits/stdc.h> using llunsigned long long; #define int ll const int N2e510; int k0; std::string s; int…

嵌入式UI开发-lvgl+wsl2+vscode系列:1、资料收集以及Windows下WSL2模拟环境运行示例demo

文章目录 一、前言二、资料收集三、Windows下WSL2上编译运行lvgl的demo程序1、lvgl简介2、lvgl特性3、配置要求4、Windows下vscodewsl2模拟环境搭建4.1、安装vscodewsl24.2、下载获取项目&#xff1a;4.3、安装显卡驱动4.4、下载lvgl并编译运行示例demo 四、最后 一、前言 UI界…

Python数据分析-心脏病(随机森林预测分析)

本次案例分析用心脏病数据集来做随机森林模型预测 导入基本的数据分析包 import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score…

云计算-关系型数据库(Relational Database)

关系数据库服务&#xff08;RDS&#xff09;&#xff08;Relational Database Service (RDS)&#xff09; Amazon RDS 可用于在云中设置和运行关系数据库。它支持多种数据库实例类型以及多个数据库引擎&#xff0c;如 Amazon Aurora、PostgreSQL、MySQL、MariaDB、Oracle 数据库…

基于Vue的应届毕业生财务管理系统-计算机毕业设计源码82886

摘 要 随着互联网大趋势的到来&#xff0c;社会的方方面面&#xff0c;各行各业都在考虑利用互联网作为媒介将自己的信息更及时有效地推广出去&#xff0c;而其中最好的方式就是建立网络管理系统&#xff0c;并对其进行信息管理。由于现在网络的发达&#xff0c;应届毕业生财务…

c# sqlite使用

安装包 使用 const string strconn "Data Sourcedata.db"; using (SQLiteConnection conn new SQLiteConnection(strconn)) {conn.Open();var cmd conn.CreateCommand();//创建表cmd.CommandText "create table t1(id int,name varchar(10))";var obj…

二百三十八、Hive——Hive中为每条数据创建唯一ID

一、目的 由于Kafka的JSON中缺少唯一的ID标识字段&#xff0c;因此发现后面的需求中DWD层表需要有一个唯一ID字段&#xff0c;这样才能与数据质量表更好的关联 二、Hive版本 尚硅谷的3.1.2版本 三、Hive创建唯一ID方法 网上的创建唯一ID方法有很多&#xff0c;这里展示一些…

子网划分,交换机原理与配置

子网划分 IP地址 IPv4由32位二进制数组成&#xff0c;一般用点分十进制来表示 IPv4是由32位二进制数组成&#xff0c;分成四组,第组八位。例如:11000000.10101000.00000000.00000010 为了便于配置通常表示成点分十进制形式例如:192.168.0.2 255.255.255.0 IPv6由128位组成&…

HDR视频相关标准-HDR vivid(二)

上文介绍了HDRvivid的一些技术。今天从全局角度来看看HDR视频的处理流程&#xff0c;HDR视频系统&#xff0c;即建立一个比SDR视频更大的色彩/亮度坐标体系&#xff0c;并改变系统的传输函数&#xff0c;以再现更大的色域(WCG)和更高的亮度动态范围。 菁彩 HDR技术的专业术语 …

充电宝哪个牌子好用?充电宝品牌怎么选?充电宝最好的牌子排名

现在市面上的充电宝品牌琳琅满目&#xff0c;但并不是所有的充电宝都安全可靠。据央视的一个报道&#xff0c;市面上有35%充电宝质量是不过关的!充电宝买不对就非常容易出现爆炸的一个情况&#xff0c;所以大家对选充电宝不仅能保障设备的安全。那么&#xff0c;充电宝哪个牌子…

TypeScript学习日志-第三十二天(infer关键字)

infer关键字 一、作用与使用 infer 的作用就是推导泛型参数&#xff0c;infer 声明只能出现在 extends 子语句中&#xff0c;使用如下&#xff1a; 可以看出 已经推导出类型是 User 了 二、协变 infer 的 协变会返回联合类型&#xff0c;如图&#xff1a; 三、逆变 infer…

FusionCharts 隐藏试用图标

1、找到fusioncharts.js文件 2、搜索“raphael-group-” 3、找到此处进行替换黄线部分 将&#xff1a;"a.setAttribute("class","raphael-group-"t)" 替换成"(a.setAttribute("class","raphael-group-"t),a.setAttr…

基于 Wireshark 分析 TCP 协议

一、TCP 协议 TCP&#xff08;Transmission Control Protocol&#xff09;是一种面向连接的、可靠的传输层协议。它在网络通信中扮演着重要的角色&#xff0c;用于保证数据的可靠传输。 TCP协议的特点如下&#xff1a; 1. 面向连接&#xff1a;在通信前需要先建立连接&#x…

学习Uni-app开发小程序Day26

这一章学习的内容细节较多&#xff0c;主要是分为&#xff1a;首次加载减少网络消耗、获取图片的详细信息、图片的评分和避免重复评分、将图片下载到本地并且获取设备的授权 加载图片减少网络消耗 这里突出这个功能&#xff0c;是根据老师视频上的描述&#xff0c;个人觉得很…

如何彻底搞懂组合(Composite)设计模式?

当我们在设计系统对象关系时&#xff0c;有时候会碰到这样一种场景&#xff0c;一个对象中包含了另一组对象&#xff0c;两者构成一种”部分-整体”的关联关系。 正如上图中所展示的&#xff0c;当我们面对这样一种对象关系时&#xff0c;通常都需要分别构建单独的访问方式&…

11.Redis之zset类型

1.zset类型基本介绍 有序描述的是&#xff1a;升序/降序 Set 集合 1.唯一 2. 无序 孙行者,行者孙, 者行孙 >同一只猴~~ List有序的 孙行者,行者孙, 者行孙 >不同的猴~~ zset 中的 member 仍然要求是唯一的!!(score 则可以重复) 排序的规则是啥? 给 zset 中的 member 同…