Skip to content

Commit

Permalink
feat: add ratelimit
Browse files Browse the repository at this point in the history
  • Loading branch information
novohit committed Nov 14, 2023
1 parent 32c4f27 commit 356ab13
Show file tree
Hide file tree
Showing 17 changed files with 539 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,37 @@ const {multipleSelection} = storeToRefs(fileStore)
const loading = ref(false)
const doDownload = (item) => {
let url = panUtil.getUrlPrefix() + '/file/download/' + item.fileId + '?Authorization=' + getToken(),
fileName = item.fileName,
link = document.createElement('a')
console.log(url)
link.style.display = 'none'
link.href = url
link.setAttribute('download', fileName)
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
let url = panUtil.getUrlPrefix() + '/file/download/' + item.fileId,
fileName = item.fileName;
fetch(url, {
method: 'GET',
headers: {
'Authorization': getToken(),
},
})
.then(response => {
if (!response.ok) {
ElMessage.error("操作频繁")
return;
}
return response.blob();
})
.then(blob => {
// 创建一个虚拟链接并模拟点击以触发下载
const link = document.createElement('a');
link.style.display = 'none';
const objectUrl = window.URL.createObjectURL(blob);
link.href = objectUrl;
link.setAttribute('download', fileName);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(objectUrl);
})
.catch(error => {
// 在这里处理错误,可以弹出提示信息或执行其他操作
});
}
const doDownLoads = (items, i) => {
Expand Down
Binary file modified cloudshare.xmind
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ public FileDataSyncHandler(FileDocRepository fileDocRepository) {

public void syncData(CanalBinlogEvent message) {
List<Map<String, Object>> data = message.getData();

if (!message.getDatabase().equals("cloudshare")) {
return;
}
for (Map<String, Object> obj : data) {
String jsonString = JSON.toJSONString(obj);
// TODO fastjson2 升级 https://github.com/alibaba/fastjson2/issues/1615
Expand Down
36 changes: 36 additions & 0 deletions framework/limit-spring-boot-starter/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>framework</artifactId>
<groupId>com.cloudshare</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<groupId>org.novo</groupId>
<artifactId>limit-spring-boot-starter</artifactId>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.novo.limit.annotation;

import org.novo.limit.enums.LimitType;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @author novo
* @since 2023/11/14
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimit {

String key() default "rate_limit:";

/**
* 限流时间窗
*
* @return
*/
int time() default 60;

/**
* 在时间窗内的限流次数
*
* @return
*/
int count() default 100;

/**
* 限流类型
*
* @return
*/
LimitType limitType() default LimitType.DEFAULT;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.novo.limit.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.novo.limit.annotation.RateLimit;
import org.novo.limit.enums.LimitType;
import org.novo.limit.exception.RateLimitException;
import org.novo.limit.util.IpUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.Collections;

/**
* @author novo
* @since 2023/11/14
*/
@Aspect
public class RateLimiterAspect {

private static final Logger logger = LoggerFactory.getLogger(RateLimiterAspect.class);

private final RedisTemplate<String, Long> redisTemplate;

private final RedisScript<Long> redisScript;

public RateLimiterAspect(RedisTemplate<String, Long> redisTemplate, RedisScript<Long> redisScript) {
this.redisTemplate = redisTemplate;
this.redisScript = redisScript;
}

@Before("@annotation(rateLimit)")
public void doBefore(JoinPoint joinPoint, RateLimit rateLimit) {
int time = rateLimit.time();
int count = rateLimit.count();
String combineKey = getCombineKey(rateLimit, joinPoint);
Long current = redisTemplate.execute(redisScript, Collections.singletonList(combineKey), time, count);
if (current == null || current.intValue() > count) {
// 超过限流
logger.info("当前接口已达到最大限流次数");
throw new RateLimitException("访问过于频繁,请稍后访问");
}
logger.info("一个时间窗内请求次数:{},当前请求次数:{},缓存的 key 为{}", count, current, combineKey);
}

/**
* 组合key
* redis上的key
* 基于IP:
* 前缀:IP-方法的唯一标识
* rate_limit:10.10.1.1-com.wyu.controller.HelloController-hello
* <p>
* 默认限流:
* rate_limit:com.wyu.controller.HelloController-hello
*
* @param rateLimit
* @param joinPoint
* @return
*/
private String getCombineKey(RateLimit rateLimit, JoinPoint joinPoint) {
StringBuilder key = new StringBuilder(rateLimit.key());
if (rateLimit.limitType() == LimitType.IP) {
key.append(IpUtil.getIpAddr(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest()))
.append("-");
}
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
key.append(method.getDeclaringClass().getName())
.append("-") // 类名
.append(method.getName()); // 方法名
logger.info(key.toString());
return key.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.novo.limit.config;

import org.novo.limit.aspect.RateLimiterAspect;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scripting.support.ResourceScriptSource;

/**
* @author novo
* @since 2023/11/14
*/
public class RateLimitAutoConfiguration {

@Bean("limitScript")
public DefaultRedisScript<Long> limitScript() {
DefaultRedisScript<Long> limitScript = new DefaultRedisScript<>();
// 设置脚本返回的类型
limitScript.setResultType(Long.class);
// 设置脚本位置 根目录从resource开始
limitScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit.lua")));
return limitScript;
}


@Bean("limitRedisTemplate")
public RedisTemplate<String, Long> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Long> redisTemplate = new RedisTemplate<>();
Jackson2JsonRedisSerializer<Long> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Long.class);
//设置string key和value序列器
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}

@Bean
public RateLimiterAspect rateLimiterAspect(@Qualifier("limitRedisTemplate") RedisTemplate<String, Long> redisTemplate,
@Qualifier("limitScript") RedisScript<Long> redisScript) {
return new RateLimiterAspect(redisTemplate, redisScript);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.novo.limit.enums;

/**
* @author novo
* @since 2023/11/14
*/
public enum LimitType {

/**
* 默认限流策略 针对某一个接口
*/
DEFAULT,

/**
* 针对某一个ip
*/
IP,

;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.novo.limit.exception;

import java.io.Serial;

/**
* @author novo
* @since 2023/11/14
*/
public class RateLimitException extends RuntimeException {

@Serial
private static final long serialVersionUID = 7364848679991344873L;

public RateLimitException(String message) {
super(message);
}
}
Loading

0 comments on commit 356ab13

Please sign in to comment.