Procházet zdrojové kódy

优化代码上传

yuhongqi před 4 dny
rodič
revize
66945e751e
41 změnil soubory, kde provedl 531 přidání a 27 odebrání
  1. 8 0
      fs-admin/src/main/java/com/fs/live/controller/LiveController.java
  2. 16 0
      fs-common/src/main/java/com/fs/common/constant/RedisConstant.java
  3. 139 0
      fs-common/src/main/java/com/fs/common/core/redis/service/StockDeductService.java
  4. 2 2
      fs-live-socket/src/main/java/com/fs/live/task/Task.java
  5. 2 1
      fs-live-socket/src/main/java/com/fs/live/websocket/service/WebSocketServer.java
  6. 1 1
      fs-live-socket/src/main/resources/application.yml
  7. 6 0
      fs-service-system/pom.xml
  8. 4 0
      fs-service-system/src/main/java/com/fs/live/mapper/LiveCompletionPointsRecordMapper.java
  9. 5 0
      fs-service-system/src/main/java/com/fs/live/mapper/LiveCouponUserMapper.java
  10. 6 0
      fs-service-system/src/main/java/com/fs/live/mapper/LiveDataMapper.java
  11. 17 0
      fs-service-system/src/main/java/com/fs/live/mapper/LiveGoodsMapper.java
  12. 3 0
      fs-service-system/src/main/java/com/fs/live/mapper/LiveMapper.java
  13. 5 0
      fs-service-system/src/main/java/com/fs/live/mapper/LiveOrderMapper.java
  14. 3 0
      fs-service-system/src/main/java/com/fs/live/mapper/LiveOrderPaymentMapper.java
  15. 4 0
      fs-service-system/src/main/java/com/fs/live/mapper/LiveWatchUserMapper.java
  16. 4 0
      fs-service-system/src/main/java/com/fs/live/service/ILiveGoodsService.java
  17. 2 0
      fs-service-system/src/main/java/com/fs/live/service/ILiveOrderService.java
  18. 2 0
      fs-service-system/src/main/java/com/fs/live/service/ILiveService.java
  19. 1 1
      fs-service-system/src/main/java/com/fs/live/service/impl/LiveCompletionPointsRecordServiceImpl.java
  20. 20 4
      fs-service-system/src/main/java/com/fs/live/service/impl/LiveGoodsServiceImpl.java
  21. 172 7
      fs-service-system/src/main/java/com/fs/live/service/impl/LiveOrderServiceImpl.java
  22. 10 2
      fs-service-system/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java
  23. 2 2
      fs-service-system/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java
  24. 1 1
      fs-service-system/src/main/java/com/fs/store/config/StoreConfig.java
  25. 4 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsShippingTemplatesMapper.java
  26. 5 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsShippingTemplatesRegionMapper.java
  27. 4 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsStoreCouponUserMapper.java
  28. 5 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsStoreProductAttrValueMapper.java
  29. 3 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsStoreProductMapper.java
  30. 4 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsUserAddressMapper.java
  31. 4 0
      fs-service-system/src/main/java/com/fs/store/mapper/FsUserMapper.java
  32. 1 0
      fs-service-system/src/main/java/com/fs/store/param/LiveOrderComputedParam.java
  33. 11 0
      fs-service-system/src/main/resources/application-config.yml
  34. 24 0
      fs-service-system/src/main/resources/mapper/live/LiveGoodsMapper.xml
  35. 3 0
      fs-user-app/src/main/java/com/fs/app/controller/CommonController.java
  36. 10 0
      fs-user-app/src/main/java/com/fs/app/controller/LiveDataController.java
  37. 0 1
      fs-user-app/src/main/java/com/fs/app/controller/LiveOrderController.java
  38. 13 0
      fs-user-app/src/main/java/com/fs/app/controller/WxUserController.java
  39. 1 1
      fs-user-app/src/main/java/com/fs/core/config/properties/DruidProperties.java
  40. 3 3
      fs-user-app/src/main/resources/application-dev.yml
  41. 1 1
      fs-user-app/src/main/resources/application.yml

+ 8 - 0
fs-admin/src/main/java/com/fs/live/controller/LiveController.java

@@ -169,5 +169,13 @@ public class LiveController extends BaseController {
         return liveService.startLive(live);
     }
 
+    /**
+     * 清除直播间缓存
+     */
+    @Log(title = "直播", businessType = BusinessType.UPDATE)
+    @PostMapping("/clearCache/{liveId}")
+    public R clearCache(@PathVariable("liveId") Long liveId) {
+        return liveService.clearLiveCache(liveId);
+    }
 
 }

+ 16 - 0
fs-common/src/main/java/com/fs/common/constant/RedisConstant.java

@@ -0,0 +1,16 @@
+package com.fs.common.constant;
+/**
+ * 库存与锁相关常量(Java 8 静态常量优化)
+ */
+public class RedisConstant {
+    // 库存Key前缀
+    public static final String STOCK_KEY_PREFIX = "product:stock:";
+    // 分布式锁Key前缀
+    public static final String LOCK_KEY_PREFIX = "product:lock:";
+    // 锁过期时间(30秒,避免死锁,大于业务执行时间)
+    public static final long LOCK_EXPIRE_SECONDS = 3L;
+    // 锁重试间隔(50毫秒,非阻塞重试,避免线程阻塞)
+    public static final long LOCK_RETRY_INTERVAL = 100L;
+    // 锁最大重试次数(3次,避免无限重试)
+    public static final int LOCK_MAX_RETRY = 20;
+}

+ 139 - 0
fs-common/src/main/java/com/fs/common/core/redis/service/StockDeductService.java

