本文目录导读:

我来为你提供一个完整的Java接口防刷案例,包含多种实现方案:
基于IP地址的限制方案
1 使用Guava Cache实现(单机版)
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@RestController
public class IpRateLimitController {
// 创建缓存,key为IP,value为访问次数
private Cache<String, AtomicInteger> ipCache = CacheBuilder.newBuilder()
.expireAfterWrite(1, TimeUnit.MINUTES) // 1分钟过期
.maximumSize(10000) // 最大缓存数量
.build();
@GetMapping("/api/test")
public String test(HttpServletRequest request) {
// 获取客户端IP
String ip = getClientIp(request);
try {
// 获取当前IP的访问次数
AtomicInteger counter = ipCache.get(ip, () -> new AtomicInteger(0));
// 增加访问次数
int count = counter.incrementAndGet();
// 限制每分钟10次访问
if (count > 10) {
return "访问过于频繁,请稍后再试";
}
return "访问成功,当前访问次数:" + count;
} catch (Exception e) {
e.printStackTrace();
return "系统错误";
}
}
private String getClientIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
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.getRemoteAddr();
}
return ip;
}
}
使用Redis实现分布式限流
1 添加依赖
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
2 Redis配置
spring:
redis:
host: localhost
port: 6379
timeout: 5000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
3 自定义注解
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface RateLimit {
// 限制时间,单位:秒
int time() default 60;
// 限制次数
int count() default 10;
// 限制类型
LimitType limitType() default LimitType.IP;
enum LimitType {
IP, // 基于IP
USER, // 基于用户
CUSTOM // 自定义
}
}
4 Redis限流实现
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
@Component
public class RedisRateLimitService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// Lua脚本实现原子性操作
private static final String LUA_SCRIPT =
"local key = KEYS[1]\n" +
"local limit = tonumber(ARGV[1])\n" +
"local expireTime = tonumber(ARGV[2])\n" +
"local current = redis.call('get', key)\n" +
"if current and tonumber(current) > limit then\n" +
" return 0\n" +
"end\n" +
"local count = redis.call('incr', key)\n" +
"if count == 1 then\n" +
" redis.call('expire', key, expireTime)\n" +
"end\n" +
"if count > limit then\n" +
" return 0\n" +
"end\n" +
"return 1";
public boolean tryAccess(String key, int limit, int expireTime) {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(LUA_SCRIPT);
redisScript.setResultType(Long.class);
Long result = redisTemplate.execute(
redisScript,
Arrays.asList(key),
String.valueOf(limit),
String.valueOf(expireTime)
);
return result != null && result == 1L;
}
// 获取当前访问次数
public int getCurrentCount(String key) {
Object count = redisTemplate.opsForValue().get(key);
return count == null ? 0 : Integer.parseInt(count.toString());
}
}
5 切面实现
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.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;
@Aspect
@Component
public class RateLimitAspect {
@Autowired
private RedisRateLimitService rateLimitService;
@Around("@annotation(com.example.demo.annotation.RateLimit)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取方法签名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 获取注解
RateLimit rateLimit = method.getAnnotation(RateLimit.class);
if (rateLimit == null) {
return joinPoint.proceed();
}
// 生成限流key
String key = generateKey(rateLimit.limitType(), joinPoint);
// 检查是否允许访问
boolean allowed = rateLimitService.tryAccess(key, rateLimit.count(), rateLimit.time());
if (!allowed) {
throw new RuntimeException("访问过于频繁,请稍后再试");
}
return joinPoint.proceed();
}
private String generateKey(RateLimit.LimitType limitType, ProceedingJoinPoint joinPoint) {
StringBuilder key = new StringBuilder("rate_limit:");
switch (limitType) {
case IP:
key.append(getClientIp());
break;
case USER:
key.append(getCurrentUserId());
break;
case CUSTOM:
key.append(generateCustomKey(joinPoint));
break;
}
// 添加方法名作为后缀
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
key.append(":").append(signature.getMethod().getName());
return key.toString();
}
private String getClientIp() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
return "unknown";
}
HttpServletRequest request = attributes.getRequest();
// 获取IP逻辑...
return request.getRemoteAddr();
}
private String getCurrentUserId() {
// 从Session或Token中获取用户ID
return "user_" + 123;
}
private String generateCustomKey(ProceedingJoinPoint joinPoint) {
// 自定义key生成逻辑
return "custom_key";
}
}
6 使用示例
@RestController
public class TestController {
@GetMapping("/api/test1")
@RateLimit(time = 60, count = 10, limitType = RateLimit.LimitType.IP)
public String test1() {
return "访问成功";
}
@GetMapping("/api/test2")
@RateLimit(time = 3600, count = 1000, limitType = RateLimit.LimitType.USER)
public String test2() {
return "访问成功";
}
}
使用令牌桶算法实现
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class TokenBucketRateLimiter {
// 存储不同接口的限流器
private ConcurrentHashMap<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<>();
public boolean tryAcquire(String apiKey, double permitsPerSecond) {
RateLimiter rateLimiter = rateLimiterMap.computeIfAbsent(
apiKey,
k -> RateLimiter.create(permitsPerSecond)
);
return rateLimiter.tryAcquire();
}
}
// 使用
@RestController
public class TokenBucketController {
@Autowired
private TokenBucketRateLimiter rateLimiter;
@GetMapping("/api/token-bucket")
public String test() {
// 允许每秒10个请求
if (rateLimiter.tryAcquire("test_api", 10)) {
return "请求成功";
}
return "请求过于频繁";
}
}
使用拦截器实现
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class RateLimitInterceptor implements HandlerInterceptor {
@Autowired
private RedisRateLimitService rateLimitService;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String ip = request.getRemoteAddr();
String requestUri = request.getRequestURI();
String key = "interceptor:" + ip + ":" + requestUri;
// 限制每分钟10次
boolean allowed = rateLimitService.tryAccess(key, 10, 60);
if (!allowed) {
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("{\"code\":429,\"msg\":\"访问过于频繁\"}");
return false;
}
return true;
}
}
// 注册拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private RateLimitInterceptor rateLimitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(rateLimitInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns("/api/login", "/api/register");
}
}
使用Spring Cloud Gateway实现
spring:
cloud:
gateway:
routes:
- id: rate_limit_route
uri: http://localhost:8080
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 10
redis-rate-limiter.burstCapacity: 20
key-resolver: "#{@ipKeyResolver}"
predicates:
- Path=/api/**
@Configuration
public class RateLimiterConfig {
@Bean
public KeyResolver ipKeyResolver() {
return exchange -> {
String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
return Mono.just(ip);
};
}
}
完整的使用示例
@RestController
@Slf4j
public class RateLimitDemoController {
@Autowired
private RedisRateLimitService rateLimitService;
@Autowired
private TokenBucketRateLimiter tokenBucketRateLimiter;
// 方法1:使用注解
@GetMapping("/api/annotation")
@RateLimit(time = 10, count = 5)
public String withAnnotation() {
return "通过注解限流";
}
// 方法2:手动控制
@GetMapping("/api/manual")
public String manualControl(HttpServletRequest request) {
String ip = request.getRemoteAddr();
String key = "manual:" + ip + ":test_api";
if (!rateLimitService.tryAccess(key, 10, 60)) {
log.warn("IP: {} 访问过于频繁", ip);
return "访问过于频繁";
}
return "访问成功,当前访问次数:" + rateLimitService.getCurrentCount(key);
}
// 方法3:令牌桶
@GetMapping("/api/token")
public String tokenBucket() {
if (tokenBucketRateLimiter.tryAcquire("test", 10)) {
return "令牌获取成功";
}
return "令牌获取失败";
}
}
异常处理
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public Result handleRateLimitException(RuntimeException e) {
if (e.getMessage().contains("访问过于频繁")) {
return Result.error(429, "访问过于频繁,请稍后再试");
}
return Result.error(500, "系统错误");
}
}
@Data
class Result {
private int code;
private String message;
private Object data;
public static Result error(int code, String message) {
Result result = new Result();
result.setCode(code);
result.setMessage(message);
return result;
}
}
这些方案可以根据你的具体需求选择使用:
- 单机应用:使用Guava Cache或令牌桶
- 分布式应用:使用Redis+Lua脚本
- 网关层面:使用Spring Cloud Gateway
- 需要灵活配置:使用注解+AOP
建议根据实际业务场景选择最合适的方案,也可以组合使用多种方案来实现更完善的防刷机制。