Spring Boot IP 封禁系统实现

这是一个基于 Spring Boot 和 Redisson 的 IP 封禁系统,支持三种封禁类型(全局 IP 封禁、接口封禁、IP+接口封禁),通过单一注解 @Ban 实现,结合白名单和黑名单功能。白名单和黑名单通过 application.yml 配置,不使用 Redis 存储,其他封禁信息存储在 Redis 中。

功能概述

  • 封禁类型
    • 全局 IP 封禁:通过 @Ban(type = BanType.IP) 或全局开关(ip.ban.global-enabled=true),限制特定 IP 访问所有接口。
    • 接口封禁:通过 @Ban(type = BanType.INTERFACE),限制所有 IP 访问特定接口。
    • IP+接口封禁:通过 @Ban(type = BanType.IP_INTERFACE),限制特定 IP 访问特定接口。
  • 自动封禁:通过注解配置时间窗口(windowSeconds)、最大请求次数(maxRequests)、封禁时长(banDuration)和时间单位(timeUnit),超出频率触发自动封禁。
  • 白名单:在 application.yml 配置,IP 免受所有封禁检查,直接放行。
  • 黑名单:在 application.yml 配置,IP 直接全局封禁。
  • 优先级:白名单 > 黑名单 > 全局 IP 封禁 > 注解封禁。
  • Redis 键
    • IP 封禁:banned:ips
    • 接口封禁:banned:interfaces
    • IP+接口封禁:banned:ip:interface:<ip>&<interface>
    • 请求计数:rate:limit:<ip/interface/ip&interface>
  • 执行顺序:全局检查(白名单、黑名单、全局 IP 封禁)先于 @Ban 注解检查。

代码实现

1. 封禁类型枚举 (BanType.java)

定义封禁类型枚举。

package com.minimalism.redis.ban;

public enum BanType {
    IP,         // 全局 IP 封禁
    INTERFACE,  // 接口封禁
    IP_INTERFACE // IP+接口封禁
}

2. 自定义注解 (Ban.java)

定义 @Ban 注解,包含封禁类型和自动封禁参数。

package com.minimalism.redis.aop.ban;

import com.minimalism.redis.ban.BanType;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Ban {
    BanType type() default BanType.IP; // 封禁类型
    long windowSeconds() default 60;   // 请求时间窗口(秒)
    long maxRequests() default 10;     // 最大请求次数
    long banDuration() default 3600;   // 自动封禁时长
    TimeUnit timeUnit() default TimeUnit.SECONDS; // 时间单位
}

3. 配置文件 (application.yml)

配置白名单、黑名单和全局 IP 封禁开关。

ip:
  ban:
    global-enabled: false  # 全局 IP 封禁开关,默认禁用
  whitelist: 192.168.1.100,192.168.1.101  # 白名单 IP,逗号分隔
  blacklist: 192.168.1.200,192.168.1.201  # 黑名单 IP,逗号分隔

4. Redisson 配置 (RedissonConfig.java)

配置 Redisson 客户端。

package com.minimalism.redis.config;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://localhost:6379");
        // config.setPassword("your_password"); // 如果需要密码
        return Redisson.create(config);
    }
}

5. 抽象切面接口 (AbstractRedisAspect.java)

定义抽象切面接口。

package com.minimalism.abstractinterface.aop;

import org.aspectj.lang.ProceedingJoinPoint;

public interface AbstractRedisAspect {
    void pointcutAspect();
    default Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        return joinPoint.proceed();
    }
}

6. 封禁管理接口 (BanManager.java)

定义封禁管理接口。

package com.minimalism.abstractinterface.ban;

import com.minimalism.redis.ban.BanType;

import java.util.concurrent.TimeUnit;

public interface BanManager {
    void banIP(String ipAddress, long duration, TimeUnit timeUnit);
    boolean isIPBanned(String ipAddress);
    void unbanIP(String ipAddress);

    void banInterface(String interfaceName, long duration, TimeUnit timeUnit);
    boolean isInterfaceBanned(String interfaceName);
    void unbanInterface(String interfaceName);

