最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Redis+攔截器實(shí)現(xiàn)接口防刷

 更新時(shí)間:2023年08月11日 11:27:33   作者:Bummon  
接口防刷有很多種實(shí)現(xiàn)思路,例如:攔截器/AOP+Redis、攔截器/AOP+本地緩存、前端限制等等很多種實(shí)現(xiàn)思路,本文主要來講一下?攔截器+Redis?的實(shí)現(xiàn)方式,需要的可以參考下

前言

我們?cè)跒g覽網(wǎng)站后臺(tái)的時(shí)候,假如我們頻繁請(qǐng)求,那么網(wǎng)站會(huì)提示 “請(qǐng)勿重復(fù)提交” 的字樣,那么這個(gè)功能究竟有什么用呢,又是如何實(shí)現(xiàn)的呢?

其實(shí)這就是接口防刷的一種處理方式,通過在一定時(shí)間內(nèi)限制同一用戶對(duì)同一個(gè)接口的請(qǐng)求次數(shù),其目的是為了防止惡意訪問導(dǎo)致服務(wù)器和數(shù)據(jù)庫的壓力增大,也可以防止用戶重復(fù)提交。

思路分析

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

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

具體實(shí)現(xiàn)

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

編寫 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 ****************/
    /**
     * 指定緩存失效時(shí)間
     *
     * @param key  鍵
     * @param time 時(shí)間(秒)
     * @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;
        }
    }
    /**
     * 根據(jù)key 獲取過期時(shí)間
     *
     * @param key 鍵 不能為null
     * @return 時(shí)間(秒) 返回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 可以傳一個(gè)值 或多個(gè)
     */
    @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;
        }
    }
    /**
     * 普通緩存放入并設(shè)置時(shí)間
     *
     * @param key   鍵
     * @param value 值
     * @param time  時(shí)間(秒) time要大于0 如果time小于等于0 將設(shè)置無限期
     * @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;
/**
 * @author Bummon
 * @description 重復(fù)請(qǐng)求攔截
 * @date 2023-08-10 14:14
 */
@Component
public class RepeatRequestIntercept extends HandlerInterceptorAdapter {
    @Autowired
    private RedisUtils redisUtils;
    /**
     * 限定時(shí)間 單位:秒
     */
    private final int seconds = 1;
    /**
     * 限定請(qǐng)求次數(shù)
     */
    private final int max = 1;
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判斷請(qǐng)求是否為方法的請(qǐng)求
        if (handler instanceof HandlerMethod) {
            String key = request.getRemoteAddr() + "-" + request.getMethod() + "-" + request.getRequestURL();
            Object requestCountObj = redisUtils.get(key);
            if (Objects.isNull(requestCountObj)) {
                //若為空則為第一次請(qǐng)求
                redisUtils.set(key, 1, seconds);
            } else {
                response.setContentType("application/json;charset=utf-8");
                ServletOutputStream os = response.getOutputStream();
                AjaxResult<Void> result = AjaxResult.error(100, "請(qǐng)求已提交,請(qǐng)勿重復(fù)請(qǐng)求");
                String jsonString = JSON.toJSONString(result);
                os.write(jsonString.getBytes());
                os.flush();
                os.close();
                return false;
            }
        }
        return true;
    }
}

然后我們 將攔截器注冊(cè)到容器中

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;
/**
 * @author Bummon
 * @description 
 * @date 2023-08-10 14:17
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private RepeatRequestIntercept repeatRequestIntercept;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(repeatRequestIntercept);
    }
}

我們?cè)賮砭帉懸粋€(gè)接口用于測(cè)試

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;
/**
 * @author Bummon
 * @description
 * @date 2023-08-10 14:35
 */
@RestController
public class TestController {
    @GetMapping("/test")
    public String test(){
        return "SUCCESS";
    }
}

最后我們來看一下結(jié)果是否符合我們的預(yù)期:

