本文目录导读:

我来为你提供一个完整的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
建议根据实际业务场景选择最合适的方案,也可以组合使用多种方案来实现更完善的防刷机制。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播