说明:使用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
redisTemplate(RedisConnectionFactory factory){ -
- RedisSerializer
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
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();
-
- }
-
- }