spring boot 防止重复提交实现方法详解

 更新时间:2019年11月06日 10:49:11   作者:苍青浪  
这篇文章主要介绍了spring boot 防止重复提交实现方法,结合实例形式详细分析了spring boot 防止重复提交具体配置、实现方法及操作注意事项,需要的朋友可以参考下

本文实例讲述了spring boot 防止重复提交实现方法。分享给大家供大家参考,具体如下:

服务器端实现方案:同一客户端在2秒内对同一URL的提交视为重复提交

上代码吧

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>com.example</groupId>
 <artifactId>springboot-repeat-submit</artifactId>
 <version>1.0</version>
 <packaging>jar</packaging>
 <parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.0.4.RELEASE</version>
  <relativePath/> <!-- lookup parent from repository -->
 </parent>
 <properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  <java.version>1.8</java.version>
 </properties>
 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
  </dependency>
  <dependency>
   <groupId>com.google.guava</groupId>
   <artifactId>guava</artifactId>
   <version>24.0-jre</version>
  </dependency>
 </dependencies>
 <build>
  <plugins>
   <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
   </plugin>
  </plugins>
 </build>
</project>

Application.java

package com;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * @author www.gaozz.club
 * @功能描述 防重复提交
 * @date 2018-08-26
 */
@SpringBootApplication
public class Application {
 public static void main(String[] args) {
  SpringApplication.run(Application.class, args);
 }
}

自定义注解NoRepeatSubmit.java

package com.common;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD) // 作用到方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时有效
/**
 * @功能描述 防止重复提交标记注解
 * @author www.gaozz.club
 * @date 2018-08-26
 */
public @interface NoRepeatSubmit {
}

aop解析注解NoRepeatSubmitAop.java

package com.common;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.google.common.cache.Cache;
@Aspect
@Component
/**
 * @功能描述 aop解析注解
 * @author www.gaozz.club
 * @date 2018-08-26
 */
public class NoRepeatSubmitAop {
 private Log logger = LogFactory.getLog(getClass());
 @Autowired
 private Cache<String, Integer> cache;
 @Around("execution(* com.example..*Controller.*(..)) && @annotation(nrs)")
 public Object arround(ProceedingJoinPoint pjp, NoRepeatSubmit nrs) {
  try {
   ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
   String sessionId = RequestContextHolder.getRequestAttributes().getSessionId();
   HttpServletRequest request = attributes.getRequest();
   String key = sessionId + "-" + request.getServletPath();
   if (cache.getIfPresent(key) == null) {// 如果缓存中有这个url视为重复提交
    Object o = pjp.proceed();
    cache.put(key, 0);
    return o;
   } else {
    logger.error("重复提交");
    return null;
   }
  } catch (Throwable e) {
   e.printStackTrace();
   logger.error("验证重复提交时出现未知异常!");
   return "{\"code\":-889,\"message\":\"验证重复提交时出现未知异常!\"}";
  }
 }
}

缓存类

package com.common;
import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
@Configuration
/**
 * @功能描述 内存缓存
 * @author www.gaozz.club
 * @date 2018-08-26
 */
public class UrlCache {
 @Bean
 public Cache<String, Integer> getCache() {
  return CacheBuilder.newBuilder().expireAfterWrite(2L, TimeUnit.SECONDS).build();// 缓存有效期为2秒
 }
}

测试Controller

package com.example;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.common.NoRepeatSubmit;
/**
 * @功能描述 测试Controller
 * @author www.gaozz.club
 * @date 2018-08-26
 */
@RestController
public class TestController {
 @RequestMapping("/test")
 @NoRepeatSubmit
 public String test() {
  return ("程序逻辑返回");
 }
}

浏览器输入http://localhost:8080/test

然后F5刷新查看效果

以下为新版内容:解决了程序集群部署时请求可能会落到多台机器上的问题,把内存缓存换成了redis

application.yml

spring:
 redis:
 host: 192.168.1.92
 port: 6379
 password: 123456

RedisConfig.java

package com.common;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
@Configuration
public class RedisConfig {
 @Bean
 @ConfigurationProperties(prefix = "spring.redis")
 public JedisConnectionFactory getConnectionFactory() {
  return new JedisConnectionFactory(new RedisStandaloneConfiguration(), JedisClientConfiguration.builder().build());
 }
 @Bean
 <K, V> RedisTemplate<K, V> getRedisTemplate() {
  RedisTemplate<K, V> redisTemplate = new RedisTemplate<K, V>();
  redisTemplate.setConnectionFactory(getConnectionFactory());
  return redisTemplate;
 }
}

调整切面类NoRepeatSubmitAop.java

package com.common;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Aspect
@Component
/**
 * @功能描述 aop解析注解
 * @author www.gaozz.club
 * @date 2018-11-02
 */
