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

SpringBoot處理接口冪等性的兩種方法詳解

 更新時(shí)間:2022年06月21日 08:32:04   作者:_江南一點(diǎn)雨  
接口冪等性處理算是一個(gè)非常常見(jiàn)的需求了,我們?cè)诤芏囗?xiàng)目中其實(shí)都會(huì)遇到。本文為大家總結(jié)了兩個(gè)處理接口冪等性的兩種常見(jiàn)方案,需要的可以參考一下

在上周發(fā)布的 TienChin 項(xiàng)目視頻中,我和大家一共梳理了六種冪等性解決方案,接口冪等性處理算是一個(gè)非常常見(jiàn)的需求了,我們?cè)诤芏囗?xiàng)目中其實(shí)都會(huì)遇到。今天我們來(lái)看看兩種比較簡(jiǎn)單的實(shí)現(xiàn)思路。

1. 接口冪等性實(shí)現(xiàn)方案梳理

其實(shí)接口冪等性的實(shí)現(xiàn)方案還是蠻多的,我這里和小伙伴們分享兩種比較常見(jiàn)的方案。

1.1 基于 Token

基于 Token 這種方案的實(shí)現(xiàn)思路很簡(jiǎn)單,整個(gè)流程分兩步:

  • 客戶端發(fā)送請(qǐng)求,從服務(wù)端獲取一個(gè) Token 令牌,每次請(qǐng)求獲取到的都是一個(gè)全新的令牌。
  • 客戶端發(fā)送請(qǐng)求的時(shí)候,攜帶上第一步的令牌,處理請(qǐng)求之前,先校驗(yàn)令牌是否存在,當(dāng)請(qǐng)求處理成功,就把令牌刪除掉。

大致的思路就是上面這樣,當(dāng)然具體的實(shí)現(xiàn)則會(huì)復(fù)雜很多,有很多細(xì)節(jié)需要注意

1.2 基于請(qǐng)求參數(shù)校驗(yàn)

最近在 TienChin 項(xiàng)目中使用的是另外一種方案,這種方案是基于請(qǐng)求參數(shù)來(lái)判斷的,如果在短時(shí)間內(nèi),同一個(gè)接口接收到的請(qǐng)求參數(shù)相同,那么就認(rèn)為這是重復(fù)的請(qǐng)求,拒絕處理,大致上就是這么個(gè)思路。

相比于第一種方案,第二種方案相對(duì)來(lái)說(shuō)省事一些,因?yàn)橹挥幸淮握?qǐng)求,不需要專(zhuān)門(mén)去服務(wù)端拿令牌。在高并發(fā)環(huán)境下這種方案優(yōu)勢(shì)比較明顯。

所以今天我就來(lái)和大家聊聊第二種方案的實(shí)現(xiàn),后面在 TienChin 項(xiàng)目視頻中也會(huì)和大家細(xì)講。

2. 基于請(qǐng)求參數(shù)的校驗(yàn)

首先我們新建一個(gè) Spring Boot 項(xiàng)目,引入 Web 和 Redis 依賴,新建完成后,先來(lái)配置一下 Redis 的基本信息,如下:

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=123

為了后續(xù) Redis 操作方便,我們?cè)賮?lái)對(duì) Redis 進(jìn)行一個(gè)簡(jiǎn)單封裝,如下:

@Component
public class RedisCache {
    @Autowired
    public RedisTemplate redisTemplate;
    public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }
    public <T> T getCacheObject(final String key) {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }
}

這個(gè)比較簡(jiǎn)單,一個(gè)存數(shù)據(jù),一個(gè)讀數(shù)據(jù)。

接下來(lái)我們自定義一個(gè)注解,在需要進(jìn)行冪等性處理的接口上,添加該注解即可,將來(lái)這個(gè)接口就會(huì)自動(dòng)的進(jìn)行冪等性處理。

@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RepeatSubmit {
    /**
     * 間隔時(shí)間(ms),小于此時(shí)間視為重復(fù)提交
     */
    public int interval() default 5000;

    /**
     * 提示消息
     */
    public String message() default "不允許重復(fù)提交,請(qǐng)稍候再試";
}

