xdd 3 týždňov pred
rodič
commit
ef45d54861

+ 18 - 2
fs-admin/src/main/java/com/fs/core/config/RedisConfig.java

@@ -4,17 +4,20 @@ import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.PropertyAccessor;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.scripting.support.ResourceScriptSource;
 
 /**
  * redis配置
- * 
- 
+ *
+
  */
 @Configuration
 @EnableCaching
@@ -40,4 +43,17 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+
+
+    /**
+     * redis lua防重复提交脚本
+     * @return DefaultRedisScript<Boolean>
+     */
+    @Bean("prevent-resubmit")
+    public DefaultRedisScript<Boolean> resubmit(){
+        DefaultRedisScript preventResubmitScript = new DefaultRedisScript<>();
+        preventResubmitScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("scripts/prevent-resubmit.lua")));
+        preventResubmitScript.setResultType(Boolean.class);
+        return preventResubmitScript;
+    }
 }

+ 7 - 7
fs-admin/src/main/java/com/fs/core/interceptor/RepeatSubmitInterceptor.java

@@ -3,10 +3,11 @@ package com.fs.core.interceptor;
 import java.lang.reflect.Method;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+
+import com.alibaba.fastjson.JSON;
 import org.springframework.stereotype.Component;
 import org.springframework.web.method.HandlerMethod;
 import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
-import com.alibaba.fastjson.JSONObject;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.utils.ServletUtils;
@@ -27,15 +28,13 @@ public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter
             HandlerMethod handlerMethod = (HandlerMethod) handler;
             Method method = handlerMethod.getMethod();
             RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
-            if (annotation != null)
-            {
-                if (this.isRepeatSubmit(request))
+            if (annotation != null && this.isRepeatSubmit(request,annotation.intervalTime()))
                 {
-                    AjaxResult ajaxResult = AjaxResult.error("不允许重复提交,请稍后再试");
-                    ServletUtils.renderString(response, JSONObject.toJSONString(ajaxResult));
+                    AjaxResult ajaxResult = AjaxResult.error("请求太过频繁,请稍后再试!");
+                    ServletUtils.renderString(response, JSON.toJSONString(ajaxResult));
                     return false;
                 }
-            }
+
             return true;
         }
         else
@@ -52,4 +51,5 @@ public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter
      * @throws Exception
      */
     public abstract boolean isRepeatSubmit(HttpServletRequest request);
+    public abstract boolean isRepeatSubmit(HttpServletRequest request,Integer time);
 }

+ 34 - 59
fs-admin/src/main/java/com/fs/core/interceptor/impl/SameUrlDataInterceptor.java

@@ -1,15 +1,18 @@
 package com.fs.core.interceptor.impl;
 
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
+import java.util.Arrays;
+import java.util.List;
 import javax.servlet.http.HttpServletRequest;
+
+import com.alibaba.fastjson.JSON;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.stereotype.Component;
-import com.alibaba.fastjson.JSONObject;
 import com.fs.common.constant.Constants;
-import com.fs.common.core.redis.RedisCache;
 import com.fs.common.filter.RepeatedlyRequestWrapper;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.http.HttpHelper;
@@ -18,38 +21,39 @@ import com.fs.core.interceptor.RepeatSubmitInterceptor;
 /**
  * 判断请求url和数据是否和上一次相同,
  * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
- * 
  */
+@Slf4j
 @Component
 public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
 {
-    public final String REPEAT_PARAMS = "repeatParams";
-
-    public final String REPEAT_TIME = "repeatTime";
 
     // 令牌自定义标识
     @Value("${token.header}")
     private String header;
+    @Autowired
+    private RedisTemplate redisTemplate;
 
     @Autowired
-    private RedisCache redisCache;
+    @Qualifier("prevent-resubmit")
+    private DefaultRedisScript<Boolean> preventResubmitScript;
 
     /**
      * 间隔时间,单位:秒 默认10秒
-     * 
+     *
      * 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据
      */
     private int intervalTime = 10;
 
-    public void setIntervalTime(int intervalTime)
-    {
-        this.intervalTime = intervalTime;
-    }
 
     @SuppressWarnings("unchecked")
     @Override
     public boolean isRepeatSubmit(HttpServletRequest request)
     {
+       return this.isRepeatSubmit(request,this.intervalTime);
+    }
+
+    @Override
+    public boolean isRepeatSubmit(HttpServletRequest request, Integer intervalTime) {
         String nowParams = "";
         if (request instanceof RepeatedlyRequestWrapper)
         {
@@ -60,12 +64,8 @@ public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
         // body参数为空,获取Parameter的数据
         if (StringUtils.isEmpty(nowParams))
         {
-            nowParams = JSONObject.toJSONString(request.getParameterMap());
+            nowParams = JSON.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();
 
@@ -78,47 +78,22 @@ public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
 
         // 唯一标识(指定key + 消息头)
         String cache_repeat_key = Constants.REPEAT_SUBMIT_KEY + submitKey;
+        // 当前时间戳
+        long currentTimeMillis = System.currentTimeMillis();
 
-        Object sessionObj = redisCache.getCacheObject(cache_repeat_key);
-        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))
-                {
-                    return true;
-                }
-            }
-        }
-        Map<String, Object> cacheMap = new HashMap<String, Object>();
-        cacheMap.put(url, nowDataMap);
-        redisCache.setCacheObject(cache_repeat_key, cacheMap, intervalTime, TimeUnit.SECONDS);
-        return false;
-    }
+        // 执行Lua脚本
+        List<String> keys = Arrays.asList(cache_repeat_key, url);
 
