[Redis] 使用Redis限制短信发送次数和发送频率

本文介绍如何利用Redis的数据结构和时间戳来实现用户发送短信的频率限制,包括一小时内5次和一天内10次的计数器,以及防止发送过于频繁的检查机制。通过Spring Boot集成Redis,展示了关键代码片段和数据模型的设计。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

🔖 简介

ℹ️ 使用Redis实现比如 一小时内允许用户发送5次短信,一天内允许用户发送10条短信的需求,改需要怎么做呢

在这里插入图片描述

🎯 方法 / 步骤

☝️ 方法一:使用redis : 格式

appc_verifyCode_20201118_13312341234 :
{
"verifyCode": 1234,
"sentTimes":6,
"sendDateTime":[
	"12:00:01 01:01:01",
	"12:00:01 01:01:01",
	"12:00:01 01:01:01",
	"12:00:01 01:01:01",
	"12:00:01 01:01:01",
	"12:00:01 01:01:01"
	]
}
  • Redis 数据模型
    在这里插入图片描述

  • 关键代码

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * Description:
 *
 * @Author: Yanggc
 * DateTime: 11/17 11:04
 */
@Service
public class SmsServiceImpl implements SmsService {

    @Autowired
    RedisTemplate redisTemplate;

    LocalDateTime now = LocalDateTime.now();
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    String nowDateTime = dateTimeFormatter.format(now);


    @Override
    public String getVerifyCode(String phoneNumber) {
        String cacheKey = "appc_verifyCode_"+DateTimeFormatter.ofPattern("yyyyMMdd").format(now)+"_"+phoneNumber;

        if(redisTemplate.hasKey(cacheKey)){
            Map<String,Object> cacheMap = (Map<String, Object>) redisTemplate.opsForValue().get(cacheKey);
            Integer sentTimes = (Integer) cacheMap.get("sentTimes");
            List<String> sendTimeList = (List<String>) cacheMap.get("sendTimeList");


            int sendTimeListSize = sendTimeList.size();

            /**
             *60S发送一次:最后一次发送时间距离现在不超过一分钟,返回发送次数太快
             */
            LocalDateTime lastSendTime = LocalDateTime.parse(sendTimeList.get(sendTimeListSize-1),dateTimeFormatter);
            if(lastSendTime.plusSeconds(60).isAfter(now)){
                return "overRate";
            }

            /**
             * 一小时最多发送5次,如果超过5次,取最后5次的“第一次”,判断时间距离现在是否超过一小时,如果超过一小时,提示过一个小时候再发送
             */

            if(sendTimeList.size()>=5){
                String lastFiveTimesFirst = sendTimeList.get(sendTimeListSize - 5);
                LocalDateTime lastFiveTimesFirstTime = LocalDateTime.parse(lastFiveTimesFirst,dateTimeFormatter);
                if(lastFiveTimesFirstTime.plusHours(1).isAfter(now)){
                    return "overRateOneHour";
                }
            }

            //一天发送10次
            if(sentTimes > 10){
                return "timesExceededOneDay";
            }

            /**
             * 更新cache操作:超过一分钟,并且是合法请求,重新生成验证码
             */
            String verifyCode = generateVerifyCode();

            Map<String, Object> resultCacheMap = new HashMap<>();
            resultCacheMap.put("sentTimes",sentTimes+1);
            resultCacheMap.put("verifyCode",verifyCode);

            sendTimeList.add(nowDateTime);
            resultCacheMap.put("sendTimeList",sendTimeList);
            redisTemplate.boundValueOps(cacheKey).set(resultCacheMap,24,TimeUnit.HOURS);

            return verifyCode;
        }


        dateTimeFormatter.format(now);

        Map<String, Object> verifyCodeMap = new HashMap<>();

        String verifyCode = generateVerifyCode();

        verifyCodeMap.put("verifyCode",verifyCode);
        verifyCodeMap.put("sentTimes",1);
        List<String> dateTimeList = new ArrayList<>();
        dateTimeList.add(nowDateTime);
        verifyCodeMap.put("sendTimeList",dateTimeList);

        redisTemplate.boundValueOps(cacheKey).set(verifyCodeMap,24,TimeUnit.HOURS);
        return verifyCode;
    }


    public String generateVerifyCode(){
        Integer randNum = (int) (Math.random() * (9999) + 1);
        String verifyCode = String.format("%04d", randNum);
        return verifyCode;
    }

✌️ 方法二:

2.1 编写Redis工具类

编写一个工具类,用于与Redis进行交互。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class RedisUtil {

    @Autowired
    private StringRedisTemplate redisTemplate;

    public boolean setIfAbsent(String key, String value, long timeout, TimeUnit unit) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
    }

    public void set(String key, String value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    public String get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    public void increment(String key, long value) {
        redisTemplate.opsForValue().increment(key, value);
    }

    public Long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }
}