1秒內(nèi)的第一次請(qǐng)求:

1秒內(nèi)的第二次請(qǐng)求:

確實(shí)已經(jīng)達(dá)到了我們的預(yù)期,但是如果我們對(duì)特定接口進(jìn)行攔截,或?qū)Σ煌涌诘南薅〝r截時(shí)間和次數(shù)不同的話,這種實(shí)現(xiàn)方式無法滿足我們的需求,所以我們要提出改進(jìn)。

改進(jìn)

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

自定義注解 RequestLimit

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @author Bummon
 * @description 冪等性注解
 * @date 2023-08-10 15:10
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestLimit {
    /**
     * 限定時(shí)間
     */
    int seconds() default 1;
    /**
     * 限定請(qǐng)求次數(shù)
     */
    int max() default 1;
}

改進(jìn) RepeatRequestIntercept

/**
 * @author Bummon
 * @description 重復(fù)請(qǐng)求攔截
 * @date 2023-08-10 15:14
 */
@Component
public class RepeatRequestIntercept extends HandlerInterceptorAdapter {
    @Autowired
    private RedisUtils redisUtils;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判斷請(qǐng)求是否為方法的請(qǐng)求
        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)) {
                //若為空則為第一次請(qǐng)求
                redisUtils.set(key, 1, seconds);
            } else {
                //限定時(shí)間內(nèi)的第n次請(qǐng)求
                int requestCount = Integer.parseInt(requestCountObj.toString());
                //判斷是否超過最大限定請(qǐng)求次數(shù)
                if (requestCount < max) {
                    //未超過則請(qǐng)求次數(shù)+1
                    redisUtils.incr(key, 1);
                } else {
                    //否則拒絕請(qǐng)求并返回信息
                    refuse(response);
                    return false;
                }
            }
        }
        return true;
    }
    /**
     * @param response
     * @date 2023-08-10 15:25
     * @author Bummon
     * @description 拒絕請(qǐng)求并返回結(jié)果
     */
    private void refuse(HttpServletResponse response) throws IOException {
        response.setContentType("application/json;charset=utf-8");
        ServletOutputStream os = response.getOutputStream();
        AjaxResult<Void> result = AjaxResult.error(100, "請(qǐng)求已提交,請(qǐng)勿重復(fù)請(qǐng)求");
        String jsonString = JSON.toJSONString(result);
        os.write(jsonString.getBytes());
        os.flush();
        os.close();
    }
}

這樣我們就可以實(shí)現(xiàn)我們的需求了。

