说明:使用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();
}
}