@@ -0,0 +1,139 @@
+package com.fs.common.core.redis.service;
+
+import com.fs.common.constant.RedisConstant;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.core.script.DefaultRedisScript;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.Optional;
+import java.util.Random;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.IntStream;
+
+/**
+ * 高并发库存扣减服务(Java 8 + Redis分布式锁)
+ */
+@Slf4j
+@Service
+public class StockDeductService {
+
+    // 注入RedisTemplate
+    public final RedisTemplate<String, Object> redisTemplate;
+
+    // 构造器注入(Spring 推荐,Java 8 支持)
+    public StockDeductService(RedisTemplate<String, Object> redisTemplate) {
+        this.redisTemplate = redisTemplate;
+    }
+
+    // 库存扣减Lua脚本(预编译,提升高并发性能)
+    private static final DefaultRedisScript<Long> STOCK_DEDUCT_SCRIPT;
+    // 锁释放Lua脚本(预编译)
+    private static final DefaultRedisScript<Long> LOCK_RELEASE_SCRIPT;
+
+    // 库存扣减Lua脚本(优化后,增强健壮性)
+    static {
+        // 初始化库存扣减脚本
+        STOCK_DEDUCT_SCRIPT = new DefaultRedisScript<>();
+        STOCK_DEDUCT_SCRIPT.setScriptText("if redis.call('exists', KEYS[1]) ~= 1 then " + "return -2; " + "end " + "local stock_str = redis.call('get', KEYS[1]); " + "local stock = tonumber(stock_str); " + "if stock == nil then " + "return -3; " + "end " + "local deductNum_str = ARGV[1]; " + "local deductNum = tonumber(deductNum_str); " + "if deductNum == nil or deductNum <= 0 then " + "return -4; " + "end " + "if stock >= deductNum then " + "return redis.call('decrby', KEYS[1], deductNum); " + "else " + "return -1; " + "end");
+        STOCK_DEDUCT_SCRIPT.setResultType(Long.class);
+
+        // 锁释放脚本保持不变
+        LOCK_RELEASE_SCRIPT = new DefaultRedisScript<>();
+        LOCK_RELEASE_SCRIPT.setScriptText("if redis.call('get', KEYS[1]) == ARGV[1] then " + "return redis.call('del', KEYS[1]) " + "else return 0 end");
+        LOCK_RELEASE_SCRIPT.setResultType(Long.class);
+    }
+
+    /**
+     * 初始化商品库存(Redis)
+     *
+     * @param productId 商品ID
+     * @param initStock 初始库存
+     */
+    public void initStock(Long productId, Long liveId, Integer initStock) {
+        String stockKey = RedisConstant.STOCK_KEY_PREFIX + liveId + ":" + productId;
+        redisTemplate.opsForValue().set(stockKey, initStock, 24 * 60 * 60, TimeUnit.SECONDS);
+        log.info("商品" + productId + "库存初始化完成,初始库存:" + initStock);
+    }
+
+    /**
+     * 高并发库存扣减(核心方法,落地Java 8特性)
+     *
+     * @param productId 商品ID
+     * @param deductNum 扣减数量(默认1)
+     * @return 扣减结果:true=成功,false=失败
+     */
+    public CompletableFuture<Boolean> deductStockAsync(Long productId, Long liveId, Integer deductNum, Long userId) {
+        // Java 8 CompletableFuture 异步处理,提升高并发吞吐量
+        return CompletableFuture.supplyAsync(() -> {
+            // 1. 参数校验(Java 8 Optional 空值处理)
+            Integer num = Optional.ofNullable(deductNum).orElse(1);
+            String stockKey = RedisConstant.STOCK_KEY_PREFIX + liveId + ":" + productId;
+            String lockKey = RedisConstant.LOCK_KEY_PREFIX + liveId + ":" + productId;
+
+            // 3. 尝试获取分布式锁(非阻塞重试,Java 8 Stream API 实现重试)
+// 3. 尝试获取分布式锁(优化:加入随机延迟,避免惊群效应)
+            boolean isLockAcquired = IntStream.range(0, RedisConstant.LOCK_MAX_RETRY).anyMatch(retryCount -> {
+                Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, userId, RedisConstant.LOCK_EXPIRE_SECONDS, TimeUnit.SECONDS);
+                if (Boolean.TRUE.equals(result)) {
+                    return true;
+                }
+                try {
+                    // 随机延迟:50ms~150ms,避免所有请求同时重试
+                    long randomDelay = RedisConstant.LOCK_RETRY_INTERVAL + new Random().nextInt(100);
+                    TimeUnit.MILLISECONDS.sleep(randomDelay);
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                    return false;
+                }
+                return false;
+            });
+            // 4. 未获取到锁,直接返回失败
+            if (!isLockAcquired) {
+                System.err.println("商品" + productId + "获取锁失败,高并发限流中");
+                return false;
+            }
+
+            try {
+                // 5. 执行库存扣减Lua脚本(原子操作,防超卖)
+                // 新增日志:打印当前库存值和扣减数量
+                Integer currentStockStr = (Integer) redisTemplate.opsForValue().get(stockKey);
+                log.info("拿到锁成功 → 库存Key:{},当前库存值:{},扣减数量:{}", stockKey, currentStockStr, num);
+
+                // 执行库存扣减Lua脚本
+                Long remainingStock = redisTemplate.execute(
+                        STOCK_DEDUCT_SCRIPT,
+                        Collections.singletonList(stockKey),
+                        1
+                );
+
+                // 新增日志:打印Lua返回结果
+                log.info("Lua脚本返回值:{}", remainingStock);
+
+                // 6. 判断扣减结果
+                if (remainingStock >= 0) {
+                    log.info("商品{}库存扣减成功,剩余库存:{}", productId, remainingStock);
+                    return true;
+                } 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;
+                    }
+                    log.info("商品{}扣减失败:{}", productId, errorMsg);
+                    return false;
+                }
+            } finally {
+                // 7. 释放分布式锁(Lua脚本保证原子性,仅释放自己持有的锁)
+                redisTemplate.execute(LOCK_RELEASE_SCRIPT, Collections.singletonList(lockKey), userId);
+                log.info("商品{}锁释放成功,持有者:{}", productId, userId);
+            }
+        });
+    }
+}

+ 2 - 2
fs-live-socket/src/main/java/com/fs/live/task/Task.java

@@ -208,8 +208,8 @@ public class Task {
             liveService.asyncToCache();
         }
     }