到此這篇關(guān)于Redis+攔截器實(shí)現(xiàn)接口防刷的文章就介紹到這了,更多相關(guān)接口防刷內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 一文教你學(xué)會(huì)Redis的事務(wù)

    一文教你學(xué)會(huì)Redis的事務(wù)

    Redis?作為內(nèi)存的存儲(chǔ)中間件,已經(jīng)是面試的面試題必問之一了。今天小編就來和大家一起來聊聊Redis的事務(wù)吧,希望對(duì)大家有所幫助
    2022-08-08
  • Windows系統(tǒng)設(shè)置Redis服務(wù)使其開機(jī)自啟動(dòng)

    Windows系統(tǒng)設(shè)置Redis服務(wù)使其開機(jī)自啟動(dòng)

    Redis是一種鍵值對(duì)數(shù)據(jù)庫,也稱為內(nèi)存數(shù)據(jù)庫,因?yàn)樗梢詫?shù)據(jù)存儲(chǔ)在內(nèi)存中,而不是在磁盤上,下面這篇文章主要給大家介紹了關(guān)于Windows系統(tǒng)設(shè)置Redis服務(wù)使其開機(jī)自啟動(dòng)的相關(guān)資料,需要的朋友可以參考下
    2024-01-01
  • 詳解redis數(shù)據(jù)結(jié)構(gòu)之壓縮列表

    詳解redis數(shù)據(jù)結(jié)構(gòu)之壓縮列表

    這篇文章主要介紹了詳解redis數(shù)據(jù)結(jié)構(gòu)之壓縮列表的相關(guān)資料,壓縮列表在redis中的結(jié)構(gòu)體名稱為ziplist,其是redis為了節(jié)約內(nèi)存而聲明的一種數(shù)據(jù)結(jié)構(gòu),需要的朋友可以參考下
    2017-05-05
  • Redis基本數(shù)據(jù)類型哈希Hash常用操作命令

    Redis基本數(shù)據(jù)類型哈希Hash常用操作命令

    這篇文章主要為大家介紹了Redis基本數(shù)據(jù)類型哈希Hash常用操作,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • 使用Spring?Boot實(shí)現(xiàn)Redis鍵過期回調(diào)功能示例詳解

    使用Spring?Boot實(shí)現(xiàn)Redis鍵過期回調(diào)功能示例詳解

    這篇文章主要介紹了使用Spring?Boot實(shí)現(xiàn)Redis鍵過期回調(diào)功能,就是一個(gè)實(shí)現(xiàn)Redis鍵過期回調(diào)功能的Spring?Boot應(yīng)用的示例,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-07-07
  • Redis中管道操作pipeline的實(shí)現(xiàn)

    Redis中管道操作pipeline的實(shí)現(xiàn)

    RedisPipeline是一種優(yōu)化客戶端與服務(wù)器通信的技術(shù),通過批量發(fā)送和接收命令減少網(wǎng)絡(luò)往返次數(shù),提高命令執(zhí)行效率,本文就來介紹一下Redis中管道操作pipeline的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-03-03
  • Redis 搭建哨兵集群的操作步驟

    Redis 搭建哨兵集群的操作步驟

    在 主從架構(gòu)Redis搭建主從集群 中,一個(gè)slave節(jié)點(diǎn)掛了無影響,但是master節(jié)點(diǎn)掛了,就無法進(jìn)行寫操作了,影響高可用,Redis 提供了哨兵(Sentinel)機(jī)制來實(shí)現(xiàn)主從集群的自動(dòng)故障恢復(fù),本文給大家介紹了Redis 搭建哨兵集群的操作步驟,需要的朋友可以參考下
    2023-08-08
  • Redis出現(xiàn)中文亂碼的問題及解決

    Redis出現(xiàn)中文亂碼的問題及解決

    這篇文章主要介紹了Redis出現(xiàn)中文亂碼的問題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-06-06
  • Redis的數(shù)據(jù)淘汰策略使用及注意事項(xiàng)

    Redis的數(shù)據(jù)淘汰策略使用及注意事項(xiàng)

    Redis的數(shù)據(jù)淘汰策略是指在內(nèi)存不足時(shí),如何決定哪些數(shù)據(jù)需要被淘汰以釋放內(nèi)存空間,Redis提供了多種數(shù)據(jù)淘汰策略,如noeviction、allkeys-lru、allkeys-lfu、volatile-lru、volatile-lfu、volatile-random、allkeys-random和volatile-ttl
    2025-12-12
  • Redis中Scan命令的踩坑實(shí)錄

    Redis中Scan命令的踩坑實(shí)錄

    這篇文章主要給大家介紹了關(guān)于Redis中Scan命令踩坑的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07

最新評(píng)論

托里县| 柯坪县| 上杭县| 永修县| 北海市| 松潘县| 西乌珠穆沁旗| 深圳市| 原平市| 恭城| 商水县| 安乡县| 泊头市| 资源县| 化德县| 镇平县| 策勒县| 公主岭市| 临颍县| 马山县| 旬阳县| 梨树县| 枣庄市| 定南县| 苍南县| 安徽省| 泰和县| 托克托县| 房产| 台东市| 榆林市| 德钦县| 夹江县| 无为县| 香河县| 隆安县| 巴林右旗| 屏东市| 喀什市| 南陵县| 康马县|