    void banIPInterface(String ipAddress, String interfaceName, long duration, TimeUnit timeUnit);
    boolean isIPInterfaceBanned(String ipAddress, String interfaceName);
    void unbanIPInterface(String ipAddress, String interfaceName);

    boolean checkAndBan(String key, long windowSeconds, long maxRequests, long banDuration, TimeUnit timeUnit, BanType banType, String ipAddress, String interfaceName);
}

7. 封禁管理实现 (SimpleBanManager.java)

实现封禁和请求计数逻辑。

package com.minimalism.redis.ban;

import com.minimalism.abstractinterface.ban.BanManager;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RSet;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
public class SimpleBanManager implements BanManager {
    private final RedissonClient redissonClient;
    private static final String BANNED_IPS_KEY = "banned:ips"; // 全局 IP 封禁
    private static final String BANNED_INTERFACES_KEY = "banned:interfaces"; // 接口封禁
    private static final String BANNED_IP_INTERFACE_PREFIX = "banned:ip:interface:"; // IP+接口封禁前缀
    private static final String RATE_LIMIT_PREFIX = "rate:limit:"; // 请求计数前缀

    public SimpleBanManager(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Override
    public void banIP(String ipAddress, long duration, TimeUnit timeUnit) {
        RSet<String> bannedIps = redissonClient.getSet(BANNED_IPS_KEY);
        bannedIps.add(ipAddress);
        if (duration > 0) {
            bannedIps.expire(duration, timeUnit);
        }
    }

    @Override
    public boolean isIPBanned(String ipAddress) {
        RSet<String> bannedIps = redissonClient.getSet(BANNED_IPS_KEY);
        return bannedIps.contains(ipAddress);
    }

    @Override
    public void unbanIP(String ipAddress) {
        RSet<String> bannedIps = redissonClient.getSet(BANNED_IPS_KEY);
        bannedIps.remove(ipAddress);
    }

    @Override
    public void banInterface(String interfaceName, long duration, TimeUnit timeUnit) {
        RSet<String> bannedInterfaces = redissonClient.getSet(BANNED_INTERFACES_KEY);
        bannedInterfaces.add(interfaceName);
        if (duration > 0) {
            bannedInterfaces.expire(duration, timeUnit);
        }
    }

    @Override
    public boolean isInterfaceBanned(String interfaceName) {
        RSet<String> bannedInterfaces = redissonClient.getSet(BANNED_INTERFACES_KEY);
        return bannedInterfaces.contains(interfaceName);
    }

    @Override
    public void unbanInterface(String interfaceName) {
        RSet<String> bannedInterfaces = redissonClient.getSet(BANNED_INTERFACES_KEY);
        bannedInterfaces.remove(interfaceName);
    }

    @Override
    public void banIPInterface(String ipAddress, String interfaceName, long duration, TimeUnit timeUnit) {
        String key = BANNED_IP_INTERFACE_PREFIX + ipAddress + "&" + interfaceName;
        redissonClient.getBucket(key).set(true);
        if (duration > 0) {
            redissonClient.getBucket(key).expire(duration, timeUnit);
        }
    }

    @Override
    public boolean isIPInterfaceBanned(String ipAddress, String interfaceName) {
        String key = BANNED_IP_INTERFACE_PREFIX + ipAddress + "&" + interfaceName;
        return redissonClient.getBucket(key).isExists();
    }

    @Override
    public void unbanIPInterface(String ipAddress, String interfaceName) {
        String key = BANNED_IP_INTERFACE_PREFIX + ipAddress + "&" + interfaceName;
        redissonClient.getBucket(key).delete();
    }

    @Override
    public boolean checkAndBan(String key, long windowSeconds, long maxRequests, long banDuration, TimeUnit timeUnit, BanType banType, String ipAddress, String interfaceName) {
        String rateKey = RATE_LIMIT_PREFIX + key;
        RAtomicLong counter = redissonClient.getAtomicLong(rateKey);
        long count = counter.incrementAndGet();

        if (count == 1) {
            counter.expire(windowSeconds, TimeUnit.SECONDS);
        }

        if (count > maxRequests) {
            switch (banType) {
                case IP:
                    banIP(ipAddress, banDuration, timeUnit);
                    break;
                case INTERFACE:
                    banInterface(interfaceName, banDuration, timeUnit);
                    break;
                case IP_INTERFACE:
                    banIPInterface(ipAddress, interfaceName, banDuration, timeUnit);
                    break;
            }
            return true; // 已封禁
        }
        return false; // 未封禁
    }
}

8. 封禁配置类 (BanConfiguration.java)

管理白名单、黑名单和全局封禁开关。

package com.minimalism.redis.ban;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

@Component
public class BanConfiguration {
    @Value("${ip.ban.global-enabled:false}")
    private boolean globalBanEnabled;