這個(gè)注解我們通過(guò)攔截器來(lái)進(jìn)行解析,解析代碼如下:

public abstract class RepeatSubmitInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            Method method = handlerMethod.getMethod();
            RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
            if (annotation != null) {
                if (this.isRepeatSubmit(request, annotation)) {
                    Map<String, Object> map = new HashMap<>();
                    map.put("status", 500);
                    map.put("msg", annotation.message());
                    response.setContentType("application/json;charset=utf-8");
                    response.getWriter().write(new ObjectMapper().writeValueAsString(map));
                    return false;
                }
            }
            return true;
        } else {
            return true;
        }
    }

    /**
     * 驗(yàn)證是否重復(fù)提交由子類(lèi)實(shí)現(xiàn)具體的防重復(fù)提交的規(guī)則
     *
     * @param request
     * @return
     * @throws Exception
     */
    public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);
}

這個(gè)攔截器是一個(gè)抽象類(lèi),將接口方法攔截下來(lái),然后找到接口上的 @RepeatSubmit 注解,調(diào)用 isRepeatSubmit 方法去判斷是否是重復(fù)提交的數(shù)據(jù),該方法在這里是一個(gè)抽象方法,我們需要再定義一個(gè)類(lèi)繼承自這個(gè)抽象類(lèi),在新的子類(lèi)中,可以有不同的冪等性判斷邏輯,這里我們就是根據(jù) URL 地址+參數(shù) 來(lái)判斷冪等性條件是否滿足:

@Component
public class SameUrlDataInterceptor extends RepeatSubmitInterceptor {
    public final String REPEAT_PARAMS = "repeatParams";

    public final String REPEAT_TIME = "repeatTime";
    public final static String REPEAT_SUBMIT_KEY = "REPEAT_SUBMIT_KEY";

    private String header = "Authorization";

    @Autowired
    private RedisCache redisCache;

    @SuppressWarnings("unchecked")
    @Override
    public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) {
        String nowParams = "";
        if (request instanceof RepeatedlyRequestWrapper) {
            RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
            try {
                nowParams = repeatedlyRequest.getReader().readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // body參數(shù)為空,獲取Parameter的數(shù)據(jù)
        if (StringUtils.isEmpty(nowParams)) {
            try {
                nowParams = new ObjectMapper().writeValueAsString(request.getParameterMap());
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        Map<String, Object> nowDataMap = new HashMap<String, Object>();
        nowDataMap.put(REPEAT_PARAMS, nowParams);
        nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());

        // 請(qǐng)求地址(作為存放cache的key值)
        String url = request.getRequestURI();

        // 唯一值(沒(méi)有消息頭則使用請(qǐng)求地址)
        String submitKey = request.getHeader(header);

        // 唯一標(biāo)識(shí)(指定key + url + 消息頭)
        String cacheRepeatKey = REPEAT_SUBMIT_KEY + url + submitKey;

        Object sessionObj = redisCache.getCacheObject(cacheRepeatKey);
        if (sessionObj != null) {
            Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
            if (compareParams(nowDataMap, sessionMap) && compareTime(nowDataMap, sessionMap, annotation.interval())) {
                return true;
            }
        }
        redisCache.setCacheObject(cacheRepeatKey, nowDataMap, annotation.interval(), TimeUnit.MILLISECONDS);
        return false;
    }

    /**
     * 判斷參數(shù)是否相同
     */
    private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) {
        String nowParams = (String) nowMap.get(REPEAT_PARAMS);
        String preParams = (String) preMap.get(REPEAT_PARAMS);
        return nowParams.equals(preParams);
    }

    /**
     * 判斷兩次間隔時(shí)間
     */
    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, int interval) {
        long time1 = (Long) nowMap.get(REPEAT_TIME);
        long time2 = (Long) preMap.get(REPEAT_TIME);
        if ((time1 - time2) < interval) {
            return true;
        }
        return false;
    }
}

