服务器端实现方案:同一客户端在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 ("程序逻辑返回"); 
    } 
 
}

浏览器输入IT虾米网

然后F5刷新查看效果

 
QQ图片20180914165017.png

以下为新版内容:解决了程序集群部署时请求可能会落到多台机器上的问题,把内存缓存换成了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\":\"验证重复提交时出现未知异常!\"}"; 
        } 
 
    } 
 
}

转自https://www.jianshu.com/p/09c6b05b670a


发布评论
IT序号网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

Spring AOP详解知识解答
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。