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

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

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

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

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

其實(shí)接口冪等性的實(shí)現(xià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ù)來判斷的,如果在短時(shí)間內(nèi),同一個(gè)接口接收到的請(qǐng)求參數(shù)相同,那么就認(rèn)為這是重復(fù)的請(qǐng)求,拒絕處理,大致上就是這么個(gè)思路。

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

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

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

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

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

為了后續(xù) Redis 操作方便,我們?cè)賮韺?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ù)。

接下來我們自定義一個(gè)注解,在需要進(jìn)行冪等性處理的接口上,添加該注解即可,將來這個(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è)注解我們通過攔截器來進(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ù)提交由子類實(shí)現(xiàn)具體的防重復(fù)提交的規(guī)則
     *
     * @param request
     * @return
     * @throws Exception
     */
    public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation);
}

這個(gè)攔截器是一個(gè)抽象類,將接口方法攔截下來,然后找到接口上的 @RepeatSubmit 注解,調(diào)用 isRepeatSubmit 方法去判斷是否是重復(fù)提交的數(shù)據(jù),該方法在這里是一個(gè)抽象方法,我們需要再定義一個(gè)類繼承自這個(gè)抽象類,在新的子類中,可以有不同的冪等性判斷邏輯,這里我們就是根據(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());

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

        // 唯一值(沒有消息頭則使用請(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;
    }
}

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

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

好啦,做完這一切,最后我們?cè)賮砼渲靡幌聰r截器即可:

@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)文章

  • Java?Chassis3應(yīng)用視角的配置管理技術(shù)解密

    Java?Chassis3應(yīng)用視角的配置管理技術(shù)解密

    這篇文章主要為大家介紹了Java?Chassis3應(yīng)用視角的配置管理相關(guān)的機(jī)制和背后故事,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • Java PriorityQueue優(yōu)點(diǎn)和缺點(diǎn)面試精講

    Java PriorityQueue優(yōu)點(diǎn)和缺點(diǎn)面試精講

    這篇文章主要為大家介紹了Java面試中PriorityQueue的優(yōu)點(diǎn)和缺點(diǎn)及使用注意詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • SpringMVC 向jsp頁(yè)面?zhèn)鬟f數(shù)據(jù)庫(kù)讀取到的值方法

    SpringMVC 向jsp頁(yè)面?zhèn)鬟f數(shù)據(jù)庫(kù)讀取到的值方法

    下面小編就為大家分享一篇SpringMVC 向jsp頁(yè)面?zhèn)鬟f數(shù)據(jù)庫(kù)讀取到的值方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • Spring中的集合注入代碼實(shí)例

    Spring中的集合注入代碼實(shí)例

    這篇文章主要介紹了Spring中的集合注入代碼實(shí)例,集合注入是指在Spring框架中,通過配置文件或注解的方式將集合類型的數(shù)據(jù)注入到Bean中,集合類型包括List、Set、Map和Properties等,需要的朋友可以參考下
    2023-11-11
  • springboot項(xiàng)目打成war包部署到tomcat遇到的一些問題

    springboot項(xiàng)目打成war包部署到tomcat遇到的一些問題

    這篇文章主要介紹了springboot項(xiàng)目打成war包部署到tomcat遇到的一些問題,需要的朋友可以參考下
    2017-06-06
  • Vue3源碼解讀effectScope API及實(shí)現(xiàn)原理

    Vue3源碼解讀effectScope API及實(shí)現(xiàn)原理

    這篇文章主要為大家介紹了Vue3源碼解讀effectScope API及實(shí)現(xiàn)原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • 用Java實(shí)現(xiàn)連連看小游戲

    用Java實(shí)現(xiàn)連連看小游戲

    這篇文章主要為大家詳細(xì)介紹了用Java實(shí)現(xiàn)連連看小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • mybatisplus添加真正的批量新增、批量更新的實(shí)現(xiàn)

    mybatisplus添加真正的批量新增、批量更新的實(shí)現(xiàn)

    這篇文章主要介紹了mybatisplus添加真正的批量新增、批量更新的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • IDEA Ultimate2020.2版本配置Tomcat詳細(xì)教程

    IDEA Ultimate2020.2版本配置Tomcat詳細(xì)教程

    這篇文章主要介紹了IDEA Ultimate2020.2版本配置Tomcat教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-09-09
  • java垃圾回收原理之GC算法基礎(chǔ)

    java垃圾回收原理之GC算法基礎(chǔ)

    本章簡(jiǎn)要介紹GC的基本原理和相關(guān)技術(shù), 下一章節(jié)再詳細(xì)講解GC算法的具體實(shí)現(xiàn)。各種垃圾收集器的實(shí)現(xiàn)細(xì)節(jié)雖然并不相同,但總體而言,垃圾收集器都專注于兩件事情:查找所有存活對(duì)象,拋棄其他的部分,即死對(duì)象,不再使用的對(duì)象
    2022-01-01

最新評(píng)論