Kaynağa Gözat

fix: 重复支付

xdd 4 gün önce
ebeveyn
işleme
ee584b93a6

+ 6 - 0
fs-user-app/pom.xml

@@ -121,6 +121,12 @@
             <artifactId>spring-boot-starter-test</artifactId>
             <scope>test</scope>
         </dependency>
+        <!-- Redisson -->
+        <dependency>
+            <groupId>org.redisson</groupId>
+            <artifactId>redisson</artifactId>
+            <version>3.13.6</version>
+        </dependency>
 
     </dependencies>
 

+ 52 - 14
fs-user-app/src/main/java/com/fs/app/controller/StoreOrderController.java

@@ -18,6 +18,8 @@ import com.github.pagehelper.PageInfo;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import org.apache.commons.collections4.CollectionUtils;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -25,6 +27,7 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.*;
+import java.util.concurrent.TimeUnit;
 
 
 @Api("商城接口")
@@ -41,7 +44,8 @@ public class StoreOrderController extends  AppBaseController {
 
     @Autowired
     private IFsStorePaymentService fsStorePaymentService;
-
+    @Autowired
+    private RedissonClient redissonClient;
 
     @Login
     @ApiOperation("获取我的订单列表")
@@ -125,23 +129,40 @@ public class StoreOrderController extends  AppBaseController {
     @Login
     @ApiOperation("支付")
     @PostMapping("/pay")
-    @RepeatSubmit(intervalTime = 1)
     public R pay(@Validated @RequestBody FsStoreOrderPayParam param)
     {
-        logger.info("开始处理支付请求, 订单号: {}, 支付类型: {}", param.getOrderId(), param.getPayType());
+        Long orderId = param.getOrderId();
+        logger.info("开始处理支付请求, 订单号: {}, 支付类型: {}", orderId, param.getPayType());
+
+        RLock lock = redissonClient.getLock("payment:lock:" + orderId);
         R result = null;
+
         try {
+            // 尝试获取锁,最多等待500ms,锁定30秒
+            boolean locked = lock.tryLock(3000, 30000, TimeUnit.MILLISECONDS);
+
+            if (!locked) {
+                logger.warn("订单正在处理中,获取锁失败, 订单号: {}", orderId);
+                return R.error("订单正在处理中,请勿重复提交");
+            }
+
             result = orderService.pay(param);
+
+        } catch (InterruptedException e) {
+            logger.error("获取支付锁的过程被中断, 订单号: {}", orderId, e);
+            Thread.currentThread().interrupt();
+            return R.error("支付处理被中断,请稍后重试");
         } catch (Throwable e) {
-            logger.error("支付过程中发生异常, 订单号: {}", param.getOrderId(), e);
+            logger.error("支付过程中发生异常, 订单号: {}", orderId, e);
             throw e;
         } finally {
-            if (result != null) {
-                logger.info("支付请求处理完成, 订单号: {}, 返回结果: {}", param.getOrderId(), result);
-            } else {
-                logger.info("支付请求处理完成, 订单号: {}, 返回结果为空", param.getOrderId());
+            // 确保锁被释放
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+                logger.debug("支付锁已释放, 订单号: {}", orderId);
             }
         }
+
         return result;
     }
     @Login
@@ -177,21 +198,38 @@ public class StoreOrderController extends  AppBaseController {
     @ApiOperation("亲友支付")
     @PostMapping("/otherPayment")
     public R otherPayment(@Validated @RequestBody FsStoreOrderOtherPayParam param){
+        Long orderId = param.getOrderId();
+        logger.info("开始处理支付请求, 订单号: {}, 支付类型: 亲友支付", orderId);
 
-        logger.info("开始处理支付请求, 订单号: {}, 支付类型: 亲友支付", param.getOrderId());
+        RLock lock = redissonClient.getLock("friendPayment:lock:" + orderId);
         R result = null;
+
         try {
+            // 尝试获取锁,最多等待500ms,锁定30秒
+            boolean locked = lock.tryLock(3000, 30000, TimeUnit.MILLISECONDS);
+
+            if (!locked) {
+                logger.warn("亲友支付订单正在处理中,获取锁失败, 订单号: {}", orderId);
+                return R.error("订单正在处理中,请勿重复提交");
+            }
+
             result = orderService.otherPayment(param);
+
+        } catch (InterruptedException e) {
+            logger.error("获取亲友支付锁的过程被中断, 订单号: {}", orderId, e);
+            Thread.currentThread().interrupt();
+            return R.error("支付处理被中断,请稍后重试");
         } catch (Throwable e) {
-            logger.error("支付过程中发生异常, 订单号: {}", param.getOrderId(), e);
+            logger.error("亲友支付过程中发生异常, 订单号: {}", orderId, e);
             throw e;
         } finally {
-            if (result != null) {
-                logger.info("支付请求处理完成, 订单号: {}, 返回结果: {}", param.getOrderId(), result);
-            } else {
-                logger.info("支付请求处理完成, 订单号: {}, 返回结果为空", param.getOrderId());
+            // 确保锁被释放
+            if (lock.isHeldByCurrentThread()) {
+                lock.unlock();
+                logger.debug("亲友支付锁已释放, 订单号: {}", orderId);
             }
         }
+
         return result;
     }
 

+ 48 - 0
fs-user-app/src/main/java/com/fs/core/config/RedisConfig.java

@@ -3,6 +3,10 @@ package com.fs.core.config;
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.PropertyAccessor;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.context.annotation.Bean;
@@ -20,6 +24,19 @@ import org.springframework.data.redis.serializer.StringRedisSerializer;
 @EnableCaching
 public class RedisConfig extends CachingConfigurerSupport
 {
+
+    @Value("${spring.redis.host:localhost}")
+    private String host;
+
+    @Value("${spring.redis.port:6379}")
+    private int port;
+
+    @Value("${spring.redis.password:}")
+    private String password;
+
+    @Value("${spring.redis.database:0}")
+    private int database;
+
     @Bean
     @SuppressWarnings(value = { "unchecked", "rawtypes" })
     public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory)
@@ -40,4 +57,35 @@ public class RedisConfig extends CachingConfigurerSupport
         template.afterPropertiesSet();
         return template;
     }
+
+    /**
+     * 配置Redisson客户端
+     */
+    @Bean
+    public RedissonClient redissonClient() {
+        Config config = new Config();
+
+        // 构建Redis连接地址
+        String redisUrl = "redis://" + host + ":" + port;
+
+        // 单节点模式配置
+        if (password != null && !password.isEmpty()) {
+            config.useSingleServer()
+                    .setAddress(redisUrl)
+                    .setDatabase(database)
+                    .setPassword(password);
+        } else {
+            config.useSingleServer()
+                    .setAddress(redisUrl)
+                    .setDatabase(database);
+        }
+
+        // 连接池配置
+        config.useSingleServer()
+                .setConnectionMinimumIdleSize(8)
+                .setConnectionPoolSize(32)
+                .setIdleConnectionTimeout(10000);
+
+        return Redisson.create(config);
+    }
 }