<!-- Spring Boot 启动父依赖 --><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.13.RELEASE</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><exclusion><artifactId>io.lettuce</artifactId><groupId>lettuce-core</groupId></exclusion></exclusions></dependency><dependency><groupId>com.github.ben-manes.caffeine</groupId><artifactId>caffeine</artifactId><version>2.9.3</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.12.0</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version></dependency></dependencies>
spring : redis : host : 192.168.56.10port : 6379
import java. lang. annotation. ElementType ;
import java. lang. annotation. Retention ;
import java. lang. annotation. RetentionPolicy ;
import java. lang. annotation. Target ;
@Retention ( RetentionPolicy . RUNTIME)
@Target ( ElementType . METHOD)
public @interface RepeatSubmit { int value ( ) default 3000 ; int MAX_INTERVAL = 30000 ;
}
抽象类 获取票据 获得时间戳 这个时间戳是为了和当前时间作比较 如果从redis里拿到key(票据)时间和当前时间比较 看是否点击过了
import java. util. function. Function ;
public abstract class AbstractRepeatSubmitTicket { private Function < String , String > ticketFunction; public AbstractRepeatSubmitTicket ( Function < String , String > ticketFunction) { this . ticketFunction = ticketFunction; } public String getTicket ( String ticketToken) { return this . ticketFunction. apply ( ticketToken) ; } public abstract Long getTicketTimestamp ( String ticket) ; public abstract void putTicket ( String ticket) ; public abstract void removeTicket ( String ticket) ;
}
import com. gouying. anno. RepeatSubmit ;
import org. springframework. data. redis. core. ValueOperations ; import java. util. concurrent. TimeUnit ;
import java. util. function. Function ;
public class RepeatSubmitRedisTicket extends AbstractRepeatSubmitTicket { private ValueOperations < String , String > redisValueOperations; public RepeatSubmitRedisTicket ( ValueOperations < String , String > redisValueOperations, Function < String , String > ticketFunction) { super ( ticketFunction) ; this . redisValueOperations = redisValueOperations; } @Override public Long getTicketTimestamp ( String ticket) { Long timeStamp = System . currentTimeMillis ( ) ; boolean setFlag = redisValueOperations. setIfAbsent ( ticket, String . valueOf ( timeStamp) , RepeatSubmit . MAX_INTERVAL, TimeUnit . MILLISECONDS) ; if ( ! setFlag) { timeStamp = Long . valueOf ( redisValueOperations. get ( ticket) ) ; } return timeStamp; } @Override public void putTicket ( String ticket) { redisValueOperations. getOperations ( ) . delete ( ticket) ; this . getTicketTimestamp ( ticket) ; } @Override public void removeTicket ( String ticket) { redisValueOperations. getOperations ( ) . delete ( ticket) ; }
}
redis配置 这里ticket()就是redis向父抽象提交的function
import com. gouying. abs. RepeatSubmitRedisTicket ;
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. RedisTemplate ;
import org. springframework. data. redis. core. ValueOperations ; import javax. annotation. Resource ;
@Configuration
public class RepeatSubmitConfig { @Resource ( name = "redisTemplate" ) private ValueOperations < String , String > valueOperations; @Bean public RepeatSubmitAspect repeatSubmitAspect ( ) { RepeatSubmitRedisTicket redisTicket = new RepeatSubmitRedisTicket ( valueOperations, this :: ticket ) ; return new RepeatSubmitAspect ( redisTicket) ; } @Bean public RedisTemplate < String , String > redisTemplate ( RedisConnectionFactory connectionFactory) { RedisTemplate < String , String > template = new RedisTemplate < > ( ) ; template. setConnectionFactory ( connectionFactory) ; return template; } private String ticket ( String servletPath) { String userId = "zs" ; if ( null == userId) { return "" ; } return servletPath + "_" + userId; }
}
import com. gouying. abs. AbstractRepeatSubmitTicket ;
import com. gouying. anno. RepeatSubmit ;
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. reflect. MethodSignature ;
import org. springframework. web. context. request. RequestContextHolder ;
import org. springframework. web. context. request. ServletRequestAttributes ; import java. lang. reflect. Method ;
@Aspect
@Slf4j
public class RepeatSubmitAspect { private AbstractRepeatSubmitTicket repeatSubmitTicket; public RepeatSubmitAspect ( AbstractRepeatSubmitTicket repeatSubmitTicket) { this . repeatSubmitTicket = repeatSubmitTicket; } @Around ( "@annotation(com.gouying.anno.RepeatSubmit)" ) public Object around ( ProceedingJoinPoint point) throws Throwable { ServletRequestAttributes attributes = ( ServletRequestAttributes ) RequestContextHolder . getRequestAttributes ( ) ; String ticketToken = attributes. getRequest ( ) . getServletPath ( ) ; String ticket = this . repeatSubmitTicket. getTicket ( ticketToken) ; if ( StringUtils . isEmpty ( ticket) ) { return point. proceed ( ) ; } Long lastRequestTime = this . repeatSubmitTicket. getTicketTimestamp ( ticket) ; if ( lastRequestTime != null ) { Method method= ( ( MethodSignature ) point. getSignature ( ) ) . getMethod ( ) ; RepeatSubmit annotation= method. getAnnotation ( RepeatSubmit . class ) ; int interval = Math . min ( annotation. value ( ) , RepeatSubmit . MAX_INTERVAL) ; if ( System . currentTimeMillis ( ) < lastRequestTime + interval) { return "REPEAT_SUBMIT" ; } } Object obj = null ; try { this . repeatSubmitTicket. putTicket ( ticket) ; obj = point. proceed ( ) ; } catch ( Throwable throwable) { log. error ( "" , throwable) ; throw throwable; } return obj; } }
import com. gouying. anno. RepeatSubmit ;
import org. springframework. web. bind. annotation. GetMapping ;
import org. springframework. web. bind. annotation. RestController ; @RestController
public class TestController { @GetMapping ( "/test1" ) @RepeatSubmit public String test1 ( String str) { return "防止重复提交:" + str; } @GetMapping ( "/test3" ) @RepeatSubmit public String test3 ( String str) { return "防止重复提交3:" + str; } @GetMapping ( "test2" ) public String test2 ( String str) { return "正常提交:" + str; }
}