我們來(lái)看下具體的實(shí)現(xiàn)邏輯:

  1. 首先判斷當(dāng)前的請(qǐng)求對(duì)象是不是 RepeatedlyRequestWrapper,如果是,說(shuō)明當(dāng)前的請(qǐng)求參數(shù)是 JSON,那么就通過(guò) IO 流將參數(shù)讀取出來(lái),這塊小伙伴們要結(jié)合上篇文章共同來(lái)理解,否則可能會(huì)覺(jué)得云里霧里的,傳送門(mén)JSON 數(shù)據(jù)讀一次就沒(méi)了,怎么辦?。
  2. 如果在第一步中,并沒(méi)有拿到參數(shù),那么說(shuō)明參數(shù)可能并不是 JSON 格式,而是 key-value 格式,那么就以 key-value 的方式讀取出來(lái)參數(shù),并將之轉(zhuǎn)為一個(gè) JSON 字符串。
  3. 接下來(lái)構(gòu)造一個(gè) Map,將前面讀取到的參數(shù)和當(dāng)前時(shí)間存入到 Map 中。
  4. 接下來(lái)構(gòu)造存到 Redis 中的數(shù)據(jù)的 key,這個(gè) key 由固定前綴 + 請(qǐng)求 URL 地址 + 請(qǐng)求頭的認(rèn)證令牌組成,這塊請(qǐng)求頭的令牌還是非常重要需要有的,只有這樣才能區(qū)分出來(lái)當(dāng)前用戶提交的數(shù)據(jù)(如果是 RESTful 風(fēng)格的接口,那么為了區(qū)分,也可以將接口的請(qǐng)求方法作為參數(shù)拼接到 key 中)。
  5. 接下來(lái)就去 Redis 中獲取數(shù)據(jù),獲取到之后,分別去比較參數(shù)是否相同以及時(shí)間是否過(guò)期。
  6. 如果判斷都沒(méi)問(wèn)題,返回 true,表示這個(gè)請(qǐng)求重復(fù)了。
  7. 否則返回說(shuō)明這是用戶對(duì)這個(gè)接口第一次提交數(shù)據(jù)或者是已經(jīng)過(guò)了時(shí)間窗口了,那么就把參數(shù)字符串重新緩存到 Redis 中,并返回 false,表示請(qǐng)求沒(méi)問(wèn)題。

好啦,做完這一切,最后我們?cè)賮?lái)配置一下攔截器即可:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    RepeatSubmitInterceptor repeatSubmitInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(repeatSubmitInterceptor)
                .addPathPatterns("/**");
    }
}

如此,我們的接口冪等性就處理好啦~在需要的時(shí)候,就可以直接在接口上使用啦:

@RestController
public class HelloController {

    @PostMapping("/hello")
    @RepeatSubmit(interval = 100000)
    public String hello(@RequestBody String msg) {
        System.out.println("msg = " + msg);
        return "hello";
    }
}

