SpringBoot+Redis实现频繁操作接口警告

最近写个小demo的想实现了多少秒内操作几次接口提示频繁的警告、首先的你先有个思路、我的思路呢就是百度找例子!!!

话不多说先上代码

百度例子:

                                     //存入redis的key |每次自增多少
long count = redisTemplate.opsForValue().increment("a", 1);
if(count == 3){
           //存入redis的key | 设置key为15秒 | 时间单位 
    redisTemplate.expire("a", 15, TimeUnit.SECONDS)
}
if(count > 3){
    // 这个是我封装的响应
    return R.failed(ApiErrorCode.FREQUENTLY.getCode(), "操作频繁");
}
复制代码

看完之后细心的朋友已经发现bug了吧、按照上面的流程走的话就是在请求第二次的时候count已经加一到了三然后继续走下面count == 3成立了设置key的时间为15秒过期、再次请求、也就是count从三加一变成了四此时count == 3就不成立了、则count > 3 成立、此时这样的逻辑看起来是没问题、但是就按上面的逻辑来说这种只适合多次不停点击请求响应、但是你有没有考虑间接性请求会有很大的问题!!!

问题

如:我设置key过期时间15秒内请求3次、正常来说我一次请求3次在第4次的情况下会出现提示操作频繁、但是我请求第2次的时候、我停一分钟在请求、这下就发现了问题、就是说我在一分钟后再次接上请求2次就会提示频繁操作、这种间接性请求的会导致体验很差!!!

后需改进的代码

RedisExpired :封装存数据的类

/**
 * 功能描述: 用于封装频繁请求接口、将数据存入redis中使用到的对象
 *
 * @Author: 魏
 * @Date: 2021/6/18 20:13
 */
@Data
public class RedisExpired {
    // redis key
    private String key;
    // redis value
    private Object value;
    // 设置频繁操作次数
    private Integer frequentlyTimes;
    // 过期时间
    private long ExpiredTime;
    // 状态
    private Boolean isFlag;

    public RedisExpired(String key, Integer frequentlyTimes, long expiredTime) {
        this.key = key;
        this.frequentlyTimes = frequentlyTimes;
        ExpiredTime = expiredTime;
    }
}
复制代码

具体实现方法

 public static RedisExpired addRedisExpiredTimes(RedisExpired redisExpired){
    Integer i = (Integer) get(redisExpired.getKey());
    if (i != null && i > redisExpired.getFrequentlyTimes()){
            redisExpired.setValue(get(redisExpired.getKey()));
            // 设置key的过期时间
            redisExpired.setExpiredTime(getExpire(redisExpired.getKey()));
            // 设置状态
            redisExpired.setIsFlag(false);
            return redisExpired;
    }
    // redis 自增1  返回自增后的数 、并设置过期时间
    long count = incrBy(redisExpired.getKey(), 1,redisExpired.getExpiredTime());
    // 自增后的数等于设置
    if(count == redisExpired.getFrequentlyTimes() + 1){
        redisExpired.setIsFlag(false);
        return redisExpired;
    }
    redisExpired.setIsFlag(true);
    return redisExpired;

}
复制代码

Service层做的操作

/**
 *  添加/取消 关注 操作
 * @param accountId 用户id
 * @param attentionId 关注的id
 * @param flag 当前关注的状态  true 关注  false 未关注
 */
@Override
public R<Object> addOrCancellFollow(String accountId, String attentionId, boolean flag) {
    RedisExpired redisExpired = RedisUtil.addRedisExpiredTimes(new RedisExpired(
            StrUtil.format("{}::{}", AccountUtils.getLoginAccount().getName(), accountId),
            3, 15));
    if (!redisExpired.getIsFlag()) {
        return R.failed(ApiErrorCode.FREQUENTLY.getCode(), StrUtil.format("频繁操作请{}在试", TimeUtils.formatDateTime(redisExpired.getExpiredTime())));
    }
    if (flag){
        // 添加关注
        fansMapper.attentionAccount(accountId,attentionId);
        return R.success(true,"关注成功");
    }
    // 取消关注
    fansMapper.cancelAttentionAccount(accountId,attentionId);
    return R.success(false,"取消关注");
}
复制代码

上面的只是我自己封装的而已、最主要的就是incrBy()方法

 /**
 * 增加(自增长), 负数则为自减
 *
 * @param key
 * @return
 */
public static Long incrBy(String key, long increment,long time) {
    if (time != 0){
        Long aLong = MyRedisTemplate.opsForValue().increment(key, increment);
        expire(key, time, TimeUnit.SECONDS);
        return aLong;
    }
    return MyRedisTemplate.opsForValue().increment(key, increment);
}
复制代码

解决间接性请求就是在redis自增的后设置过期时间。

具体流程图

image.png

  • 改进后的代码有两个改变
    • 1、在达到指定次数后在不进行redis增加方法、直接返回频繁操作。
    • 2、间接性请求。(每次redis自增的时候设置key的缓存时间)
  • 最后实现的功能就是在多少秒内允许请求多少次、在时间过期后能再次能请求、允许间接性请求。

想法1

还可以改变就是说、我们将第一个改变去掉(在达到指定次数后在不进行redis增加方法、直接返回频繁操作。)没有这个添加的话redis就会一直请求增加、如在15秒内请求、100次的情况下我们给他下次请求必须的等三分钟、200次就9分钟、500次我们直接将账号封禁!!!

想法2

通过AOP的方式来指定某些请求限制请求多少秒中请求多少次、AOP前置通知来实现

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享