亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

基于SpringBoot接口+Redis解決用戶重復(fù)提交問題

 更新時(shí)間:2023年10月18日 11:35:01   作者:summo  
當(dāng)網(wǎng)絡(luò)延遲的情況下用戶多次點(diǎn)擊submit按鈕導(dǎo)致表單重復(fù)提交,用戶提交表單后,點(diǎn)擊瀏覽器的【后退】按鈕回退到表單頁面后進(jìn)行再次提交也會(huì)出現(xiàn)用戶重復(fù)提交,辦法有很多,我這里只說一種,利用Redis的set方法搞定,需要的朋友可以參考下

前言

1. 為什么會(huì)出現(xiàn)用戶重復(fù)提交

  • 網(wǎng)絡(luò)延遲的情況下用戶多次點(diǎn)擊submit按鈕導(dǎo)致表單重復(fù)提交;
  • 用戶提交表單后,點(diǎn)擊【刷新】按鈕導(dǎo)致表單重復(fù)提交(點(diǎn)擊瀏覽器的刷新按鈕,就是把瀏覽器上次做的事情再做一次,因?yàn)檫@樣也會(huì)導(dǎo)致表單重復(fù)提交);
  • 用戶提交表單后,點(diǎn)擊瀏覽器的【后退】按鈕回退到表單頁面后進(jìn)行再次提交。

2. 重復(fù)提交不攔截可能導(dǎo)致的問題

  • 重復(fù)數(shù)據(jù)入庫,造成臟數(shù)據(jù)。即使數(shù)據(jù)庫表有UK索引,該操作也會(huì)增加系統(tǒng)的不必要負(fù)擔(dān);
  • 會(huì)成為黑客爆破攻擊的入口,大量的請(qǐng)求會(huì)導(dǎo)致應(yīng)用崩潰;
  • 用戶體驗(yàn)差,多條重復(fù)的數(shù)據(jù)還需要一條條的刪除等。

3. 解決辦法

辦法有很多,我這里只說一種,利用Redis的set方法搞定(不是redisson)

項(xiàng)目代碼

項(xiàng)目結(jié)構(gòu)

配置文件

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.9</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>RequestLock</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>RequestLock</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- redis依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- web依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 切面 -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.5</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

application.properties

spring.application.name=RequestLock
server.port=8080

# Redis服務(wù)器地址
spring.redis.host=127.0.0.1
# Redis服務(wù)器連接端口
spring.redis.port=6379
# Redis服務(wù)器連接密碼(默認(rèn)為空)
spring.redis.password=
# 連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
spring.redis.jedis.pool.max-active=20
# 連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制)
spring.redis.jedis.pool.max-wait=-1
# 連接池中的最大空閑連接
spring.redis.jedis.pool.max-idle=10
# 連接池中的最小空閑連接
spring.redis.jedis.pool.min-idle=0
# 連接超時(shí)時(shí)間(毫秒)
spring.redis.timeout=1000

代碼文件

RequestLockApplication.java

package com.example.requestlock;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RequestLockApplication {

    public static void main(String[] args) {
        SpringApplication.run(RequestLockApplication.class, args);
    }

}

User.java

package com.example.requestlock.model;


import com.example.requestlock.lock.annotation.RequestKeyParam;

public class User {
    
    private String name;

    private Integer age;

    @RequestKeyParam(name = "phone")
    private String phone;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", phone='" + phone + '\'' +
                '}';
    }
}


RequestKeyParam.java

package com.example.requestlock.lock.annotation;

import java.lang.annotation.*;

/**
 * @description 加上這個(gè)注解可以將參數(shù)也設(shè)置為key,唯一key來源
 */
@Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RequestKeyParam {

    /**
     * key值名稱
     *
     * @return 默認(rèn)為空
     */
    String name() default "";
}

RequestLock.java