    @Value("${ip.whitelist:}")
    private String whitelistIps;

    @Value("${ip.blacklist:}")
    private String blacklistIps;

    private final Set<String> whitelist;
    private final Set<String> blacklist;

    public BanConfiguration() {
        this.whitelist = new HashSet<>(Arrays.asList(whitelistIps.split(",")));
        this.blacklist = new HashSet<>(Arrays.asList(blacklistIps.split(",")));
        this.whitelist.remove("");
        this.blacklist.remove("");
    }

    public boolean isGlobalBanEnabled() {
        return globalBanEnabled;
    }

    public Set<String> getWhitelist() {
        return whitelist;
    }

    public Set<String> getBlacklist() {
        return blacklist;
    }
}

9. 自定义异常 (BanException.java)

定义封禁异常。

package com.minimalism.redis.exception;

public class BanException extends RuntimeException {
    public BanException(String message) {
        super(message);
    }
}

10. AOP 切面 (RedisBanAspect.java)

实现白名单、黑名单、全局封禁和注解检查逻辑,确保全局检查先执行。

package com.minimalism.redis.aop.aspect;

import cn.hutool.extra.spring.SpringUtil;
import com.minimalism.abstractinterface.aop.AbstractRedisAspect;
import com.minimalism.abstractinterface.ban.BanManager;
import com.minimalism.redis.aop.ban.Ban;
import com.minimalism.redis.ban.BanConfiguration;
import com.minimalism.redis.ban.SimpleBanManager;
import com.minimalism.redis.ban.BanType;
import com.minimalism.redis.exception.BanException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
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.util.Set;

import static cn.hutool.extra.servlet.ServletUtil.getClientIP;

@Aspect
@Slf4j
@Component
public class RedisBanAspect implements AbstractRedisAspect {
    static SimpleBanManager DEFAULT_BAN_MANAGER;
    static BanConfiguration DEFAULT_BAN_CONFIGURATION;

    static {
        try {
            DEFAULT_BAN_MANAGER = SpringUtil.getBean(SimpleBanManager.class);
        } catch (NoSuchBeanDefinitionException e) {
            log.warn("SimpleBanManager not found, creating default instance: {}", e.getMessage());
            DEFAULT_BAN_MANAGER = new SimpleBanManager(SpringUtil.getBean(RedissonClient.class));
        }

        try {
            DEFAULT_BAN_CONFIGURATION = SpringUtil.getBean(BanConfiguration.class);
        } catch (NoSuchBeanDefinitionException e) {
            log.warn("BanConfiguration not found, creating default instance: {}", e.getMessage());
            DEFAULT_BAN_CONFIGURATION = new BanConfiguration();
        }
    }

    @Override
    @Pointcut("@annotation(com.minimalism.redis.aop.ban.Ban)")
    public void pointcutAspect() {
    }

