Skip to content

Commit fb706c8

Browse files
committed
feat: 演示请求接口限制次数DEMO
1 parent 373c3a8 commit fb706c8

File tree

19 files changed

+803
-0
lines changed

19 files changed

+803
-0
lines changed

springboot-api-lock/.gitignore

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
HELP.md
2+
target/
3+
!.mvn/wrapper/maven-wrapper.jar
4+
!**/src/main/**
5+
!**/src/test/**
6+
7+
### STS ###
8+
.apt_generated
9+
.classpath
10+
.factorypath
11+
.project
12+
.settings
13+
.springBeans
14+
.sts4-cache
15+
16+
### IntelliJ IDEA ###
17+
.idea
18+
*.iws
19+
*.iml
20+
*.ipr
21+
22+
### NetBeans ###
23+
/nbproject/private/
24+
/nbbuild/
25+
/dist/
26+
/nbdist/
27+
/.nb-gradle/
28+
build/
29+
30+
### VS Code ###
31+
.vscode/

springboot-api-lock/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# 工程简介
2+
## 基础环境
3+
### (1)技术选型
4+
1. JDK11
5+
2. SpringBoot 2.3.7-RELEASE
6+
3. MySQL
7+
4. Redis
8+
5. Gommon-IO
9+
6. Fastjson
10+
11+
12+
### (2)搭建步骤
13+
14+
15+
16+
17+
# 延伸阅读
18+

springboot-api-lock/pom.xml

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<groupId>com.study.module</groupId>
6+
<artifactId>springboot-api-lock</artifactId>
7+
<version>0.0.1-SNAPSHOT</version>
8+
<name>springboot-api-lock</name>
9+
<description>Demo project for Spring Boot</description>
10+
11+
<properties>
12+
<java.version>11</java.version>
13+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
14+
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
15+
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
16+
</properties>
17+
18+
<dependencies>
19+
<dependency>
20+
<groupId>org.springframework.boot</groupId>
21+
<artifactId>spring-boot-starter-data-redis</artifactId>
22+
</dependency>
23+
<dependency>
24+
<groupId>org.springframework.boot</groupId>
25+
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
26+
</dependency>
27+
<dependency>
28+
<groupId>org.springframework.boot</groupId>
29+
<artifactId>spring-boot-starter-web</artifactId>
30+
</dependency>
31+
<!-- aop依赖 -->
32+
<dependency>
33+
<groupId>org.springframework.boot</groupId>
34+
<artifactId>spring-boot-starter-aop</artifactId>
35+
</dependency>
36+
<dependency>
37+
<groupId>redis.clients</groupId>
38+
<artifactId>jedis</artifactId>
39+
<version>2.9.0</version>
40+
</dependency>
41+
<dependency>
42+
<groupId>com.alibaba</groupId>
43+
<artifactId>fastjson</artifactId>
44+
<version>1.2.72</version>
45+
</dependency>
46+
<dependency>
47+
<groupId>org.apache.commons</groupId>
48+
<artifactId>commons-lang3</artifactId>
49+
<version>3.1</version>
50+
</dependency>
51+
52+
<dependency>
53+
<groupId>mysql</groupId>
54+
<artifactId>mysql-connector-java</artifactId>
55+
<scope>runtime</scope>
56+
</dependency>
57+
<dependency>
58+
<groupId>org.projectlombok</groupId>
59+
<artifactId>lombok</artifactId>
60+
<optional>true</optional>
61+
</dependency>
62+
<dependency>
63+
<groupId>org.springframework.boot</groupId>
64+
<artifactId>spring-boot-starter-test</artifactId>
65+
<scope>test</scope>
66+
<exclusions>
67+
<exclusion>
68+
<groupId>org.junit.vintage</groupId>
69+
<artifactId>junit-vintage-engine</artifactId>
70+
</exclusion>
71+
</exclusions>
72+
</dependency>
73+
<dependency>
74+
<groupId>io.projectreactor</groupId>
75+
<artifactId>reactor-test</artifactId>
76+
<scope>test</scope>
77+
</dependency>
78+
<dependency>
79+
<groupId>commons-codec</groupId>
80+
<artifactId>commons-codec</artifactId>
81+
<version>1.10</version>
82+
</dependency>
83+
</dependencies>
84+
85+
<dependencyManagement>
86+
<dependencies>
87+
<dependency>
88+
<groupId>org.springframework.boot</groupId>
89+
<artifactId>spring-boot-dependencies</artifactId>
90+
<version>${spring-boot.version}</version>
91+
<type>pom</type>
92+
<scope>import</scope>
93+
</dependency>
94+
</dependencies>
95+
</dependencyManagement>
96+
97+
<build>
98+
<plugins>
99+
<plugin>
100+
<groupId>org.apache.maven.plugins</groupId>
101+
<artifactId>maven-compiler-plugin</artifactId>
102+
<version>3.8.1</version>
103+
<configuration>
104+
<source>11</source>
105+
<target>11</target>
106+
<encoding>UTF-8</encoding>
107+
</configuration>
108+
</plugin>
109+
<plugin>
110+
<groupId>org.springframework.boot</groupId>
111+
<artifactId>spring-boot-maven-plugin</artifactId>
112+
<version>2.3.7.RELEASE</version>
113+
<configuration>
114+
<mainClass>com.study.module.springbootapilock.SpringbootApiLockApplication</mainClass>
115+
</configuration>
116+
<executions>
117+
<execution>
118+
<id>repackage</id>
119+
<goals>
120+
<goal>repackage</goal>
121+
</goals>
122+
</execution>
123+
</executions>
124+
</plugin>
125+
</plugins>
126+
</build>
127+
128+
</project>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.study.module.springbootapilock;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.context.annotation.Bean;
6+
import org.springframework.core.io.ClassPathResource;
7+
import org.springframework.data.redis.core.script.DefaultRedisScript;
8+
import org.springframework.scripting.support.ResourceScriptSource;
9+
10+
/**
11+
* 请求接口次数限制
12+
*
13+
* @author drew
14+
*/
15+
@SpringBootApplication
16+
public class SpringbootApiLockApplication {
17+
18+
@Bean
19+
public DefaultRedisScript<Long> limitScript() {
20+
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
21+
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/limit.lua")));
22+
redisScript.setResultType(Long.class);
23+
return redisScript;
24+
}
25+
26+
public static void main(String[] args) {
27+
SpringApplication.run(SpringbootApiLockApplication.class, args);
28+
}
29+
30+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.study.module.springbootapilock.annotation;
2+
3+
import com.study.module.springbootapilock.enums.LimitType;
4+
5+
import java.lang.annotation.*;
6+
7+
/**
8+
* @author drew
9+
*/
10+
@Target(ElementType.METHOD)
11+
@Retention(RetentionPolicy.RUNTIME)
12+
@Documented
13+
public @interface RateLimiter {
14+
/**
15+
* 限流key
16+
*/
17+
String key() default "rate_limit:";
18+
19+
/**
20+
* 限流时间,单位秒
21+
*/
22+
int time() default 60;
23+
24+
/**
25+
* 限流次数
26+
*/
27+
int count() default 100;
28+
29+
/**
30+
* 限流类型
31+
*/
32+
LimitType limitType() default LimitType.DEFAULT;
33+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.study.module.springbootapilock.aspect;
2+
3+
import com.study.module.springbootapilock.annotation.RateLimiter;
4+
import com.study.module.springbootapilock.enums.LimitType;
5+
import com.study.module.springbootapilock.exception.ServiceException;
6+
import com.study.module.springbootapilock.util.IpUtils;
7+
import org.aspectj.lang.JoinPoint;
8+
import org.aspectj.lang.annotation.Aspect;
9+
import org.aspectj.lang.annotation.Before;
10+
import org.aspectj.lang.reflect.MethodSignature;
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
13+
import org.springframework.beans.factory.annotation.Autowired;
14+
import org.springframework.data.redis.core.RedisTemplate;
15+
import org.springframework.data.redis.core.script.RedisScript;
16+
import org.springframework.stereotype.Component;
17+
import org.springframework.web.context.request.RequestContextHolder;
18+
import org.springframework.web.context.request.ServletRequestAttributes;
19+
20+
import java.lang.reflect.Method;
21+
import java.util.Collections;
22+
import java.util.List;
23+
24+
/**
25+
* 核心:切面(限定标记的接口指定时间段内请求的次数)
26+
*
27+
* @author zl
28+
* @create 2022-06-11 10:25
29+
*/
30+
@Aspect
31+
@Component
32+
public class RateLimiterAspect {
33+
private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class);
34+
35+
@Autowired
36+
private RedisTemplate<Object, Object> redisTemplate;
37+
38+
@Autowired
39+
private RedisScript<Long> limitScript;
40+
41+
@Before("@annotation(rateLimiter)")
42+
public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable {
43+
String key = rateLimiter.key();
44+
int time = rateLimiter.time();
45+
int count = rateLimiter.count();
46+
47+
String combineKey = getCombineKey(rateLimiter, point);
48+
List<Object> keys = Collections.singletonList(combineKey);
49+
Long number = redisTemplate.execute(limitScript, keys, count, time);
50+
if (number == null || number.intValue() > count) {
51+
throw new ServiceException(500, "访问过于频繁,请稍候再试");
52+
}
53+
log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), key);
54+
}
55+
56+
public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) {
57+
StringBuilder stringBuffer = new StringBuilder(rateLimiter.key());
58+
if (rateLimiter.limitType() == LimitType.IP) {
59+
stringBuffer.append(IpUtils.getIpAddress(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest())).append("-");
60+
}
61+
MethodSignature signature = (MethodSignature) point.getSignature();
62+
Method method = signature.getMethod();
63+
Class<?> targetClass = method.getDeclaringClass();
64+
stringBuffer.append(targetClass.getName()).append("-").append(method.getName());
65+
return stringBuffer.toString();
66+
}
67+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.study.module.springbootapilock.config;
2+
3+
import com.fasterxml.jackson.annotation.JsonAutoDetect;
4+
import com.fasterxml.jackson.annotation.PropertyAccessor;
5+
import com.fasterxml.jackson.databind.ObjectMapper;
6+
import org.springframework.context.annotation.Bean;
7+
import org.springframework.context.annotation.Configuration;
8+
import org.springframework.data.redis.connection.RedisConnectionFactory;
9+
import org.springframework.data.redis.core.RedisTemplate;
10+
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
11+
12+
/**
13+
* @author zl
14+
* @create 2022-06-11 10:20
15+
*/
16+
@Configuration
17+
public class RedisConfig {
18+
@Bean
19+
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
20+
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
21+
redisTemplate.setConnectionFactory(connectionFactory);
22+
// 使用Jackson2JsonRedisSerialize 替换默认序列化(默认采用的是JDK序列化)
23+
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
24+
ObjectMapper om = new ObjectMapper();
25+
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
26+
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
27+
jackson2JsonRedisSerializer.setObjectMapper(om);
28+
redisTemplate.setKeySerializer(jackson2JsonRedisSerializer);
29+
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
30+
redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);
31+
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
32+
return redisTemplate;
33+
}
34+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.study.module.springbootapilock.controller;
2+
3+
import com.study.module.springbootapilock.annotation.RateLimiter;
4+
import com.study.module.springbootapilock.enums.LimitType;
5+
import org.springframework.web.bind.annotation.GetMapping;
6+
import org.springframework.web.bind.annotation.RequestMapping;
7+
import org.springframework.web.bind.annotation.RestController;
8+
9+
/**
10+
* 测试控制类:演示请求锁
11+
*
12+
* @author zl
13+
* @create 2022-06-11 9:58
14+
*/
15+
@RestController
16+
@RequestMapping(value = "/test")
17+
public class TestController {
18+
19+
/**
20+
* 演示:每一个 IP 地址,在 5 秒内只能访问 3 次。
21+
*/
22+
@GetMapping("/hi")
23+
@RateLimiter(time = 5, count = 3, limitType = LimitType.IP)
24+
public String hi() {
25+
return "hi";
26+
}
27+
}

0 commit comments

Comments
 (0)