免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
java開發(fā)中 防止重復提交的幾種方案
userphoto

2022.12.21 湖南

關(guān)注

暴力小熊

已于 2022-07-01 16:13:00 修改

3624

 收藏 25

分類專欄: redis java 數(shù)據(jù)庫 文章標簽: java 前端 面試

版權(quán)

redis

同時被 3 個專欄收錄

4 篇文章1 訂閱

訂閱專欄

java

25 篇文章2 訂閱

訂閱專欄

數(shù)據(jù)庫

8 篇文章0 訂閱

訂閱專欄

開場白:老鐵們對于文章有錯誤、不準確,或需要補充的請留言討論 ,大家共同學習。如果覺得還不錯的請關(guān)注、留言、點贊 、收藏。 創(chuàng)作不易,且看且珍惜

一、產(chǎn)生原因

對于重復提交的問題,主要由于重復點擊或者網(wǎng)絡(luò)重發(fā)請求, 我要先了解產(chǎn)生原因幾種方式:

點擊提交按鈕兩次;

點擊刷新按鈕;

使用瀏覽器后退按鈕重復之前的操作,導致重復提交表單;

使用瀏覽器歷史記錄重復提交表單;

瀏覽器重復的HTTP請;

nginx重發(fā)等情況;

分布式RPC的try重發(fā)等點擊提交按鈕兩次;

等… …

二、冪等

對于重復提交的問題 主要涉及到時 冪等 問題,那么先說一下什么是冪等。

冪等:F(F(X)) = F(X)多次運算結(jié)果一致;簡單點說就是對于完全相同的操作,操作一次與操作多次的結(jié)果是一樣的。

在開發(fā)中,我們都會涉及到對數(shù)據(jù)庫操作。例如:

select 查詢天然冪等

delete 刪除也是冪等,刪除同一個多次效果一樣

update 直接更新某個值(如:狀態(tài) 字段固定值),冪等

update 更新累加操作(如:商品數(shù)量 字段),非冪等

(可以采用簡單的樂觀鎖和悲觀鎖 個人更喜歡樂觀鎖。

樂觀鎖:數(shù)據(jù)庫表加version字段的方式;

悲觀鎖:用了 select…for update 的方式,* 要使用悲觀鎖,我們必須關(guān)閉mysql數(shù)據(jù)庫的自動提交屬性。

這種在大數(shù)據(jù)量和高并發(fā)下效率依賴數(shù)據(jù)庫硬件能力,可針對并發(fā)量不高的非核心業(yè)務(wù);)

insert 非冪等操作,每次新增一條 重點 (數(shù)據(jù)庫簡單方案:可采取數(shù)據(jù)庫唯一索引方式;這種在大數(shù)據(jù)量和高并發(fā)下效率依賴數(shù)據(jù)庫硬件能力,可針對并發(fā)量不高的非核心業(yè)務(wù);)

三、解決方案

1. 方案對比

序號 前端/后端 方案 優(yōu)點 缺點 代碼實現(xiàn)

1) 前端 前端js提交后禁止按鈕,返回結(jié)果后解禁等 簡單 方便 只能控制頁面,通過工具可繞過不安全

2) 后端 提交后重定向到其他頁面,防止用戶F5和瀏覽器前進后退等重復提交問題 簡單 方便 體驗不好,適用部分場景,若是遇到網(wǎng)絡(luò)問題 還會出現(xiàn)

3) 后端 在表單、session、token 放入唯一標識符(如:UUID),每次操作時,保存標識一定時間后移除,保存期間有相同的標識就不處理或提示 相對簡單 表單:有時需要前后端協(xié)商配合; session、token:加大服務(wù)性能開銷

4) 后端 ConcurrentHashMap 、LRUMap 、google Cache 都是采用唯一標識(如:用戶ID+請求路徑+參數(shù)) 相對簡單 適用于單機部署的應(yīng)用 見下

5) 后端 redis 是線程安全的,可以實現(xiàn)redis分布式鎖。設(shè)置唯一標識(如:用戶ID+請求路徑+參數(shù))當做key ,value值可以隨意(推薦設(shè)置成過期的時間點),在設(shè)置key的過期時間 單機、分布式、高并發(fā)都可以決絕 相對復雜需要部署維護redis 見下

2. 代碼實現(xiàn)

4). google cache 代碼實現(xiàn) 注解方式 Single lock

pom.xml 引入

<dependency>

   <groupId>com.google.guava</groupId>

    <artifactId>guava</artifactId>

    <version>28.2-jre</version>

</dependency>

1

2

3

4

5

配置文件 .yml

resubmit:

  local:

    timeOut: 30

1

2

3

實現(xiàn)代碼

import java.lang.annotation.*;