到此這篇關(guān)于SpringBoot處理接口冪等性的兩種方法詳解的文章就介紹到這了,更多相關(guān)SpringBoot處理接口冪等性內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Mybatis-Plus進(jìn)階分頁(yè)與樂(lè)觀鎖插件及通用枚舉和多數(shù)據(jù)源詳解

    Mybatis-Plus進(jìn)階分頁(yè)與樂(lè)觀鎖插件及通用枚舉和多數(shù)據(jù)源詳解

    這篇文章主要介紹了Mybatis-Plus的分頁(yè)插件與樂(lè)觀鎖插件還有通用枚舉和多數(shù)據(jù)源的相關(guān)介紹,文中代碼附有詳細(xì)的注釋?zhuān)信d趣的朋友來(lái)看看吧
    2022-03-03
  • Java調(diào)用新浪api通過(guò)Ip查詢地區(qū)

    Java調(diào)用新浪api通過(guò)Ip查詢地區(qū)

    這篇文章主要介紹了Java調(diào)用新浪接口通過(guò)Ip查詢地區(qū),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-05-05
  • Java反射和動(dòng)態(tài)代理的使用解讀

    Java反射和動(dòng)態(tài)代理的使用解讀

    這篇文章主要介紹了Java反射和動(dòng)態(tài)代理的概念、使用方法和應(yīng)用場(chǎng)景,反射允許在運(yùn)行時(shí)動(dòng)態(tài)地獲取類(lèi)的信息和調(diào)用類(lèi)的方法,而動(dòng)態(tài)代理則可以在不修改原有代碼的情況下,為方法調(diào)用添加額外的功能
    2025-02-02
  • idea中創(chuàng)建多module的maven工程的方法

    idea中創(chuàng)建多module的maven工程的方法

    這篇文章主要介紹了idea中創(chuàng)建多module的maven工程的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-10-10
  • Java實(shí)現(xiàn)數(shù)字金額轉(zhuǎn)中文大寫(xiě)金額的完整過(guò)程

    Java實(shí)現(xiàn)數(shù)字金額轉(zhuǎn)中文大寫(xiě)金額的完整過(guò)程

    本程序?qū)崿F(xiàn)將用戶輸入的整數(shù)金額(0~9999999)轉(zhuǎn)換為符合財(cái)務(wù)規(guī)范的中文大寫(xiě)金額,并自動(dòng)添加單位符號(hào)(如“萬(wàn)”、“仟”、“佰”等),例如輸入 1234,輸出 壹仟貳佰叁拾肆元,文中通過(guò)代碼示例講解的非常詳細(xì),需要的朋友可以參考下
    2025-04-04
  • Java開(kāi)發(fā)崗位面試被問(wèn)到泛型怎么辦

    Java開(kāi)發(fā)崗位面試被問(wèn)到泛型怎么辦

    泛型在java中有很重要的地位,在面向?qū)ο缶幊碳案鞣N設(shè)計(jì)模式中有非常廣泛的應(yīng)用。java泛型知識(shí)點(diǎn)也是Java開(kāi)發(fā)崗位必問(wèn)的一個(gè)話題,今天小編就給大家普及下Java泛型常見(jiàn)面試題,感興趣的朋友一起看看吧
    2021-07-07
  • 深入理解JAVA 備忘錄模式

    深入理解JAVA 備忘錄模式

    這篇文章主要介紹了JAVA 備忘錄模式的的相關(guān)資料,文中示例代碼非常細(xì)致,供大家參考和學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06
  • SpringBoot開(kāi)啟異步調(diào)用方法

    SpringBoot開(kāi)啟異步調(diào)用方法

    這篇文章主要為大家詳細(xì)介紹了SpringBoot開(kāi)啟異步調(diào)用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • 僅用5分鐘極速入門(mén)Dubbo使用教程

    僅用5分鐘極速入門(mén)Dubbo使用教程

    今天給大家介紹一款高性能、透明的遠(yuǎn)程過(guò)程調(diào)用框架dubbo,通過(guò)本文學(xué)習(xí)可以快速掌握Dubbo知識(shí),感興趣的朋友跟隨小編一起看看吧
    2021-06-06
  • mybatis的坑-integer類(lèi)型為0的數(shù)據(jù)if?test失效問(wèn)題

    mybatis的坑-integer類(lèi)型為0的數(shù)據(jù)if?test失效問(wèn)題

    這篇文章主要介紹了mybatis的坑-integer類(lèi)型為0的數(shù)據(jù)if?test失效問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01

最新評(píng)論

威宁| 宜川县| 云梦县| 邯郸县| 南乐县| 红安县| 徐闻县| 崇左市| 韶山市| 铁岭市| 静海县| 邛崃市| 巨鹿县| 平舆县| 通渭县| 桐城市| 年辖:市辖区| 思南县| 赞皇县| 凯里市| 绍兴县| 扎兰屯市| 左权县| 米泉市| 武义县| 凤山县| 板桥市| 达尔| 绥阳县| 宁蒗| 建平县| 壤塘县| 九江县| 驻马店市| 阿坝县| 札达县| 田阳县| 兴业县| 德清县| 淄博市| 乌拉特前旗|