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

SpringBoot+Redisson自定義注解一次解決重復(fù)提交問題

 更新時間:2024年03月03日 10:32:29   作者:程序員濟癲  
項目中經(jīng)常會出現(xiàn)重復(fù)提交的問題,本文主要介紹了SpringBoot+Redisson自定義注解一次解決重復(fù)提交問題,具有一定的參考價值,感興趣的可以了解一下

前言

項目中經(jīng)常會出現(xiàn)重復(fù)提交的問題,而接口冪等性也一直以來是做任何項目都要關(guān)注的疑難點,網(wǎng)上可以查到非常多的方案,我歸納了幾點如下:

1)、數(shù)據(jù)庫層面,對責任字段設(shè)置唯一索引,這是最直接有效的方式,不好的地方就是一旦觸發(fā)就會在服務(wù)端拋數(shù)據(jù)庫相關(guān)異常;

2)、代碼層面,增加業(yè)務(wù)邏輯判斷,先查詢一遍若沒有才插入,這也是最容易想到的方式,反正寫上就對了,不好的地方就是分布式場景下依然避免不了問題;

3)、前端層面,對于觸發(fā)事件的操作比如按鈕等,最好點擊過后都設(shè)置幾秒的置灰時間,能很大程度上解決惡意提交的問題。

以上幾點經(jīng)常在項目中結(jié)合使用,不過有一種更通用的方案,就是自定義注解,寫一個專門處理這類問題的注解,之后在有需要用到的接口上直接加上這個注解即可,十分方便。

實現(xiàn)步驟

1、引入依賴

完整依賴如下:

使用的SpringBoot版本3.0之前最新的發(fā)布版本2.6.3,3.0版本要求JDK17,所以短時間內(nèi)不會在行業(yè)中普遍的,企業(yè)中成熟的版本依然是2.x。

<?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.6.3</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.example</groupId>
   <artifactId>resubmit</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>resubmit</name>
   <description>Demo project for Spring Boot</description>
   <properties>
      <java.version>1.8</java.version>
   </properties>
   <dependencies>
        <!-- spring web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- spring aop 實現(xiàn)自定義注解用到 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- spring processor 加載項目配置使用,也可以不用,但IDEA配置類頂端會有警告 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- spring jdbc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- mysql 不填寫版本號默認是8.0以上 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- springData redis 不填寫版本號默認和springboot版本一致 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- redission 注意版本號和springData-redis要對應(yīng) 這里26對應(yīng)springData-redis的2.6版本 -->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-data-26</artifactId>
            <version>3.16.8</version>
        </dependency>
        <!-- fastjson 解析json用到,也可以換成自己喜歡用的 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.79</version>
        </dependency>
        <!-- mybatis-plus 參考官網(wǎng),目前是最新版 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!-- 代碼生成器 mybatisPlus自帶的生成器 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!-- freemarker模板生成器 引入代碼生成器需要 -->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.31</version>
        </dependency>
        <!-- swagger 因為mybatisPlus代碼生成器會自帶swagger的注解 -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.7.0</version>
        </dependency>
        <!-- lombok 因為mybatisPlus代碼生成器會自帶lombok的注解 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
               <excludes>
                  <exclude>
                     <groupId>org.projectlombok</groupId>
                     <artifactId>lombok</artifactId>
                  </exclude>
               </excludes>
            </configuration>
         </plugin>
      </plugins>
   </build>

</project>

2、配置application.yml

這里注意,生產(chǎn)環(huán)境項目是要區(qū)分application.yml、application-dev.yml、application-test.yml、application-prod.yml的,這里只是實現(xiàn)功能的小案例,就只寫了這一個。

# 端口,改成自己的。
server:
  port: 8888

# 數(shù)據(jù)源,使用hikari,現(xiàn)在一般項目都是默認的這個數(shù)據(jù)源了,更詳細的配置可以百度。
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/resubmit_demo?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&useSSL=false
    username: root # 換成自己數(shù)據(jù)庫的賬號
    password: 123456 # 換成自己數(shù)據(jù)庫的密碼
  # redis配置,換成自己的。
  redis:
    database: 11
    host: 192.168.1.197
    port: 6379
    password: 123456
    jedis:
      pool:
        max-active: 1000
        max-wait: -1ms
        max-idle: 50
        min-idle: 1

# redission配置,這里直接讀取的redis變量.
redisson:
  singleserverconfig:
    address: "redis://${spring.redis.host}:${spring.redis.port}"
    password: ${spring.redis.password}
    database: ${spring.redis.database}

3、編寫配置類

1)、redis配置類

