Просмотр исходного кода

Merge remote-tracking branch 'origin/master'

xgb 1 неделя назад
Родитель
Сommit
371e0cfb1d

+ 75 - 5
fs-common/src/main/java/com/fs/common/core/redis/service/StockDeductService.java

@@ -59,6 +59,67 @@ public class StockDeductService {
         log.info("商品" + productId + "库存初始化完成,初始库存:" + initStock);
     }
 
+    /**
+     * 同步获取商品库存(基础版,适用于低并发/实时性要求高的场景)
+     *
+     * @param productId 商品ID
+     * @param liveId    直播间ID(贴合原有库存Key的拆分粒度)
+     * @return 库存数量(库存不存在/非数字时返回0)
+     */
+    public Integer getStock(Long productId, Long liveId) {
+        // 1. 参数合法性校验(避免空ID导致Redis Key异常)
+        if (productId == null || liveId == null) {
+            log.warn("获取库存失败:商品ID/直播间ID为空");
+            return 0;
+        }
+
+        // 2. 拼接库存Key(与初始化/扣减逻辑保持一致)
+        String stockKey = RedisConstant.STOCK_KEY_PREFIX + liveId + ":" + productId;
+
+        try {
+            // 3. 读取Redis库存值(Java 8 Optional处理空值)
+            Object stockObj = redisTemplate.opsForValue().get(stockKey);
+            Integer stock = Optional.ofNullable(stockObj)
+                    // 处理Redis值非数字的情况(如脏数据)
+                    .map(val -> {
+                        if (val instanceof Integer) {
+                            return (Integer) val;
+                        } else if (val instanceof String) {
+                            try {
+                                return Integer.parseInt((String) val);
+                            } catch (NumberFormatException e) {
+                                log.error("库存值格式异常,Key:{},值:{}", stockKey, val, e);
+                                return 0;
+                            }
+                        } else {
+                            log.error("库存值类型不支持,Key:{},类型:{}", stockKey, val.getClass().getName());
+                            return 0;
+                        }
+                    })
+                    // 库存Key不存在时返回0
+                    .orElse(0);
+
+            log.info("获取商品{}库存成功,直播间{},当前库存:{}", productId, liveId, stock);
+            return stock;
+        } catch (Exception e) {
+            // 4. 异常兜底(Redis连接异常/超时等场景)
+            log.error("获取商品{}库存异常,直播间{}", productId, liveId, e);
+            return 0;
+        }
+    }
+
+    /**
+     * 异步获取商品库存(高并发版,适配原有异步扣减逻辑)
+     *
+     * @param productId 商品ID
+     * @param liveId    直播间ID
+     * @return 异步结果:库存数量(异常/空值返回0)
+     */
+    public CompletableFuture<Integer> getStockAsync(Long productId, Long liveId) {
+        // 复用CompletableFuture异步化,避免主线程阻塞(Java 8特性)
+        return CompletableFuture.supplyAsync(() -> getStock(productId, liveId));
+    }
+
     /**
      * 高并发库存扣减(核心方法,落地Java 8特性)
      *
@@ -120,11 +181,20 @@ public class StockDeductService {
                 } else {
                     String errorMsg = "";
                     switch (remainingStock.intValue()) {
-                        case -1: errorMsg = "库存不足"; break;
-                        case -2: errorMsg = "库存Key不存在"; break;
-                        case -3: errorMsg = "库存值非数字"; break;
-                        case -4: errorMsg = "扣减数量无效"; break;
-                        default: errorMsg = "未知错误,错误码:" + remainingStock;
+                        case -1:
+                            errorMsg = "库存不足";
+                            break;
+                        case -2:
+                            errorMsg = "库存Key不存在";
+                            break;
+                        case -3:
+                            errorMsg = "库存值非数字";
+                            break;
+                        case -4:
+                            errorMsg = "扣减数量无效";
+                            break;
+                        default:
+                            errorMsg = "未知错误,错误码:" + remainingStock;
                     }
                     log.info("商品{}扣减失败:{}", productId, errorMsg);
                     return false;

+ 7 - 8
fs-service/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java

@@ -3792,16 +3792,8 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         if (liveOrder.getUserPhone() == null) return R.error("用户手机号不能为空");
         if (liveOrder.getUserAddress() == null) return R.error("用户地址不能为空");
         if (liveOrder.getTotalNum() == null) return R.error("商品数量不能为空");
-
-        Live live = liveService.selectLiveByLiveId(liveOrder.getLiveId());
-        if (live == null) return R.error("当前直播不存在");
-        FsStoreProductScrm fsStoreProduct = fsStoreProductService.selectFsStoreProductById(liveOrder.getProductId());
         LiveGoods goods = liveGoodsMapper.selectLiveGoodsByProductId(liveOrder.getLiveId(), liveOrder.getProductId());
         if (goods == null) return R.error("当前商品不存在");
-        if (fsStoreProduct == null) return R.error("商品不存在,购买失败");
-        if (fsStoreProduct.getIsShow() == 0 || goods.getStatus() == 0) return R.error("商品已下架,购买失败");
-        if (!"1".equals(fsStoreProduct.getIsAudit())) return R.error("商品已下架,购买失败");
-        if (liveOrder.getTotalNum() == null || StringUtils.isEmpty(liveOrder.getTotalNum())) liveOrder.setTotalNum("1");
         String configJson = configService.selectConfigByKey("his.store");
         if (org.apache.commons.lang3.StringUtils.isNotEmpty(configJson)) {
             com.fs.hisStore.config.StoreConfig config = com.alibaba.fastjson.JSON.parseObject(configJson, com.fs.hisStore.config.StoreConfig.class);
@@ -3823,6 +3815,13 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
                 if (goods.getStock() == null) return R.error("直播间商品库存不足");
             }
         }
+        Live live = liveService.selectLiveByLiveId(liveOrder.getLiveId());
+        if (live == null) return R.error("当前直播不存在");
+        FsStoreProductScrm fsStoreProduct = fsStoreProductService.selectFsStoreProductById(liveOrder.getProductId());
+        if (fsStoreProduct == null) return R.error("商品不存在,购买失败");
+        if (fsStoreProduct.getIsShow() == 0 || goods.getStatus() == 0) return R.error("商品已下架,购买失败");
+        if (!"1".equals(fsStoreProduct.getIsAudit())) return R.error("商品已下架,购买失败");
+        if (liveOrder.getTotalNum() == null || StringUtils.isEmpty(liveOrder.getTotalNum())) liveOrder.setTotalNum("1");
         FsStoreProductAttrValueScrm attrValue = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueById(liveOrder.getAttrValueId());
 //        if(fsStoreProduct.getStock() < Integer.parseInt(liveOrder.getTotalNum()) || goods.getStock() < Integer.parseInt(liveOrder.getTotalNum()) || attrValue.getStock() < Integer.parseInt(liveOrder.getTotalNum())){
 //            return R.error("抱歉,这款商品已被抢光,暂时无库存~");

+ 115 - 76
fs-service/src/main/resources/application-druid-bjzm.yml

@@ -42,6 +42,45 @@ spring:
                   url: jdbc:mysql://172.16.16.40:3306/fs_his?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                   username: root
                   password: Ylrz_1q2w3e4r5t6y
+                  # 初始连接数
+                  initialSize: 500
+                  # 最小连接池数量
+                  minIdle: 500
+                  # 最大连接池数量
+                  maxActive: 2000
+                  # 配置获取连接等待超时的时间
+                  maxWait: 60000
+                  # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                  timeBetweenEvictionRunsMillis: 60000
+                  # 配置一个连接在池中最小生存的时间,单位是毫秒
+                  minEvictableIdleTimeMillis: 300000
+                  # 配置一个连接在池中最大生存的时间,单位是毫秒
+                  maxEvictableIdleTimeMillis: 900000
+                  # 配置检测连接是否有效
+                  validationQuery: SELECT 1 FROM DUAL
+                  testWhileIdle: true
+                  testOnBorrow: false
+                  testOnReturn: false
+                  webStatFilter:
+                      enabled: true
+                  statViewServlet:
+                      enabled: true
+                      # 设置白名单,不填则允许所有访问
+                      allow:
+                      url-pattern: /druid/*
+                      # 控制台管理用户名和密码
+                      login-username: fs
+                      login-password: 123456
+                  filter:
+                      stat:
+                          enabled: true
+                          # 慢SQL记录
+                          log-slow-sql: true
+                          slow-sql-millis: 1000
+                          merge-sql: true
+                      wall:
+                          config:
+                              multi-statement-allow: true
                 # 从库数据源
                 slave:
                     # 从数据源开关/默认关闭
@@ -49,45 +88,45 @@ spring:
                     url: jdbc:mysql://172.16.0.24:3306/fs_his?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                     username: root
                     password: Ylrz_1q2w3e4r5t6y
-                # 初始连接数
-                initialSize: 50
-                # 最小连接池数量
-                minIdle: 50
-                # 最大连接池数量
-                maxActive: 500
-                # 配置获取连接等待超时的时间
-                maxWait: 60000
-                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
-                timeBetweenEvictionRunsMillis: 60000
-                # 配置一个连接在池中最小生存的时间,单位是毫秒
-                minEvictableIdleTimeMillis: 300000
-                # 配置一个连接在池中最大生存的时间,单位是毫秒
-                maxEvictableIdleTimeMillis: 900000
-                # 配置检测连接是否有效
-                validationQuery: SELECT 1 FROM DUAL
-                testWhileIdle: true
-                testOnBorrow: false
-                testOnReturn: false
-                webStatFilter:
-                    enabled: true
-                statViewServlet:
-                    enabled: true
-                    # 设置白名单,不填则允许所有访问
-                    allow:
-                    url-pattern: /druid/*
-                    # 控制台管理用户名和密码
-                    login-username: fs
-                    login-password: 123456
-                filter:
-                    stat:
+                    # 初始连接数
+                    initialSize: 500
+                    # 最小连接池数量
+                    minIdle: 500
+                    # 最大连接池数量
+                    maxActive: 2000
+                    # 配置获取连接等待超时的时间
+                    maxWait: 60000
+                    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                    timeBetweenEvictionRunsMillis: 60000
+                    # 配置一个连接在池中最小生存的时间,单位是毫秒
+                    minEvictableIdleTimeMillis: 300000
+                    # 配置一个连接在池中最大生存的时间,单位是毫秒
+                    maxEvictableIdleTimeMillis: 900000
+                    # 配置检测连接是否有效
+                    validationQuery: SELECT 1 FROM DUAL
+                    testWhileIdle: true
+                    testOnBorrow: false
+                    testOnReturn: false
+                    webStatFilter:
+                        enabled: true
+                    statViewServlet:
                         enabled: true
-                        # 慢SQL记录
-                        log-slow-sql: true
-                        slow-sql-millis: 1000
-                        merge-sql: true
-                    wall:
-                        config:
-                            multi-statement-allow: true
+                        # 设置白名单,不填则允许所有访问
+                        allow:
+                        url-pattern: /druid/*
+                        # 控制台管理用户名和密码
+                        login-username: fs
+                        login-password: 123456
+                    filter:
+                        stat:
+                            enabled: true
+                            # 慢SQL记录
+                            log-slow-sql: true
+                            slow-sql-millis: 1000
+                            merge-sql: true
+                        wall:
+                            config:
+                                multi-statement-allow: true
         sop:
             type: com.alibaba.druid.pool.DruidDataSource
             driverClassName: com.mysql.cj.jdbc.Driver
@@ -97,45 +136,45 @@ spring:
                     url: jdbc:mysql://gz-cdb-ofgnuz1n.sql.tencentcdb.com:26872/fs_his_sop?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
                     username: root
                     password: Ylrz_1q2w3e4r5t6y
-                # 初始连接数
-                initialSize: 5
-                # 最小连接池数量
-                minIdle: 10
-                # 最大连接池数量
-                maxActive: 20
-                # 配置获取连接等待超时的时间
-                maxWait: 60000
-                # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
-                timeBetweenEvictionRunsMillis: 60000
-                # 配置一个连接在池中最小生存的时间,单位是毫秒
-                minEvictableIdleTimeMillis: 300000
-                # 配置一个连接在池中最大生存的时间,单位是毫秒
-                maxEvictableIdleTimeMillis: 900000
-                # 配置检测连接是否有效
-                validationQuery: SELECT 1 FROM DUAL
-                testWhileIdle: true
-                testOnBorrow: false
-                testOnReturn: false
-                webStatFilter:
-                    enabled: true
-                statViewServlet:
-                    enabled: true
-                    # 设置白名单,不填则允许所有访问
-                    allow:
-                    url-pattern: /druid/*
-                    # 控制台管理用户名和密码
-                    login-username: fs
-                    login-password: 123456
-                filter:
-                    stat:
+                    # 初始连接数
+                    initialSize: 5
+                    # 最小连接池数量
+                    minIdle: 10
+                    # 最大连接池数量
+                    maxActive: 20
+                    # 配置获取连接等待超时的时间
+                    maxWait: 60000
+                    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
+                    timeBetweenEvictionRunsMillis: 60000
+                    # 配置一个连接在池中最小生存的时间,单位是毫秒
+                    minEvictableIdleTimeMillis: 300000
+                    # 配置一个连接在池中最大生存的时间,单位是毫秒
+                    maxEvictableIdleTimeMillis: 900000
+                    # 配置检测连接是否有效
+                    validationQuery: SELECT 1 FROM DUAL
+                    testWhileIdle: true
+                    testOnBorrow: false
+                    testOnReturn: false
+                    webStatFilter:
+                        enabled: true
+                    statViewServlet:
                         enabled: true
-                        # 慢SQL记录
-                        log-slow-sql: true
-                        slow-sql-millis: 1000
-                        merge-sql: true
-                    wall:
-                        config:
-                            multi-statement-allow: true
+                        # 设置白名单,不填则允许所有访问
+                        allow:
+                        url-pattern: /druid/*
+                        # 控制台管理用户名和密码
+                        login-username: fs
+                        login-password: 123456
+                    filter:
+                        stat:
+                            enabled: true
+                            # 慢SQL记录
+                            log-slow-sql: true
+                            slow-sql-millis: 1000
+                            merge-sql: true
+                        wall:
+                            config:
+                                multi-statement-allow: true
 rocketmq:
     name-server: localhost:8080
     producer: