使用redis 進行請求限流

java_使用redis 進行請求限流

應用場景:在后端處理流程復雜,前端可能會高頻點擊的情況下,做請求限流來進行系統保護;

本文的應用場景為:前端請求導出excel,出現大數據量的情況下,限制每個ip 用戶對于每個地區條件,一分鐘內只能請求兩次

思路:使用注解在對要限流的方法進行標識,自定義攔截器,在進入這個方法體之前,通過redis 獲取緩存的key,該key 設置一個超時時間,該key 的value 為訪問次數,每訪問一次便+1,達到次數后則不進入方法體,直接返回請求已滿,直到key 過期;

代碼實現:

0.配置信息:

pom.xml :

<dependency>? <groupId>org.springframework.boot</groupId>? <artifactId>spring-boot-starter-data-redis</artifactId>? <version>2.5.7</version></dependency>

1, 自定義注解 ======================================================

@Retention(RUNTIME)//運行時有效

@Target(ElementType.METHOD)//用在方法上

public @interface AccessLimit {

?? //時間范圍(單位:秒)

?? int seconds();

?? //在這個時間范圍內最大訪問次數

?? int maxCount();

}

2.自定義攔截器============================================================

@Slf4j

@Component

public class ExportExcelLimitInterceptor implements HandlerInterceptor {

?? @Autowired

?? private RedisTemplate redisTemplate;

?? @Override

?? public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

? ? ?? if (handler instanceof HandlerMethod) {

? ? ? ? ?? HandlerMethod hm = (HandlerMethod) handler;

? ? ? ? ?? //設置redisTemplate的序列化方式(必須設置為這種方式,因為要用到incr)

? ? ? ? ?? redisTemplate.setKeySerializer(new StringRedisSerializer());

? ? ? ? ?? redisTemplate.setValueSerializer(new StringRedisSerializer());

? ? ? ? ?? AccessLimit accessLimit = hm.getMethodAnnotation(AccessLimit.class);

? ? ? ? ?? if (null == accessLimit) {

? ? ? ? ? ? ?? return true;

? ? ? ? ?? }

? ? ? ? ?? int seconds = accessLimit.seconds();

? ? ? ? ?? int maxCount = accessLimit.maxCount();

? ? ? ? ?? String ipAddr = IpUtil.getIpAddr(request);

? ? ? ? ?? String key = request.getContextPath() + ":" + request.getServletPath() + "_exportExcel"+":" + ipAddr ;

? ? ? ? ?? String countStr = (String) redisTemplate.opsForValue().get(key);

? ? ? ? ?? Integer count = null;

? ? ? ? ?? //如果不是第一次訪問,則把訪問次數轉換為integer類型

? ? ? ? ?? if(countStr != null){

? ? ? ? ? ? ?? count = Integer.valueOf(redisTemplate.opsForValue().get(key).toString());

? ? ? ? ?? }

? ? ? ? ?? log.info("count:{}",count);

? ? ? ? ?? //拿到訪問次數的過期時間

? ? ? ? ?? Long keySeconds = redisTemplate.getExpire(key);

? ? ? ? ?? if (null == count || -1 == count) {

? ? ? ? ? ? ?? redisTemplate.opsForValue().set(key,String.valueOf(1));

? ? ? ? ? ? ?? //設置過期時間

? ? ? ? ? ? ?? redisTemplate.expire(key,seconds, TimeUnit.SECONDS);

? ? ? ? ? ? ?? return true;

? ? ? ? ?? }

? ? ? ? ?? if (count < maxCount) {

? ? ? ? ? ? ?? log.info("count:{}",count);

? ? ? ? ? ? ?? //訪問次數+1

? ? ? ? ? ? ?? redisTemplate.opsForValue().increment(key,1);

? ? ? ? ? ? ?? //設置剩余過期時間(修改完該key的value值后,對應的過期時間會失效,需重新設置)

? ? ? ? ? ? ?? redisTemplate.expire(key,keySeconds, TimeUnit.SECONDS);

? ? ? ? ? ? ?? return true;

? ? ? ? ?? }

? ? ? ? ?? if (count >= maxCount) {

// ? ? ? ? ? ? ?? response 返回 json 請求過于頻繁請稍后再試

? ? ? ? ? ? ?? this.responseResult(response, new Result(500, "訪問次數已超過限制,請稍后重試"));

? ? ? ? ? ? ?? return false;

? ? ? ? ?? }

? ? ?? }

? ? ?? return true;

?? }

?? void responseResult(HttpServletResponse response, Result result) throws IOException {

? ? ?? response.setHeader("Content-Type", "application/json;charset=utf8");

? ? ?? Writer writer = response.getWriter();

? ? ?? writer.write(JSONObject.toJSONString(result));

? ? ?? writer.close();

?? }

}

3.添加攔截器到webconfig ===============================================================================

@Configuration

public class WebMvcConfig implements WebMvcConfigurer {

?? @Autowired

?? private ExportExcelLimitInterceptor excelLimitInterceptor; // 訪問限制攔截器


?? @Override

?? public void addInterceptors(InterceptorRegistry registry) {

? ? ?? registry.addInterceptor(excelLimitInterceptor)

? ? ? ? ? ? ?? .addPathPatterns("/qiannan/test");

?? }

}

4.添加注解到方法體===================================================================================

@AccessLimit(seconds=30,maxCount=2)

@GetMapping("/qiannan/test")

public Object test(String id) throws IOException {

?? return linkFeignClient.detail(id);

}

5. 用到的工具類:

public class IpUtil {

?? private IpUtil() { }

?? /**

? ? * 獲取登錄用戶的IP地址

? ? * @param request

? ? * @return

? ? */

?? public static String getIpAddr(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();

? ? ?? }

? ? ?? if (ip.equals("0:0:0:0:0:0:0:1")) {

? ? ? ? ?? ip = "127.0.0.1";

? ? ?? }

? ? ?? if (ip.split(",").length > 1) {

? ? ? ? ?? ip = ip.split(",")[0];

? ? ?? }

? ? ?? return ip;

?? }

?? //ip轉化為有序的長整形

?? public static long getIp2long(String ip) {

? ? ?? ip = ip.trim();

? ? ?? String[] ips = ip.split("\\.");

? ? ?? long ip2long = 0L;

? ? ?? for (int i = 0; i < 4; ++i) {

? ? ? ? ?? ip2long = ip2long << 8 | Integer.parseInt(ips[i]);

? ? ?? }

? ? ?? return ip2long;

?? }

}

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容