1. 添加Maven依赖
<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>
2. 创建自定义注解
import java.lang.annotation.*;
/**
* 接口防刷注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface AccessLimit {
/**
* 限制时间范围(秒)
*/
int time() default 60;
/**
* 时间范围内最大访问次数
*/
int maxCount() default 10;
/**
* 是否检查IP地址
*/
boolean checkIp() default true;
/**
* 是否检查用户身份(需要登录)
*/
boolean checkUser() default false;
/**
* 触发限制时的提示信息
*/
String message() default "操作过于频繁,请稍后再试";
}
3. 创建AOP切面实现防护逻辑
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
@Aspect
@Component
public class AccessLimitAspect {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Around("@annotation(accessLimit)")
public Object around(ProceedingJoinPoint joinPoint, AccessLimit accessLimit) throws Throwable {
// 获取请求对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
return joinPoint.proceed();
}
HttpServletRequest request = attributes.getRequest();
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 构建Redis key
String key = buildKey(request, method, accessLimit);
// 获取当前计数
ValueOperations<String, Object> operations = redisTemplate.opsForValue();
Integer count = (Integer) operations.get(key);
if (count == null) {
// 第一次访问
operations.set(key, 1, accessLimit.time(), TimeUnit.SECONDS);
} else if (count < accessLimit.maxCount()) {
// 计数增加
operations.increment(key);
} else {
// 超出限制,抛出异常
throw new RuntimeException(accessLimit.message());
}
return joinPoint.proceed();
}
/**
* 构建Redis key
*/
private String buildKey(HttpServletRequest request, Method method, AccessLimit accessLimit) {
StringBuilder key = new StringBuilder("access_limit:");
// 添加方法标识
key.append(method.getDeclaringClass().getName())
.append(".")
.append(method.getName())
.append(":");
// 添加IP标识
if (accessLimit.checkIp()) {
String ip = getClientIp(request);
key.append(ip).append(":");
}
// 添加用户标识(需要实现获取当前用户的方法)
if (accessLimit.checkUser()) {
// 这里需要根据你的用户系统实现获取当前用户ID的方法
String userId = getCurrentUserId();
if (userId != null) {
key.append(userId).append(":");
}
}
return key.toString();
}
/**
* 获取客户端IP
*/
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if (ip.contains(",")) {
ip = ip.split(",")[0];
}
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
return ip;
}
/**
* 获取当前用户ID(需要根据实际情况实现)
*/
private String getCurrentUserId() {
// 实现获取当前用户ID的逻辑
// 可以从Session、Token或Spring Security上下文等获取
return null;
}
}
4. 创建全局异常处理器
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public Result handleRuntimeException(RuntimeException e) {
return Result.error(e.getMessage());
}
}
5. 在Controller中使用注解
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class DemoController {
// 基于IP的限制:60秒内最多访问10次
@AccessLimit(time = 60, maxCount = 10, checkIp = true, checkUser = false)
@GetMapping("/public/data")
public String getPublicData() {
return "这是公开数据";
}
// 基于用户的限制:30秒内最多访问5次
@AccessLimit(time = 30, maxCount = 5, checkIp = false, checkUser = true)
@GetMapping("/user/data")
public String getUserData() {
return "这是用户数据";
}
// 同时基于IP和用户的限制:60秒内最多访问3次
@AccessLimit(time = 60, maxCount = 3, checkIp = true, checkUser = true)
@PostMapping("/submit")
public String submitData(@RequestBody String data) {
return "提交成功: " + data;
}
}