欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

教你實現Java接口防刷

 更新時間:2024年05月12日 11:01:47   作者:逆流°只是風景-bjhxcc  
有些人會惡意提交,本文主要介紹了教你實現Java接口防刷,通過在一定時間內限制同一用戶對同一個接口的請求次數,具有一定的參考價值,感興趣的可以了解一下

前言

我們在瀏覽網站后臺的時候,假如我們頻繁請求,那么網站會提示 “請勿重復提交” 的字樣,那么這個功能究竟有什么用呢,又是如何實現的呢?

其實這就是接口防刷的一種處理方式,通過在一定時間內限制同一用戶對同一個接口的請求次數,其目的是為了防止惡意訪問導致服務器和數據庫的壓力增大,也可以防止用戶重復提交。

思路分析

接口防刷有很多種實現思路,例如:攔截器/AOP+Redis、攔截器/AOP+本地緩存、前端限制等等很多種實現思路,在這里我們來講一下 攔截器+Redis 的實現方式。

其原理就是 在接口請求前由攔截器攔截下來,然后去 redis 中查詢是否已經存在請求了,如果不存在則將請求緩存,若已經存在則返回異常。具體可以參考下圖

在這里插入圖片描述

具體實現

注:以下代碼中的 AjaxResult 為統一返回對象,這里就不貼出代碼了,大家可以根據自己的業(yè)務場景來編寫。

編寫 RedisUtils

import com.apply.core.exception.MyRedidsException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * Redis工具類
 */
@Component
public class RedisUtils {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /****************** common start ****************/
    /**
     * 指定緩存失效時間
     *
     * @param key  鍵
     * @param time 時間(秒)
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根據key 獲取過期時間
     *
     * @param key 鍵 不能為null
     * @return 時間(秒) 返回0代表為永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判斷key是否存在
     *
     * @param key 鍵
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 刪除緩存
     *
     * @param key 可以傳一個值 或多個
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }
    /****************** common end ****************/


    /****************** String start ****************/

    /**
     * 普通緩存獲取
     *
     * @param key 鍵
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通緩存放入
     *
     * @param key   鍵
     * @param value 值
     * @return true成功 false失敗
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通緩存放入并設置時間
     *
     * @param key   鍵
     * @param value 值
     * @param time  時間(秒) time要大于0 如果time小于等于0 將設置無限期
     * @return true成功 false 失敗
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 遞增
     *
     * @param key   鍵
     * @param delta 要增加幾(大于0)
     * @return
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new MyRedidsException("遞增因子必須大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 遞減
     *
     * @param key   鍵
     * @param delta 要減少幾(小于0)
     * @return
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new MyRedidsException("遞減因子必須大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }
    /****************** String end ****************/
}

定義Interceptor

import com.alibaba.fastjson.JSON;
import com.apply.common.utils.redis.RedisUtils;
import com.apply.common.validator.annotation.AccessLimit;
import com.apply.core.http.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

/**
 * @description 重復請求攔截
 * @date 2023-08-13 14:14
 */
@Component
public class RepeatRequestIntercept extends HandlerInterceptorAdapter {

    @Autowired
    private RedisUtils redisUtils;

    /**
     * 限定時間 單位:秒
     */
    private final int seconds = 1;

    /**
     * 限定請求次數
     */
    private final int max = 1;

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判斷請求是否為方法的請求
        if (handler instanceof HandlerMethod) {
            String key = request.getRemoteAddr() + "-" + request.getMethod() + "-" + request.getRequestURL();
            Object requestCountObj = redisUtils.get(key);
            if (Objects.isNull(requestCountObj)) {
                //若為空則為第一次請求
                redisUtils.set(key, 1, seconds);
            } else {
                response.setContentType("application/json;charset=utf-8");
                ServletOutputStream os = response.getOutputStream();
                AjaxResult<Void> result = AjaxResult.error(100, "請求已提交,請勿重復請求");
                String jsonString = JSON.toJSONString(result);
                os.write(jsonString.getBytes());
                os.flush();
                os.close();
                return false;
            }
        }
        return true;
    }

}

然后我們 將攔截器注冊到容器中

import com.apply.common.validator.intercept.RepeatRequestIntercept;
import com.apply.core.base.entity.Constants;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @description 
 * @date 2023-08-13 14:17
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private RepeatRequestIntercept repeatRequestIntercept;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(repeatRequestIntercept);
    }
}

我們再來編寫一個接口用于測試

import com.apply.common.validator.annotation.AccessLimit;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @description
 * @date 2023-08-13 14:35
 */
@RestController
public class TestController {

    @GetMapping("/test")
    public String test(){
        return "SUCCESS";
    }

}

最后我們來看一下結果是否符合我們的預期:

1秒內的第一次請求:

在這里插入圖片描述

1秒內的第二次請求:

在這里插入圖片描述

確實已經達到了我們的預期,但是如果我們對特定接口進行攔截,或對不同接口的限定攔截時間和次數不同的話,這種實現方式無法滿足我們的需求,所以我們要提出改進。

改進

