分享

项目实战:防刷

 田维常 2023-06-16 发布于广东

  你好,我是田哥

最近在搞充电桩项目,用户使用短信验证码形式进行注册和登录,那么问题来了?

分布式微服务项目实战:充电桩项目

实战分布式事务【Seata+Spring Cloud】

项目实战:自定义异常和统一参数验证(附源码)

如果用户无聊或恶意的一直点发送验证码,虽然每条短信没便宜,但短信量大了,这也是一笔不小的开销的。

因此,我们需要对此做一些限制的策略。

如何限制?这里就不得不提一些限流算法。

限流的常见算法有以下三种:

  • 时间窗口算法
  • 漏桶算法
  • 令牌算法

下面来看看充电桩项目中是如何实现的。

/**
@author tianwc  公众号:java后端技术全栈、面试专栏
@version 1.0.0
@date 2023年05月27日 09:13
* 博客地址:<a href="http:///">博客地址</a>
*/

@Component
public class LimiterUtil {
@Resource
private RedisTemplate<String, String> redisTemplate;

/**
 * 固定窗口限流算法
 *
 * @return true 限流  false 放行
 */

public boolean fixedWindow(String key, int count) {
    long countCache = redisTemplate.opsForValue().increment(key);
    return countCache > count;
}

/**
 * intervalTime 时间内 最多只能访问5次
 *
 * @param key          缓存key
 * @param currentTime  当前时间  new Date().getTime();
 * @param intervalTime 有效期
 * @param count        限流次数
 * @return true 限流 false 放行
 */

public boolean slidingWindow(String key, Long currentTime, Long intervalTime, int count) {
    //发送次数+1
    redisTemplate.opsForZSet().add(key, UUID.randomUUID().toString(), currentTime);
    // intervalTime是限流的时间
    int countCache = Objects.requireNonNull(redisTemplate.opsForZSet().rangeByScore(key, currentTime - intervalTime, currentTime)).size();
    return countCache > count; 
}
}

发送短信单位时间内次数限制采用的是slidingWindow()方法的参数。

调用案例:

service层代码:

public CommonResult<Boolean> sendCode(String phone, String codeCachePre, Integer msgType) {
        if (StringUtil.isEmpty(phone)) {
            return CommonResult.failed(ResultCode.VALIDATE_FAILED);
        }
        ParamValidate.isNull(phone, "phone参数为空");
        //同一个手机号码 发送次数限流
        //同一个手机号每秒 最多只能发5次
        boolean limit = limiterUtil.slidingWindow(RedisConstantPre.MESSAGE_LIMIT_KEY_PRE + phone, (new Date()).getTime(), 60000L5);
        if (limit) {
            return CommonResult.failed(ResultCode.SEND_MESSAGE_LIMIT);
        }
        CommonResult<MessageTemplateDto> commonResult = messageTemplateFeignClient.queryByMessageType(msgType);
        if (commonResult.getCode() != ResultCode.SUCCESS.getCode()) {
            log.error("短信模板不存在,tye={}", msgType);
            return CommonResult.failed(ResultCode.VALIDATE_FAILED);
        }
        MessageTemplateDto messageTemplateDto = commonResult.getData();
        //生成随机验证码
        String code = RandomUtil.randomNumbers(6);
        log.info("登录发送验证码:{}", code);
        //发送验证码
        String content = messageTemplateDto.getContent();
        //验证码占位符
        String newContent = content.replace(messageTemplateDto.getContentParam(), code);
        //调用MQ  异步发送短信验证码
        phoneMessageProducer.sendPhoneMessage(phone, newContent);
        //存到redis中 设置有效期 60秒
        //60秒后需要重现发送
        redisConfig.set(codeCachePre + phone, code, 60);
        return CommonResult.success(Boolean.TRUE);
    }

画个图 更好地理解:

controller层代码:

/**
 * 发送登录手机验证码
 */

@PostMapping("/sendCode")
public CommonResult<Boolean> sendCode4Login(@RequestBody SendCodeReqDto sendCodeReqDto) {
  return sendCodeService.sendCode(sendCodeReqDto.getPhone(), RedisConstantPre.SEND_CODE_LOGIN_PRE, MessageTemplateTypeEnums.PAY_SUCCESS_TEMPLATE.getType());
}

测试

POST

http://localhost:9006/user/sendCode

Content-Type: application/json; charset=UTF-8

{
  "code"400015,
  "message""短信发送太频繁,请隔一会再发送!",
  "data"null
}

好了,今天就分享这么多。

如果有需要简历修改、简历优化、简历包装、面试辅导、模拟面试、技术辅导、技术支持等,欢迎加我微xtj20120622

个人技术博客可刷题:http:///

回复77 ,获取《面试小抄2.0版》

回复电子书,获取后端必读的200本电子书籍

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多