spirngboot项目 使用AOP限流用户单位时间请求接口次数

说明:使用AOP+redis 实现限制用户单位时间内多次访问接口,我这里使用参数中的userId(用户唯一标识),也可以通过IP或者其他参数来做限制。

package com.student.demo.configuration;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;

import java.time.Duration;

/**
 * @Date: 2023/2/9
 */
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){

        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        //Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer();

        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

        //ObjectMapper om = new ObjectMapper();
         指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
        //om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
         指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        //om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.EVERYTHING, JsonTypeInfo.As.PROPERTY);
        //jackson2JsonRedisSerializer.setObjectMapper(om);

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(redisSerializer);
        template.setValueSerializer(genericJackson2JsonRedisSerializer);
        template.setHashKeySerializer(redisSerializer);
        template.setHashValueSerializer(genericJackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

自定义注解

package com.student.demo.annotation;

import java.lang.annotation.*;

/**
 * @Date: 2023/9/7
 */
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
    /**
     * 限制时间 单位:秒(默认值:1分钟)
     * @return
     */
    long period() default 60;

    /**
     * 允许请求的次数(默认值:5次)
     * @return
     */
    long count() default 5;
}

AOP部分

package com.student.demo.aop;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.student.demo.annotation.AccessLimit;
import com.student.demo.enums.RetCode;
import com.student.demo.exception.BizException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @Date: 2023/9/7
 */
@Slf4j
@Aspect
@Component
public class AccessLimitAspect {

    @Resource
    private RedisTemplate redisTemplate;

    @Pointcut("@annotation(com.student.demo.annotation.AccessLimit)")
    public void methodPointcut(){}

    @Around(value = "methodPointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        AccessLimit annotation = AnnotationUtils.findAnnotation(method, AccessLimit.class);

        // get parameter from annotation
        long period = annotation.period();
        long limitCount = annotation.count();

        // get request info from args
        Object[] args = joinPoint.getArgs();
        JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(args[0]));
        String userId = jsonObject.getString("userId");
        String key = "ACCESS_LIMIT:".concat(userId);

        ZSetOperations zSetOperations = redisTemplate.opsForZSet();

        // add current timestamp
        long currentMs = System.currentTimeMillis();
        zSetOperations.add(key, currentMs, currentMs);

        // set the expiration time for the code user
        redisTemplate.expire(key, period, TimeUnit.SECONDS);

        // remove the value that out of current window
        zSetOperations.removeRangeByScore(key, 0, currentMs - period * 1000);

        // check all available count
        Long count = zSetOperations.zCard(key);

        if (count > limitCount) {
            log.error("接口拦截:{} 请求超过限制频率【{}次/{}s】,UserId为:{}", method.getName(), limitCount, period, userId);
        }

        // execute the user request
        return  joinPoint.proceed();

    }

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值