-    @Scheduled(cron = "0/1 * * * * ?")
-    @DistributeLock(key = "liveLotteryTask", scene = "task")
+//    @Scheduled(cron = "0/1 * * * * ?")
+//    @DistributeLock(key = "liveLotteryTask", scene = "task")
     public void liveLotteryTask() {
         long currentTime = Instant.now().toEpochMilli(); // 当前时间戳(毫秒)
         String lotteryKey = "live:lottery_task:*";

+ 2 - 1
fs-live-socket/src/main/java/com/fs/live/websocket/service/WebSocketServer.java

@@ -88,7 +88,7 @@ public class WebSocketServer {
         if (live == null) {
             throw new BaseException("未找到直播间");
         }
-        long companyId = live.getCompanyId() == null ? -1L : live.getCompanyId();
+        long companyId = -1L;
         long companyUserId = -1L;
         if (!Objects.isNull(userProperties.get("companyId"))) {
             companyId = (long) userProperties.get("companyId");
@@ -184,6 +184,7 @@ public class WebSocketServer {
                 liveUserFirstEntry.setUpdateTime( date);
                 liveUserFirstEntryService.insertLiveUserFirstEntry(liveUserFirstEntry);
             }
+//            redisCache.setCacheObject( "live:user:first:entry:" + liveId + ":" + userId, liveUserFirstEntry, 4, TimeUnit.HOURS);
 
 
         } else {

+ 1 - 1
fs-live-socket/src/main/resources/application.yml

@@ -58,7 +58,7 @@ server:
 # 日志配置
 logging:
   level:
-    com.fs: info
+#    com.fs: info
     org.springframework: warn
     org.springframework.web: info
 

+ 6 - 0
fs-service-system/pom.xml

@@ -191,5 +191,11 @@
             <artifactId>cos-sts_api</artifactId>
             <version>3.1.1</version>
         </dependency>
+
+        <dependency>
+            <groupId>org.springframework.retry</groupId>
+            <artifactId>spring-retry</artifactId>
+            <version>1.3.1</version>
+        </dependency>
     </dependencies>
 </project>

+ 4 - 0
fs-service-system/src/main/java/com/fs/live/mapper/LiveCompletionPointsRecordMapper.java

@@ -1,5 +1,7 @@
 package com.fs.live.mapper;
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveCompletionPointsRecord;
 import org.apache.ibatis.annotations.Param;
 
@@ -36,6 +38,7 @@ public interface LiveCompletionPointsRecordMapper {
     /**
      * 查询用户在某直播间最近一次完课记录(不限制日期)
      */
+    @DataSource(DataSourceType.SLAVE)
     LiveCompletionPointsRecord selectLatestByUserAndLiveId(@Param("liveId") Long liveId, 
                                                             @Param("userId") Long userId);
 
@@ -48,6 +51,7 @@ public interface LiveCompletionPointsRecordMapper {
     /**
      * 查询用户的完课积分领取记录列表
      */
+    @DataSource(DataSourceType.SLAVE)
     List<LiveCompletionPointsRecord> selectRecordsByUser(@Param("liveId") Long liveId, 
                                                           @Param("userId") Long userId);
 

+ 5 - 0
fs-service-system/src/main/java/com/fs/live/mapper/LiveCouponUserMapper.java

@@ -1,6 +1,9 @@
 package com.fs.live.mapper;
 
 import java.util.List;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveCouponUser;
 import com.fs.live.param.CouponPO;
 import org.apache.ibatis.annotations.Param;
@@ -20,6 +23,7 @@ public interface LiveCouponUserMapper
      * @param id 优惠券发放记录ID
      * @return 优惠券发放记录
      */
+    @DataSource(DataSourceType.SLAVE)
     public LiveCouponUser selectLiveCouponUserById(Long id);
 
     /**
@@ -71,5 +75,6 @@ public interface LiveCouponUserMapper
             " and lcu.goods_id= #{coupon.goodsId}" +
             " </if>" +
             "</script>")
+    @DataSource(DataSourceType.SLAVE)
     List<LiveCouponUser> curCoupon(@Param("coupon") CouponPO coupon);
 }

+ 6 - 0
fs-service-system/src/main/java/com/fs/live/mapper/LiveDataMapper.java

@@ -1,6 +1,8 @@
 package com.fs.live.mapper;
 
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveData;
 import com.fs.live.vo.*;
 import org.apache.ibatis.annotations.Param;
@@ -130,6 +132,7 @@ public interface LiveDataMapper {
      * @param liveId 直播间ID
      * @return 详情数据
      */
+    @DataSource(DataSourceType.SLAVE)
     LiveDataDetailVo selectLiveDataDetailBySql(@Param("liveId") Long liveId);
 
     /**
@@ -137,6 +140,7 @@ public interface LiveDataMapper {
      * @param liveId 直播间ID
      * @return 用户详情列表
      */
+    @DataSource(DataSourceType.SLAVE)
     List<LiveUserDetailVo> selectLiveUserDetailListBySql(@Param("liveId") Long liveId,@Param("companyId") Long companyId,@Param("companyUserId") Long companyUserId);
 
     /**
@@ -144,6 +148,7 @@ public interface LiveDataMapper {
      * @param liveIds 直播间ID列表
      * @return 统计数据
      */
+    @DataSource(DataSourceType.SLAVE)
     LiveDataStatisticsVo selectLiveDataStatistics(@Param("liveIds") List<Long> liveIds);
 
     /**
@@ -151,6 +156,7 @@ public interface LiveDataMapper {
      * @param liveIds 直播间ID列表
      * @return 列表数据
      */
+    @DataSource(DataSourceType.SLAVE)
     List<LiveDataListVo> selectLiveDataListByLiveIds(@Param("liveIds") List<Long> liveIds);
 
 }

+ 17 - 0
fs-service-system/src/main/java/com/fs/live/mapper/LiveGoodsMapper.java

@@ -1,5 +1,7 @@
 package com.fs.live.mapper;
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveGoods;
 import com.fs.live.domain.LiveOrder;
 import com.fs.live.vo.LiveGoodsListVo;
@@ -90,6 +92,7 @@ public interface LiveGoodsMapper {
      * @param liveGoods 直播商品
      * @return 商品信息集合
      */
+    @DataSource(DataSourceType.SLAVE)
     List<LiveGoodsVo> selectProductListByLiveId(LiveGoods liveGoods);
 
     /**
@@ -112,6 +115,7 @@ public interface LiveGoodsMapper {
     List<LiveGoodsVo> selectProductListByOrder(LiveOrder liveOrder);
 
     @Select("select * from live_goods where live_id = #{liveId} and product_id = #{productId}")
+    @DataSource(DataSourceType.SLAVE)
     LiveGoods selectLiveGoodsByProductId(@Param("liveId") Long liveId,@Param("productId") Long productId);
 
     /**
@@ -129,6 +133,7 @@ public interface LiveGoodsMapper {
 
     int handleDeleteSelectedAdmin(@Param("listVo") LiveGoodsListVo listVo);
 
+    @DataSource(DataSourceType.SLAVE)
     List<LiveGoodsVo> selectProductListByLiveIdAll(LiveGoods liveGoods);
 
     @Select("select count(1) from live_goods where is_show = true and live_id = #{liveId}")
@@ -141,6 +146,7 @@ public interface LiveGoodsMapper {
     LiveGoodsVo showGoods(@Param("liveId") Long liveId);
 
     @Select("select * from live_goods where live_id = #{liveId}")
+    @DataSource(DataSourceType.SLAVE)
     List<LiveGoods> selectLiveGoodsByLiveId(@Param("liveId") Long liveId);
 
     @Update({"<script>" +
@@ -156,4 +162,15 @@ public interface LiveGoodsMapper {
      */
     @Update("update live_goods set status = #{status} where goods_id = #{goodsId}")
     void updateLiveGoodsStatus(@Param("goodsId") Long goodsId, @Param("status") Integer status);
+
+    @Select("select * from live_goods")
+    List<LiveGoods> listAll();
+
+    /**
+     * 批量根据ID更新直播商品
+     *
+     * @param liveGoodsList 直播商品列表
+     * @return 结果
+     */
+    int updateBatchById(@Param("list") List<LiveGoods> liveGoodsList);
 }

+ 3 - 0
fs-service-system/src/main/java/com/fs/live/mapper/LiveMapper.java

@@ -1,6 +1,8 @@
 package com.fs.live.mapper;
 
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.Live;
 import com.fs.live.param.LiveDataParam;
 import com.fs.live.vo.LiveListVo;
@@ -26,6 +28,7 @@ public interface LiveMapper
      * @param liveId 直播主键
      * @return 直播
      */
+    @DataSource(DataSourceType.SLAVE)
     public Live selectLiveByLiveId(Long liveId);
 
     /**

+ 5 - 0
fs-service-system/src/main/java/com/fs/live/mapper/LiveOrderMapper.java

@@ -1,6 +1,8 @@
 package com.fs.live.mapper;
 
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveOrder;
 import com.fs.live.vo.LiveOrderListVo;
 import com.fs.live.vo.LiveOrderQueryVO;
@@ -36,6 +38,7 @@ public interface LiveOrderMapper {
      * @param liveOrder 订单
      * @return 订单集合
      */
+    @DataSource(DataSourceType.SLAVE)
     List<LiveOrder> selectLiveOrderList(LiveOrderQueryVO liveOrder);
 
     /**
@@ -95,12 +98,14 @@ public interface LiveOrderMapper {
             "</where> " +
             "order by create_time desc" +
             "</script>"})
+    @DataSource(DataSourceType.SLAVE)
     List<LiveOrderListVo> selectLiveOrderListVo(@Param("userId") String userId,@Param("status") Integer status);
 
     @Select("select * from live_order where `status` = 3 AND TIMESTAMPDIFF(HOUR, start_time, NOW()) >= 48  ")
     List<LiveOrder> selectLiveOrderByFinish();
 
     @Select("select * from live_order where `status` = 2 and extend_order_id is not null ")
+    @DataSource(DataSourceType.SLAVE)
     List<LiveOrder> selectUpdateExpress();
 
     @Select("select order_id from live_order where `status` = 2")

+ 3 - 0
fs-service-system/src/main/java/com/fs/live/mapper/LiveOrderPaymentMapper.java

@@ -1,6 +1,8 @@
 package com.fs.live.mapper;
 
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveOrderPayment;
 import com.fs.live.vo.LiveOrderPaymentVo;
 import org.apache.ibatis.annotations.MapKey;
@@ -24,6 +26,7 @@ public interface LiveOrderPaymentMapper {
      * @param paymentId 支付明细主键
      * @return 支付明细
      */
+    @DataSource(DataSourceType.SLAVE)
     LiveOrderPayment selectLiveOrderPaymentByPaymentId(Long paymentId);
 
     /**

+ 4 - 0
fs-service-system/src/main/java/com/fs/live/mapper/LiveWatchUserMapper.java

@@ -1,6 +1,8 @@
 package com.fs.live.mapper;
 
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveWatchUser;
 import com.fs.live.vo.LiveWatchUserStatistics;
 import com.fs.live.vo.LiveWatchUserVO;
@@ -81,6 +83,7 @@ public interface LiveWatchUserMapper {
      * @param params 参数
      * @return list
      */
+    @DataSource(DataSourceType.SLAVE)
     List<LiveWatchUserVO> selectWatchUserListByLiveId(@Param("params") Map<String, Object> params);
 
     /**
@@ -100,6 +103,7 @@ public interface LiveWatchUserMapper {
     @Select("select a.*,fu.nickname as nick_name from (select lws.* from live_watch_user lws where live_id=#{liveId} and online = 0 and " +
             "user_id in (select user_id from live_lottery_registration where live_id = #{liveId} and lottery_id=#{lotteryId} and registration_id >= " +
             "(SELECT FLOOR(RAND() * (SELECT MAX(registration_id) FROM live_lottery_registration)))) ) a left join fs_user fu on fu.user_id = a.user_id")
+    @DataSource(DataSourceType.SLAVE)
     List<LiveWatchUser> selectLiveWatchAndRegisterUser(@Param("liveId") Long liveId,@Param("lotteryId") Long lotteryId);
 
     @Select("select * from live_watch_user where live_id = #{liveId} and user_id = #{userId}")

+ 4 - 0
fs-service-system/src/main/java/com/fs/live/service/ILiveGoodsService.java

@@ -126,4 +126,8 @@ public interface ILiveGoodsService {
     R getStoreByLiveId(Long liveId, String key, String userId);
 
     void updateLiveGoodsStatus(Long goodsId, Integer status);
+
+    List<LiveGoods> listAll();
+
+    void updateBatchById(List<LiveGoods> logsToInsert);
 }

+ 2 - 0
fs-service-system/src/main/java/com/fs/live/service/ILiveOrderService.java

@@ -212,4 +212,6 @@ public interface ILiveOrderService {
     R createRewardLiveOrder(LiveOrder liveOrder);
 
     R payConfirmReward(LiveOrder liveOrder);
+
+    void initStock();
 }

+ 2 - 0
fs-service-system/src/main/java/com/fs/live/service/ILiveService.java

@@ -204,4 +204,6 @@ public interface ILiveService
      * 获取直播间数量
      */
     Integer getLiveCountByMap(Map<String, Object> params);
+
+    R clearLiveCache(Long liveId);
 }

+ 1 - 1
fs-service-system/src/main/java/com/fs/live/service/impl/LiveCompletionPointsRecordServiceImpl.java

@@ -344,7 +344,7 @@ public class LiveCompletionPointsRecordServiceImpl implements ILiveCompletionPoi
             }
 
             // 8. 计算积分
-            int points = calculatePoints(continuousDays);
+            int points = Math.toIntExact(config.getScoreAmount());
 
             // 9. 创建完课记录
             LiveCompletionPointsRecord record = new LiveCompletionPointsRecord();

+ 20 - 4
fs-service-system/src/main/java/com/fs/live/service/impl/LiveGoodsServiceImpl.java

@@ -3,6 +3,7 @@ package com.fs.live.service.impl;
 
 import com.fs.common.core.domain.R;
 import com.fs.common.core.domain.entity.SysUser;
+import com.fs.common.core.redis.service.StockDeductService;
 import com.fs.common.utils.DateUtils;
 import com.fs.company.domain.CompanyUser;
 import com.fs.live.domain.LiveGoods;
@@ -22,10 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -46,6 +44,10 @@ public class LiveGoodsServiceImpl  implements ILiveGoodsService {
     @Autowired
     private ILiveAutoTaskService liveAutoTaskService;
 
+    @Autowired
+    private StockDeductService stockDeductService;
+
+
     /**
      * 查询直播商品
      *
@@ -101,6 +103,7 @@ public class LiveGoodsServiceImpl  implements ILiveGoodsService {
             if(fsStoreProduct == null) return R.error("商品不存在");
             if(fsStoreProduct.getIsShow() == 0 || existGoods.getStatus() == 0) return R.error("商品已下架");
             if(fsStoreProduct.getStock() < liveGoods.getStock()) return R.error("商品库存不足");
+            stockDeductService.initStock(existGoods.getProductId(), existGoods.getLiveId(), liveGoods.getStock().intValue());
         }
         baseMapper.updateLiveGoods(liveGoods);
         return R.ok();
@@ -232,6 +235,16 @@ public class LiveGoodsServiceImpl  implements ILiveGoodsService {
         baseMapper.updateLiveGoodsStatus(goodsId, status);
     }
 
+    @Override
+    public List<LiveGoods> listAll() {
+        return baseMapper.listAll();
+    }
+
+    @Override
+    public void updateBatchById(List<LiveGoods> logsToInsert) {
+        baseMapper.updateBatchById(logsToInsert);
+    }
+
     /**
      * 批量新增直播商品
      *
@@ -314,6 +327,9 @@ public class LiveGoodsServiceImpl  implements ILiveGoodsService {
                 .collect(Collectors.toList());
 //
 //        // 批量插入
+        liveGoodsList.forEach(e -> {
+            stockDeductService.initStock(e.getProductId(), liveId, e.getStock().intValue());
+        });
         return baseMapper.insertLiveGoodsList(liveGoodsList);
     }
 

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

@@ -9,7 +9,7 @@ import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.time.temporal.ChronoUnit;
 import java.util.*;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
 import java.util.stream.Collectors;
 
 import cn.hutool.core.collection.CollectionUtil;
@@ -28,6 +28,7 @@ import com.fs.common.config.FSSysConfig;
 import com.fs.common.config.LoginContextManager;
 import com.fs.common.core.domain.R;
 import com.fs.common.core.redis.RedisCache;
+import com.fs.common.core.redis.service.StockDeductService;
 import com.fs.common.event.TemplateBean;
 import com.fs.common.event.TemplateEvent;
 import com.fs.common.event.TemplateListenEnum;
@@ -79,11 +80,16 @@ import org.apache.http.util.Asserts;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.retry.annotation.Backoff;
+import org.springframework.retry.annotation.Recover;
+import org.springframework.retry.annotation.Retryable;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.interceptor.TransactionAspectSupport;
 
+import javax.annotation.PreDestroy;
+
 import static com.fs.store.constants.StoreConstants.DELIVERY;
 
 
@@ -101,7 +107,10 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
     @Autowired
     private LiveOrderMapper baseMapper;
 
-
+    @Autowired
+    private StockDeductService stockDeductService;
+    @Autowired
+    private ILiveGoodsService liveGoodsService;
 
     @Autowired
     private LiveMapper liveMapper;
@@ -217,6 +226,93 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
     @Autowired
     private ILiveOrderService liveOrderService;
 
+    private final BlockingQueue<LiveGoods> liveGoodsQueue = new LinkedBlockingQueue<>();
+    private ExecutorService liveGoodsExecutor;
+    private final int BATCH_SIZE = 500;
+    // 新增:优雅退出标记(volatile保证多线程可见性)
+    private volatile boolean isRunning = true;
+
+    private void startConsumers(){
+        liveGoodsExecutor = Executors.newSingleThreadExecutor(r -> new Thread(r, "liveGoodsConsumer"));
+        liveGoodsExecutor.submit(this::consumeLiveGoods);
+    }
+    private void consumeLiveGoods() {
+        List<LiveGoods> batch = new ArrayList<>(BATCH_SIZE);
+        log.info("liveGoodsStock 消费线程启动,队列当前大小: {}", liveGoodsQueue.size());
+        while (isRunning) {
+            try {
+                LiveGoods goods = liveGoodsQueue.poll(1, TimeUnit.SECONDS);
+                if (goods != null) {
+                    batch.add(goods);
+                    log.info("消费线程获取到数据,当前批量大小: {}", batch.size());
+                    if (batch.size() >= BATCH_SIZE) {
+                        log.info("批量阈值达到{},执行更新", BATCH_SIZE);
+                        batchUpdateLiveGoods(new ArrayList<>(batch));
+                        batch.clear();
+                    }
+                } else {
+                    if (!batch.isEmpty()) {
+                        log.info("队列超时无数据,执行剩余{}条数据更新", batch.size());
+                        batchUpdateLiveGoods(new ArrayList<>(batch));
+                        batch.clear();
+                    }
+                }
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                log.error("liveGoodsStock 消费线程被中断: {}", e.getMessage(), e);
+                // 中断时退出循环,保证优雅关闭
+                isRunning = false;
+                break;
+            }
+        }
+
+        // 处理剩余的数据(退出循环后)
+        if (!batch.isEmpty()) {
+            batchUpdateLiveGoods(batch);
+        }
+    }
+    // 新增:优雅停止消费线程的方法(如应用关闭时调用)
+    @PreDestroy
+    public void stopConsumers() {
+        isRunning = false;
+        if (liveGoodsExecutor != null) {
+            liveGoodsExecutor.shutdown();
+            try {
+                // 等待线程终止(最多等5秒)
+                if (!liveGoodsExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
+                    liveGoodsExecutor.shutdownNow();
+                }
+            } catch (InterruptedException e) {
+                liveGoodsExecutor.shutdownNow();
+            }
+        }
+        log.info("liveGoodsStock 消费线程已停止");
+    }
+    @Transactional
+    @Retryable(
+            value = { Exception.class },
+            maxAttempts = 3,
+            backoff = @Backoff(delay = 2000)
+    )
+    public void batchUpdateLiveGoods(List<LiveGoods> logsToInsert) {
+        try {
+            liveGoodsService.updateBatchById(logsToInsert);
+            log.info("批量更新 liveGoodsStock 完成,共更新 {} 条记录。", logsToInsert.size());
+        } catch (Exception e) {
+            log.error("批量更新 liveGoodsStock 失败,将触发重试: {}", e.getMessage(), e);
+            // 核心:必须抛出异常,否则Retryable不会生效
+            throw new RuntimeException("批量更新失败", e);
+        }
+    }
+
+    // 新增:重试失败后的兜底处理(可选)
+    @Recover
+    public void recoverBatchUpdate(Exception e, List<LiveGoods> logsToInsert) {
+        log.error("批量更新 liveGoodsStock 重试3次仍失败,记录失败数据: {}", e.getMessage(), e);
+        // 方案:将失败数据写入本地文件/数据库/死信队列,后续人工处理
+        // failureQueue.offer(logsToInsert);
+    }
+
 
     public LiveOrderServiceImpl(RedisCache redisCache) {
         this.redisCache = redisCache;
@@ -1627,8 +1723,20 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             return null;
         }
         FsStoreProduct fsStoreProduct = fsStoreProductService.selectFsStoreProductById(param.getProductId());
+        if (fsStoreProduct == null) {
+            log.error("商品不存在");
+            return null;
+        }
+        // todo yhq 计算价格接口 是否传入了规格参数
+        FsStoreProductAttrValue fsStoreProductAttrValue = null;
+        if (!Objects.isNull(param.getAttrValueId())) {
+            fsStoreProductAttrValue = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueById(param.getAttrValueId());
+        }
         BigDecimal totalPrice = BigDecimal.ZERO;
         BigDecimal payPrice = fsStoreProduct.getPrice().multiply(new BigDecimal(param.getTotalNum()));
+        if (fsStoreProductAttrValue != null) {
+            payPrice = fsStoreProductAttrValue.getPrice().multiply(new BigDecimal(param.getTotalNum()));
+        }
         totalPrice = totalPrice.add(payPrice);
         BigDecimal payDelivery = BigDecimal.ZERO;
         BigDecimal deductionPrice = BigDecimal.ZERO;
@@ -2261,7 +2369,7 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         if(liveOrder.getUserAddress() == null) return R.error("用户地址不能为空");
         if(liveOrder.getTotalNum() == null) return R.error("商品数量不能为空");
 
-        Live live = liveMapper.selectLiveByLiveId(liveOrder.getLiveId());
+        Live live = liveService.selectLiveByLiveId(liveOrder.getLiveId());
         if(live == null) return R.error("当前直播不存在");
         FsStoreProduct fsStoreProduct = fsStoreProductService.selectFsStoreProductById(liveOrder.getProductId());
         LiveGoods goods = liveGoodsMapper.selectLiveGoodsByProductId(liveOrder.getLiveId(), liveOrder.getProductId());
@@ -2433,6 +2541,20 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
     }
 
 
+
+
+    @Override
+    public void initStock() {
+        List<LiveGoods> list = liveGoodsService.listAll();
+        list.forEach(e -> {
+            if(e.getProductId() == null || e.getLiveId() == null || e.getStock() == null) return;
+            stockDeductService.initStock(e.getProductId(), e.getLiveId(), e.getStock().intValue());
+        });
+    }
+
+    @Autowired
+    ILiveService liveService;
+
     @Override
     @Transactional(rollbackFor = Throwable.class,propagation = Propagation.REQUIRED)
     public R createLiveOrder(LiveOrder liveOrder) {
@@ -2451,20 +2573,61 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
         if(liveOrder.getUserAddress() == null) return R.error("用户地址不能为空");
         if(liveOrder.getTotalNum() == null) return R.error("商品数量不能为空");
 
-        Live live = liveMapper.selectLiveByLiveId(liveOrder.getLiveId());
+        Live live = liveService.selectLiveByLiveId(liveOrder.getLiveId());
         if(live == null) return R.error("当前直播不存在");
         FsStoreProduct 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(fsStoreProduct.getStock() < Integer.parseInt(liveOrder.getTotalNum()) || goods.getStock() < Integer.parseInt(liveOrder.getTotalNum())) return R.error("抱歉,这款商品已被抢光,暂时无库存~");
+//        if(fsStoreProduct.getStock() < Integer.parseInt(liveOrder.getTotalNum()) || goods.getStock() < Integer.parseInt(liveOrder.getTotalNum())) return R.error("抱歉,这款商品已被抢光,暂时无库存~");
+//
+//        String configJson = configService.selectConfigByKey("store.config");
+//        if (org.apache.commons.lang3.StringUtils.isNotEmpty(configJson)) {
+//            com.fs.store.config.StoreConfig config = com.alibaba.fastjson.JSON.parseObject(configJson, com.fs.store.config.StoreConfig.class);
+//            if (config != null && Boolean.TRUE.equals(config.getCheckStock())) {
+//                CompletableFuture<Boolean> completableFuture = stockDeductService.deductStockAsync(liveOrder.getProductId(), liveOrder.getLiveId(), Integer.parseInt(liveOrder.getTotalNum()), Long.parseLong(liveOrder.getUserId()));
+//                try {
+//                    log.info("{}, 商品REDIS 库存扣减成功!", goods.getLiveId());
+//                    if (!completableFuture.get()) {
+//                        return R.error("抱歉,这款商品已被抢光,暂时无库存~");
+//                    }
+//                    log.info("{}, 商品REDIS 库存扣减成功!", goods.getLiveId());
+//                } catch (InterruptedException e) {
+//                    log.error("高并发处理失败", e);
+//                    return R.error("订单创建失败:" + e.getMessage());
+//                } catch (ExecutionException e) {
+//                    log.error("高并发处理失败", e);
+//                    return R.error("订单创建失败:" + e.getMessage());
+//                }
+//                if (goods.getStock() == null) return R.error("直播间商品库存不足");
+//            }
+//        }
+//        FsStoreProductAttrValueScrm attrValue = fsStoreProductAttrValueMapper.selectFsStoreProductAttrValueById(liveOrder.getAttrValueId());
+
 
         // 更改店铺库存
-        fsStoreProduct.setStock(fsStoreProduct.getStock()-Integer.parseInt(liveOrder.getTotalNum()));
-        fsStoreProductService.updateFsStoreProduct(fsStoreProduct);
+//        fsStoreProduct.setStock(fsStoreProduct.getStock()-Integer.parseInt(liveOrder.getTotalNum()));
+//        fsStoreProductService.updateFsStoreProduct(fsStoreProduct);
         // 更新直播间库存
         goods.setStock(goods.getStock()-Integer.parseInt(liveOrder.getTotalNum()));
+        goods.setSales(goods.getSales() + Integer.parseInt(liveOrder.getTotalNum()));
         liveGoodsMapper.updateLiveGoods(goods);
+//        LiveGoods liveGoods = new LiveGoods();
+//        liveGoods.setGoodsId(goods.getGoodsId());
+//        liveGoods.setStock(goods.getStock() - Integer.parseInt(liveOrder.getTotalNum()));
+//        liveGoods.setSales(goods.getSales() + Integer.parseInt(liveOrder.getTotalNum()));
+//        log.info("商品库存修改添加队列:{}", goods.getGoodsId());
+//        try {
+//            boolean offered = liveGoodsQueue.offer(liveGoods, 5, TimeUnit.SECONDS);
+//            if (!offered) {
+//                log.error("liveGoodsStock 队列已满,无法添加日志: {}", JSON.toJSONString(liveGoods));
+//                // 处理队列已满的情况,例如记录到失败队列或持久化存储
+//            }
+//        } catch (InterruptedException e) {
+//            Thread.currentThread().interrupt();
+//            log.error("插入 liveGoodsStock 队列时被中断: {}", e.getMessage(), e);
+//        }
 
         //判断是否是三种特定产品
         if (fsStoreProduct.getProductId() != null && (fsStoreProduct.getProductId().equals(3168L)
@@ -2673,6 +2836,8 @@ public class LiveOrderServiceImpl implements ILiveOrderService {
             fsStoreProductService.updateFsStoreProduct(fsStoreProduct);
             goods.setStock(goods.getStock()+Long.parseLong(liveOrder.getTotalNum()));
             liveGoodsMapper.updateLiveGoods(goods);
+
+
             this.refundCoupon(order);
 
             return R.ok("操作成功");

+ 10 - 2
fs-service-system/src/main/java/com/fs/live/service/impl/LiveServiceImpl.java

@@ -1283,11 +1283,19 @@ public class LiveServiceImpl implements ILiveService
      * 清除直播间数据缓存
      * @param liveId 直播间ID
      */
-    private void clearLiveCache(Long liveId) {
-        if (liveId != null) {
+    @Override
+    public R clearLiveCache(Long liveId) {
+        if (liveId == null) {
+            return R.error("直播间ID不能为空");
+        }
+        try {
             String cacheKey = String.format(LiveKeysConstant.LIVE_DATA_CACHE, liveId);
             redisCache.deleteObject(cacheKey);
             log.debug("清除直播间缓存: liveId={}", liveId);
+            return R.ok("缓存清理成功");
+        } catch (Exception e) {
+            log.error("清除直播间缓存失败: liveId={}", liveId, e);
+            return R.error("缓存清理失败: " + e.getMessage());
         }
     }
 

+ 2 - 2
fs-service-system/src/main/java/com/fs/live/service/impl/LiveWatchUserServiceImpl.java

@@ -234,7 +234,7 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
         liveWatchUser.setAvatar(fsUser.getAvatar());
         liveWatchUser.setNickName(fsUser.getNickname());
         String hashKey = String.format(LiveKeysConstant.LIVE_WATCH_USERS, liveId);
-        redisCache.hashPut(hashKey, String.valueOf(userId), JSON.toJSONString(liveWatchUser));
+//        redisCache.hashPut(hashKey, String.valueOf(userId), JSON.toJSONString(liveWatchUser));
         return liveWatchUser;
     }
     @Override
@@ -354,7 +354,7 @@ public class LiveWatchUserServiceImpl implements ILiveWatchUserService {
                                 watchUserVO -> String.valueOf(watchUserVO.getUserId()),
                                 JSON::toJSONString
                         ));
-                redisUtil.hashPut(hashKey,collect);
+//                redisUtil.hashPut(hashKey,collect);
             });
         }
         return liveWatchUserVOS;

