Sfoglia il codice sorgente

update [重大改动]重写 防重提交实现 使用分布式锁 解决并发问题 压测通过

疯狂的狮子li 3 anni fa
parent
commit
931ed0eb00

+ 93 - 0
ruoyi-framework/src/main/java/com/ruoyi/framework/aspectj/RepeatSubmitAspect.java

@@ -0,0 +1,93 @@
+package com.ruoyi.framework.aspectj;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.SecureUtil;
+import com.baomidou.lock.LockInfo;
+import com.baomidou.lock.LockTemplate;
+import com.ruoyi.common.annotation.RepeatSubmit;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.common.properties.TokenProperties;
+import com.ruoyi.common.utils.ServletUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.framework.config.properties.RepeatSubmitProperties;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.Signature;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.aspectj.lang.annotation.Pointcut;
+import org.aspectj.lang.reflect.MethodSignature;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.servlet.http.HttpServletRequest;
+import java.lang.reflect.Method;
+
+/**
+ * 防止重复提交
+ *
+ * @author Lion Li
+ */
+@Slf4j
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+@Aspect
+@Component
+public class RepeatSubmitAspect {
+
+    private final TokenProperties tokenProperties;
+    private final RepeatSubmitProperties repeatSubmitProperties;
+    private final LockTemplate lockTemplate;
+
+    // 配置织入点
+    @Pointcut("@annotation(com.ruoyi.common.annotation.RepeatSubmit)")
+    public void repeatSubmitPointCut() {
+    }
+
+    @Before("repeatSubmitPointCut()")
+    public void doBefore(JoinPoint point) throws Throwable {
+        RepeatSubmit repeatSubmit = getAnnotationRateLimiter(point);
+        // 如果注解不为0 则使用注解数值
+        long intervalTime = repeatSubmitProperties.getIntervalTime();
+        if (repeatSubmit.intervalTime() > 0) {
+            intervalTime = repeatSubmit.timeUnit().toMillis(repeatSubmit.intervalTime());
+        }
+        if (intervalTime < 1000) {
+            throw new ServiceException("重复提交间隔时间不能小于'1'秒");
+        }
+        HttpServletRequest request = ServletUtils.getRequest();
+        String nowParams = StrUtil.join(",", point.getArgs());
+
+        // 请求地址(作为存放cache的key值)
+        String url = request.getRequestURI();
+
+        // 唯一值(没有消息头则使用请求地址)
+        String submitKey = request.getHeader(tokenProperties.getHeader());
+        if (StringUtils.isEmpty(submitKey)) {
+            submitKey = url;
+        }
+        submitKey = SecureUtil.md5(submitKey + ":" + nowParams);
+        // 唯一标识(指定key + 消息头)
+        String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + submitKey;
+        LockInfo lock = lockTemplate.lock(cacheRepeatKey, intervalTime, intervalTime / 2);
+        if (lock == null) {
+            throw new ServiceException("不允许重复提交,请稍后再试!");
+        }
+    }
+
+    /**
+     * 是否存在注解,如果存在就获取
+     */
+    private RepeatSubmit getAnnotationRateLimiter(JoinPoint joinPoint) {
+        Signature signature = joinPoint.getSignature();
+        MethodSignature methodSignature = (MethodSignature) signature;
+        Method method = methodSignature.getMethod();
+
+        if (method != null) {
+            return method.getAnnotation(RepeatSubmit.class);
+        }
+        return null;
+    }
+
+}

+ 5 - 22
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java

@@ -1,52 +1,35 @@
 package com.ruoyi.framework.config;
 
-import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.cors.CorsConfiguration;
 import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 import org.springframework.web.filter.CorsFilter;
-import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
 import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 
 /**
  * 通用配置
  *
- * @author ruoyi
+ * @author Lion Li
  */
 @Configuration
