Java實現(xiàn)API限流

企業(yè)應用中,特別是商城網(wǎng)站,針對于某些API接口請求過于頻繁作出某時間段限制訪問次數(shù),此文介紹的是通過注解+攔截器來實現(xiàn)限流。

  • 自定義注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentLimit {

    /**
     * 請求次數(shù)
     *
     * @return
     */
    int number();

    /**
     * 時間限制
     *
     * @return
     */
    long time();
}
  • 限流攔截器
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

import com.lei.tang.common.annotation.CurrentLimit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

@Slf4j
@Component
public class CurrentLimitInterceptor implements HandlerInterceptor {

    private final static String SEPARATOR = "-";

    @Autowired
    private RedisTemplate<String, Long> redisTemplate;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            //通過HandlerMethod獲取方法CurrentLimit注解
            CurrentLimit currentLimit = handlerMethod.getMethodAnnotation(CurrentLimit.class);
            //如果此方法存在限流注解
            if (currentLimit != null) {
                int number = currentLimit.number();
                long time = currentLimit.time();
                //如果次數(shù)和時間限制都大于0證明此處需要限流
                if (time > 0 && number > 0) {
                    //這里的可以定義的是項目路徑+API路徑+ip,當然我這里就沒去獲取實際的ip了。key可以根據(jù)你們項目實際場景去設定
                    String key = request.getContextPath() + SEPARATOR + request.getServletPath() + SEPARATOR + "ip";
                    //獲取reids緩存中的訪問次數(shù)
                    Long numberRedis = redisTemplate.opsForValue().get(key);
                    //如果是第一次訪問,則設置此ip訪問此API次數(shù)為1,并設置失效時間為注解中的時間
                    if (null == numberRedis) {
                        redisTemplate.opsForValue().set(key, 1L, time, TimeUnit.SECONDS);
                        return true;
                    }
                    //如果訪問次數(shù)大于注解設定則拋出異常
                    if (numberRedis >= number) {
                        throw new RuntimeException("請求頻繁,請稍后重試!");
                    }
                    //如果滿足限流條件則更新緩存次數(shù)
                    redisTemplate.opsForValue().set(key, numberRedis + 1);
                }
            }
        }
        return true;
    }
}
  • 將自定義的限流攔截器添加到WebMvcConfigurer中
import com.lei.tang.common.config.currentlimit.CurrentLimitInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Slf4j
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Autowired
    private CurrentLimitInterceptor currentLimitInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //此處也可以通過addPathPatterns方法添加此攔截器對部分請求路徑有效,也可以通過excludePathPatterns過濾請求路徑
        registry.addInterceptor(currentLimitInterceptor);
    }
}
  • 測試
import com.lei.tang.common.annotation.CurrentLimit;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/currentLimit")
public class CurrentLimitController {

    // 此處設置的是5秒內(nèi)運行訪問1次
    @CurrentLimit(number = 1, time = 5)
    @GetMapping("/test")
    public String test() {
        return "正常訪問";
    }
}

如有不到之處,歡迎各位留言指正,不慎感激。
更多文章:
點擊跳轉(zhuǎn)CSDN博客
點擊跳轉(zhuǎn)簡書博客
公眾號:代碼小搬運

掃碼關注代碼小搬運公眾號

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容