package com.example.resubmit.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * <p>
 *  redis配置類
 * </p>
 *
 * @author 福隆苑居士,公眾號:【Java分享客?!?
 * @since 2022-02-08
 */
@Configuration
public class RedisConfig {

   @Bean
   public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
      StringRedisTemplate redisTemplate = new StringRedisTemplate();
      redisTemplate.setConnectionFactory(redisConnectionFactory);
      return redisTemplate;
   }

   @Bean
   public RedisTemplate redisTemplate(RedisConnectionFactory factory) {
      RedisTemplate<String, Object> template = new RedisTemplate<>();
      template.setConnectionFactory(factory);

      Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
      ObjectMapper objectMapper = new ObjectMapper();
      objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
      objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
      jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

      StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
      // key采用String的序列化方式
      template.setKeySerializer(stringRedisSerializer);
      // hash的key也采用String的序列化方式
      template.setHashKeySerializer(stringRedisSerializer);
      // value序列化方式采用jackson
      template.setValueSerializer(jackson2JsonRedisSerializer);
      // hash的value序列化方式采用jackson
      template.setHashValueSerializer(jackson2JsonRedisSerializer);
      template.afterPropertiesSet();
      return template;
   }
}

2)、Redisson配置類

package com.example.resubmit.config;

import com.fasterxml.jackson.core.JsonProcessingException;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig;
import org.redisson.spring.data.connection.RedissonConnectionFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;

/**
 * <p>
 *  redission配置類
 * </p>
 *
 * @author 福隆苑居士,公眾號:【Java分享客?!?
 * @since 2022-02-08
 */
@Configuration
@ConfigurationProperties(prefix = "redisson.singleserverconfig")
public class RedissonSpringDataConfig {

   private static final Logger log = LoggerFactory.getLogger(RedissonSpringDataConfig.class);

    private String address;
    private int database;
    private String password;

