实现原理:
- 自定义防止重复提交标记(@AvoidRepeatableCommit)。
- 对需要防止重复提交的Congtroller里的mapping方法加上该注解。
- 新增Aspect切入点,为@AvoidRepeatableCommit加入切入点。
- 每次提交表单时,Aspect都会保存当前key到reids(须设置过期时间)。
- 重复提交时Aspect会判断当前redis是否有该key,若有则拦截。
自定义标签
package com.youxiang.common;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @InterfaceName: AvoidRepeatableCommit
* @Description: 防重复提交
* @Author youxiang
* @Date 2020/4/9 15:58
* @Copyright niwodai © 2019
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AvoidRepeatableCommit {
/**
* 指定时间内不可重复提交,单位秒
* @return
*/
long timeout() default 3;
}
自定义切入点Aspect
package com.youxiang.common;
import org.springframework.data.redis.core.RedisTemplate;
import com.youxiang.exception.BusinessException;
import com.youxiang.util.UUIDUtil;
import com.youxiang.util.IpUtil;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
/**
* @ClassName: AvoidRepeatableCommitAspect
* @Description: 重复提交aop
* @Author youxiang
* @Date 2020/4/9 16:02
* @Copyright niwodai © 2019
*/
@Aspect
@Component
public class AvoidRepeatableCommitAspect {
private static Logger log = LoggerFactory.getLogger(AvoidRepeatableCommitAspect.class);
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* @param point
*/
@Around("@annotation(com.youxiang.common.AvoidRepeatableCommit)")
public Object around(ProceedingJoinPoint point) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String ip = IpUtil.getIpAddr(request);
//获取注解
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
//目标类、方法
String className = method.getDeclaringClass().getName();
String name = method.getName();
String methodKey = String.format("%s#%s",className,name);
int hashCode = Math.abs(methodKey.hashCode());
String key = String.format("%s_%d",ip,hashCode);
log.info("methodKey={},hashCode={},key={}",methodKey,hashCode,key);
AvoidRepeatableCommit avoidRepeatableCommit = method.getAnnotation(AvoidRepeatableCommit.class);
long timeout = avoidRepeatableCommit.timeout();
if (timeout < 0){
//过期时间5分钟
timeout = 60*5;
}
Object value = redisTemplate.opsForValue().get(key);
if (value != null && StringUtils.isNotBlank(String.valueOf(value))){
throw new BusinessException("请勿重复提交");
}
redisTemplate.opsForValue().set(key, UUIDUtil.uuid(), timeout, TimeUnit.SECONDS);
//执行方法
Object object = point.proceed();
return object;
}
}
找到spring mvc配置文件
<!-- Spring MVC -->
<servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
在配置文件中添加
<aop:aspectj-autoproxy proxy-target-class="true" />
<context:component-scan base-package="com.youxiang"/>
<task:annotation-driven/>