    @Override
    @Around("execution(* com.minimalism..*Controller.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("[{}] 执行全局检查", System.currentTimeMillis());
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String ipAddress = getClientIP(request);
        BanConfiguration banConfiguration = DEFAULT_BAN_CONFIGURATION;
        boolean globalBanEnabled = banConfiguration.isGlobalBanEnabled();
        Set<String> whitelist = banConfiguration.getWhitelist();
        Set<String> blacklist = banConfiguration.getBlacklist();

        // 检查白名单
        if (whitelist.contains(ipAddress)) {
            log.info("IP {} 在白名单中,直接放行", ipAddress);
            return joinPoint.proceed();
        }

        // 检查黑名单
        if (blacklist.contains(ipAddress)) {
            log.warn("IP {} 在黑名单中,拒绝访问", ipAddress);
            throw new BanException("IP 地址 " + ipAddress + " 在黑名单中");
        }

        BanManager banManager = DEFAULT_BAN_MANAGER;

        // 检查全局 IP 封禁
        if (globalBanEnabled && banManager.isIPBanned(ipAddress)) {
            log.warn("IP {} 已被全局封禁", ipAddress);
            throw new BanException("IP 地址 " + ipAddress + " 已被全局封禁");
        }

        // 检查 @Ban 注解(仅对带有 @Ban 注解的方法执行)
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Ban ban = signature.getMethod().getAnnotation(Ban.class);
        if (ban != null) {
            log.info("[{}] 执行 @Ban 注解检查", System.currentTimeMillis());
            String interfaceName = joinPoint.getSignature().toString();
            BanType banType = ban.type();
            long windowSeconds = ban.windowSeconds();
            long maxRequests = ban.maxRequests();
            long banDuration = ban.banDuration();
            java.util.concurrent.TimeUnit timeUnit = ban.timeUnit();

            String rateKey;
            switch (banType) {
                case IP:
                    rateKey = ipAddress;
                    if (banManager.checkAndBan(rateKey, windowSeconds, maxRequests, banDuration, timeUnit, banType, ipAddress, interfaceName)
                            || banManager.isIPBanned(ipAddress)) {
                        log.warn("IP {} 已被封禁", ipAddress);
                        throw new BanException("IP 地址 " + ipAddress + " 已被封禁");
                    }
                    break;
                case INTERFACE:
                    rateKey = interfaceName;
                    if (banManager.checkAndBan(rateKey, windowSeconds, maxRequests, banDuration, timeUnit, banType, ipAddress, interfaceName)
                            || banManager.isInterfaceBanned(interfaceName)) {
                        log.warn("接口 {} 已被封禁", interfaceName);
                        throw new BanException("接口 " + interfaceName + " 已被封禁");
                    }
                    break;
                case IP_INTERFACE:
                    rateKey = ipAddress + "&" + interfaceName;
                    if (banManager.checkAndBan(rateKey, windowSeconds, maxRequests, banDuration, timeUnit, banType, ipAddress, interfaceName)
                            || banManager.isIPInterfaceBanned(ipAddress, interfaceName)) {
                        log.warn("IP {} 在接口 {} 上被封禁", ipAddress, interfaceName);
                        throw new BanException("IP 地址 " + ipAddress + " 在接口 " + interfaceName + " 上被封禁");
                    }
                    break;
            }
        }

        return joinPoint.proceed();
    }
}

11. 测试控制器 (TestController.java)

展示白名单、黑名单和三种封禁的使用。

package com.minimalism.redis.controller;

import com.minimalism.redis.aop.ban.Ban;
import com.minimalism.redis.ban.BanType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/api")
public class TestController {
    @Ban(type = BanType.IP, windowSeconds = 60, maxRequests = 5, banDuration = 3600, timeUnit = TimeUnit.SECONDS)
    @GetMapping("/ip-ban")
    public String ipBanEndpoint() {
        return "访问成功(IP 封禁检查,60秒内超5次请求封禁1小时)!";
    }

    @Ban(type = BanType.INTERFACE, windowSeconds = 60, maxRequests = 10, banDuration = 1800, timeUnit = TimeUnit.SECONDS)
    @GetMapping("/interface-ban")
    public String interfaceBanEndpoint() {
        return "访问成功(接口封禁检查,60秒内超10次请求封禁30分钟)!";
    }

    @Ban(type = BanType.IP_INTERFACE, windowSeconds = 30, maxRequests = 3, banDuration = 600, timeUnit = TimeUnit.SECONDS)
    @GetMapping("/ip-interface-ban")
    public String ipInterfaceBanEndpoint() {
        return "访问成功(IP+接口封禁检查,30秒内超3次请求封禁10分钟)!";
    }

