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

SpringBoot系列教程之防重放與操作冪等

 更新時間:2022年04月12日 10:25:08   作者:huanzi-qch  
同一條數(shù)據(jù)被用戶點擊了多次,導(dǎo)致數(shù)據(jù)冗余,需要防止弱網(wǎng)絡(luò)等環(huán)境下的重復(fù)點擊,下面這篇文章主要給大家介紹了關(guān)于SpringBoot系列教程之防重放與操作冪等的相關(guān)資料,需要的朋友可以參考下

前言

日常開發(fā)中,我們可能會碰到需要進(jìn)行防重放與操作冪等的業(yè)務(wù),本文記錄SpringBoot實現(xiàn)簡單防重與冪等

防重放,防止數(shù)據(jù)重復(fù)提交

操作冪等性,多次執(zhí)行所產(chǎn)生的影響均與一次執(zhí)行的影響相同

解決什么問題?

表單重復(fù)提交,用戶多次點擊表單提交按鈕

接口重復(fù)調(diào)用,接口短時間內(nèi)被多次調(diào)用

思路如下:

  1、前端頁面表提交鈕置灰不可點擊+js節(jié)流防抖

  2、Redis防重Token令牌

  3、數(shù)據(jù)庫唯一主鍵 + 樂觀鎖

具體方案

pom引入依賴

<!-- Redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- thymeleaf模板 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <!--添加MyBatis-Plus依賴 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.0</version>
        </dependency>

        <!--添加MySQL驅(qū)動依賴 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

一個測試表