+ 1 - 1
fs-service-system/src/main/java/com/fs/store/config/StoreConfig.java

@@ -21,5 +21,5 @@ public class StoreConfig implements Serializable {
     private String videoUrl;
     private String refundPhoneNumber;
     private String refundAddress;
-
+    private Boolean checkStock;//是否检查库存,默认关闭
 }

+ 4 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsShippingTemplatesMapper.java

@@ -1,6 +1,9 @@
 package com.fs.store.mapper;
 
 import java.util.List;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.store.domain.FsShippingTemplates;
 import org.apache.ibatis.annotations.Select;
 
@@ -60,6 +63,7 @@ public interface FsShippingTemplatesMapper
      */
     public int deleteFsShippingTemplatesByIds(Long[] id);
     @Select("select * from fs_shipping_templates where find_in_set(id,#{ids})")
+    @DataSource(DataSourceType.SLAVE)
     List<FsShippingTemplates> selectFsShippingTemplatesByIds(String ids);
 
     /**

+ 5 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsShippingTemplatesRegionMapper.java

@@ -1,6 +1,9 @@
 package com.fs.store.mapper;
 
 import java.util.List;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.store.domain.FsShippingTemplatesRegion;
 import org.apache.ibatis.annotations.Delete;
 import org.apache.ibatis.annotations.Param;
@@ -65,9 +68,11 @@ public interface FsShippingTemplatesRegionMapper
     @Delete("delete from fs_shipping_templates_region where temp_id=#{tempId}")
     int deleteFsShippingTemplatesRegionByTempId(Long tempId);
     @Select("select * from fs_shipping_templates_region where find_in_set(temp_id,#{tempIds}) and find_in_set(city_id,#{cityIds})")
+    @DataSource(DataSourceType.SLAVE)
     List<FsShippingTemplatesRegion> selectFsShippingTemplatesRegionListByTempIdsAndCityIds(@Param("tempIds") String tempIds,@Param("cityIds") String cityIds);
 
     @Select("select * from fs_shipping_templates_region where temp_id=${templateId}")
+    @DataSource(DataSourceType.SLAVE)
     List<FsShippingTemplatesRegion> selectTempRegionByTempIdAndCityId(@Param("templateId") Long templateId);
 
     void batchSaveRegions(@Param("list") List<FsShippingTemplatesRegion> fsShippingTemplatesRegions);

+ 4 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsStoreCouponUserMapper.java

@@ -1,6 +1,9 @@
 package com.fs.store.mapper;
 
 import java.util.List;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.store.domain.FsStoreCouponUser;
 import com.fs.store.param.FsCouponUserEnableParam;
 import com.fs.store.vo.FsStoreCouponUserVO;
@@ -22,6 +25,7 @@ public interface FsStoreCouponUserMapper
      * @param id 优惠券发放记录ID
      * @return 优惠券发放记录
      */