    @Bean
    public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redisson) {
        return new RedissonConnectionFactory(redisson);
    }

    @Bean(destroyMethod = "shutdown")
    public RedissonClient redisson() throws JsonProcessingException {
      log.debug("[RedissonSpringDataConfig][redisson]>>>> address: {}, database: {}, password: {}", address, database, password);
        Config config = new Config();
        SingleServerConfig sconfig= config.useSingleServer()
            .setAddress(address)
         .setDatabase(database);
        // 如果redis設(shè)置了密碼,這里不設(shè)置密碼就會報“org.redisson.client.RedisAuthRequiredException: NOAUTH Authentication required”錯誤。
        if(StringUtils.hasText(password)){
            sconfig.setPassword(password);
        }
        return Redisson.create(config);
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public int getDatabase() {
        return database;
    }

    public void setDatabase(int database) {
        this.database = database;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

4、MybatisPlus代碼生成器

這個生成器我參考官網(wǎng)的做了優(yōu)化和注釋,一看就能明白。

package com.example.resubmit.generator;

import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;

import java.util.Collections;

/**
 * @作者: 福隆苑居士,公眾號:【Java分享客?!?
 * @日期: 2022/2/8 20:51
 * @描述: mybatis-plus代碼生成器
 */
public class CodeGenerator {

   private static final String url = "jdbc:mysql://localhost:3306/resubmit_demo?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&useSSL=false";
   private static final String username = "root";
   private static final String password = "123456";
   private static final String outputDir = "D:\workspace\workspace_java\resubmit\src\main\java"; // entity、mapper、service、controller生成的目錄地址,換成自己項目的。
   private static final String xmlOutputDir = "D:\workspace\workspace_java\resubmit\src\main\resources\mapper"; // xxMapper.xml生成的目錄地址,換成自己項目的。

   public static void main(String[] args) {
      FastAutoGenerator.create(url, username, password)
            .globalConfig(builder -> {
               builder.author("福隆苑居士,公眾號:【9i分享客棧】") // 設(shè)置作者
                     .enableSwagger() // 開啟 swagger 模式
                     .fileOverride() // 覆蓋已生成文件
                     .outputDir(outputDir); // 指定輸出目錄
            })
            .packageConfig(builder -> {
               builder.parent("com.example.resubmit") // 設(shè)置父包名,和自己項目的父包名一致即可。
                     .moduleName("") // 設(shè)置父包模塊名,為空就會直接生成在父包名目錄下。
                     .pathInfo(Collections.singletonMap(OutputFile.mapperXml, xmlOutputDir)); // 設(shè)置mapperXml生成路徑
            })
            .strategyConfig(builder -> {
               builder.addInclude("tb_user") // 設(shè)置需要生成的表名,多個用逗號隔開。
                     .addTablePrefix("t_", "tb_", "c_"); // 設(shè)置過濾表前綴,多個用逗號隔開。
            })
            .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默認的是Velocity引擎模板
            .execute();
   }

}

5、定義響應(yīng)實體

這個因人而定,可以自己定義,也可以直接用我的。

package com.example.resubmit.util;

import com.example.resubmit.enums.ResponseCodeEnum;
import com.fasterxml.jackson.annotation.JsonIgnore;

/**
 * <p>
 *  自定義響應(yīng)結(jié)果
 * </p>
 *
 * @author 福隆苑居士,公眾號:【Java分享客棧】
 * @since 2022-02-08
 */
public class ResultEntity<T> {

    private String code;

    private String msg;

    private T data;

    public ResultEntity(){}

    public ResultEntity(String code, String msg){
        this.code = code;
        this.msg = msg;
    }

    public ResultEntity(String code, String msg, T data){
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    @JsonIgnore
    public boolean isSuccess() {
        return ResponseCodeEnum.SUCCESS.getCode().equals(this.getCode());
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static ResultEntity fail(String code, String msg) {
        return new ResultEntity(code, msg);
    }

    public static <T> ResultEntity fail(String code, String msg, T data) {
        return new ResultEntity(code, msg, data);
    }

    public static ResultEntity ok(String code, String msg) {
        return new ResultEntity(code, msg);
    }

    public static <T> ResultEntity ok(String code, String msg, T data) {
        return new ResultEntity(code, msg, data);
    }

    public static ResultEntity ok(String msg) {
        return new ResultEntity(ResponseCodeEnum.SUCCESS.getCode(), msg);
    }

    public static <T> ResultEntity ok(String msg, T data) {
        return new ResultEntity(ResponseCodeEnum.SUCCESS.getCode(), msg, data);
    }

    public static ResultEntity fail(String msg) {
        return new ResultEntity(ResponseCodeEnum.FAIL.getCode(), msg);
    }
}

6、Redisson工具類

package com.example.resubmit.redission;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 加鎖解鎖工具類
 * </p>
 *
 * @author 福隆苑居士,公眾號:【Java分享客?!?
 * @since 2022-02-08
 */
@Component
public class RedisLock {
   private static final Logger log = LoggerFactory.getLogger(RedisLock.class);

   // todo  待優(yōu)化,最好使用自定義的線程池,自定義工作隊列和最大線程數(shù)。
   private static final ScheduledExecutorService EXECUTOR_SERVICE = Executors.newScheduledThreadPool(4);
   @Resource
   private Redisson redisson;

   /**
    * Redission獲取鎖
    *
    * @param lockKey      鎖名
    * @param uuid         唯一標識
    * @param delaySeconds 過期時間
    * @param unit         單位
    * @return 是否獲取成功
    */
   public boolean Rlock(String lockKey, final String uuid, long delaySeconds, final TimeUnit unit) {
      RLock rLock = redisson.getLock(lockKey);
      boolean success = false;
      try {
         // log.debug("===lock thread id is :{}", Thread.currentThread().getId());
         success = rLock.tryLock(0, delaySeconds, unit);
      } catch (InterruptedException e) {
         log.error("[RedisLock][Rlock]>>>> 加鎖異常: ", e);
      }
      return success;
   }

   /**
    * Redission釋放鎖
    *
    * @param lockKey 鎖名
    */
   public void Runlock(String lockKey) {
      RLock rLock = redisson.getLock(lockKey);
      log.debug("[RedisLock][Rlock]>>>> {}, status: {} === unlock thread id is: {}", rLock.isHeldByCurrentThread(), rLock.isLocked(),
            Thread.currentThread().getId());
      rLock.unlock();
   }

   /**
    * Redission延遲釋放鎖
    *
    * @param lockKey 鎖名
    * @param delayTime 延遲時間
    * @param unit 單位
    */
   public void delayUnlock(final String lockKey, long delayTime, TimeUnit unit) {
      if (!StringUtils.hasText(lockKey)) {
         return;
      }
      if (delayTime <= 0) {
         Runlock(lockKey);
      } else {
         EXECUTOR_SERVICE.schedule(() -> Runlock(lockKey), delayTime, unit);
      }
   }

}

7、自定義注解

這里增加了延遲時間的屬性,默認8秒。

package com.example.resubmit.redission;

import java.lang.annotation.*;

/**
 * <p>
 * 防重復(fù)提交注解
 * </p>
 *
 * @author 福隆苑居士,公眾號:【Java分享客棧】
 * @since 2022-02-08
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NotResubmit {

    /**
     * 延時時間 在延時多久后可以再次提交,默認8秒
     * @return 秒
     */
    int delaySeconds() default 8;
}

8、防重注解的實現(xiàn)

這里是實現(xiàn)防重注解的核心代碼,這里append實體屬性時用到了toString()方法,所以要求我們生成的實體對象一定要有toString()方法。

package com.example.resubmit.redission;

import com.example.resubmit.enums.ResponseCodeEnum;
import com.example.resubmit.util.ResultEntity;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * 防重復(fù)提交注解的實現(xiàn),使用AOP。
 * </p>
 *
 * @author 福隆苑居士,公眾號:【Java分享客?!?
 * @since 2022-02-08
 */
@Aspect
@Component
public class LockMethodAOP {
   private static final Logger log = LoggerFactory.getLogger(LockMethodAOP.class);

   @Resource
   private RedisLock redisLock;

   /**
    * 這里注意,我的注解寫在同一個包下所以沒有包名,如果換自己的目錄,要改為@annotation(com.xxx.NotResubmit)加上完整包名.
    */
   @Around("execution(public * *(..)) && @annotation(NotResubmit)")
   public Object interceptor(ProceedingJoinPoint pjp) throws Throwable {
      // 獲取到這個注解
      MethodSignature signature = (MethodSignature) pjp.getSignature();
      Method method = signature.getMethod();
      NotResubmit lock = method.getAnnotation(NotResubmit.class);

      final String lockKey = generateKey(pjp);

      // 上鎖
      final boolean success = redisLock.Rlock(lockKey, null, lock.delaySeconds(), TimeUnit.SECONDS);
      if (!success) {
         // 這里也可以改為自己項目自定義的異常拋出
         return ResponseEntity.badRequest().body(ResultEntity.fail(ResponseCodeEnum.FAIL.getCode(), "操作太頻繁"));
      }
      return pjp.proceed();
   }

   private String generateKey(ProceedingJoinPoint pjp) {
      StringBuilder sb = new StringBuilder();
      Signature signature = pjp.getSignature();
      MethodSignature methodSignature = (MethodSignature) signature;
      Method method = methodSignature.getMethod();
      sb.append(pjp.getTarget().getClass().getName())//類名
            .append(method.getName());//方法名
      for (Object o : pjp.getArgs()) {
         sb.append(o.toString());
      }
      return DigestUtils.md5DigestAsHex(sb.toString().getBytes(Charset.defaultCharset()));
   }

}

9、編寫控制器

這里在插入方法上加了@NotResubmit(delaySeconds = 10)注解,表示這個插入方法執(zhí)行時,10秒內(nèi)不允許重復(fù)提交,10秒后才可以插入成功,這個時間可以根據(jù)需要在不同的接口上自行修改。

package com.example.resubmit.controller;

import com.example.resubmit.entity.User;
import com.example.resubmit.redission.NotResubmit;
import com.example.resubmit.service.IUserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.stereotype.Controller;

import java.time.LocalDateTime;
import java.util.List;

/**
 * <p>
 *  控制器
 * </p>
 *
 * @author 福隆苑居士,公眾號:【Java分享客棧】
 * @since 2022-02-08
 */
@Controller
@RequestMapping("/api/user")
public class UserController {

   private final IUserService userService;

   public UserController(IUserService userService) {
      this.userService = userService;
   }

   /**
    * 查詢列表
    * @return 結(jié)果
    */
   @GetMapping("/list")
   public ResponseEntity<List<User>> list() {
      return ResponseEntity.ok().body(userService.list());
   }

   /**
    * 插入記錄
    * @return 結(jié)果
    */
   @NotResubmit(delaySeconds = 10)
   @PostMapping("/insert")
   public ResponseEntity<List<User>> insert(@RequestBody User user) {
      // 插入
      user.setCreatedAt(LocalDateTime.now());
      user.setCreatedBy("冰敦敦");
      user.setUpdatedAt(LocalDateTime.now());
      user.setUpdatedBy("冰敦敦");
      userService.save(user);
      // 返回列表
      return ResponseEntity.ok().body(userService.list());
   }
}

10、效果

1)、執(zhí)行接口插入一條記錄

111.png

222.png

2)、連續(xù)點擊看注解是否生效

可以發(fā)現(xiàn),會返回操作太頻繁的提示,并沒有插入數(shù)據(jù)庫。

333.png

3)、等待10秒過后再執(zhí)行

