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

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

 更新時間:2022年06月21日 08:32:04   作者:_江南一點雨  
接口冪等性處理算是一個非常常見的需求了,我們在很多項目中其實都會遇到。本文為大家總結了兩個處理接口冪等性的兩種常見方案,需要的可以參考一下

在上周發(fā)布的 TienChin 項目視頻中,我和大家一共梳理了六種冪等性解決方案,接口冪等性處理算是一個非常常見的需求了,我們在很多項目中其實都會遇到。今天我們來看看兩種比較簡單的實現(xiàn)思路。

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

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

1.1 基于 Token

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

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

大致的思路就是上面這樣,當然具體的實現(xiàn)則會復雜很多,有很多細節(jié)需要注意

1.2 基于請求參數(shù)校驗

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

相比于第一種方案,第二種方案相對來說省事一些,因為只有一次請求,不需要專門去服務端拿令牌。在高并發(fā)環(huán)境下這種方案優(yōu)勢比較明顯。

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

2. 基于請求參數(shù)的校驗

首先我們新建一個 Spring Boot 項目,引入 Web 和 Redis 依賴,新建完成后,先來配置一下 Redis 的基本信息,如下:

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

為了后續(xù) Redis 操作方便,我們再來對 Redis 進行一個簡單封裝,如下:

@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);
    }
}

這個比較簡單,一個存數(shù)據(jù),一個讀數(shù)據(jù)。

接下來我們自定義一個注解,在需要進行冪等性處理的接口上,添加該注解即可,將來這個接口就會自動的進行冪等性處理。

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

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

這個注解我們通過攔截器來進行解析,解析代碼如下:

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

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

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

@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());

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

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

        // 唯一標識(指定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);
    }

    /**
     * 判斷兩次間隔時間
     */
    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;
    }
}

我們來看下具體的實現(xiàn)邏輯:

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

好啦,做完這一切,最后我們再來配置一下攔截器即可:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    RepeatSubmitInterceptor repeatSubmitInterceptor;

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

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

@RestController
public class HelloController {

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

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

相關文章

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

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

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

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

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

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

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

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

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

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

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

    Java開發(fā)崗位面試被問到泛型怎么辦

    泛型在java中有很重要的地位,在面向對象編程及各種設計模式中有非常廣泛的應用。java泛型知識點也是Java開發(fā)崗位必問的一個話題,今天小編就給大家普及下Java泛型常見面試題,感興趣的朋友一起看看吧
    2021-07-07
  • 深入理解JAVA 備忘錄模式

    深入理解JAVA 備忘錄模式

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

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

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

    僅用5分鐘極速入門Dubbo使用教程

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

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

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

最新評論

定州市| 特克斯县| 江口县| 陆川县| 桐梓县| 米泉市| 芷江| 云林县| 盐池县| 察哈| 崇阳县| 开化县| 左权县| 会理县| 桂林市| 丰镇市| 万山特区| 吉水县| 南丰县| 黄大仙区| 涪陵区| 西平县| 商丘市| 尼木县| 榆社县| 高台县| 建昌县| 甘泉县| 祥云县| 疏勒县| 福海县| 互助| 长治市| 思南县| 屏南县| 禄劝| 库伦旗| 尉氏县| 确山县| 梧州市| 探索|