+    @DataSource(DataSourceType.SLAVE)
     public FsStoreCouponUser selectFsStoreCouponUserById(Long id);
 
     /**

+ 5 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsStoreProductAttrValueMapper.java

@@ -1,6 +1,9 @@
 package com.fs.store.mapper;
 
 import java.util.List;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.store.domain.FsStoreProductAttrValue;
 import com.fs.store.param.FsProductAttrValueParam;
 import com.fs.store.param.FsStoreProductAttrValueQueryParam;
@@ -27,6 +30,7 @@ public interface FsStoreProductAttrValueMapper
      * @param id 商品属性值ID
      * @return 商品属性值
      */
+    @DataSource(DataSourceType.SLAVE)
         public FsStoreProductAttrValue selectFsStoreProductAttrValueById(Long id);
 
     /**
@@ -71,6 +75,7 @@ public interface FsStoreProductAttrValueMapper
     @Delete("delete from fs_store_product_attr_value where product_id=#{productId}")
     int deleteFsStoreProductAttrValueByProductId(Long productId);
     @Select("select * from fs_store_product_attr_value where  product_id=#{productId}")
+    @DataSource(DataSourceType.SLAVE)
     List<FsStoreProductAttrValue> selectFsStoreProductAttrValueByProductId(Long productId);
     @Select("select ifnull(stock,0) from fs_store_product_attr_value where  id=#{productAttrValueId}")
     int selectFsStoreProductStockById(Long productAttrValueId);

+ 3 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsStoreProductMapper.java

@@ -3,6 +3,8 @@ package com.fs.store.mapper;
 import java.util.List;
 import java.util.Map;
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.live.domain.LiveGoods;
 import com.fs.store.domain.FsStoreProduct;
 import com.fs.store.param.FsStoreProductQueryParam;
@@ -23,6 +25,7 @@ public interface FsStoreProductMapper
      * @param productId 商品ID
      * @return 商品
      */
+    @DataSource(DataSourceType.SLAVE)
     public FsStoreProduct selectFsStoreProductById(Long productId);
 
     /**

+ 4 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsUserAddressMapper.java

@@ -1,6 +1,9 @@
 package com.fs.store.mapper;
 
 import java.util.List;
+
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.store.domain.FsUserAddress;
 import org.apache.ibatis.annotations.Select;
 import org.apache.ibatis.annotations.Update;
@@ -61,6 +64,7 @@ public interface FsUserAddressMapper
      */
     public int deleteFsUserAddressByIds(Long[] ids);
     @Select("select * from fs_user_address where user_id=#{uid} and is_default=1 and is_del=0 limit 1")
+    @DataSource(DataSourceType.SLAVE)
     FsUserAddress selectFsUserAddressByDefaultAddress(long uid);
     @Update("update fs_user_address set is_default=0 where user_id=#{userId}")
     int clearIsDefalut(long userId);

+ 4 - 0
fs-service-system/src/main/java/com/fs/store/mapper/FsUserMapper.java

@@ -2,6 +2,8 @@ package com.fs.store.mapper;
 
 import java.util.List;
 
+import com.fs.common.annotation.DataSource;
+import com.fs.common.enums.DataSourceType;
 import com.fs.store.domain.FsUser;
 import com.fs.store.vo.FsUserListVO;
 import com.fs.store.vo.FsUserVO;