發(fā)現(xiàn)又可以插入進去了

444.png

總結(jié)

通過最終效果可以發(fā)現(xiàn),自定義的防重注解實現(xiàn)起來并沒有那么難,核心思想如下:

把實體類的屬性append并進行簽名,作為redisson加鎖的key,在aop攔截到使用這個注解的接口方法時,就會根據(jù)傳入的對象和上一次提交時傳入的對象進行屬性簽名的匹配,只要完全一致,代表是重復(fù)提交,只有在超過延遲時間后才能成功通過攔截,最終執(zhí)行業(yè)務(wù)。

這也是選取整合mybatisPlus的原因,因為它的代碼生成器會自動生成帶有toString()方法的代碼,如果不想使用,直接用lombok注解也可以。

最后,你其實可以發(fā)現(xiàn),掌握了方法后,這個注解不僅可以拿來做防重,進行優(yōu)化改造后還可以用來做接口限流,是不是很有意思呢。

到此這篇關(guān)于SpringBoot+Redisson自定義注解一次解決重復(fù)提交問題 的文章就介紹到這了,更多相關(guān)SpringBoot Redisson重復(fù)提交內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java 深入探究講解抽象工廠模式

    Java 深入探究講解抽象工廠模式

    當系統(tǒng)所提供的工廠所需生產(chǎn)的具體產(chǎn)品并不是一個簡單的對象,而是多個位于不同產(chǎn)品等級結(jié)構(gòu)中屬于不同類型的具體產(chǎn)品時需要使用抽象工廠模式,抽象工廠模式是所有形式的工廠模式中最為抽象和最具一般性的一種形態(tài)
    2022-04-04
  • Java畢業(yè)設(shè)計實戰(zhàn)之教室預(yù)訂管理系統(tǒng)的實現(xiàn)

    Java畢業(yè)設(shè)計實戰(zhàn)之教室預(yù)訂管理系統(tǒng)的實現(xiàn)

    這是一個使用了java+SpringBoot+Maven+Vue+mysql開發(fā)的教室預(yù)訂管理系統(tǒng),是一個畢業(yè)設(shè)計的實戰(zhàn)練習,具有教室預(yù)訂管理該有的所有功能,感興趣的朋友快來看看吧
    2022-02-02
  • 原理分析Java?Mybatis中的Mapper

    原理分析Java?Mybatis中的Mapper

    這篇文章主要為大家介紹了Java?Mybatis中的Mapper,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-01-01
  • spring data jpa使用詳解(推薦)

    spring data jpa使用詳解(推薦)

    這篇文章主要介紹了spring data jpa使用詳解(推薦),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04
  • SpringBoot項目中@RestControllerAdvice全局異常失效問題的解決

    SpringBoot項目中@RestControllerAdvice全局異常失效問題的解決

    @RestController注解是一個用于定義RESTful Web服務(wù)的控制器的特殊注解,它是@Controller和@ResponseBody注解的結(jié)合體,意味著你不需要在每個處理請求的方法上都添加@ResponseBody,本文給大家介紹了解決SpringBoot項目中@RestControllerAdvice全局異常失效問題
    2024-11-11
  • Java中ShardingSphere分庫分表實戰(zhàn)

    Java中ShardingSphere分庫分表實戰(zhàn)

    我們做項目的時候,數(shù)據(jù)量比較大,單表千萬級別的,需要分庫分表,本文主要介紹了Java中ShardingSphere分庫分表實戰(zhàn),感興趣的可以了解一下
    2021-09-09
  • 詳解Java?POI?excel自定義設(shè)置單元格格式

    詳解Java?POI?excel自定義設(shè)置單元格格式

    這篇文章主要介紹了Java?POI?excel設(shè)置單元格格式,自定義設(shè)置,設(shè)置單元格格式:來源_formats,更多數(shù)據(jù)類型從formats里面發(fā)現(xiàn),需要的朋友可以參考下
    2024-01-01
  • JAVA(SpringBoot)集成Jasypt進行加密、解密功能

    JAVA(SpringBoot)集成Jasypt進行加密、解密功能

    Jasypt是一個Java庫,專門用于簡化加密和解密操作,提供多種加密算法支持,集成到SpringBoot等框架中,通過使用Jasypt,可以有效保護配置文件中的敏感信息,如數(shù)據(jù)庫密碼等,避免被未授權(quán)訪問,Jasypt還支持自定義加密器,提高擴展性和安全性,適用于各種需要加密保護應(yīng)用場景
    2024-09-09
  • jvm crash的崩潰日志詳細分析及注意點

    jvm crash的崩潰日志詳細分析及注意點

    本篇文章主要介紹了jvm crash的崩潰日志詳細分析及注意點。具有很好的參考價值,下面跟著小編一起來看下吧
    2017-04-04
  • java Lombok之@Accessors用法及說明

    java Lombok之@Accessors用法及說明

    這篇文章主要介紹了java Lombok之@Accessors用法及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03

最新評論