-public class ResourcesConfig implements WebMvcConfigurer
-{
-    @Autowired
-    private RepeatSubmitInterceptor repeatSubmitInterceptor;
+public class ResourcesConfig implements WebMvcConfigurer {
 
     @Override
-    public void addResourceHandlers(ResourceHandlerRegistry registry)
-    {
-    }
-
-    /**
-     * 自定义拦截规则
-     */
-    @Override
-    public void addInterceptors(InterceptorRegistry registry)
-    {
-        registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**");
+    public void addResourceHandlers(ResourceHandlerRegistry registry) {
     }
 
     /**
      * 跨域配置
      */
     @Bean
-    public CorsFilter corsFilter()
-    {
+    public CorsFilter corsFilter() {
         UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
         CorsConfiguration config = new CorsConfiguration();
         config.setAllowCredentials(true);
         // 设置访问源地址
-		config.addAllowedOriginPattern("*");
+        config.addAllowedOriginPattern("*");
         // 设置访问源请求头
         config.addAllowedHeader("*");
         // 设置访问源请求方法

+ 0 - 50
ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/RepeatSubmitInterceptor.java

@@ -1,50 +0,0 @@
-package com.ruoyi.framework.interceptor;
-
-import com.ruoyi.common.annotation.RepeatSubmit;
-import com.ruoyi.common.core.domain.AjaxResult;
-import com.ruoyi.common.utils.JsonUtils;
-import com.ruoyi.common.utils.ServletUtils;
-import org.springframework.stereotype.Component;
-import org.springframework.web.method.HandlerMethod;
-import org.springframework.web.servlet.HandlerInterceptor;
-
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.lang.reflect.Method;
-
-/**
- * 防止重复提交拦截器
- *
- * 移除继承 HandlerInterceptorAdapter 过期类
- * 改为实现 HandlerInterceptor 接口(官方推荐写法)
- *
- * @author Lion Li
- */
-@Component
-public abstract class RepeatSubmitInterceptor implements HandlerInterceptor {
-
-	@Override
-	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
-		throws Exception {
-		if (handler instanceof HandlerMethod) {
-			HandlerMethod handlerMethod = (HandlerMethod) handler;
-			Method method = handlerMethod.getMethod();
-			RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
-			if (annotation != null) {
-				if (this.isRepeatSubmit(annotation, request)) {
-					AjaxResult ajaxResult = AjaxResult.error("不允许重复提交,请稍后再试");
-					ServletUtils.renderString(response, JsonUtils.toJsonString(ajaxResult));
-					return false;
-				}
-			}
-			return true;
-		} else {
-			return HandlerInterceptor.super.preHandle(request, response, handler);
-		}
-	}
-
-	/**
-	 * 验证是否重复提交由子类实现具体的防重复提交的规则
-	 */
-	public abstract boolean isRepeatSubmit(RepeatSubmit annotation, HttpServletRequest request);
-}

+ 0 - 114
ruoyi-framework/src/main/java/com/ruoyi/framework/interceptor/impl/SameUrlDataInterceptor.java

@@ -1,114 +0,0 @@
-package com.ruoyi.framework.interceptor.impl;
-
-import cn.hutool.core.convert.Convert;
-import cn.hutool.core.io.IoUtil;
-import com.ruoyi.common.annotation.RepeatSubmit;
-import com.ruoyi.common.constant.Constants;
-import com.ruoyi.common.filter.RepeatedlyRequestWrapper;
-import com.ruoyi.common.utils.JsonUtils;
-import com.ruoyi.common.utils.RedisUtils;
-import com.ruoyi.common.utils.StringUtils;
-import com.ruoyi.framework.config.properties.RepeatSubmitProperties;
-import com.ruoyi.common.properties.TokenProperties;
-import com.ruoyi.framework.interceptor.RepeatSubmitInterceptor;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Component;
-
-import javax.servlet.http.HttpServletRequest;
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
-/**
- * 判断请求url和数据是否和上一次相同,
- * 如果和上次相同,则是重复提交表单。
- *
- * @author Lion Li
- */
-@Slf4j
-@RequiredArgsConstructor(onConstructor_ = @Autowired)
-@Component
-public class SameUrlDataInterceptor extends RepeatSubmitInterceptor {
-	public final String REPEAT_PARAMS = "repeatParams";
-
-	public final String REPEAT_TIME = "repeatTime";
-
-	private final TokenProperties tokenProperties;
-	private final RepeatSubmitProperties repeatSubmitProperties;
-
-
-	@SuppressWarnings("unchecked")
-	@Override
-	public boolean isRepeatSubmit(RepeatSubmit repeatSubmit, HttpServletRequest request) {
-		// 如果注解不为0 则使用注解数值
-		long intervalTime = repeatSubmitProperties.getIntervalTime();
-		if (repeatSubmit.intervalTime() > 0) {
-			intervalTime = repeatSubmit.timeUnit().toMillis(repeatSubmit.intervalTime());
-		}
-		String nowParams = "";
-		if (request instanceof RepeatedlyRequestWrapper) {
-			RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request;
-			try {
-				nowParams = IoUtil.readUtf8(repeatedlyRequest.getInputStream());
-			} catch (IOException e) {
-				log.warn("读取流出现问题!");
-			}
-		}
-
-		// body参数为空,获取Parameter的数据
-		if (StringUtils.isEmpty(nowParams)) {
-			nowParams = JsonUtils.toJsonString(request.getParameterMap());
-		}
-		Map<String, Object> nowDataMap = new HashMap<String, Object>();
-		nowDataMap.put(REPEAT_PARAMS, nowParams);
-		nowDataMap.put(REPEAT_TIME, System.currentTimeMillis());
-
-		// 请求地址(作为存放cache的key值)
-		String url = request.getRequestURI();
-
-		// 唯一值(没有消息头则使用请求地址)
-		String submitKey = request.getHeader(tokenProperties.getHeader());
-		if (StringUtils.isEmpty(submitKey)) {
-			submitKey = url;
-		}
-
-		// 唯一标识(指定key + 消息头)
-		String cacheRepeatKey = Constants.REPEAT_SUBMIT_KEY + submitKey;
-
-		Object sessionObj = RedisUtils.getCacheObject(cacheRepeatKey);
-		if (sessionObj != null) {
-			Map<String, Object> sessionMap = (Map<String, Object>) sessionObj;
-			if (sessionMap.containsKey(url)) {
-				Map<String, Object> preDataMap = (Map<String, Object>) sessionMap.get(url);
-				if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, intervalTime)) {
-					return true;
-				}
-			}
-		}
-		Map<String, Object> cacheMap = new HashMap<String, Object>();
-		cacheMap.put(url, nowDataMap);
-		RedisUtils.setCacheObject(cacheRepeatKey, cacheMap, Convert.toInt(intervalTime), TimeUnit.MILLISECONDS);
-		return false;
-	}
-
-	/**
-	 * 判断参数是否相同
-	 */
-	private boolean compareParams(Map<String, Object> nowMap, Map<String, Object> preMap) {
-		String nowParams = (String) nowMap.get(REPEAT_PARAMS);
-		String preParams = (String) preMap.get(REPEAT_PARAMS);
-		return nowParams.equals(preParams);
-	}
-
-	/**
-	 * 判断两次间隔时间
-	 */
-	private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap, long intervalTime) {
-		long time1 = (Long) nowMap.get(REPEAT_TIME);
-		long time2 = (Long) preMap.get(REPEAT_TIME);
-		return (time1 - time2) < intervalTime;
-	}
-}