Redis接口计时防刷,防重复提交

文章介绍了如何使用Redis来确保接口的幂等性,通过设置唯一约束、使用锁机制、Redis的Set数据结构防止重复请求。同时,通过记录请求时间来防止短时间内重复调用同一接口,防止暴力刷接口的行为。示例代码展示了在发送短信验证码场景中的具体实现。
摘要由CSDN通过智能技术生成

生活中避免因为忘记,导致重复做某件事.方法就是没做完一件事就拿笔记录一次,放到程序也一样.接口被调用了,就使用Redis记录一次.要想短时间内不能调用接口,就记好开始时间.下次再调用就检查时间间隔.为了知道是不是同一个人调用,所以还需要存这个人唯一标志.

幂等性解决方案:

  1. 数据库字段添加唯一约束

  1. 锁机制(数据库乐观锁/悲观锁,业务层的分布式锁)

  1. redis的set防重复

  1. 全局请求唯一id (验证码)

  1. 防重表(原理跟3是类似)

redis 接口计时防刷:

/**
 * User: ldj
 * Date: 2022/9/22
 * Time: 20:15
 * Description: 使用restTemplate发送http请求获取短信验证码
 */
@Slf4j
@Component
public class SmsHandler {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private SmsProperties smsProperties;

    public void sendSmsCode(String phoneNum) {

        //TODO 接口计时防暴刷
        //第一种方案(推荐): Redis设置过期时间60s
        String redisValue = redisTemplate.opsForValue().get(SmsConstant.SMS_CODE_CACHE_PREFIX + phoneNum);
        if (StringUtils.isNotBlank(redisValue)) {
            return;
        }

        //第二种方案(不推荐): 如果缓存时间设置长于60s
        //if (StringUtils.isNotBlank(redisValue)) {
        //    long lastRequestTime = Long.parseLong(redisValue.split("_")[1]);
        //    //距离上次请求时间间隔不超过60s,不能再发送
        //    if (System.currentTimeMillis() - lastRequestTime < 60 * 1000){
        //        return;
        //    }
        //}

        Long requestTime = System.currentTimeMillis();
        UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(smsProperties.getUrl());
        URI uri = uriComponentsBuilder.build().encode(StandardCharsets.UTF_8).toUri();

        //请求头
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add(SmsConstant.AUTHORIZATION, smsProperties.getAppCode());
        httpHeaders.setContentType(MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE));

        String smsCode = SerialNumUtils.getRandomNumber(6);

        MultiValueMap<String, String> requestMap = new LinkedMultiValueMap<>();
        requestMap.add(SmsConstant.PHONE_NUMBER, phoneNum);
        requestMap.add(SmsConstant.TEMPLATE_KEY, smsProperties.getTemplateId());
        requestMap.add(SmsConstant.CONTENT, SmsConstant.CODE + smsCode);

        RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
                .post(uri)
                .headers(httpHeaders)
                .body(requestMap);

        ResponseEntity<ResponseDTO> responseEntity = null;

        try {
            responseEntity = restTemplate.exchange(requestEntity, ResponseDTO.class);
        } catch (Exception e) {
            log.error("[RestTemplate] (SmsComponent) http request error", e);
        }

        SmsHandler.dataHandler(responseEntity, phoneNum, smsCode, requestTime, redisTemplate);
    }

    private static void dataHandler(ResponseEntity<ResponseDTO> responseEntity,
                                    String phoneNum,
                                    String smsCode,
                                    Long requestTime,
                                    StringRedisTemplate redisTemplate) {

        if (Objects.isNull(responseEntity)) {
            log.warn("[RestTemplate] responseEntity is null");
            return;
        }

        HttpStatus statusCode = responseEntity.getStatusCode();
        log.info("[RestTemplate] statusCode:[{}]", statusCode.value());

        //2.验证码发送成功后,在Redis记录请求的开始时间
        if (Objects.equals(200, statusCode.value())) {
            String value = smsCode + "_" + requestTime;
            redisTemplate.opsForValue().set(SmsConstant.SMS_CODE_CACHE_PREFIX + phoneNum, value, 1, TimeUnit.MINUTES);
            log.info(JacksonUtils.writeValueAsString(responseEntity.getBody()));
        }
    }

}

后续补充: 计时防刷计数器也可以实现(推荐)

redis防重复提交实现:

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Test
    void redisTest() {

        //用户id+商品id
        String order = "user123456:goods65769579476444";

        //MD5加密处理
        String encryptOrder = encrypt(order);

        //模拟用户多次点击提交订单
        BoundSetOperations<String, String> operations = redisTemplate.boundSetOps("name");
        for (int i = 0; i < 3; i++) {
            operations.add(order);
            operations.expire(30, TimeUnit.MINUTES);
        }
        
        Boolean member = operations.isMember(encryptOrder);
        if (member != null && member) {
            //本次订单已经提交过了....返回空数据,提示重复提交
        }else {
            //提交订单
        }
        
    }

    //java中实现MD5加密方式
    private static String encrypt(String pwd) {
        String encryptedPwd = null;
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(pwd.getBytes());
            encryptedPwd = new BigInteger(1, md.digest()).toString(16);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return encryptedPwd;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值