public class NoRepeatSubmitAop {
 private Log logger = LogFactory.getLog(getClass());
 @Autowired
 private RedisTemplate<String, Integer> template;
 @Around("execution(* com.example..*Controller.*(..)) && @annotation(nrs)")
 public Object arround(ProceedingJoinPoint pjp, NoRepeatSubmit nrs) {
  ValueOperations<String, Integer> opsForValue = template.opsForValue();
  try {
   ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
   String sessionId = RequestContextHolder.getRequestAttributes().getSessionId();
   HttpServletRequest request = attributes.getRequest();
   String key = sessionId + "-" + request.getServletPath();
   if (opsForValue.get(key) == null) {// 如果缓存中有这个url视为重复提交
    Object o = pjp.proceed();
    opsForValue.set(key, 0, 2, TimeUnit.SECONDS);
    return o;
   } else {
    logger.error("重复提交");
    return null;
   }
  } catch (Throwable e) {
   e.printStackTrace();
   logger.error("验证重复提交时出现未知异常!");
   return "{\"code\":-889,\"message\":\"验证重复提交时出现未知异常!\"}";
  }
 }
}

附:GitHub源码地址:https://github.com/gzz2017gzz/spring-boot2-example/tree/master/54-spring-boot-repeat-submit-single

更多关于java相关内容感兴趣的读者可查看本站专题:《Spring框架入门与进阶教程》、《Java数据结构与算法教程》、《Java操作DOM节点技巧总结》、《Java文件与目录操作技巧汇总》和《Java缓存操作技巧汇总

希望本文所述对大家java程序设计有所帮助。

相关文章

  • StringUtils,CollectionUtils判断为空的方法和原生代码哪个效率最高

    StringUtils,CollectionUtils判断为空的方法和原生代码哪个效率最高

    这篇文章主要介绍了StringUtils,CollectionUtils判断为空的方法和原生代码哪个效率最高,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • Java基础夯实之线程问题全面解析

    Java基础夯实之线程问题全面解析

    操作系统支持多个应用程序并发执行,每个应用程序至少对应一个进程 。进程是资源分配的最小单位,而线程是CPU调度的最小单位。本文将带大家全面解析线程相关问题,感兴趣的可以了解一下
    2022-11-11
  • 详解SpringBoot 统一后端返回格式的方法

    详解SpringBoot 统一后端返回格式的方法

    今天我们来聊一聊在基于SpringBoot前后端分离开发模式下,如何友好的返回统一的标准格式以及如何优雅的处理全局异常,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2022-05-05
  • MyBatis-Plus如何关闭SQL日志打印详解

    MyBatis-Plus如何关闭SQL日志打印详解

    在使用mybatisplus进行开发时,日志是一个非常有用的工具,它可以帮助我们更好地了解和调试我们的代码,这篇文章主要给大家介绍了关于MyBatis-Plus如何关闭SQL日志打印的相关资料,需要的朋友可以参考下
    2024-03-03
  • 深入剖析Java中的各种异常处理方式

    深入剖析Java中的各种异常处理方式

    这篇文章主要介绍了深入剖析Java中的各种异常处理方式,是Java入门学习中的基础知识,需要的朋友可以参考下
    2015-07-07
  • 浅谈Java垃圾回收机制

    浅谈Java垃圾回收机制

    这篇文章主要介绍了浅谈Java垃圾回收机制,文中有非常详细的图文示例及代码示例,对正在学习java的小伙伴们有很好的帮助,需要的朋友可以参考下
    2021-05-05
  • java使用HttpSession实现QQ访问记录

    java使用HttpSession实现QQ访问记录

    这篇文章主要介绍了java使用HttpSession实现QQ的访问记录的相关资料,需要的朋友可以参考下
    2016-03-03
  • 使用SpringAOP实现公共字段填充功能

    使用SpringAOP实现公共字段填充功能

    在新增员工或者新增菜品分类时需要设置创建时间、创建人、修改时间、修改人等字段,在编辑员工或者编辑菜品分类时需要设置修改时间、修改人等字段,这些字段属于公共字段,本文将给大家介绍使用SpringAOP实现公共字段填充功能,需要的朋友可以参考下
    2024-08-08
  • JAVA中String介绍及常见面试题小结

    JAVA中String介绍及常见面试题小结

    这篇文章主要介绍了JAVA中String介绍及常见面试题,在java面试中经常会被面试官问到,小编通过实例代码相结合给大家详细介绍,需要的朋友可以参考下
    2020-02-02
  • 一篇文章带你理解Java Spring三级缓存和循环依赖

    一篇文章带你理解Java Spring三级缓存和循环依赖

    这篇文章主要介绍了浅谈Spring 解决循环依赖必须要三级缓存吗,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-09-09

最新评论