@@ -27,6 +29,7 @@ public interface FsUserMapper
      * @param userId 用户ID
      * @return 用户
      */
+    @DataSource(DataSourceType.SLAVE)
     public FsUser selectFsUserById(Long userId);
 
     /**
@@ -123,6 +126,7 @@ public interface FsUserMapper
             ") b ON u.user_id=b.user_id " +
             " LEFT JOIN fs_store_payment p ON u.user_id=p.user_id AND b.last_buy_time=p.pay_time AND b.payment_id=p.payment_id"+
             " WHERE u.user_id=#{userId}")
+    @DataSource(DataSourceType.SLAVE)
     FsUserVO selectFsUserByUserId(Long userId);
 
 

+ 1 - 0
fs-service-system/src/main/java/com/fs/store/param/LiveOrderComputedParam.java

@@ -17,6 +17,7 @@ public class LiveOrderComputedParam implements Serializable
     private Long productId;
     private String totalNum;
     private Long cityId;
+    private Long attrValueId;
 
 
     @ApiModelProperty(value = "使用积分 1使用")

+ 11 - 0
fs-service-system/src/main/resources/application-config.yml

@@ -1,4 +1,15 @@
 #配置
+server:
+  servlet:
+    # 应用的访问路径
+    context-path: /
+  tomcat:
+    # tomcat的URI编码
+    uri-encoding: UTF-8
+    # tomcat最大线程数,默认为200
+    max-threads: 800
+    # Tomcat启动初始化的线程数,默认值25
+    min-spare-threads: 30
 fsConfig:
   omsCode: "CQRXSF.0235487868_241231"
   #快递鸟

+ 24 - 0
fs-service-system/src/main/resources/mapper/live/LiveGoodsMapper.xml

@@ -300,4 +300,28 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </foreach>
     </delete>
 
+    <update id="updateBatchById" parameterType="java.util.List">
+        <foreach collection="list" item="item" open="" close="" separator=";">
+            update live_goods
+            <trim prefix="SET" suffixOverrides=",">
+                <if test="item.liveId != null">live_id = #{item.liveId},</if>
+                <if test="item.companyId != null">company_id = #{item.companyId},</if>
+                <if test="item.companyUserId != null">company_user_id = #{item.companyUserId},</if>
+                <if test="item.storeId != null">store_id = #{item.storeId},</if>
+                <if test="item.productId != null">product_id = #{item.productId},</if>
+                <if test="item.createTime != null">create_time = #{item.createTime},</if>
+                <if test="item.createBy != null">create_by = #{item.createBy},</if>
+                <if test="item.updateBy != null">update_by = #{item.updateBy},</if>
+                <if test="item.updateTime != null">update_time = #{item.updateTime},</if>
+                <if test="item.remark != null">remark = #{item.remark},</if>
+                <if test="item.status != null">status = #{item.status},</if>
+                <if test="item.stock != null">stock = #{item.stock},</if>
+                <if test="item.sort != null">sort = #{item.sort},</if>
+                <if test="item.isShow != null">is_show = #{item.isShow},</if>
+                <if test="item.sales != null">sales = #{item.sales},</if>
+            </trim>
+            where goods_id = #{item.goodsId}
+        </foreach>
+    </update>
+
 </mapper>

+ 3 - 0
fs-user-app/src/main/java/com/fs/app/controller/CommonController.java

@@ -556,6 +556,9 @@ public class CommonController extends AppBaseController {
 		FsProjectAddressConfig config = redisCache.getCacheObject(redisKey);
 		if (Objects.isNull(config)) {
 			config = projectAddressConfigService.selectDomainByCode(projectCode);
+			if(config == null || config.getAddressUrl() == null){
+				return R.ok();
+			}
 			redisCache.setCacheObject(redisKey, config, 5, TimeUnit.MINUTES);
 		}
 		String addressUrl = "";

+ 10 - 0
fs-user-app/src/main/java/com/fs/app/controller/LiveDataController.java

@@ -17,6 +17,7 @@ import springfox.documentation.annotations.ApiIgnore;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
 
 @RestController
 @RequestMapping("/app/live/liveData")
@@ -110,7 +111,16 @@ public class LiveDataController extends AppBaseController{
     public R getRecentLiveViewers(@PathVariable  Long liveId) {
         Map<String, Object> params = new HashMap<>();
         params.put("liveId", liveId);
+        String key = "live:" + liveId + ":getRecentLiveViewers";
+        try {
+            List<LiveWatchUserVO> object = redisCache.getCacheObject(key);
+            if(object != null){
+                return R.ok().put("recentLiveViewers", object);
+            }
+        }catch (Exception ignored){
+        }
         List<LiveWatchUserVO> recentLiveViewers = liveWatchUserService.selectWatchUserList(params);
+        redisCache.setCacheObject(key, recentLiveViewers);
         return R.ok().put("recentLiveViewers", recentLiveViewers);
     }
 

+ 0 - 1
fs-user-app/src/main/java/com/fs/app/controller/LiveOrderController.java

@@ -352,7 +352,6 @@ public class LiveOrderController extends AppBaseController
     @GetMapping(value = "/liveOrderUser/{liveId}")
     public R liveOrderUser(@PathVariable  String liveId)
     {
-        log.info("正在购买的用户 参数: {}",liveId);
         return liveOrderService.liveOrderUser(liveId);
     }
 

+ 13 - 0
fs-user-app/src/main/java/com/fs/app/controller/WxUserController.java

@@ -4,6 +4,7 @@ import cn.hutool.core.date.DateTime;
 import com.fs.common.core.domain.R;
 import com.fs.common.exception.CustomException;
 import com.fs.common.utils.OrderUtils;
+import com.fs.live.service.ILiveOrderService;
 import com.fs.store.param.LoginMpWxParam;
 import com.fs.common.utils.IpUtil;
 import com.fs.wx.miniapp.config.WxMaConfiguration;
@@ -48,6 +49,18 @@ public class WxUserController extends AppBaseController{
     @Autowired
     private IFsUserService userService;
 
+
+    @Autowired
+    private ILiveOrderService liveOrderServiceImpl;
+
+
+    @ApiOperation("初始化库存")
+    @GetMapping("/initStock")
+    public R initStock(Long id) {
+        liveOrderServiceImpl.initStock();
+        return R.ok();
+    }
+
     /**
      * 登陆接口
      */

+ 1 - 1
fs-user-app/src/main/java/com/fs/core/config/properties/DruidProperties.java

@@ -6,7 +6,7 @@ import org.springframework.context.annotation.Configuration;
 
 /**
  * druid 配置属性
- * 
+ *
 
  */
 @Configuration

+ 3 - 3
fs-user-app/src/main/resources/application-dev.yml

@@ -39,11 +39,11 @@ spring:
                 username: root
                 password: Ylrz_1q2w3e4r5t6y
             # 初始连接数
-            initialSize: 5
+            initialSize: 50
             # 最小连接池数量
-            minIdle: 10
+            minIdle: 50
             # 最大连接池数量
-            maxActive: 20
+            maxActive: 500
             # 配置获取连接等待超时的时间
             maxWait: 60000
             # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒

+ 1 - 1
fs-user-app/src/main/resources/application.yml

@@ -58,7 +58,7 @@ server:
 # 日志配置
 logging:
   level:
-    com.fs: debug
+#    com.fs: debug
     org.springframework: warn
     org.springframework.web: info
     cn.binarywang.wx.miniapp: debug