使用redis 進(jìn)行請求限流

java_使用redis 進(jìn)行請求限流

應(yīng)用場景:在后端處理流程復(fù)雜,前端可能會高頻點(diǎn)擊的情況下,做請求限流來進(jìn)行系統(tǒng)保護(hù);

本文的應(yīng)用場景為:前端請求導(dǎo)出excel,出現(xiàn)大數(shù)據(jù)量的情況下,限制每個ip 用戶對于每個地區(qū)條件,一分鐘內(nèi)只能請求兩次

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

代碼實現(xiàn):

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)//運(yùn)行時有效

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

public @interface AccessLimit {

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

?? int seconds();

?? //在這個時間范圍內(nèi)最大訪問次數(shù)

?? 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;

? ? ? ? ?? //設(shè)置redisTemplate的序列化方式(必須設(shè)置為這種方式,因為要用到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;

? ? ? ? ?? //如果不是第一次訪問,則把訪問次數(shù)轉(zhuǎn)換為integer類型

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

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

? ? ? ? ?? }

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

? ? ? ? ?? //拿到訪問次數(shù)的過期時間

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

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

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

? ? ? ? ? ? ?? //設(shè)置過期時間

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

? ? ? ? ? ? ?? return true;

? ? ? ? ?? }

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

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

? ? ? ? ? ? ?? //訪問次數(shù)+1

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

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

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

? ? ? ? ? ? ?? return true;

? ? ? ? ?? }

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

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

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

? ? ? ? ? ? ?? 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轉(zhuǎn)化為有序的長整形

?? 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;

?? }

}

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

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

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