【NFTurbo】基于Redisson滑动窗口实现验证码发送限流
【NFTurbo】基于Redisson滑动窗口实现验证码发送限流
- 1. 场景
- 2. 代码
1. 场景
服务在登录和注册的时候需要短信验证,为了防止被灰黑产抓包盗刷,需要限制次数,这里我们在前端做了按钮的置灰,但是后端接口还是要做防控的。
2. 代码
@GetMapping("/sendCaptcha")
public Result<Boolean> sendCaptcha(@IsMobile String telephone) {NoticeResponse noticeResponse = noticeFacadeService.generateAndSendSmsCaptcha(telephone);return Result.success(noticeResponse.getSuccess());
}
package cn.hollis.nft.turbo.notice.facade;import cn.hollis.nft.turbo.api.notice.response.NoticeResponse;
import cn.hollis.nft.turbo.api.notice.service.NoticeFacadeService;
import cn.hollis.nft.turbo.base.exception.SystemException;
import cn.hollis.nft.turbo.limiter.SlidingWindowRateLimiter;
import cn.hollis.nft.turbo.notice.domain.constant.NoticeState;
import cn.hollis.nft.turbo.notice.domain.entity.Notice;
import cn.hollis.nft.turbo.notice.domain.service.NoticeService;
import cn.hollis.nft.turbo.rpc.facade.Facade;
import cn.hollis.nft.turbo.sms.SmsService;
import cn.hollis.nft.turbo.sms.response.SmsSendResponse;
import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;import java.util.Date;
import java.util.concurrent.TimeUnit;import static cn.hollis.nft.turbo.api.notice.constant.NoticeConstant.CAPTCHA_KEY_PREFIX;
import static cn.hollis.nft.turbo.base.exception.BizErrorCode.SEND_NOTICE_DUPLICATED;/*** @author Hollis*/
@DubboService(version = "1.0.0")
public class NoticeFacadeServiceImpl implements NoticeFacadeService {@Autowiredprivate SlidingWindowRateLimiter slidingWindowRateLimiter;@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate NoticeService noticeService;@Autowiredprivate SmsService smsService;/*** 生成并发送短信验证码** @param telephone 接收验证码的手机号码* @return 验证码发送结果响应对象*/@Facade@Overridepublic NoticeResponse generateAndSendSmsCaptcha(String telephone) {// 使用滑动窗口限流器控制验证码发送频率,每个手机号60秒内只能发送1次Boolean access = slidingWindowRateLimiter.tryAcquire(telephone, 1, 60);if (!access) {throw new SystemException(SEND_NOTICE_DUPLICATED);}// 生成4位随机数字验证码String captcha = RandomUtil.randomNumbers(4);// 将验证码存储到Redis中,设置5分钟过期时间redisTemplate.opsForValue().set(CAPTCHA_KEY_PREFIX + telephone, captcha, 5, TimeUnit.MINUTES);Notice notice = noticeService.saveCaptcha(telephone, captcha);// 异步发送短信验证码Thread.ofVirtual().start(() -> {SmsSendResponse result = smsService.sendMsg(notice.getTargetAddress(), notice.getNoticeContent());if (result.getSuccess()) {notice.setState(NoticeState.SUCCESS);notice.setSendSuccessTime(new Date());noticeService.updateById(notice);} else {notice.setState(NoticeState.FAILED);notice.addExtendInfo("executeResult", JSON.toJSONString(result));noticeService.updateById(notice);}});return new NoticeResponse.Builder().setSuccess(true).build();}
}
package cn.hollis.nft.turbo.limiter;import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;/*** 滑动窗口限流服务** @author Hollis*/
public class SlidingWindowRateLimiter implements RateLimiter {private RedissonClient redissonClient;private static final String LIMIT_KEY_PREFIX = "nft:turbo:limit:";public SlidingWindowRateLimiter(RedissonClient redissonClient) {this.redissonClient = redissonClient;}@Overridepublic Boolean tryAcquire(String key, int limit, int windowSize) {RRateLimiter rRateLimiter = redissonClient.getRateLimiter(LIMIT_KEY_PREFIX + key);if (!rRateLimiter.isExists()) {rRateLimiter.trySetRate(RateType.OVERALL, limit, windowSize, RateIntervalUnit.SECONDS);}return rRateLimiter.tryAcquire();}
}