    @GetMapping("/no-ban")
    public String noBanEndpoint() {
        return "此接口仅受全局 IP 封禁、黑名单或白名单影响";
    }
}

12. 管理员控制器 (AdminController.java)

管理封禁(白名单和黑名单通过配置文件管理)。

package com.minimalism.redis.controller;

import com.minimalism.abstractinterface.ban.BanManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/admin")
public class AdminController {
    @Autowired
    private BanManager banManager;

    @GetMapping("/ban-ip")
    public String banIP(@RequestParam String ip, @RequestParam(defaultValue = "0") long duration, @RequestParam(defaultValue = "SECONDS") String timeUnit) {
        banManager.banIP(ip, duration, TimeUnit.valueOf(timeUnit));
        return "IP " + ip + " 已全局封禁";
    }

    @GetMapping("/unban-ip")
    public String unbanIP(@RequestParam String ip) {
        banManager.unbanIP(ip);
        return "IP " + ip + " 已解除全局封禁";
    }

    @GetMapping("/ban-interface")
    public String banInterface(@RequestParam String interfaceName, @RequestParam(defaultValue = "0") long duration, @RequestParam(defaultValue = "SECONDS") String timeUnit) {
        banManager.banInterface(interfaceName, duration, TimeUnit.valueOf(timeUnit));
        return "接口 " + interfaceName + " 已封禁";
    }

    @GetMapping("/unban-interface")
    public String unbanInterface(@RequestParam String interfaceName) {
        banManager.unbanInterface(interfaceName);
        return "接口 " + interfaceName + " 已解除封禁";
    }

    @GetMapping("/ban-ip-interface")
    public String banIPInterface(@RequestParam String ip, @RequestParam String interfaceName, @RequestParam(defaultValue = "0") long duration, @RequestParam(defaultValue = "SECONDS") String timeUnit) {
        banManager.banIPInterface(ip, interfaceName, duration, TimeUnit.valueOf(timeUnit));
        return "IP " + ip + " 在接口 " + interfaceName + " 上已封禁";
    }

    @GetMapping("/unban-ip-interface")
    public String unbanIPInterface(@RequestParam String ip, @RequestParam String interfaceName) {
        banManager.unbanIPInterface(ip, interfaceName);
        return "IP " + ip + " 在接口 " + interfaceName + " 上已解除封禁";
    }
}

13. 全局异常处理 (GlobalExceptionHandler.java)

处理封禁异常。

package com.minimalism.redis.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(BanException.class)
    public ResponseEntity<String> handleBanException(BanException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.FORBIDDEN);
    }
}

14. Maven 依赖 (pom.xml 片段)

确保添加必要的依赖。

<dependencies>
    <dependency>
        <groupId>org.redisson</groupId>
        <artifactId>redisson</artifactId>
        <version>3.36.0</version>
    </dependency>
    <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>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.28</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.34</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

使用说明

1. 环境准备

  • Redis:确保 Redis 运行在 localhost:6379,必要时配置密码。
  • Spring Boot:确保项目包含上述 Maven 依赖。
  • 配置文件:修改 application.yml,设置白名单、黑名单和全局封禁开关。

2. 白名单和黑名单

  • 白名单:在 application.ymlip.whitelist 配置 IP(逗号分隔,如 192.168.1.100,192.168.1.101),免受所有封禁检查。
  • 黑名单:在 application.ymlip.blacklist 配置 IP(逗号分隔,如 192.168.1.200,192.168.1.201),直接全局封禁。
  • 管理:修改 application.yml 后需重启应用。
  • 优先级:白名单 > 黑名单 > 全局 IP 封禁 > 注解封禁。