我們可以去寫一個自定義的注解,并將 seconds 和 max 設置為該注解的屬性,再在攔截器中判斷請求的方法是否包含該注解,如果包含則執(zhí)行攔截方法,如果不包含則直接返回。
自定義注解 RequestLimit

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @description 冪等性注解
 * @date 2023-08-13 15:10
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestLimit {

    /**
     * 限定時間
     */
    int seconds() default 1;

    /**
     * 限定請求次數
     */
    int max() default 1;

}

改進 RepeatRequestIntercept

/**
 * @description 重復請求攔截
 * @date 2023-08-13 15:14
 */
@Component
public class RepeatRequestIntercept extends HandlerInterceptorAdapter {

    @Autowired
    private RedisUtils redisUtils;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判斷請求是否為方法的請求
        if (handler instanceof HandlerMethod) {
            HandlerMethod hm = (HandlerMethod) handler;
            //獲取方法中是否有冪等性注解
            RequestLimit anno = hm.getMethodAnnotation(RequestLimit.class);
            //若注解為空則直接返回
            if (Objects.isNull(anno)) {
                return true;
            }
            int seconds = anno.seconds();
            int max = anno.max();
            String key = request.getRemoteAddr() + "-" + request.getMethod() + "-" + request.getRequestURL();
            Object requestCountObj = redisUtils.get(key);
            if (Objects.isNull(requestCountObj)) {
                //若為空則為第一次請求
                redisUtils.set(key, 1, seconds);
            } else {
                //限定時間內的第n次請求
                int requestCount = Integer.parseInt(requestCountObj.toString());
                //判斷是否超過最大限定請求次數
                if (requestCount < max) {
                    //未超過則請求次數+1
                    redisUtils.incr(key, 1);
                } else {
                    //否則拒絕請求并返回信息
                    refuse(response);
                    return false;
                }
            }
        }
        return true;
    }
    /**
     * @date 2023-08-14 15:25
     * @author Bummon
     * @description 拒絕請求并返回結果
     */
    private void refuse(HttpServletResponse response) throws IOException {
        response.setContentType("application/json;charset=utf-8");
        ServletOutputStream os = response.getOutputStream();
        AjaxResult<Void> result = AjaxResult.error(100, "請求已提交,請勿重復請求");
        String jsonString = JSON.toJSONString(result);
        os.write(jsonString.getBytes());
        os.flush();
        os.close();
    }

}

這樣我們就可以實現我們的需求了。

到此這篇關于教你實現Java接口防刷的文章就介紹到這了,更多相關Java接口防刷內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 使用開源項目JAVAE2 進行視頻格式轉換

    使用開源項目JAVAE2 進行視頻格式轉換

    這篇文章主要介紹了使用開源項目JAVAE 進行視頻格式轉換,幫助大家更好的利用Java處理視頻,完成自身需求,感興趣的朋友可以了解下
    2020-11-11
  • 淺析如何在SpringBoot中實現數據脫敏

    淺析如何在SpringBoot中實現數據脫敏

    脫敏是指在不改變原數據結構的前提下,通過某種方式處理數據,使數據不能直接暴露用戶的真實信息,下面我們就來看看SpringBoot中實現數據脫敏的具體方法吧
    2024-03-03
  • 詳細解讀java同步之synchronized解析

    詳細解讀java同步之synchronized解析

    synchronized關鍵字是Java里面最基本的同步手段,下面我們來一起學習一下
    2019-05-05
  • 基于spring cloud多個消費端重復定義feign client的問題

    基于spring cloud多個消費端重復定義feign client的問題

    這篇文章主要介紹了spring cloud多個消費端重復定義feign client的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • Spring Cloud Gateway打造可擴展的微服務網關

    Spring Cloud Gateway打造可擴展的微服務網關

    微服務網關是一個位于客戶端和后端微服務之間的服務器,用于處理所有與客戶端的通信,Spring Cloud Gateway都是一個值得考慮的選擇,它將幫助您更好地管理和保護您的微服務,感興趣的朋友一起看看吧
    2023-11-11
  • Java開發(fā)常用類庫之Hutool詳解

    Java開發(fā)常用類庫之Hutool詳解

    這篇文章主要介紹了Java開發(fā)常用類庫之Hutool,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-02-02
  • Java jdk安裝及javac命令無效解決方案

    Java jdk安裝及javac命令無效解決方案

    這篇文章主要介紹了Java jdk安裝及javac命令無效解決方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-12-12
  • java中的匿名內部類詳細總結

    java中的匿名內部類詳細總結

    以下是對java中的匿名內部類進行了詳細的總結介紹,需要的朋友可以過來參考下,希望對大家有所幫助
    2013-10-10
  • 詳解Java設計模式——命令模式

    詳解Java設計模式——命令模式

    這篇文章主要介紹了Java設計模式——命令模式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-04-04
  • IDEA巧用Postfix Completion讓碼速起飛(小技巧)

    IDEA巧用Postfix Completion讓碼速起飛(小技巧)

    這篇文章主要介紹了IDEA巧用Postfix Completion讓碼速起飛,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-08-08

最新評論