【已解决】Java 项目中利用 Redis 配合 Lua 脚本对短信推送消息做推送限制

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 【已解决】Java 项目中利用 Redis 配合 Lua 脚本对短信推送消息做推送限制

🎉场景介绍:在实际应用场景中,我们通常需要对短信推送消息做发送限制的处理,避免一定时间间隔内发送过多相同内容的推送短信对用户造成骚扰,也要防止攻击者恶意调用短信推送接口造成短信资源的极大浪费。尽管部分三方接口提供方的原生接口就已经做了限制,但是为了能够更好地满足定制化需求以及在编码过程中的疏忽造成接口重复调用导致资源浪费,因此我们需要在项目中做短信推送限制的处理。

💡方案设计:

首先,将需要推送的短信内容以及接收短信的手机号进行拼接成新的字符串,并对该字符串进行加密处理转换成16进制字符串,作为存入到 redis 中的 key.

其次,执行 Lua 脚本,该脚本用于在 redis 中存储具有过期时间的 key,并返回重复校验的结果。

最后,获取校验结果,根据校验结果进行相应的处理操作,如果是重复发送的短信就将该接口和手机号加入数据埋点,用于监控推送限制情况,并抛出重复发送的异常;否则,正常发送短信消息。

在对项目中的业务代码做了抽离后,只保留了和方案设计相关的代码,如下所示

 public class SmsService {
  private static Logger logger = LoggerFactory.getLogger(SmsService.class);
  @Autowired
    private JedisTemplate jedisTemplate;
  @Autowired
  private MeterRegistry meterRegistry;
    private static final String LIMIT_LUA = "local num = redis.call('incr', KEYS[1])\n" +
            "if num == 1 then\n" +
            "      redis.call('expire', KEYS[1], ARGV[1])\n" +
            "end\n" +
            "if num > tonumber(ARGV[2]) then\n" +
            "      return 0\n" +
            "end\n" +
            "return 1";
  /**
     * 发送短信
     * @param sendSmsRequest 发送短信请求信息
     * @return 
     */
    @Transactional
    public String sendSms(SendSmsRequest sendSmsRequest) {
      String key = sendSmsRequest.getPhoneNumber() + sendSmsRequest.getTemplateCode() + sendSmsRequest.getTemplateParam();
    key = DigestUtils.md5Hex(key);
        // 60秒内同一手机号相同内容只允许发送两次
        boolean limitFlag = checkIsLimited(key, 60L, 2L);
        if (limitFlag) {
            logger.warn("短信推送被限制,sendSmsRequest:{}", JsonTool.serialize(sendSmsRequest));
            // 加入数据埋点,用于监控超出推送限制的情况
            meterRegistry.counter("sms.repeat.limit.count", "/test/sms", uri, sendSmsRequest.getPhoneNumber(), user).increment();
            throw new ServiceException("短信推送超出限制");
        }
    // 推送短信,次数省略100w行代码...
     }
    /**
     * 推送频率限制,seconds内只允许limitNum次
     * @param key key
     * @param seconds 时间 秒
     * @param limitNum 限制次数
     * @return true:限制 false:不限制
     */
    public boolean checkIsLimited(String key, long seconds, long limitNum) {
        try {
            Long result = (Long) jedisTemplate.eval(LIMIT_LUA, CoreTool.array2List(key), CoreTool.array2List(String.valueOf(seconds), String.valueOf(limitNum)));
            return result == 0;
        } catch (Exception e) {
            logger.warn("校验是否需要推送限制异常", e);
        }
          return false;
      }
  }
}

其中,对上述 Lua 脚本功能具体的注释如下:

local num = redis.call('incr', KEYS[1])  -- 计数加1(第一次调用时,key不存在,默认会创建并赋值1)
if num == 1 then                         -- 第一次调用
  redis.call('expire', KEYS[1], ARGV[1])  -- 对key设置过期时间
end
if num > tonumber(ARGV[2]) then          -- 非第一次访问,判断是否在过期时间内大于限定的访问次数
  return 0
end
return 1


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
8天前
|
NoSQL Java API
springboot项目Redis统计在线用户
通过本文的介绍,您可以在Spring Boot项目中使用Redis实现在线用户统计。通过合理配置Redis和实现用户登录、注销及统计逻辑,您可以高效地管理在线用户。希望本文的详细解释和代码示例能帮助您在实际项目中成功应用这一技术。
18 3
|
9天前
|
存储 消息中间件 NoSQL
使用Java操作Redis数据类型的详解指南
通过使用Jedis库,可以在Java中方便地操作Redis的各种数据类型。本文详细介绍了字符串、哈希、列表、集合和有序集合的基本操作及其对应的Java实现。这些示例展示了如何使用Java与Redis进行交互,为开发高效的Redis客户端应用程序提供了基础。希望本文的指南能帮助您更好地理解和使用Redis,提升应用程序的性能和可靠性。
23 1
|
1月前
|
缓存 NoSQL Java
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
59 3
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
|
1月前
|
消息中间件 NoSQL Kafka
Flink-10 Flink Java 3分钟上手 Docker容器化部署 JobManager TaskManager Kafka Redis Dockerfile docker-compose
Flink-10 Flink Java 3分钟上手 Docker容器化部署 JobManager TaskManager Kafka Redis Dockerfile docker-compose
38 4
|
1月前
|
缓存 NoSQL Java
Java中redis面试题
Java中redis面试题
34 1
|
21天前
|
存储 NoSQL Java
Java 使用 Redis
10月更文挑战第22天
26 0
|
1月前
|
NoSQL Java API
Java操作redis
Java操作redis
|
9天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
6天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
25 9