refactor: 发送短信验证码新增限流处理

This commit is contained in:
Charles7c 2023-12-18 21:47:39 +08:00
parent 57b2cc5d2c
commit e719d207fb
4 changed files with 25 additions and 15 deletions

View File

@ -94,11 +94,6 @@ public class CaptchaProperties {
*/ */
private long expirationInMinutes; private long expirationInMinutes;
/**
* 限制时间
*/
private long limitInSeconds;
/** /**
* 模板 ID * 模板 ID
*/ */

View File

@ -21,6 +21,7 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import jakarta.mail.MessagingException; import jakarta.mail.MessagingException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
@ -33,6 +34,7 @@ import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse; import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.comm.constant.SupplierConstant; import org.dromara.sms4j.comm.constant.SupplierConstant;
import org.dromara.sms4j.core.factory.SmsFactory; import org.dromara.sms4j.core.factory.SmsFactory;
import org.redisson.api.RateType;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -45,6 +47,7 @@ import cn.hutool.core.lang.Dict;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.servlet.JakartaServletUtil;
import top.charles7c.continew.admin.common.config.properties.CaptchaProperties; import top.charles7c.continew.admin.common.config.properties.CaptchaProperties;
import top.charles7c.continew.admin.common.constant.CacheConstants; import top.charles7c.continew.admin.common.constant.CacheConstants;
@ -114,14 +117,31 @@ public class CaptchaController {
@Operation(summary = "获取短信验证码", description = "发送验证码到指定手机号") @Operation(summary = "获取短信验证码", description = "发送验证码到指定手机号")
@GetMapping("/sms") @GetMapping("/sms")
public R getSmsCaptcha( public R getSmsCaptcha(
@NotBlank(message = "手机号不能为空") @Pattern(regexp = RegexConstants.MOBILE, message = "手机号格式错误") String phone) { @NotBlank(message = "手机号不能为空") @Pattern(regexp = RegexConstants.MOBILE, message = "手机号格式错误") String phone,
HttpServletRequest request) {
CaptchaProperties.CaptchaSms captchaSms = captchaProperties.getSms();
String templateId = captchaSms.getTemplateId();
String limitKeyPrefix = CacheConstants.LIMIT_KEY_PREFIX; String limitKeyPrefix = CacheConstants.LIMIT_KEY_PREFIX;
String captchaKeyPrefix = CacheConstants.CAPTCHA_KEY_PREFIX; String captchaKeyPrefix = CacheConstants.CAPTCHA_KEY_PREFIX;
String limitCaptchaKey = RedisUtils.formatKey(limitKeyPrefix, captchaKeyPrefix, phone); String limitTemplateKeyPrefix = RedisUtils.formatKey(limitKeyPrefix, captchaKeyPrefix);
long limitTimeInMillisecond = RedisUtils.getTimeToLive(limitCaptchaKey); // 限制短信发送频率
CheckUtils.throwIf(limitTimeInMillisecond > 0, "发送验证码过于频繁,请您 {}s 后再试", limitTimeInMillisecond / 1000); // 1.同一号码同一短信模板1分钟2条1小时8条24小时20条e.g. LIMIT:CAPTCHA:XXX:188xxxxx:1
CheckUtils.throwIf(!RedisUtils.rateLimit(RedisUtils.formatKey(limitTemplateKeyPrefix, "MIN", phone, templateId),
RateType.OVERALL, 2, 60), "验证码发送过于频繁,请稍后后再试");
CheckUtils
.throwIf(!RedisUtils.rateLimit(RedisUtils.formatKey(limitTemplateKeyPrefix, "HOUR", phone, templateId),
RateType.OVERALL, 8, 60 * 60), "验证码发送过于频繁,请稍后后再试");
CheckUtils.throwIf(!RedisUtils.rateLimit(RedisUtils.formatKey(limitTemplateKeyPrefix, "DAY", phone, templateId),
RateType.OVERALL, 20, 60 * 60 * 24), "验证码发送过于频繁,请稍后后再试");
// 2.同一号码所有短信模板 24 小时 100 e.g. LIMIT:CAPTCHA:188xxxxx
String limitPhoneKey = RedisUtils.formatKey(limitKeyPrefix, captchaKeyPrefix, phone);
CheckUtils.throwIf(!RedisUtils.rateLimit(limitPhoneKey, RateType.OVERALL, 100, 60 * 60 * 24),
"验证码发送过于频繁,请稍后后再试");
// 3.同一 IP 每分钟限制发送 30 e.g. LIMIT:CAPTCHA:PHONE:1xx.1xx.1xx.1xx
String limitIpKey =
RedisUtils.formatKey(limitKeyPrefix, captchaKeyPrefix, "PHONE", JakartaServletUtil.getClientIP(request));
CheckUtils.throwIf(!RedisUtils.rateLimit(limitIpKey, RateType.OVERALL, 30, 60), "验证码发送过于频繁,请稍后后再试");
// 生成验证码 // 生成验证码
CaptchaProperties.CaptchaSms captchaSms = captchaProperties.getSms();
String captcha = RandomUtil.randomNumbers(captchaSms.getLength()); String captcha = RandomUtil.randomNumbers(captchaSms.getLength());
// 发送验证码 // 发送验证码
Long expirationInMinutes = captchaSms.getExpirationInMinutes(); Long expirationInMinutes = captchaSms.getExpirationInMinutes();
@ -135,7 +155,6 @@ public class CaptchaController {
// 保存验证码 // 保存验证码
String captchaKey = RedisUtils.formatKey(captchaKeyPrefix, phone); String captchaKey = RedisUtils.formatKey(captchaKeyPrefix, phone);
RedisUtils.set(captchaKey, captcha, Duration.ofMinutes(expirationInMinutes)); RedisUtils.set(captchaKey, captcha, Duration.ofMinutes(expirationInMinutes));
RedisUtils.set(limitCaptchaKey, captcha, Duration.ofSeconds(captchaSms.getLimitInSeconds()));
return R.ok(String.format("发送成功,验证码有效期 %s 分钟", expirationInMinutes)); return R.ok(String.format("发送成功,验证码有效期 %s 分钟", expirationInMinutes));
} }
} }

View File

@ -112,8 +112,6 @@ captcha:
length: 4 length: 4
# 过期时间 # 过期时间
expirationInMinutes: 5 expirationInMinutes: 5
# 限制时间
limitInSeconds: 60
# 模板 ID # 模板 ID
templateId: 1 templateId: 1

View File

@ -114,8 +114,6 @@ captcha:
length: 4 length: 4
# 过期时间 # 过期时间
expirationInMinutes: 5 expirationInMinutes: 5
# 限制时间
limitInSeconds: 60
# 模板 ID # 模板 ID
templateId: 1 templateId: 1