package com.example.requestlock.lock.annotation;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 * @description 請(qǐng)求防抖鎖,用于防止前端重復(fù)提交導(dǎo)致的錯(cuò)誤
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RequestLock {
    /**
     * redis鎖前綴
     *
     * @return 默認(rèn)為空,但不可為空
     */
    String prefix() default "";

    /**
     * redis鎖過期時(shí)間
     *
     * @return 默認(rèn)2秒
     */
    int expire() default 2;

    /**
     * redis鎖過期時(shí)間單位
     *
     * @return 默認(rèn)單位為秒
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;

    /**
     * redis  key分隔符
     *
     * @return 分隔符
     */
    String delimiter() default ":";
}

RequestLockMethodAspect.java

package com.example.requestlock.lock.aspect;

import com.example.requestlock.lock.annotation.RequestLock;
import com.example.requestlock.lock.keygenerator.RequestKeyGenerator;
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.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;

/**
 * @description 請(qǐng)求鎖切面處理器
 */
@Aspect
@Configuration
public class RequestLockMethodAspect {

    private final StringRedisTemplate stringRedisTemplate;
    private final RequestKeyGenerator requestKeyGenerator;


    @Autowired
    public RequestLockMethodAspect(StringRedisTemplate stringRedisTemplate, RequestKeyGenerator requestKeyGenerator) {
        this.requestKeyGenerator = requestKeyGenerator;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Around("execution(public * * (..)) && @annotation(com.example.requestlock.lock.annotation.RequestLock)")
    public Object interceptor(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        RequestLock requestLock = method.getAnnotation(RequestLock.class);
        if (StringUtils.isEmpty(requestLock.prefix())) {
//            throw new RuntimeException("重復(fù)提交前綴不能為空");
            return "重復(fù)提交前綴不能為空";
        }
        //獲取自定義key
        final String lockKey = requestKeyGenerator.getLockKey(joinPoint);
        final Boolean success = stringRedisTemplate.execute(
                (RedisCallback<Boolean>) connection -> connection.set(lockKey.getBytes(), new byte[0], Expiration.from(requestLock.expire(), requestLock.timeUnit())
                        , RedisStringCommands.SetOption.SET_IF_ABSENT));
        if (!success) {
//            throw new RuntimeException("您的操作太快了,請(qǐng)稍后重試");
            return "您的操作太快了,請(qǐng)稍后重試";
        }
        try {
            return joinPoint.proceed();
        } catch (Throwable throwable) {
//            throw new RuntimeException("系統(tǒng)異常");
            return "系統(tǒng)異常";
        }
    }
}

RequestKeyGenerator.java

package com.example.requestlock.lock.keygenerator;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 加鎖key生成器
 */
public interface RequestKeyGenerator {
    /**
     * 獲取AOP參數(shù),生成指定緩存Key
     *
     * @param joinPoint 切入點(diǎn)
     * @return 返回key值
     */
    String getLockKey(ProceedingJoinPoint joinPoint);
}

RequestKeyGeneratorImpl.java

package com.example.requestlock.lock.keygenerator.impl;

import com.example.requestlock.lock.annotation.RequestKeyParam;
import com.example.requestlock.lock.annotation.RequestLock;
import com.example.requestlock.lock.keygenerator.RequestKeyGenerator;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

@Service
public class RequestKeyGeneratorImpl implements RequestKeyGenerator {

