Java接口防刷案例怎么编写

wen java案例 28

本文目录导读:

Java接口防刷案例怎么编写

  1. 基于IP地址的限制方案
  2. 使用Redis实现分布式限流
  3. 使用令牌桶算法实现
  4. 使用拦截器实现
  5. 使用Spring Cloud Gateway实现
  6. 完整的使用示例
  7. 异常处理

我来为你提供一个完整的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

建议根据实际业务场景选择最合适的方案,也可以组合使用多种方案来实现更完善的防刷机制。

抱歉,评论功能暂时关闭!