2.2 编写短信发送限制逻辑

实现短信发送次数和频率限制逻辑。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class SmsService {

    private static final String SMS_COUNT_KEY = "sms:count:";
    private static final String SMS_RATE_LIMIT_KEY = "sms:rate:";

    @Autowired
    private RedisUtil redisUtil;

    public boolean sendSms(String phoneNumber, String message) {
        String countKey = SMS_COUNT_KEY + phoneNumber;
        String rateKey = SMS_RATE_LIMIT_KEY + phoneNumber;

        // 检查发送频率
        if (redisUtil.get(rateKey) != null) {
            // 发送频率过快
            return false;
        }

        // 增加发送次数
        redisUtil.increment(countKey, 1);
        
        // 设置过期时间为24小时(或其他时间)
        if (redisUtil.getExpire(countKey) == -1) {
            redisUtil.set(countKey, "1", 24, TimeUnit.HOURS);
        }

        // 获取发送次数
        String countStr = redisUtil.get(countKey);
        if (countStr != null && Integer.parseInt(countStr) > 10) {  // 限制为每天最多发送10条短信
            // 发送次数超过限制
            return false;
        }

        // 设置发送频率限制
        redisUtil.set(rateKey, "1", 1, TimeUnit.MINUTES);  // 限制为每分钟只能发送一条短信

        // 发送短信逻辑...
        // sendSmsLogic(phoneNumber, message);

        return true;
    }
}

2.3 使用示例

在控制器或其他业务逻辑中调用SmsService发送短信。

@RestController
@RequestMapping("/sms")
public class SmsController {
@Autowired
private SmsService smsService;

@PostMapping("/send")
public ResponseEntity<String> sendSms(@RequestParam String phoneNumber, @RequestParam String message) {
    boolean result = smsService.sendSms(phoneNumber, message);
    if (result) {
        return ResponseEntity.ok("短信发送成功");
    } else {
        return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("短信发送频率过高或次数超过限制");
    }
}

}

📚 参考资料 & 致谢

