1、工程中需要引入以下依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、在common工程中创建package包SlideWindowLimitAspect
3、创建SlideWindowLimit接口,内容如下:
package com.bj.internel.SlideWindowLimitAspect;
import org.aspectj.lang.annotation.Aspect;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target({ElementType.METHOD, ElementType.TYPE}) //作用在方法
public @interface SlideWindowLimit {
/**
* 限制时间范围,单位ms
*/
long period() default 1000;
/**
* 限制请求次数
*/
int limitCount() default 5;
}
4、创建切面SlideWindowLimitAspect.java,内容如下
package com.bj.internel.SlideWindowLimitAspect;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@Aspect
@Component //注入Spring ioc中
@Slf4j
public class SlideWindowLimitAspect {
@Autowired
public SlideWindowUtils slideWindowUtils;
@Pointcut(value = "@annotation(SlideWindowLimit)")
public void SlideWindowLimitAspect() {
}
@Around("SlideWindowLimitAspect()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 获取注解属性
SlideWindowLimit slideWindowLimit = ((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(SlideWindowLimit.class);
log.info("period:",slideWindowLimit.period());
log.info("limitCount:",slideWindowLimit.limitCount());
if (slideWindowUtils.slideWindowAlgorithmByLua(request.getRequestURI(), slideWindowLimit.limitCount(), slideWindowLimit.period())) {
return "操作过于频繁,请稍后重试";
}
return pjp.proceed();
}
}
5、创建工具类SlideWindowUtils.java,内容如下:
package com.bj.internel.SlideWindowLimitAspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Component
public class SlideWindowUtils {
@Autowired
RedisTemplate redisTemplate;
@Autowired
StringRedisTemplate StringRedisTemplate = new StringRedisTemplate();
public boolean slideWindowAlgorithmByLua(String operationId,int limitCount,long windowTime){
DefaultRedisScript<Boolean> script = new DefaultRedisScript<>();
//luaStr, Arrays.asList("aaaa"), Arrays.asList("ele"+i, System.currentTimeMillis(),System.currentTimeMillis()-30*1000, 30, 3 script.setScriptText(lua());
script.setResultType(Boolean.class);
long currentTimeMillis = System.currentTimeMillis();
System.out.println(String.valueOf(windowTime));
List<Object> args = new ArrayList<>();
args.add(String.valueOf(currentTimeMillis));
args.add(String.valueOf(windowTime));
args.add(String.valueOf(windowTime));
args.add(String.valueOf(limitCount));
args.add(String.valueOf(currentTimeMillis));
args.forEach(tmp->{
System.out.println(tmp);
});
// Boolean isLimit = (Boolean) redisTemplate.execute(script, Collections.singletonList(operationId),String.valueOf(currentTimeMillis),String.valueOf(windowTime),String.valueOf(windowTime),String.valueOf(limitCount),String.valueOf(currentTimeMillis));
// Boolean isLimit = (Boolean) redisTemplate.execute(script, Collections.singletonList(operationId),args);
boolean isLimit = execLuaScript(lua(), Collections.singletonList(operationId),args);
return isLimit;
}
public String lua(){
return "local key = KEYS[1]\n" +
"local currentTimeMillis = tonumber(ARGV[1])\n" +
"local ttl = tonumber(ARGV[2])\n" +
"local windowtime = tonumber(ARGV[3])\n" +
"local limitCount = tonumber(ARGV[4])\n" +
"local value = tonumber(ARGV[5])\n" +
"-- 移除过期的元素\n" +
"redis.call('zremrangebyscore',key,0,currentTimeMillis - windowtime)\n" +
"-- 获取当关窗口内的元素数量\n"+
"local count = tonumber(redis.call('zcard',key))\n" +
"-- 如果当前窗口内的元素数据超过窗口大小,删除最旧的元素\n"+
"-- 窗口内的元素数量小于窗口大小\n"+
"if count < limitCount then\n" +
"-- 将新元素添加到ZSET中\n" +
"redis.call(\"zadd\",key,currentTimeMillis,value)\n" +
"-- 设置过期时间\n"+
"redis.call(\"expire\",key,ttl)\n" +
"return false;\n" +
"else\n" +
"return true\n" +
"end";
}
public boolean execLuaScript(String luaStr, List<String> keys, List<Object> args){
RedisScript<Boolean> redisScript = RedisScript.of(luaStr, Boolean.class);
return StringRedisTemplate.execute(redisScript, keys, args.toArray());
}
}
6、使用
7、测试
使用浏览器或JMeter等测试工具访问,本次使用浏览器快速点击,如下
8、注意
需要启动redis,并在application.yml文 件中加入redis的相关配置