一、Spring Boot 校验表单重复提交操作
1、pom.xml
中引入Aop所需依赖
<!-- ================== 校验表单重复提交所需依赖 ===================== -->
<!-- AOP依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2、pom.xml
中引入缓存所需依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
3、自定义注解 @NoRepeatSubmit
// 作用到方法上
@Target(ElementType.METHOD)
// 运行时有效
@Retention(RetentionPolicy.RUNTIME)
public @interface NoRepeatSubmit {
/**
* 默认时间5秒
*/
int time() default 5 * 1000;
}
4、AOP 拦截处理
注:这里redis存储的key
值可由个人具体业务灵活发挥,这里只是示例 ex:单用户登录情况下可以组合 token + url请求路径
, 多个用户可以同时登录的话,可以再加上 ip地址
注意:如果不能进入拦截应该是拦截扫描包路径不对,这样拦截的是
lz子包的类文件
@Around("execution(* com.lz..*..*Controller.*(..)) && @annotation(noRepeatSubmit)")
package com.test;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.lz.common.annotation.NoRepeatSubmit;
import com.lz.common.util.RequestUtil;
import com.lz.exception.NoRepeatSubmitException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
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.util.Objects;
import java.util.concurrent.TimeUnit;
@Slf4j
@Aspect
@Component
public class NoRepeatSubmitAop {
/**
* cache的常用参数:
* 1. 缓存最大大小的设置:CacheBuilder.maximumSize(long)
* 2. 过期时间:expireAfterAccess(long, TimeUnit) expireAfterWrite(long, TimeUnit)
* 3. 引用类型:CacheBuilder.weakKeys() CacheBuilder.weakValues() CacheBuilder.softValues()
* 4. 自动刷新cache:CacheBuilder.refreshAfterWrite
* 5. 过期时间:CacheBuilder.expireAfterWrite
* 6.MINUTES 有效时间2分钟
*/
private static Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(2000)
.expireAfterWrite(2, TimeUnit.MINUTES)
.build();
/**
* <p> 【环绕通知】 用于拦截指定方法,判断用户表单保存操作是否属于重复提交 <p>
*
* 定义切入点表达式: execution(public * (…))
* 表达式解释: execution:主体 public:可省略 *:标识方法的任意返回值 任意包+类+方法(…) 任意参数
*
* com.zhengqing.demo.modules.*.api : 标识AOP所切服务的包名,即需要进行横切的业务类
* .*Controller : 标识类名,*即所有类
* .*(..) : 标识任何方法名,括号表示参数,两个点表示任何参数类型
*
* @param pjp:切入点对象
* @param noRepeatSubmit:自定义的注解对象
* @return: java.lang.Object
*/
@Around("execution(* com.lz.internal.controller..*..*Controller.*(..)) && @annotation(noRepeatSubmit)")
public Object doAround(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
// 拿到ip地址、请求路径、token
String ip = RequestUtil.getIpAddr(request);
String url = request.getRequestURL().toString();
String sessionId = request.getRequestedSessionId();
String method = request.getMethod();
// 现在时间
long now = System.currentTimeMillis();
// 自定义key值方式
String key = "REQUEST_FORM:" + ip + ":" + url + ":" + sessionId + ":" + method ;
String value = get(key);
if (value != null) {
// 上次表单提交时间
long lastTime = Long.parseLong(value);
// 如果现在距离上次提交时间小于设置的默认时间 则 判断为重复提交 否则 正常提交 -> 进入业务处理
if ((now - lastTime) > noRepeatSubmit.time()) {
// 非重复提交操作 - 重新记录操作时间
put(key, String.valueOf(now));
// 进入处理业务
return pjp.proceed();
} else {
throw new NoRepeatSubmitException("请勿重复提交!");
//return ApiResult.fail("请勿重复提交!");
}
} else {
// 这里是第一次操作
put(key, String.valueOf(now));
return pjp.proceed();
}
}
public static void put(String key,String value){
cache.put(key,value);
}
public static String get(String key){
return cache.getIfPresent(key);
}
}