3. 封禁类型

  • 全局 IP 封禁
    • 注解:@Ban(type = BanType.IP, windowSeconds = 60, maxRequests = 5, banDuration = 3600, timeUnit = TimeUnit.SECONDS)
    • 触发:60秒内超过5次请求,封禁1小时。
    • 全局开关:ip.ban.global-enabled=true
  • 接口封禁
    • 注解:@Ban(type = BanType.INTERFACE, windowSeconds = 60, maxRequests = 10, banDuration = 1800, timeUnit = TimeUnit.SECONDS)
    • 触发:60秒内超过10次请求,封禁30分钟。
  • IP+接口封禁
    • 注解:@Ban(type = BanType.IP_INTERFACE, windowSeconds = 30, maxRequests = 3, banDuration = 600, timeUnit = TimeUnit.SECONDS)
    • 触发:30秒内超过3次请求,封禁10分钟。

4. 测试流程

  1. 启动 Redis:确保 Redis 运行(localhost:6379)。
  2. 白名单
    • 配置 ip.whitelist=192.168.1.100,IP 192.168.1.100 访问 /api/ip-ban 等接口均放行。
  3. 黑名单
    • 配置 ip.blacklist=192.168.1.200,IP 192.168.1.200 访问任何接口返回 403。
  4. 全局 IP 封禁
    • 访问 /api/ip-ban 超过5次(60秒内),触发1小时封禁。
    • 或调用 /admin/ban-ip?ip=192.168.1.100&duration=3600&timeUnit=SECONDS
    • 启用 ip.ban.global-enabled=true,访问任何接口返回 403。
  5. 接口封禁
    • 访问 /api/interface-ban 超过10次(60秒内),触发30分钟封禁。
    • 或调用 /admin/ban-interface?interfaceName=public java.lang.String com.minimalism.redis.controller.TestController.interfaceBanEndpoint()&duration=1800&timeUnit=SECONDS
  6. IP+接口封禁
    • 访问 /api/ip-interface-ban 超过3次(30秒内),触发10分钟封禁。
    • 或调用 /admin/ban-ip-interface?ip=192.168.1.100&interfaceName=public java.lang.String com.minimalism.redis.controller.TestController.ipInterfaceBanEndpoint()&duration=600&timeUnit=SECONDS
  7. 解除封禁
    • 使用 /admin/unban-ip/admin/unban-interface/admin/unban-ip-interface
  8. 验证执行顺序
    • 检查日志,确认全局检查日志(执行全局检查)先于注解检查日志(执行 @Ban 注解检查)。

5. 接口名称

  • 默认使用方法签名(如 public java.lang.String com.minimalism.redis.controller.TestController.interfaceBanEndpoint())。
  • 建议简化为请求路径(如 /api/test),修改 interfaceName 获取逻辑(如在 RedisBanAspect 中使用 request.getRequestURI())。

注意事项

  • 白名单/黑名单:静态配置,修改 application.yml 需重启。如需动态管理,可扩展 AdminController 使用 Redis。
  • IP 获取:使用 HutoolServletUtil.getClientIP,支持代理情况。
  • Redis 配置:确保 Redis 可访问,必要时配置密码或防火墙。
  • 性能:Redisson 的 RSet, RBucket, 和 RAtomicLong 适合高并发,连接池可优化。
  • 安全性:避免 Redis 公网暴露,使用私有端点或认证。
  • 日志:使用 lombok@Slf4j 记录封禁检查日志。
  • 时间单位:支持 TimeUnit(如 SECONDS, MINUTES),灵活配置。
  • 执行顺序:全局检查(白名单、黑名单、全局 IP 封禁)先于 @Ban 注解检查,确保优先级正确。

扩展建议

  • 动态名单管理:添加 /admin/add-whitelist 等端点,使用 Redis 存储动态白名单/黑名单。
  • 日志记录:记录封禁事件到日志或数据库。
  • Redis Cluster:支持高可用 Redis 集群配置。
  • 复杂策略:支持基于用户角色或请求参数的封禁规则。
  • 接口名称优化:使用 request.getRequestURI() 获取路径(如 /api/test)替代方法签名,提升可读性。

参考

  • xAI API:如需 API 集成,参考 https://x.ai/api.
  • Redisson 文档:查看 Redisson 配置和性能优化。
  • Hutool 文档:查看 ServletUtilSpringUtil 使用方法。