前言

关于恶意访问的锁在上一篇文章中实现了【(SpringBoot中如何使用注解方式拦截恶意访问的IP)[./259988aa]】
这里依旧使用Redis锁,通过自定义注解的方式进行拦截,方便重复调用使用。
Redis的配置需要自行配置

后端代码

依赖引入 pom.xml和 yml配置文件中配置Redis

1
2
3
4
5
<!-- redis 缓存操作 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
spring:
# redis 配置
redis:
# 地址
host: 127.0.0.1
# 端口,默认为6379
port: 6379
# 数据库索引
database: 0
# 密码
password: 123456
# 连接超时时间
timeout: 10s
lettuce:
pool:
# 连接池中的最小空闲连接
min-idle: 0
# 连接池中的最大空闲连接
max-idle: 8
# 连接池的最大数据库连接数
max-active: 8
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms

RedisTemplateConfig配置类

添加这个配置类,防止存Redis的时候keyvalue乱码

拦截部分

自定义注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.lang.annotation.*;
/**
* PreventRepeatClick 类
* 注解实体
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreventRepeatClick {
/**
* 锁名称(接口名)
*/
String name();

/**
* 请求参数的属性名(例如 petId)
*/
String key();
/**
* 锁的过期时间,单位秒
*/
long time() default 3;
/**
* 提示信息
*/
String msg() default "系统繁忙,请稍后再试!";
}

拦截恶意访问接口 - 逻辑处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Field;

/**
* Redis锁
* 用途:提交防抖、计时API
*/
@Aspect
@Component
public class PreventRepeatClickAspect {

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

@Autowired
private RedissonClient redissonClient;

/**
* 切点:匹配带有 @PreventRepeatClick 注解的方法
* @param preventRepeatClick
*/
@Pointcut("@annotation(preventRepeatClick)")
public void preventRepeatClickPointcut(PreventRepeatClick preventRepeatClick) {}

/**
* 在方法执行之前检查是否重复提交
* @param joinPoint
* @param preventRepeatClick
* @throws Throwable
*/
@Before("preventRepeatClickPointcut(preventRepeatClick)")
public void handleRepeatClick(JoinPoint joinPoint, PreventRepeatClick preventRepeatClick) throws Throwable {
String name = preventRepeatClick.name(); // 方法名,通常是接口名
String key = preventRepeatClick.key(); // 入参的属性名(如:petId)
long time = preventRepeatClick.time(); // 锁的过期时间
String msg = preventRepeatClick.msg(); // 提示信息
// 获取方法的参数
Object[] args = joinPoint.getArgs();
// 获取对应的入参属性值
Object keyValue = null;
for (Object arg : args) {
if (arg != null) {
if (arg instanceof Integer || arg instanceof String) {
// 如果参数是基本类型(如 Integer 或 String),直接使用
keyValue = arg;
break;
} else {
// 如果参数是复杂对象,通过反射获取字段值
try {
Field field = arg.getClass().getDeclaredField(key); // 获取字段
field.setAccessible(true); // 设置字段可访问
keyValue = field.get(arg); // 获取字段值
break;
} catch (NoSuchFieldException | IllegalAccessException e) {
logger.error("关键参数缺失: {}", key);
throw new IllegalArgumentException("关键参数缺失: " + key, e);
}
}
}
}
// 如果找不到对应的 key 参数,抛出异常
if (keyValue == null) {
logger.error("关键参数缺失");
throw new IllegalArgumentException("关键参数缺失");
}
// 生成 Redis 锁的 key:方法名 + 入参属性名 + 入参值
String lockKey = name + ":" + keyValue.toString(); // 比如:updateUserInfo:userId:1001
// 获取 Redisson 分布式锁x
RLock lock = redissonClient.getLock(lockKey);
// 尝试获取锁,设置过期时间
boolean lockAcquired = lock.tryLock(0, time, java.util.concurrent.TimeUnit.SECONDS);
// 如果无法获取到锁,说明是重复提交
if (!lockAcquired) {
throw new RuntimeException(msg); // 抛出重复提交异常
}
}
}

Controller接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
public class TestController {
/**
* 测试
* 入参可以参考AccessLimit.java
**/
@PreventRepeatClick(name = "RepeatClickLock:addUserInfo", key = "phone", time = 3, msg = "请勿重复提交")
@ResponseBody
@GetMapping("/addUserInfo)
public String addUserInfo() {
return “SUCCESS;
}
}

测试