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 {@Autowiredprivate 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 keyString 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才是真實ipif (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;}
}