@Target({ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

public @interface LocalLock {

}

1

2

3

4

5

6

7

8

9

import com.alibaba.fastjson.JSONObject;

import com.example.mydemo.common.utils.IpUtils;

import com.example.mydemo.common.utils.Result;

import com.example.mydemo.common.utils.SecurityUtils;

import com.example.mydemo.common.utils.sign.MyMD5Util;

import com.google.common.cache.Cache;

import com.google.common.cache.CacheBuilder;

import lombok.Data;

import org.apache.commons.lang3.StringUtils;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.reflect.MethodSignature;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.context.request.RequestAttributes;

import org.springframework.web.context.request.RequestContextHolder;

import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

import java.lang.reflect.Method;

import java.security.NoSuchAlgorithmException;

import java.util.ArrayList;

import java.util.List;

import java.util.concurrent.TimeUnit;

/**

 * @author: xx

 * @description: 單機放重復提交

 */

@Data

@Aspect

@Configuration

public class LocalLockMethodInterceptor {

    @Value("${spring.profiles.active}")

    private String springProfilesActive;

    @Value("${spring.application.name}")

    private String springApplicationName;

    private static int expireTimeSecond =5;

    @Value("${resubmit:local:timeOut}")

    public void setExpireTimeSecond(int expireTimeSecond) {

        LocalLockMethodInterceptor.expireTimeSecond = expireTimeSecond;

    }

    //定義緩存,設(shè)置最大緩存數(shù)及過期日期

    private static final Cache<String,Object> CACHE =

            CacheBuilder.newBuilder().maximumSize(1000).expireAfterWrite(expireTimeSecond, TimeUnit.SECONDS).build();

    @Around("execution(public * *(..))  && @annotation(com.example.mydemo.common.interceptor.annotation.LocalLock)")

    public Object interceptor(ProceedingJoinPoint joinPoint){

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        Method method = signature.getMethod();

//        LocalLock localLock = method.getAnnotation(LocalLock.class);

        try{

        String key = getLockUniqueKey(signature,joinPoint.getArgs());

        if(CACHE.getIfPresent(key) != null){

            return Result.fail("不允許重復提交,請稍后再試");

        }

        CACHE.put(key,key);

            return joinPoint.proceed();

        }catch (Throwable throwable){

            throw new RuntimeException(throwable.getMessage());

        }finally {

        }

    }

    /**

     * 獲取唯一標識key

     *

     * @param methodSignature

     * @param args

     * @return

     */

    private String getLockUniqueKey(MethodSignature methodSignature, Object[] args) throws NoSuchAlgorithmException {

        //請求uri, 獲取類名稱,方法名稱

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;

        HttpServletRequest request = servletRequestAttributes.getRequest();

//        HttpServletResponse responese = servletRequestAttributes.getResponse();

        //獲取用戶信息

        String userMsg = SecurityUtils.getUsername(); //獲取登錄用戶名稱

        //1.判斷用戶是否登錄

        if (StringUtils.isEmpty(userMsg)) { //未登錄用戶獲取真實ip

            userMsg = IpUtils.getIpAddr(request);

        }

        String hash = "";

        List list = new ArrayList();

        if (args.length > 0) {

            String[] parameterNames = methodSignature.getParameterNames();

            for (int i = 0; i < parameterNames.length; i++) {

                Object obj = args[i];

                list.add(obj);

            }

            hash = JSONObject.toJSONString(list);

        }

        //項目名稱 + 環(huán)境編碼 + 獲取類名稱 + 方法名稱 + 唯一key

        String key = "locallock:" + springApplicationName + ":" + springProfilesActive + ":" + userMsg + ":" + request.getRequestURI();

        if (StringUtils.isNotEmpty(key)) {

            key = key + ":" + hash;

        }

        key = MyMD5Util.getMD5(key);

        return key;

    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

使用:

@LocalLock

    public void save(@RequestBody User user) {

    }

1

2

3

4

5)redis

pom.xml 引入

<dependency>

    <groupId>org.springframework.boot</groupId>

    <artifactId>spring-boot-starter-data-redis</artifactId>

</dependency>

1

2

3

4

.yml文件 redis 配置

spring:

  redis:

    host: localhost

    port: :6379

    password: 123456

1

2

3

4

5

import java.lang.annotation.*;

@Target({ElementType.METHOD})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Inherited

public @interface RedisLock {

    int expire() default 5;

}

1

2

3

4

5

6

7

8

9

10

11

import com.alibaba.fastjson.JSONObject;

import com.google.common.collect.Lists;

import com.heshu.sz.blockchain.utonhsbs.common.utils.MyMD5Util;

import com.heshu.sz.blockchain.utonhsbs.common.utils.SecurityUtils;

import com.heshu.sz.blockchain.utonhsbs.common.utils.ip.IpUtils;

import com.heshu.sz.blockchain.utonhsbs.framework.interceptor.annotation.RedisLock;

import com.heshu.sz.blockchain.utonhsbs.framework.system.domain.BaseResult;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;

import org.aspectj.lang.ProceedingJoinPoint;

import org.aspectj.lang.annotation.Around;

import org.aspectj.lang.annotation.Aspect;

import org.aspectj.lang.annotation.Pointcut;

import org.aspectj.lang.reflect.MethodSignature;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.context.annotation.Configuration;

import org.springframework.data.redis.core.StringRedisTemplate;

import org.springframework.data.redis.core.script.DefaultRedisScript;

import org.springframework.data.redis.core.script.RedisScript;

import org.springframework.web.context.request.RequestAttributes;

import org.springframework.web.context.request.RequestContextHolder;

import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

import java.lang.reflect.Method;

import java.security.NoSuchAlgorithmException;

import java.util.ArrayList;

import java.util.List;

/**

 * @author :xx

 * @description:

 * @date : 2022/7/1 9:41

 */

@Slf4j

@Aspect

@Configuration

public class RedisLockMethodInterceptor {

    @Value("${spring.profiles.active}")

    private String springProfilesActive;

    @Value("${spring.application.name}")

    private String springApplicationName;

    @Autowired

    private StringRedisTemplate stringRedisTemplate;

    @Pointcut("@annotation(com.heshu.sz.blockchain.utonhsbs.framework.interceptor.annotation.RedisLock)")

    public void point() {

    }

    @Around("point()")

    public Object doaround(ProceedingJoinPoint joinPoint) {

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();

        Method method = signature.getMethod();

        RedisLock localLock = method.getAnnotation(RedisLock.class);

        try {

            String lockUniqueKey = getLockUniqueKey(signature, joinPoint.getArgs());

            Integer expire = localLock.expire();

            if (expire < 0) {

                expire = 5;

            }

            ArrayList<String> keys = Lists.newArrayList(lockUniqueKey);

            String result = stringRedisTemplate.execute(setNxWithExpireTime, keys, expire.toString());

            if (!"ok".equalsIgnoreCase(result)) {//不存在

                return BaseResult.error("不允許重復提交,請稍后再試");

            }

            return joinPoint.proceed();

        } catch (Throwable throwable) {

            throw new RuntimeException(throwable.getMessage());

        }

    }

    /**

     * lua腳本

     */

    private RedisScript<String> setNxWithExpireTime = new DefaultRedisScript<>(

            "return redis.call('set', KEYS[1], 1, 'ex', ARGV[1], 'nx');",

            String.class

    );

    /**

     * 獲取唯一標識key

     *

     * @param methodSignature

     * @param args

     * @return

     */

    private String getLockUniqueKey(MethodSignature methodSignature, Object[] args) throws NoSuchAlgorithmException {

        //請求uri, 獲取類名稱,方法名稱

        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;

        HttpServletRequest request = servletRequestAttributes.getRequest();

//        HttpServletResponse responese = servletRequestAttributes.getResponse();

        //獲取用戶信息

        String userMsg = SecurityUtils.getUsername(); //獲取登錄用戶名稱

        //1.判斷用戶是否登錄

        if (StringUtils.isEmpty(userMsg)) { //未登錄用戶獲取真實ip

            userMsg = IpUtils.getIpAddr(request);

        }

        String hash = "";

        List list = new ArrayList();

        if (args.length > 0) {

            String[] parameterNames = methodSignature.getParameterNames();

            for (int i = 0; i < parameterNames.length; i++) {

                Object obj = args[i];

                list.add(obj);

            }

            String param = JSONObject.toJSONString(list);

            hash = MyMD5Util.getMD5(param);

        }

        //項目名稱 + 環(huán)境編碼 + 獲取類名稱 + 加密參數(shù)

        String key = "lock:" + springApplicationName + ":" + springProfilesActive + ":" + userMsg + ":" + request.getRequestURI();

        if (StringUtils.isNotEmpty(key)) {

            key = key + ":" + hash;

        }

        return key;

    }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

使用

@RedisLock

    public void save(@RequestBody User user) {

    }

1

2

3

4

文章知識

————————————————

版權(quán)聲明:本文為CSDN博主「暴力小熊」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
java接口防重提交如何處理
8 種方案解決重復提交問題,總有一種方案適合你!
SpringBoot AOP 記錄WEB請求日志
JDK動態(tài)代理和CGLIB代理的使用
AOP2
Java AOP 日志記錄
更多類似文章 >>
生活服務(wù)
分享 收藏 導長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服