<think>嗯,用户让我介绍一下如何用RedisSpring Boot实现短信验证码登录。首先,我需要回忆一下短信验证码登录的基本流程。通常,用户输入手机号,后端生成验证码,保存起来,然后通过短信服务商发送给用户。用户收到后输入验证码,后端再验证是否正确。这里的关键点是验证码的生成、存储、验证以及有效期管理。 那Redis在这里的作用是什么呢?应该是用来临时存储验证码限制发送频率的。因为Redis有过期时间,适合用来做这种临时数据的存储,而且读写速度快,适合高并发场景。比如,用户请求验证码时,把手机号验证码存入Redis,设置5分钟过期。然后当用户登录时,从Redis取出验证码比对。 接下来要考虑Spring Boot如何整合Redis。首先,项目需要引入Spring Data Redis的依赖,然后在配置文件中设置Redis的连接信息,比如主机地址、端口、密码等。可能还需要配置RedisTemplate,方便操作Redis的数据结构。 然后是具体的实现步骤。用户请求发送验证码的接口,后端生成随机数,比如4位或6位数字,存入Redis,key可以是“sms:code:手机号”,value是验证码,设置过期时间。同时要考虑防止频繁请求,比如同一个手机号60秒内只能发送一次,这可以通过Redis设置一个带有过期时间的key来实现,比如“sms:code:limit:手机号”,过期时间60秒,每次请求前检查是否存在这个key,存在的话就拒绝发送。 接下来是发送短信的部分,可能需要集成第三方短信服务,比如阿里云或者腾讯云的SDK。这部分需要调用他们的API,把验证码手机号传过去。不过在实际开发中,可能为了测试方便,先不真正发送短信,而是打印到日志里。 然后是登录验证的接口。用户提交手机号验证码,后端从Redis中取出之前存储的验证码,比较是否一致。如果一致,就生成登录凭证(比如JWT token),并返回给用户,同时删除Redis中的验证码,防止重复使用。如果验证失败,返回错误信息。 还需要考虑一些安全性用户体验的问题。比如,验证码的有效期不要太长,通常5分钟比较合适。防止暴力破解,可以限制验证码的尝试次数,比如一个验证码最多尝试3次,超过次数就失效,需要重新获取。这也可以通过Redis的计数器来实现,每次尝试失败就增加计数,超过阈值就拒绝验证。 另外,可能要考虑接口的防刷机制,比如同一个IP在短时间内频繁请求发送验证码,应该进行限制,防止被恶意攻击。这可以用Redis的计数器或者使用Spring Security的限流功能,或者借助第三方库如Guava的RateLimiter。 总结一下,实现步骤大致如下: 1. 用户输入手机号,请求发送验证码。 2. 后端校验手机号格式,检查是否频繁请求。 3. 生成验证码,保存到Redis,设置过期时间。 4. 调用短信服务发送验证码(或模拟发送)。 5. 用户输入验证码,提交登录。 6. 后端从Redis取出验证码进行比对,成功则生成登录凭证,失败则返回错误。 7. 清理已使用的验证码,防止重复使用。 在代码层面,Spring Boot中需要创建对应的Controller处理这两个接口:发送验证码登录验证。Service层处理生成验证码、存储Redis、调用短信服务等逻辑。可能还需要用到工具类来生成随机验证码,处理Redis的操作。 可能遇到的问题包括Redis连接失败、短信服务商接口调用异常、验证码被恶意刷取等。需要做好异常处理,比如Redis操作失败时返回错误信息,短信发送失败时进行重试或记录日志。安全性方面,除了频率限制,还可以对验证码进行加密存储,不过Redis本身在内部使用,可能不需要加密,但需要考虑Redis的安全配置,防止未授权访问。 测试的时候,可以用Postman或者单元测试来模拟请求,检查Redis中的数据是否正确存储过期,验证码是否正确匹配等。同时需要测试各种边界情况,比如验证码过期、错误验证码、重复提交等。 总的来说,结合RedisSpring Boot实现短信验证码登录是一个常见的场景,关键在于合理利用Redis的特性来管理验证码的生命周期访问频率,同时确保整个流程的安全性用户体验。</think>使用 Redis Spring Boot 实现短信验证码登录是一种常见的高效解决方案,以下是核心步骤代码示例: --- ### **1. 技术实现原理** - **Redis 作用**:存储手机号与验证码的映射关系,并利用过期时间实现自动清理。 - **流程**: 1. 用户请求短信验证码,后端生成并存储到 Redis(设置有效期)。 2. 短信服务商发送验证码到用户手机。 3. 用户提交手机号验证码,后端从 Redis 校验匹配性。 4. 验证成功后执行登录逻辑(如生成 Token)。 --- ### **2. 核心实现步骤** #### **(1) 添加依赖** ```xml <!-- Spring Boot Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> ``` #### **(2) 配置 Redis** ```yaml # application.yml spring: redis: host: localhost port: 6379 password: timeout: 5000 ``` --- #### **(3) 生成并发送验证码** ```java @Service public class SmsService { @Autowired private RedisTemplate<String, String> redisTemplate; // 生成随机6位验证码 private String generateCode() { return String.valueOf((int) ((Math.random() * 9 + 1) * 100000)); } public void sendCode(String phone) { // 1. 校验手机号格式 if (!isValidPhone(phone)) throw new RuntimeException("Invalid phone number"); // 2. 防刷机制:60秒内不能重复发送 String limitKey = "sms:limit:" + phone; if (Boolean.TRUE.equals(redisTemplate.hasKey(limitKey))) { throw new RuntimeException("请求过于频繁"); } // 3. 生成并存储验证码(5分钟过期) String code = generateCode(); String redisKey = "sms:code:" + phone; redisTemplate.opsForValue().set(redisKey, code, 5, TimeUnit.MINUTES); // 4. 设置防刷限制(60秒) redisTemplate.opsForValue().set(limitKey, "1", 60, TimeUnit.SECONDS); // 5. 调用短信服务(此处模拟日志输出) System.out.println("发送短信验证码:" + phone + " -> " + code); } } ``` --- #### **(4) 验证码登录校验** ```java @RestController public class LoginController { @Autowired private SmsService smsService; @Autowired private RedisTemplate<String, String> redisTemplate; @PostMapping("/sendCode") public ResponseEntity<String> sendCode(@RequestParam String phone) { smsService.sendCode(phone); return ResponseEntity.ok("验证码已发送"); } @PostMapping("/loginBySms") public ResponseEntity<String> loginBySms(@RequestParam String phone, @RequestParam String code) { String redisKey = "sms:code:" + phone; String storedCode = redisTemplate.opsForValue().get(redisKey); if (code.equals(storedCode)) { // 验证成功:执行登录逻辑(如生成JWT Token) redisTemplate.delete(redisKey); // 清除已用验证码 return ResponseEntity.ok("登录成功"); } else { return ResponseEntity.status(401).body("验证码错误"); } } } ``` --- ### **3. 关键优化点** 1. **防刷机制**: - 使用 Redis 记录发送频率(如 `sms:limit:手机号`)。 - 限制同一 IP 的请求次数。 2. **安全性**: - 验证码建议使用数字+字母混合(本示例为简化使用纯数字)。 - 重要操作需二次验证(如修改密码)。 3. **异常处理**: - 处理短信服务商 API 调用失败的重试机制。 - Redis 连接超时异常捕获。 --- ### **4. 扩展场景** - **分布式场景**:Redis 天然支持分布式,适合微服务架构。 - **验证码模板**:可配置不同业务场景的模板(如登录、注册、支付)。 通过这种方式,可以快速实现安全、高并发的短信验证码登录功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

OxYGC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值