CREATE TABLE `idem`  (
  `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '唯一主鍵',
  `msg` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '業(yè)務(wù)數(shù)據(jù)',
  `version` int(8) NOT NULL COMMENT '樂觀鎖版本號',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '防重放與操作冪等測試表' ROW_FORMAT = Compact;

前端頁面

先寫一個test頁面,引入jq

<!DOCTYPE html>
<!--解決idea thymeleaf 表達(dá)式模板報紅波浪線-->
<!--suppress ALL -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="UTF-8" />
  <title>防重放與操作冪等</title>

  <!-- 引入靜態(tài)資源 -->
  <script th:src="@{/js/jquery-1.9.1.min.js}" type="application/javascript"></script>
</head>
<body>
  <form>
    <!-- 隱藏域 -->
    <input type="hidden" id="token" th:value="${token}"/>

    <!-- 業(yè)務(wù)數(shù)據(jù) -->
    id:<input id="id" th:value="${id}"/> <br/>
    msg:<input id="msg" th:value="${msg}"/> <br/>
    version:<input id="version" th:value="${version}"/> <br/>

    <!-- 操作按鈕 -->
    <br/>
    <input type="submit" value="提交" onclick="formSubmit(this)"/>
    <input type="reset" value="重置"/>
  </form>
  <br/>

  <button id="btn">節(jié)流測試,點我</button>
  <br/>
  <button id="btn2">防抖測試,點我</button>
</body>
<script>
  /*

  //插入
  for (let i = 0; i < 5; i++) {
    $.get("http://localhost:10010/idem/insert?id=1&msg=張三"+i+"&version=1",null,function (data){
      console.log(data);
    });
  }

  //修改
  for (let i = 0; i < 5; i++) {
    $.get("http://localhost:10010/idem/update?id=1&msg=李四"+i+"&version=1",null,function (data){
      console.log(data);
    });
  }

  //刪除
  for (let i = 0; i < 5; i++) {
    $.get("http://localhost:10010/idem/delete?id=1",null,function (data){
      console.log(data);
    });
  }

  //查詢
  for (let i = 0; i < 5; i++) {
    $.get("http://localhost:10010/idem/select?id=1",null,function (data){
      console.log(data);
    });
  }

  //test表單測試
  for (let i = 0; i < 5; i++) {
    $.get("http://localhost:10010/test/test?token=abcd&id=1&msg=張三"+i+"&version=1",null,function (data){
      console.log(data);
    });
  }

  //節(jié)流測試
  for (let i = 0; i < 5; i++) {
    document.getElementById('btn').onclick();
  }

  //防抖測試
  for (let i = 0; i < 5; i++) {
    document.getElementById('btn2').onclick();
  }

   */


  function formSubmit(but){
    //按鈕置灰
    but.setAttribute("disabled","disabled");

    let token = $("#token").val();
    let id = $("#id").val();
    let msg = $("#msg").val();
    let version = $("#version").val();

    $.ajax({
      type: 'post',
      url: "/test/test",
      contentType:"application/x-www-form-urlencoded",
      data: {
        token:token,
        id:id,
        msg:msg,
        version:version,
      },
      success: function (data) {
        console.log(data);

        //按鈕恢復(fù)
        but.removeAttribute("disabled");
      },
      error: function (xhr, status, error) {
        console.error("ajax錯誤!");

        //按鈕恢復(fù)
        but.removeAttribute("disabled");
      }
    });

    return false;
  }

  document.getElementById('btn').onclick = throttle(function () {
    console.log('節(jié)流測試 helloworld');
  }, 1000)
  // 節(jié)流:給定一個時間,不管這個時間你怎么點擊,點上天,這個時間內(nèi)也只會執(zhí)行一次
  // 節(jié)流函數(shù)
  function throttle(fn, delay) {
    var lastTime = new Date().getTime()
    delay = delay || 200
    return function () {
      var args = arguments
      var nowTime = new Date().getTime()
      if (nowTime - lastTime >= delay) {
        lastTime = nowTime
        fn.apply(this, args)
      }
    }
  }

  document.getElementById('btn2').onclick = debounce(function () {
    console.log('防抖測試 helloworld');
  }, 1000)
  // 防抖:給定一個時間,不管怎么點擊按鈕,每點一次,都會在最后一次點擊等待這個時間過后執(zhí)行
  // 防抖函數(shù)
  function debounce(fn, delay) {
    var timer = null
    delay = delay || 200
    return function () {
      var args = arguments
      var that = this
      clearTimeout(timer)
      timer = setTimeout(function () {
        fn.apply(that, args)
      }, delay)
    }
  }
</script>
</html>

按鈕置灰不可點擊

點擊提交按鈕后,將提交按鈕置灰不可點擊,ajax響應(yīng)后再恢復(fù)按鈕狀態(tài)

function formSubmit(but){
    //按鈕置灰
    but.setAttribute("disabled","disabled");

    let token = $("#token").val();
    let id = $("#id").val();
    let msg = $("#msg").val();
    let version = $("#version").val();

    $.ajax({
      type: 'post',
      url: "/test/test",
      contentType:"application/x-www-form-urlencoded",
      data: {
        token:token,
        id:id,
        msg:msg,
        version:version,
      },
      success: function (data) {
        console.log(data);

        //按鈕恢復(fù)
        but.removeAttribute("disabled");
      },
      error: function (xhr, status, error) {
        console.error("ajax錯誤!");

        //按鈕恢復(fù)
        but.removeAttribute("disabled");
      }
    });

    return false;
  }

js節(jié)流、防抖

節(jié)流:給定一個時間,不管這個時間你怎么點擊,點上天,這個時間內(nèi)也只會執(zhí)行一次

document.getElementById('btn').onclick = throttle(function () {
    console.log('節(jié)流測試 helloworld');
  }, 1000)
  // 節(jié)流:給定一個時間,不管這個時間你怎么點擊,點上天,這個時間內(nèi)也只會執(zhí)行一次
  // 節(jié)流函數(shù)
  function throttle(fn, delay) {
    var lastTime = new Date().getTime()
    delay = delay || 200
    return function () {
      var args = arguments
      var nowTime = new Date().getTime()
      if (nowTime - lastTime >= delay) {
        lastTime = nowTime
        fn.apply(this, args)
      }
    }
  }

防抖:給定一個時間,不管怎么點擊按鈕,每點一次,都會在最后一次點擊等待這個時間過后執(zhí)行

document.getElementById('btn2').onclick = debounce(function () {
    console.log('防抖測試 helloworld');
  }, 1000)
  // 防抖:給定一個時間,不管怎么點擊按鈕,每點一次,都會在最后一次點擊等待這個時間過后執(zhí)行
  // 防抖函數(shù)
  function debounce(fn, delay) {
    var timer = null
    delay = delay || 200
    return function () {
      var args = arguments
      var that = this
      clearTimeout(timer)
      timer = setTimeout(function () {
        fn.apply(that, args)
      }, delay)
    }
  }

Redis

防重Token令牌

跳轉(zhuǎn)前端表單頁面時,設(shè)置一個UUID作為token,并設(shè)置在表單隱藏域

/**
     * 跳轉(zhuǎn)頁面
     */
    @RequestMapping("index")
    private ModelAndView index(String id){
        ModelAndView mv = new ModelAndView();
        mv.addObject("token",UUIDUtil.getUUID());
        if(id != null){
            Idem idem = new Idem();
            idem.setId(id);
            List select = (List)idemService.select(idem);
            idem = (Idem)select.get(0);
            mv.addObject("id", idem.getId());
            mv.addObject("msg", idem.getMsg());
            mv.addObject("version", idem.getVersion());
        }
        mv.setViewName("test.html");
        return mv;
    }
<form>
    <!-- 隱藏域 -->
    <input type="hidden" id="token" th:value="${token}"/>

    <!-- 業(yè)務(wù)數(shù)據(jù) -->
    id:<input id="id" th:value="${id}"/> <br/>
    msg:<input id="msg" th:value="${msg}"/> <br/>
    version:<input id="version" th:value="${version}"/> <br/>

    <!-- 操作按鈕 -->
    <br/>
    <input type="submit" value="提交" onclick="formSubmit(this)"/>
    <input type="reset" value="重置"/>
  </form>

后臺查詢redis緩存,如果token不存在立即設(shè)置token緩存,允許表單業(yè)務(wù)正常進(jìn)行;如果token緩存已經(jīng)存在,拒絕表單業(yè)務(wù)

PS:token緩存要設(shè)置一個合理的過期時間

/**
     * 表單提交測試
     */
    @RequestMapping("test")
    private String test(String token,String id,String msg,int version){
        //如果token緩存不存在,立即設(shè)置緩存且設(shè)置有效時長(秒)
        Boolean setIfAbsent = template.opsForValue().setIfAbsent(token, "1", 60 * 5, TimeUnit.SECONDS);

        //緩存設(shè)置成功返回true,失敗返回false
        if(Boolean.TRUE.equals(setIfAbsent)){

            //模擬耗時
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //打印測試數(shù)據(jù)
            System.out.println(token+","+id+","+msg+","+version);

            return "操作成功!";
        }else{
            return "操作失敗,表單已被提交...";
        }
    }

for循環(huán)測試中,5個操作只有一個執(zhí)行成功!

數(shù)據(jù)庫

唯一主鍵 + 樂觀鎖

查詢操作自帶冪等性

/**
     * 查詢操作,天生冪等性
     */
    @Override
    public Object select(Idem idem) {
        QueryWrapper<Idem> queryWrapper = new QueryWrapper<>();
        queryWrapper.setEntity(idem);
        return idemMapper.selectList(queryWrapper);
    }

查詢沒什么好說的,只要數(shù)據(jù)不變,查詢條件不變的情況下查詢結(jié)果必然冪等

唯一主鍵可解決插入操作、刪除操作

/**
     * 插入操作,使用唯一主鍵實現(xiàn)冪等性
     */
    @Override
    public Object insert(Idem idem) {
        String msg = "操作成功!";
        try{
            idemMapper.insert(idem);
        }catch (DuplicateKeyException e){
            msg = "操作失敗,id:"+idem.getId()+",已經(jīng)存在...";
        }
        return msg;
    }

    /**
     * 刪除操作,使用唯一主鍵實現(xiàn)冪等性
     * PS:使用非主鍵條件除外
     */
    @Override
    public Object delete(Idem idem) {
        String msg = "操作成功!";
        int deleteById = idemMapper.deleteById(idem.getId());
        if(deleteById == 0){
            msg = "操作失敗,id:"+idem.getId()+",已經(jīng)被刪除...";
        }
        return msg;
    }

利用主鍵唯一的特性,捕獲處理重復(fù)操作

樂觀鎖可解決更新操作

/**
     * 更新操作,使用樂觀鎖實現(xiàn)冪等性
     */
    @Override
    public Object update(Idem idem) {
        String msg = "操作成功!";

        // UPDATE table SET [... 業(yè)務(wù)字段=? ...], version = version+1 WHERE (id = ? AND version = ?)
        UpdateWrapper<Idem> updateWrapper = new UpdateWrapper<>();

        //where條件
        updateWrapper.eq("id",idem.getId());
        updateWrapper.eq("version",idem.getVersion());

        //version版本號要單獨設(shè)置
        updateWrapper.setSql("version = version+1");
        idem.setVersion(null);

        int update = idemMapper.update(idem, updateWrapper);
        if(update == 0){
            msg = "操作失敗,id:"+idem.getId()+",已經(jīng)被更新...";
        }

        return msg;
    }

執(zhí)行更新sql語句時,where條件帶上version版本號,如果執(zhí)行成功,除了更新業(yè)務(wù)數(shù)據(jù),同時更新version版本號標(biāo)記當(dāng)前數(shù)據(jù)已被更新

UPDATE table SET [... 業(yè)務(wù)字段=? ...], version = version+1 WHERE (id = ? AND version = ?)

執(zhí)行更新操作前,需要重新執(zhí)行插入數(shù)據(jù)

以上for循環(huán)測試中,5個操作同樣只有一個執(zhí)行成功!

后記

redis、樂觀鎖不要在代碼先查詢后if判斷,這樣會存在并發(fā)問題,導(dǎo)致數(shù)據(jù)不準(zhǔn)確,應(yīng)該把這種判斷放在redis、數(shù)據(jù)庫

錯誤示例:

//獲取最新緩存
String redisToken = template.opsForValue().get(token);

//為空則放行業(yè)務(wù)
if(redisToken == null){
    //設(shè)置緩存
    template.opsForValue().set(token, "1", 60 * 5, TimeUnit.SECONDS);

    //業(yè)務(wù)處理
}else{
    //拒絕業(yè)務(wù)
}

錯誤示例:

//獲取最新版本號
Integer version = idemMapper.selectById(idem.getId()).getVersion();

//版本號相同,說明數(shù)據(jù)未被其他人修改
if(version == idem.getVersion()){
    //正常更新
}else{
    //拒絕更新
}

防重與冪等暫時先記錄到這,后續(xù)再進(jìn)行補(bǔ)充

代碼開源

代碼已經(jīng)開源、托管到我的GitHub、碼云:

GitHub:https://github.com/huanzi-qch/springBoot

碼云:https://gitee.com/huanzi-qch/springBoot

總結(jié)

到此這篇關(guān)于SpringBoot系列教程之防重放與操作冪等的文章就介紹到這了,更多相關(guān)SpringBoot防重放與操作冪等內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 掌握SpringMVC中@InitBinder的實際應(yīng)用

    掌握SpringMVC中@InitBinder的實際應(yīng)用

    這篇文章主要介紹了掌握SpringMVC中@InitBinder的實際應(yīng)用,@InitBinder是Spring MVC框架中的一個注解,用于自定義數(shù)據(jù)綁定的方法,通過在控制器中使用@InitBinder注解,可以將特定的數(shù)據(jù)綁定邏輯應(yīng)用于請求參數(shù)的處理過程中,需要的朋友可以參考下
    2023-10-10
  • Java貪心算法超詳細(xì)講解

    Java貪心算法超詳細(xì)講解

    人之初性本善,但是隨著自身的經(jīng)歷、生活環(huán)境等因素的影響,人逐漸會生出貪嗔癡。實際上不光人有貪念,我們的算法也會有貪念,今天就和大家介紹下一個有貪念的算法模型---貪心算法,看看一個算法是怎么產(chǎn)生貪念的
    2022-05-05
  • Java下載https文件并上傳阿里云oss服務(wù)器

    Java下載https文件并上傳阿里云oss服務(wù)器

    這篇文章主要介紹了Java下載https文件并上傳到阿里云oss服務(wù)器,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-01-01
  • Java實現(xiàn)把文件壓縮成zip文件的示例代碼

    Java實現(xiàn)把文件壓縮成zip文件的示例代碼

    這篇文章主要為大家介紹了如何通過Java語言實現(xiàn)將文件壓縮成zip文件,本文中示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-02-02
  • MyBatis通用的10種寫法總結(jié)大全

    MyBatis通用的10種寫法總結(jié)大全

    這篇文章主要給大家介紹了關(guān)于MyBatis通用的10種寫法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11
  • JAVALambda表達(dá)式與函數(shù)式接口詳解

    JAVALambda表達(dá)式與函數(shù)式接口詳解

    大家好,本篇文章主要講的是JAVALambda表達(dá)式與函數(shù)式接口詳解,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下
    2022-02-02
  • WeakHashMap?和?HashMap?區(qū)別及使用場景

    WeakHashMap?和?HashMap?區(qū)別及使用場景

    這篇文章主要為大家介紹了WeakHashMap?和?HashMap?的區(qū)別是什么以及何時使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-11-11
  • Spring核心IoC容器的依賴注入接口和層級包命名規(guī)范

    Spring核心IoC容器的依賴注入接口和層級包命名規(guī)范

    這篇文章主要介紹了Spring核心IoC容器的依賴注入接口和層級包命名規(guī)范,IOC又名控制反轉(zhuǎn),把對象創(chuàng)建和對象之間的調(diào)用過程,交給Spring進(jìn)行管理,目的是為了降低耦合度,需要的朋友可以參考下
    2023-05-05
  • activemq整合springboot使用方法(個人微信小程序用)

    activemq整合springboot使用方法(個人微信小程序用)

    這篇文章主要介紹了activemq整合springboot使用(個人微信小程序用),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • Java服務(wù)端架構(gòu)之微服務(wù)與單體服務(wù)的權(quán)衡方式

    Java服務(wù)端架構(gòu)之微服務(wù)與單體服務(wù)的權(quán)衡方式

    本文比較了Java微服務(wù)與單體服務(wù)的優(yōu)缺點,并提供了相應(yīng)的示例代碼,微服務(wù)架構(gòu)具有可擴(kuò)展性、技術(shù)多樣性等優(yōu)勢,但復(fù)雜性和網(wǎng)絡(luò)延遲是缺點,單體服務(wù)架構(gòu)簡單易部署,但擴(kuò)展性差,選擇哪種架構(gòu)應(yīng)根據(jù)項目需求和團(tuán)隊能力
    2025-03-03

最新評論

封开县| 抚州市| 三都| 平塘县| 富裕县| 嘉善县| 黑山县| 潜江市| 噶尔县| 怀柔区| 麦盖提县| 大关县| 广西| 黔南| 剑川县| 若尔盖县| 巴青县| 怀化市| 宁德市| 廊坊市| 巴马| 兴宁市| 兴城市| 娄烦县| 门头沟区| 华宁县| 凭祥市| 泗洪县| 楚雄市| 通州区| 缙云县| 呈贡县| 壶关县| 盐城市| 渝北区| 图木舒克市| 沂南县| 通海县| 孟村| 二连浩特市| 昌黎县|