アイデア:
- カスタム注釈は制限サイクルと制限時間を定義します
- インターセプターはすべてのリクエストをインターセプトし、電流制限アノテーションを使用した API が見つかった場合は、そのインターフェースに対するリクエスト数が redis に記録されているかどうかを確認し、記録されている場合はカスタム アノテーションの最大しきい値を超えていないと判断します。しきい値に達すると、リクエストはインターセプトされます。それ以外の場合は、リクエストの数を 1 つ増やして解放します。
コード:
このコードはインターセプターの実装であり、特定のインターフェイスへのユーザー アクセスの頻度を制限するために使用されます。具体的には、ユーザーのIPアドレス、リクエストメソッド、リクエストURIなどの情報に基づいてRedis Keyを生成し、Redisのincr()メソッドを使用してKeyの値を自動インクリメントし、アクセスしているユーザーの数をカウントします。インターフェースの周波数。ユーザーがインターフェイスにアクセスする回数が設定された最大値を超えると、エラー メッセージが返され、ユーザーのアクセスは拒否されます。
このうち、AccessLimit はカスタム アノテーションで、アクセス間隔 (秒)、最大訪問数 (maxCount)、エラー メッセージ (msg) などのメソッドのアクセス制御情報をメソッドにマークするために使用されます。preHandle() メソッドでは、現在のリクエストに対応する HandlerMethod インスタンスがハンドラー パラメーターを通じて取得され、メソッドの AccessLimit アノテーションがインスタンスから取得されてアクセス制御情報が取得されます。メソッドに AccessLimit アノテーションがない場合は、メソッドがアクセス制御を必要とせず、インターセプターを直接通過することを意味します。
特定の実装では、Objects.nonNull() メソッドを使用してカウント変数が空かどうかを判断し、カウントが null の場合の null ポインタ例外を回避します。同時に、Redis の incr() メソッドを使用する場合は、Redis 接続が失敗する可能性にも注意する必要があるため、例外をキャプチャして処理する必要があります。
/**
* Redis拦截器
* API请求限流
* @author DarkClouds
* @date 2023/05/10
*/
@Slf4j
@Component
public class AccessLimitInterceptor implements HandlerInterceptor {
@Autowired
private RedisService redisService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
boolean result = true;
// Handler 是否为 HandlerMethod 实例
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);
//方法上没有访问控制的注解,直接通过
if (accessLimit != null) {
int seconds = accessLimit.seconds();
int maxCount = accessLimit.maxCount();
String ip = IpUtils.getIpAddress(request);
String method = request.getMethod();
String requestUri = request.getRequestURI();
String redisKey = ip + ":" + method + ":" + requestUri;
try {
Long count = redisService.incr(redisKey, 1L);
// 第一次访问
if (Objects.nonNull(count) && count == 1) {
redisService.setExpire(redisKey, seconds, TimeUnit.SECONDS);
} else if (count > maxCount) {
//触发限制时的消息提示返回给前端
WebUtils.renderString(response, JSON.toJSONString(Result.fail(accessLimit.msg())));
log.warn(redisKey + "请求次数超过每" + seconds + "秒" + maxCount + "次");
result = false;
}
} catch (RedisConnectionFailureException e) {
log.error("redis错误: " + e.getMessage());
result = false;
}
}
}
return result;
}
}
/**
* Redis限流注解
*
* @author DarkClouds
* @date 2023/05/10
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
/**
* 限制周期(秒)
*/
int seconds();
/**
* 规定周期内限制次数
*/
int maxCount();
/**
* 触发限制时的消息提示
*/
String msg() default "操作过于频繁请稍后再试";
}
デモ
@AccessLimit(seconds = 60, maxCount = 3)
public void test() {
//当60秒内连续请求该方法超过3次,将被限流,无法访问
}