-    /**
-     * 判断参数是否相同
-     */
-    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);
-    }
+        log.info("调用lua参数: {} {} {} {} {}",preventResubmitScript,keys,nowParams,currentTimeMillis,intervalTime);
 
-    /**
-     * 判断两次间隔时间
-     */
-    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap)
-    {
-        long time1 = (Long) nowMap.get(REPEAT_TIME);
-        long time2 = (Long) preMap.get(REPEAT_TIME);
-        if ((time1 - time2) < (this.intervalTime * 1000))
-        {
-            return true;
-        }
-        return false;
+        Boolean result = (Boolean) redisTemplate.execute(
+                preventResubmitScript,
+                keys,
+                nowParams,
+                currentTimeMillis,
+                intervalTime
+        );
+
+        return result != null && result;
     }
 }

+ 0 - 2
fs-admin/src/main/java/com/fs/store/controller/FsStoreProductController.java

@@ -1,7 +1,5 @@
 package com.fs.store.controller;
 
-import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Date;
 import java.util.List;
 

+ 52 - 0
fs-admin/src/main/resources/scripts/prevent-resubmit.lua

@@ -0,0 +1,52 @@
+-- 参数说明
+-- KEYS[1]: 缓存键名 (cache_repeat_key)
+-- KEYS[2]: 请求URL
+-- ARGV[1]: 当前请求参数 requestMap
+-- ARGV[2]: 当前时间戳
+-- ARGV[3]: 间隔时间(秒)
+
+-- 获取缓存数据
+local cacheData = redis.call('GET', KEYS[1])
+local result = false
+
+if cacheData then
+    -- 将JSON字符串转为Lua表
+    local sessionMap = cjson.decode(cacheData)
+
+    -- 检查是否包含当前URL
+    if sessionMap[KEYS[2]] then
+        local preDataMap = sessionMap[KEYS[2]]
+
+        -- 比较参数是否相同
+        if preDataMap['repeatParams'] == ARGV[1] then
+            -- 比较时间间隔
+            local time1 = tonumber(ARGV[2])
+            local time2 = tonumber(preDataMap['repeatTime'])
+
+            if (time1 - time2) < (tonumber(ARGV[3]) * 1000) then
+                result = true
+            end
+        end
+    end
+end
+
+if not result then
+    -- 创建新的缓存数据
+    local nowDataMap = {
+        repeatParams = ARGV[1],
+        repeatTime = ARGV[2]
+    }
+
+    local cacheMap = {}
+    if cacheData then
+        cacheMap = cjson.decode(cacheData)
+    end
+
+    cacheMap[KEYS[2]] = nowDataMap
+
+    -- 设置缓存,带过期时间
+    redis.call('SETEX', KEYS[1], tonumber(ARGV[3]), cjson.encode(cacheMap))
+end
+
+-- 返回结果:true表示重复提交,false表示不是重复提交
+return result

+ 5 - 2
fs-common/src/main/java/com/fs/common/annotation/RepeatSubmit.java

@@ -9,7 +9,7 @@ import java.lang.annotation.Target;
 
 /**
  * 自定义注解防止表单重复提交
- * 
+ *
 
  *
  */
@@ -19,5 +19,8 @@ import java.lang.annotation.Target;
 @Documented
 public @interface RepeatSubmit
 {
-
+    /**
+     * Sec
+     */
+    int intervalTime() default 10;
 }

+ 17 - 2
fs-company-app/src/main/java/com/fs/core/config/RedisConfig.java

@@ -7,14 +7,17 @@ import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.ClassPathResource;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.data.redis.serializer.StringRedisSerializer;
+import org.springframework.scripting.support.ResourceScriptSource;
 
 /**
  * redis配置
- * 
- 
+ *
+
  */
 @Configuration
 @EnableCaching
@@ -40,4 +43,16 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+
+    /**
+     * redis lua防重复提交脚本
+     * @return DefaultRedisScript<Boolean>
+     */
+    @Bean("prevent-resubmit")
+    public DefaultRedisScript<Boolean> resubmit(){
+        DefaultRedisScript preventResubmitScript = new DefaultRedisScript<>();
+        preventResubmitScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("scripts/prevent-resubmit.lua")));
+        preventResubmitScript.setResultType(Boolean.class);
+        return preventResubmitScript;
+    }
 }