    @Override
    public String getLockKey(ProceedingJoinPoint joinPoint) {
        //獲取連接點(diǎn)的方法簽名對(duì)象
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        //Method對(duì)象
        Method method = methodSignature.getMethod();
        //獲取Method對(duì)象上的注解對(duì)象
        RequestLock requestLock = method.getAnnotation(RequestLock.class);
        //獲取方法參數(shù)
        final Object[] args = joinPoint.getArgs();
        //獲取Method對(duì)象上所有的注解
        final Parameter[] parameters = method.getParameters();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < parameters.length; i++) {
            final RequestKeyParam cacheParams = parameters[i].getAnnotation(RequestKeyParam.class);
            //如果屬性不是CacheParam注解,則不處理
            if (cacheParams == null) {
                continue;
            }
            //如果屬性是CacheParam注解,則拼接 連接符(:)+ CacheParam
            sb.append(requestLock.delimiter()).append(args[i]);
        }
        //如果方法上沒有加CacheParam注解
        if (StringUtils.isEmpty(sb.toString())) {
            //獲取方法上的多個(gè)注解(為什么是兩層數(shù)組:因?yàn)榈诙訑?shù)組是只有一個(gè)元素的數(shù)組)
            final Annotation[][] parameterAnnotations = method.getParameterAnnotations();
            //循環(huán)注解
            for (int i = 0; i < parameterAnnotations.length; i++) {
                final Object object = args[i];
                //獲取注解類中所有的屬性字段
                final Field[] fields = object.getClass().getDeclaredFields();
                for (Field field : fields) {
                    //判斷字段上是否有CacheParam注解
                    final RequestKeyParam annotation = field.getAnnotation(RequestKeyParam.class);
                    //如果沒有,跳過
                    if (annotation == null) {
                        continue;
                    }
                    //如果有,設(shè)置Accessible為true(為true時(shí)可以使用反射訪問私有變量,否則不能訪問私有變量)
                    field.setAccessible(true);
                    //如果屬性是CacheParam注解,則拼接 連接符(:)+ CacheParam
                    sb.append(requestLock.delimiter()).append(ReflectionUtils.getField(field, object));
                }
            }
        }
        //返回指定前綴的key
        return requestLock.prefix() + sb;
    }
}

UserController.java

package com.example.requestlock.controller;

import com.example.requestlock.lock.annotation.RequestLock;
import com.example.requestlock.model.User;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {

    @PostMapping("/addUser1")
    public String addUser1(@RequestBody User user) {
        System.out.println("不做任何處理" + user);
        return "添加成功";
    }

    @PostMapping("/addUser2")
    @RequestLock(prefix = "addUser")
    public String addUser2(@RequestBody User user) {
        System.out.println("防重提交" + user);
        return "添加成功";
    }
}

原理解釋

該RequestLock(請(qǐng)求鎖)利用了Redis的單線程處理以及Key值過期特點(diǎn),核心通過RequestLock、RequestKeyParam注解生成一個(gè)唯一的key值,存入redis后設(shè)置一個(gè)過期時(shí)間(1-3秒),當(dāng)?shù)诙握?qǐng)求的時(shí)候,判斷生成的key值是否在Redis中存在,如果存在則認(rèn)為第二次提交是重復(fù)的。

流程圖如下:

用法說明

1. 在controller的方法上增加@RequestLock注解,并給一個(gè)前綴

 @PostMapping("/addUser2")
 @RequestLock(prefix = "addUser")
 public String addUser2(@RequestBody User user)

加了@RequestLock注解代表這個(gè)方法會(huì)進(jìn)行重復(fù)提交校驗(yàn),沒有加則不會(huì)進(jìn)行校驗(yàn)。通過注解的方式可以使用法變得靈活。

2. @RequestKeyParam注解用在對(duì)象的屬性上

@RequestKeyParam(name = "phone")
private String phone;

在對(duì)象的屬性上加@RequestKeyParam注解后,Redis的key則由 @RequestLock定義的prefix加上字段的值組成,比如當(dāng)傳入傳入phone是123456789,那么當(dāng)前的key值則為: addUser:123456789。

效果展示

調(diào)用addUser1接口

這里無論點(diǎn)擊多少次提交,都會(huì)展示添加“添加成功”,這樣是不行的。

調(diào)用addUser2接口

第一次提交,“添加成功”。

快速點(diǎn)擊第二次提交,就會(huì)出現(xiàn)“您的操作太快了,請(qǐng)稍后重試”提示。

