实现原理
- 自定义防止重复提交标记(@AvoidRepeatableCommitAspect)。
- 对需要防止重复提交的Congtroller里的mapping方法加上该注解。
- 新增Aspect切入点,为@AvoidRepeatableCommitAspect加入切入点。
- 每次提交表单时,Aspect都会保存当前key到reids(须设置过期时间)。
- 重复提交时Aspect会判断当前redis是否有该key,若有则拦截。
代码
AvoidRepeatableCommitAspect代码
import java.lang.annotation.*;
/**
* 权限注解 用于放置表达重复提交
* 该方法会从 request中取 avoidRepeatableValue 的 值
* 该值 未判断是否为重复提交的判断因素 当不同方法为统一逻辑时
* 使用 avoidRepeatableValue 判断
* 如果防止同一方法重复 则 avoidRepeatableValue 可不传 以sissionId为唯一判断值
* key为方法值 当两方法为同一逻辑时 判断是否重复提交两方法 key值必填 为判断索引值
* 如果防止同一方法重复 则 key可以不传 以方法地址+方法名 做唯一判断值
* @example @AvoidRepeatableCommitAspect
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface AvoidRepeatableCommitAspect {
String value() default "";
}
AvoidRepeatableCommitAspectAop代码
import cn.stylefeng.roses.core.util.HttpContext;
import cn.stylefeng.roses.kernel.model.exception.ServiceException;
import com.alibaba.druid.util.StringUtils;
import ....annotion.AvoidRepeatableCommitAspect;
import ....constant.cache.Cache;
import ....exception.BizExceptionEnum;
import ...util.CacheUtil;
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.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Map;
/**
* 防止重复提交的aop
*/
@Aspect
@Component
@Order(200)
public class AvoidRepeatableCommitAspectAop {
@Pointcut(value = "@annotation(com.mifu.core.common.annotion.AvoidRepeatableCommitAspect)")
private void avoidRepeatableCommitAspect() {
}
@Around("avoidRepeatableCommitAspect()")
public Object doAvoidRepeatableCommitAspect(ProceedingJoinPoint point) throws Throwable {
//获取拦截的方法名
Signature sig = point.getSignature();
MethodSignature msig = null;
if (!(sig instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
msig = (MethodSignature) sig;
Object target = point.getTarget();
Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
String methodName = currentMethod.getDeclaringClass().getName()+"."+currentMethod.getName();
//获取操作key及value
AvoidRepeatableCommitAspect avoidRepeatableCommitAspect = currentMethod.getAnnotation(AvoidRepeatableCommitAspect.class);
String key = avoidRepeatableCommitAspect.value();
//如果传的KEY值不存在 取该方法名判断
if(StringUtils.isEmpty(key)){
key = methodName;
}
String value = "";
Map<String, String> ht = HttpContext.getRequestParameters();
//如果传的avoidRepeatableValue值不存在 等于未做多方法防重提交判断 使用session做防重判断
if(ht!=null && ht.get("avoidRepeatableValue") != null && !StringUtils.isEmpty(ht.get("avoidRepeatableValue").toString())){
value = ht.get("avoidRepeatableValue").toString();
}else{
value = HttpContext.getRequest().getSession().getId();
}
String result = key + "-" + value;
//缓存查询返回值
Object reslt;
//------------------判断该值是否存在Map中------------------------------
try {
reslt = CacheUtil.get(Cache.REPEATSSUBMIT, result);
} catch (Exception e) {
e.printStackTrace();
return point.proceed();
}
if(reslt != null){
//如果存在 抛出重复提交异常
throw new ServiceException(BizExceptionEnum.REPEATABLE_COMMIT);
}else{
try {
CacheUtil.put(Cache.REPEATSSUBMIT, result, "success");
}catch (Exception e) {
e.printStackTrace();
}
return point.proceed();
}
}
}
.xml代码
<!-- 防重复提交变量:5秒过期-->
<cache name="REPEATSSUBMIT"
maxElementsInMemory="50000"
eternal="false"
clearOnFlush="true"
overflowToDisk="false"
timeToIdleSeconds="3"
timeToLiveSeconds="6"
diskSpoolBufferSizeMB="1024"
maxElementsOnDisk="100000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LFU"
transactionalMode="off">
</cache>
调用
@AvoidRepeatableCommitAspect(value = "..")
"avoidRepeatableValue":"...",