+ 6 - 8
fs-company-app/src/main/java/com/fs/core/interceptor/RepeatSubmitInterceptor.java

@@ -1,6 +1,6 @@
 package com.fs.core.interceptor;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.JSON;
 import com.fs.common.annotation.RepeatSubmit;
 import com.fs.common.core.domain.AjaxResult;
 import com.fs.common.utils.ServletUtils;
@@ -15,7 +15,6 @@ import java.lang.reflect.Method;
 /**
  * 防止重复提交拦截器
  *
-
  */
 @Component
 public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter
@@ -28,15 +27,13 @@ public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter
             HandlerMethod handlerMethod = (HandlerMethod) handler;
             Method method = handlerMethod.getMethod();
             RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
-            if (annotation != null)
-            {
-                if (this.isRepeatSubmit(request))
+            if (annotation != null && this.isRepeatSubmit(request,annotation.intervalTime()))
                 {
-                    AjaxResult ajaxResult = AjaxResult.error("不允许重复提交,请稍后再试");
-                    ServletUtils.renderString(response, JSONObject.toJSONString(ajaxResult));
+                    AjaxResult ajaxResult = AjaxResult.error("请求太过频繁,请稍后再试!");
+                    ServletUtils.renderString(response, JSON.toJSONString(ajaxResult));
                     return false;
                 }
-            }
+
             return true;
         }
         else
@@ -53,4 +50,5 @@ public abstract class RepeatSubmitInterceptor extends HandlerInterceptorAdapter
      * @throws Exception
      */
     public abstract boolean isRepeatSubmit(HttpServletRequest request);
+    public abstract boolean isRepeatSubmit(HttpServletRequest request,Integer time);
 }

+ 35 - 60
fs-company-app/src/main/java/com/fs/core/interceptor/impl/SameUrlDataInterceptor.java

@@ -1,57 +1,60 @@
 package com.fs.core.interceptor.impl;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.JSON;
 import com.fs.common.constant.Constants;
-import com.fs.common.core.redis.RedisCache;
 import com.fs.common.filter.RepeatedlyRequestWrapper;
 import com.fs.common.utils.StringUtils;
 import com.fs.common.utils.http.HttpHelper;
 import com.fs.core.interceptor.RepeatSubmitInterceptor;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.stereotype.Component;
 
 import javax.servlet.http.HttpServletRequest;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
+import java.util.Arrays;
+import java.util.List;
 
 /**
  * 判断请求url和数据是否和上一次相同,
  * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。
- * 
-
+ *
  */
+
+@Slf4j
 @Component
 public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
 {
-    public final String REPEAT_PARAMS = "repeatParams";
-
-    public final String REPEAT_TIME = "repeatTime";
 
     // 令牌自定义标识
     @Value("${fs.jwt.header}")
     private String header;
 
     @Autowired
-    private RedisCache redisCache;
+    private RedisTemplate redisTemplate;
 
+    @Autowired
+    @Qualifier("prevent-resubmit")
+    private DefaultRedisScript<Boolean> preventResubmitScript;
     /**
      * 间隔时间,单位:秒 默认10秒
-     * 
+     *
      * 两次相同参数的请求,如果间隔时间大于该参数,系统不会认定为重复提交的数据
      */
     private int intervalTime = 10;
 
-    public void setIntervalTime(int intervalTime)
-    {
-        this.intervalTime = intervalTime;
-    }
-
     @SuppressWarnings("unchecked")
     @Override
     public boolean isRepeatSubmit(HttpServletRequest request)
     {
+        return this.isRepeatSubmit(request,this.intervalTime);
+    }
+
+    @Override
+    public boolean isRepeatSubmit(HttpServletRequest request, Integer intervalTime) {
         String nowParams = "";
         if (request instanceof RepeatedlyRequestWrapper)
         {
@@ -62,11 +65,8 @@ public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
         // body参数为空,获取Parameter的数据
         if (StringUtils.isEmpty(nowParams))
         {
-            nowParams = JSONObject.toJSONString(request.getParameterMap());
+            nowParams = JSON.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();
@@ -80,47 +80,22 @@ public class SameUrlDataInterceptor extends RepeatSubmitInterceptor
 
         // 唯一标识(指定key + 消息头)
         String cache_repeat_key = Constants.REPEAT_SUBMIT_KEY + submitKey;
+        // 当前时间戳
+        long currentTimeMillis = System.currentTimeMillis();
 
-        Object sessionObj = redisCache.getCacheObject(cache_repeat_key);
-        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))
-                {
-                    return true;
-                }
-            }
-        }
-        Map<String, Object> cacheMap = new HashMap<String, Object>();
-        cacheMap.put(url, nowDataMap);
-        redisCache.setCacheObject(cache_repeat_key, cacheMap, intervalTime, TimeUnit.SECONDS);
-        return false;
-    }
+        // 执行Lua脚本
+        List<String> keys = Arrays.asList(cache_repeat_key, url);
 