以上就是基于SpringBoot接口+Redis解決用戶重復(fù)提交問題的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot+Redis解決重復(fù)提交的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Spring創(chuàng)建Bean的過程Debug的詳細(xì)流程

    Spring創(chuàng)建Bean的過程Debug的詳細(xì)流程

    這篇文章主要介紹了Spring創(chuàng)建Bean的過程Debug的流程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-11-11
  • mybatisplus的連表增強(qiáng)插件mybatis plus join

    mybatisplus的連表增強(qiáng)插件mybatis plus join

    本文主要介紹了mybatisplus的連表增強(qiáng)插件mybatis plus join,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • Java中單例模式詳解

    Java中單例模式詳解

    這篇文章主要介紹了Java中單例模式詳解,單例模式包括了懶漢式單例、餓漢式單例、登記式單例三種,想要了解的朋友可以了解一下。
    2016-11-11
  • Shiro與Springboot整合開發(fā)的基本步驟過程詳解

    Shiro與Springboot整合開發(fā)的基本步驟過程詳解

    這篇文章主要介紹了Shiro與Springboot整合開發(fā)的基本步驟,本文結(jié)合實(shí)例代碼給大家介紹整合過程,感興趣的朋友跟隨小編一起看看吧
    2023-06-06
  • Java通過正則表達(dá)式捕獲組中的文本

    Java通過正則表達(dá)式捕獲組中的文本

    這篇文章主要給大家介紹了關(guān)于利用Java如何通過正則表達(dá)式捕獲組中文本的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧下
    2019-09-09
  • 解決JavaWeb-file.isDirectory()遇到的坑問題

    解決JavaWeb-file.isDirectory()遇到的坑問題

    JavaWeb開發(fā)中,使用`file.isDirectory()`判斷路徑是否為文件夾時(shí),需要特別注意:該方法只能判斷已存在的文件夾,若路徑不存在,無論其實(shí)際是否應(yīng)為文件夾,均會(huì)返回`false`,為了解決這個(gè)問題,可以采用正則表達(dá)式進(jìn)行判斷,但要求路徑字符串的結(jié)尾必須添加反斜杠(\)
    2025-02-02
  • Java檢測(cè)網(wǎng)絡(luò)是否正常通訊

    Java檢測(cè)網(wǎng)絡(luò)是否正常通訊

    在網(wǎng)絡(luò)應(yīng)用程序中,檢測(cè)IP地址和端口是否通常是必要的,本文主要介紹了Java檢測(cè)網(wǎng)絡(luò)是否正常通訊,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-11-11
  • IDEA下SpringBoot指定配置文件啟動(dòng)項(xiàng)目的全過程

    IDEA下SpringBoot指定配置文件啟動(dòng)項(xiàng)目的全過程

    我們?cè)谑褂胹pringboot項(xiàng)目開發(fā)的時(shí)候,每次切換環(huán)境跑項(xiàng)目的時(shí)候,都得修改配置文件的數(shù)據(jù)庫地址,這樣來回修改感覺很麻煩,這篇文章主要給大家介紹了關(guān)于IDEA下SpringBoot指定配置文件啟動(dòng)項(xiàng)目的相關(guān)資料,需要的朋友可以參考下
    2023-06-06
  • Java在Word中添加多行圖片水印

    Java在Word中添加多行圖片水印

    這篇文章主要介紹了Java在Word中添加多行圖片,圖文講解的很清晰,有對(duì)于這方面不懂得同學(xué)可以跟著研究下
    2021-02-02
  • 淺析我對(duì) String、StringBuilder、StringBuffer 的理解

    淺析我對(duì) String、StringBuilder、StringBuffer 的理解

    StringBuilder、StringBuffer 和 String 一樣,都是用于存儲(chǔ)字符串的。這篇文章談?wù)勑【帉?duì)String、StringBuilder、StringBuffer 的理解,感興趣的朋友跟隨小編一起看看吧
    2020-05-05

最新評(píng)論