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

java開發(fā)中防止重復(fù)提交的幾種解決方案

 更新時間:2022年10月20日 12:01:51   作者:暴力小熊  
我們?nèi)粘i_發(fā)中有很多的應(yīng)用場景都會遇到重復(fù)提交問題,下面這篇文章主要給大家介紹了關(guān)于java開發(fā)中防止重復(fù)提交的幾種解決方案,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下

一、產(chǎn)生原因

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

  • 點擊提交按鈕兩次;
  • 點擊刷新按鈕;
  • 使用瀏覽器后退按鈕重復(fù)之前的操作,導(dǎo)致重復(fù)提交表單;
  • 使用瀏覽器歷史記錄重復(fù)提交表單;
  • 瀏覽器重復(fù)的HTTP請;
  • nginx重發(fā)等情況;
  • 分布式RPC的try重發(fā)等點擊提交按鈕兩次;
  • 等… …

二、冪等

對于重復(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和瀏覽器前進(jìn)后退等重復(fù)提交問題簡單 方便體驗不好,適用部分場景,若是遇到網(wǎng)絡(luò)問題 還會出現(xiàn)
3)后端在表單、session、token 放入唯一標(biāo)識符(如:UUID),每次操作時,保存標(biāo)識一定時間后移除,保存期間有相同的標(biāo)識就不處理或提示相對簡單表單:有時需要前后端協(xié)商配合; session、token:加大服務(wù)性能開銷
4)后端ConcurrentHashMap 、LRUMap 、google Cache 都是采用唯一標(biāo)識(如:用戶ID+請求路徑+參數(shù))相對簡單適用于單機(jī)部署的應(yīng)用見下
5)后端redis 是線程安全的,可以實現(xiàn)redis分布式鎖。設(shè)置唯一標(biāo)識(如:用戶ID+請求路徑+參數(shù))當(dāng)做key ,value值可以隨意(推薦設(shè)置成過期的時間點),在設(shè)置key的過期時間單機(jī)、分布式、高并發(fā)都可以決絕相對復(fù)雜需要部署維護(hù)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>

配置文件 .yml

resubmit:
  local:
    timeOut: 30

實現(xiàn)代碼

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface LocalLock {
	
}
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: 單機(jī)放重復(fù)提交
 */
@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("不允許重復(fù)提交,請稍后再試");
        }
        CACHE.put(key,key);

            return joinPoint.proceed();
        }catch (Throwable throwable){
            throw new RuntimeException(throwable.getMessage());
        }finally {

        }
    }



    /**
     * 獲取唯一標(biāo)識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;
    }

使用:

	@LocalLock
    public void save(@RequestBody User user) {
       
    }

5)redis

pom.xml 引入

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

.yml文件 redis 配置

spring:
  redis:
    host: localhost
    port: :6379
    password: 123456
import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RedisLock {

    int expire() default 5;
}
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("不允許重復(fù)提交,請稍后再試");
            }
            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
    );


    /**
     * 獲取唯一標(biāo)識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;
    }

使用

	@RedisLock
    public void save(@RequestBody User user) {
       
    }

總結(jié)

到此這篇關(guān)于java開發(fā)中防止重復(fù)提交的幾種解決方案的文章就介紹到這了,更多相關(guān)java防止重復(fù)提交內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java 獲取網(wǎng)絡(luò)302重定向URL的方法

    Java 獲取網(wǎng)絡(luò)302重定向URL的方法

    在本篇文章里小編給大家整理的是關(guān)于Java 獲取網(wǎng)絡(luò)302重定向URL的方法以及相關(guān)知識點,有興趣的朋友們參考下。
    2019-08-08
  • Java數(shù)據(jù)結(jié)構(gòu)之棧與隊列實例詳解

    Java數(shù)據(jù)結(jié)構(gòu)之棧與隊列實例詳解

    這篇文章主要給大家介紹了關(guān)于Java數(shù)據(jù)結(jié)構(gòu)之棧與隊列的相關(guān)資料,算是作為用java描述數(shù)據(jù)結(jié)構(gòu)的一個開始,文中通過實例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2021-11-11
  • Nacos Server部署配置全過程

    Nacos Server部署配置全過程

    本文介紹了Nacos的定義、核心功能(服務(wù)注冊、心跳、發(fā)現(xiàn)、健康檢查等)、單機(jī)和集群部署步驟,以及客戶端服務(wù)的搭建與配置,旨在幫助用戶快速了解和使用Nacos進(jìn)行服務(wù)治理和配置管理
    2025-10-10
  • spring boot集成pagehelper(兩種方式)

    spring boot集成pagehelper(兩種方式)

    這篇文章主要介紹了spring boot集成pagehelper(兩種方式),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • 詳解Spring?中?Bean?對象的存儲和取出

    詳解Spring?中?Bean?對象的存儲和取出

    由于?Spring?擁有對象的管理權(quán),所以我們也需要擁有較為高效的對象存儲和取出的手段,下面我們來分別總結(jié)一下,對Spring?中?Bean?對象的存儲和取出知識感興趣的朋友跟隨小編一起看看吧
    2022-11-11
  • SpringBoot3集成WebSocket的全過程

    SpringBoot3集成WebSocket的全過程

    WebSocket通過一個TCP連接在客戶端和服務(wù)器之間建立一個全雙工、雙向的通信通道,使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單,本文給大家介紹了SpringBoot3集成WebSocket的全過程,并有相關(guān)的代碼示例供大家參考,需要的朋友可以參考下
    2024-05-05
  • Spring中的注解之@Override和@Autowired

    Spring中的注解之@Override和@Autowired

    看別人寫的代碼,經(jīng)常會用到 @Override 和 @Autowired 這兩個注解.這邊總結(jié)一下這兩個注解的作用,對正在學(xué)習(xí)java的小伙伴們有很好地幫助,需要的朋友可以參考下
    2021-05-05
  • maven中deploy命令報401錯誤的原因及解決方案

    maven中deploy命令報401錯誤的原因及解決方案

    在mac版idea使用過程中有時候會出現(xiàn)deploy時候報401錯誤,所以本文給大家介紹了maven 中deploy命令報401錯誤的原因及解決方案,文章通過圖文結(jié)合的方式講解的非常詳細(xì),需要的朋友可以參考下
    2024-05-05
  • 一文搞懂Spring Bean中的作用域和生命周期

    一文搞懂Spring Bean中的作用域和生命周期

    Spring作為當(dāng)前Java最流行、最強(qiáng)大的輕量級框架,受到了程序員的熱烈歡迎。了解Spring?Bean的作用域與生命周期是非常必要的,快跟隨小編一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • J2ME編程中的幾個重要概念介紹

    J2ME編程中的幾個重要概念介紹

    本文介紹的是J2ME編程應(yīng)用平臺中的幾個重要概念,希望對你有幫助,一起來看。
    2015-09-09

最新評論

静海县| 北票市| 九龙城区| 许昌市| 武乡县| 涞源县| 张家港市| 塔河县| 天等县| 施秉县| 东方市| 新化县| 黄平县| 砀山县| 肃北| 大理市| 钟祥市| 沈阳市| 大埔区| 灵山县| 重庆市| 舞钢市| 津南区| 盐边县| 广州市| 桃园市| 定远县| 临潭县| 浦江县| 霍林郭勒市| 文水县| 岑溪市| 江达县| 故城县| 丰城市| 镇巴县| 沂水县| 永胜县| 九龙城区| 九江县| 大埔区|