-    /**
-     * 判断参数是否相同
-     */
-    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);
-    }
+        log.info("调用lua参数: {} {} {} {} {}",preventResubmitScript,keys,nowParams,currentTimeMillis,intervalTime);
 
-    /**
-     * 判断两次间隔时间
-     */
-    private boolean compareTime(Map<String, Object> nowMap, Map<String, Object> preMap)
-    {
-        long time1 = (Long) nowMap.get(REPEAT_TIME);
-        long time2 = (Long) preMap.get(REPEAT_TIME);
-        if ((time1 - time2) < (this.intervalTime * 1000))
-        {
-            return true;
-        }
-        return false;
+        Boolean result = (Boolean) redisTemplate.execute(
+                preventResubmitScript,
+                keys,
+                nowParams,
+                currentTimeMillis,
+                intervalTime
+        );
+
+        return result != null && result;
     }
 }

+ 52 - 0
fs-company-app/src/main/resources/scripts/prevent-resubmit.lua

@@ -0,0 +1,52 @@
+-- 参数说明
+-- KEYS[1]: 缓存键名 (cache_repeat_key)
+-- KEYS[2]: 请求URL
+-- ARGV[1]: 当前请求参数
+-- ARGV[2]: 当前时间戳
+-- ARGV[3]: 间隔时间(秒)
+
+-- 获取缓存数据
+local cacheData = redis.call('GET', KEYS[1])
+local result = false
+
+if cacheData then
+    -- 将JSON字符串转为Lua表
+    local sessionMap = cjson.decode(cacheData)
+
+    -- 检查是否包含当前URL
+    if sessionMap[KEYS[2]] then
+        local preDataMap = sessionMap[KEYS[2]]
+
+        -- 比较参数是否相同
+        if preDataMap["repeatParams"] == ARGV[1] then
+            -- 比较时间间隔
+            local time1 = tonumber(ARGV[2])
+            local time2 = tonumber(preDataMap["repeatTime"])
+
+            if (time1 - time2) < (tonumber(ARGV[3]) * 1000) then
+                result = true
+            end
+        end
+    end
+end
+
+if not result then
+    -- 创建新的缓存数据
+    local nowDataMap = {
+        repeatParams = ARGV[1],
+        repeatTime = ARGV[2]
+    }
+
+    local cacheMap = {}
+    if cacheData then
+        cacheMap = cjson.decode(cacheData)
+    end
+
+    cacheMap[KEYS[2]] = nowDataMap
+
+    -- 设置缓存,带过期时间
+    redis.call('SETEX', KEYS[1], tonumber(ARGV[3]), cjson.encode(cacheMap))
+end
+
+-- 返回结果:true表示重复提交,false表示不是重复提交
+return result

+ 1 - 1
fs-service-system/src/main/java/com/fs/store/service/impl/FsStoreOrderServiceImpl.java

@@ -1436,7 +1436,7 @@ public class FsStoreOrderServiceImpl implements IFsStoreOrderService
     @Override
     @Transactional(rollbackFor = Throwable.class,propagation = Propagation.REQUIRED)
     //类型1支付回调 类型2货到付款
-    public String payConfirm(Integer type,Long orderId,String payCode,String tradeNo,String bankTransactionId,String bankSerialNo) {
+    public synchronized String payConfirm(Integer type,Long orderId,String payCode,String tradeNo,String bankTransactionId,String bankSerialNo) {
         //支付订单
         try {
             FsStoreOrder order=null;

+ 2 - 1
fs-user-app/src/main/java/com/fs/app/controller/StoreOrderController.java

@@ -116,7 +116,7 @@ public class StoreOrderController extends  AppBaseController {
     @Login
     @ApiOperation("支付")
     @PostMapping("/pay")
-    @RepeatSubmit
+    @RepeatSubmit(intervalTime = 3)
     public R pay(@Validated @RequestBody FsStoreOrderPayParam param)
     {
         logger.info("开始处理支付请求, 订单号: {}, 支付类型: {}", param.getOrderId(), param.getPayType());
@@ -249,6 +249,7 @@ public class StoreOrderController extends  AppBaseController {
     @Login
     @ApiOperation("创建订单")
     @PostMapping("/createPackageOrder")
+    @RepeatSubmit(intervalTime = 1)
     public R createPackageOrder(@Validated @RequestBody FsStorePackageOrderCreateParam param){
         return orderService.createPackageOrder(Long.parseLong(getUserId()),param);
     }