防刷发送短信验证码接口的五种简单好用方法,绝对够用
-
前端增加图形验证码,点击发送按钮后增加60s倒计时,60s后才可以再次点击
-
后端对接口次数校验,60s内同一电话号码只能发送一次
// 生成基于电话号码的重试锁定键 String repeatLock = StringUtils.join("send_sms:", mobile); // 使用 Redis 的 setIfAbsent 进行原子性操作,尝试设置键值对,如果键已存在则返回 false if (!redisTemplate.opsForValue().setIfAbsent(repeatLock, "locked", 60, TimeUnit.SECONDS)) { // 键已存在,表示1分钟内已经发送过短信 LOGGER.error("60s内不能重复调用发送短信功能: {}", mobile); throw new RuntimeException("调用发送验证码过于频繁,请稍后再试!"); }
-
后端增加每天次数校验,每个电话号码每天只能发送50次
private static final int MAX_DAILY_SMS = 50; // 每日最大短信发送数量 // 获取当前日期作为短信发送统计的键 String dailySmsKey = getDailySmsCountKey(LocalDate.now(),mobile); // 检查每日短信发送数量是否超出限制 long currentDaySmsCount = redisTemplate.opsForValue().increment(dailySmsKey,1); //设置过期时间为当天23:59:59 LocalDateTime todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.of(23, 59, 59)); long secondsUntilMidnight = ChronoUnit.SECONDS.between(LocalDateTime.now(), todayEnd); redisTemplate.expire(dailySmsKey, secondsUntilMidnight, TimeUnit.SECONDS); if (currentDaySmsCount > MAX_DAILY_SMS) { LOGGER.error("今日短信发送已达上限,无法再发送给 {}", mobile); throw new RuntimeException("当日短信发送已达上限,无法再发送,请明日再试!"); } private String getDailySmsCountKey(LocalDate date,String mobile) { return "sms_daily_count:" +mobile + date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); }
-
对每日超过50次的ip记录下来,也可以加入黑名单,下次判断请求ip为黑名单中的ip则直接返回失败
-
前后端增加一个对称加密的校验码
准备:密钥 1234abcd;约定明文中必须包含指定字符串miyao1234
1.前端调用发送短信接口前,通过密钥(1234abcd)与加密算法,将miyao1234+(16位英文字母与数字字符串) 例如miyao1234wgly1noKSXg47Mn6 进行加密
生成校验码 vBOLxJj3wl1IyJNUcXOPvaeXvgLZK0b4f3D4J6k9DvE=
2.前端调用发送短信接口时,将校验码传递给后端
3.后端接收到后对校验码进行解密,如果解密失败,提示失败
4.如果解密后不包含约定字符串miyao1234 ,提示失败
5.如果解密后包含约定字符串miyao1234,则通过校验,此时注意⚠️,需要将此校验码存入redis,下次如果有相同校验码 则提示重复
/** * 解密后的验证码必须包含此值 */ public static final String ENCRYPT_KEY_MAIN_WORD = "miyao1234"; //密钥1234abcd public static final String encryptKey = "1234abcd"; Validate.notBlank(keyDecrypt,"发送短信时,校验码不能为空"); //进行解密 String decrypt = this.decrypt(keyDecrypt); LOGGER.error("发送验证码解密后的校验码"+decrypt); if(StringUtils.isEmpty(decrypt) || !decrypt.contains(ENCRYPT_KEY_MAIN_WORD)){ throw new RuntimeException("发送验证码校验失败"); } //设置过期时间为当天23:59:59 // 每个校验码每天只能重复使用一次 String repeatLock = StringUtils.join("decrypt_send_sms:", decrypt); LocalDateTime todayEnd = LocalDateTime.of(LocalDate.now(), LocalTime.of(23, 59, 59)); long secondsUntilMidnight = ChronoUnit.SECONDS.between(LocalDateTime.now(), todayEnd); // 使用 Redis 的 setIfAbsent 进行原子性操作,尝试设置键值对,如果键已存在则返回 false if (!redisTemplate.opsForValue().setIfAbsent(repeatLock, "locked", secondsUntilMidnight, TimeUnit.SECONDS)) { // 键已存在,表示校验码重复 throw new RuntimeException("校验码重复!"); } public String decrypt(String decrypt) { if (StringUtils.isBlank(decrypt)) { return decrypt; } return Aes128Utils.decrypt(decrypt, encryptKey, Aes128Utils.EncodeType.CBC, Aes128Utils.Padding.PKCS_7_PADDING); }