瀏覽代碼

feat: 设计滑动库存分布式锁处理奖品库存扣减

seamew 2 年之前
父節點
當前提交
a58d151df3
共有 16 個文件被更改,包括 269 次插入24 次删除
  1. 52 0
      lottery-application/src/main/java/com/seamew/lottery/application/mq/consumer/LotteryAwardDeductStockListener.java
  2. 17 0
      lottery-application/src/main/java/com/seamew/lottery/application/mq/producer/KafkaProducer.java
  3. 10 3
      lottery-application/src/main/java/com/seamew/lottery/application/process/impl/ActivityProcessImpl.java
  4. 7 0
      lottery-common/src/main/java/com/seamew/lottery/common/Constants.java
  5. 0 1
      lottery-domain/src/main/java/com/seamew/lottery/domain/activity/service/partake/BaseActivityPartake.java
  6. 27 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/res/DrawAlgorithmResult.java
  7. 30 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/vo/AwardDeductStockVO.java
  8. 4 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/vo/DrawAwardVO.java
  9. 33 3
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/repository/IStrategyRepository.java
  10. 13 11
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/draw/AbstractDrawBase.java
  11. 14 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/draw/IDrawExec.java
  12. 11 3
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/draw/impl/DrawExecImpl.java
  13. 12 3
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/dao/IStrategyDetailDao.java
  14. 29 0
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/repository/StrategyRepository.java
  15. 2 0
      lottery-interfaces/src/main/java/com/seamew/lottery/interfaces/facade/LotteryActivityBooth.java
  16. 8 0
      lottery-interfaces/src/main/resources/mybatis/mapper/StrategyDetail_Mapper.xml

+ 52 - 0
lottery-application/src/main/java/com/seamew/lottery/application/mq/consumer/LotteryAwardDeductStockListener.java

@@ -0,0 +1,52 @@
+package com.seamew.lottery.application.mq.consumer;
+
+import com.alibaba.fastjson2.JSON;
+import com.seamew.lottery.application.mq.producer.KafkaProducer;
+import com.seamew.lottery.domain.activity.model.vo.ActivityPartakeRecordVO;
+import com.seamew.lottery.domain.strategy.model.vo.AwardDeductStockVO;
+import com.seamew.lottery.domain.strategy.service.draw.IDrawExec;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.springframework.kafka.annotation.KafkaListener;
+import org.springframework.kafka.support.Acknowledgment;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * @Author: seamew
+ * @Title: LotteryAwardDeductStockListener
+ * @CreateTime: 2023年03月03日 22:48:00
+ * @Description: 削减奖品库存监听消息
+ * @Version: 1.0
+ */
+@Component
+@Slf4j
+public class LotteryAwardDeductStockListener {
+    @Resource
+    IDrawExec drawExec;
+
+    @KafkaListener(topics = KafkaProducer.TOPIC_AWARD_SURPLUS_COUNT, groupId = KafkaProducer.GROUP_NAME)
+    public void onMessage(ConsumerRecord<?, ?> record, Acknowledgment ack) {
+        // 1. 判断消息是否存在
+        if (ObjectUtils.isEmpty(record.value())) {
+            return;
+        }
+
+        try {
+            // 2. 转化对象
+            AwardDeductStockVO awardDeductStockVO = JSON.parseObject((String) record.value(), AwardDeductStockVO.class);
+            log.info("消费MQ消息,异步扣减奖品库存 message:{}", awardDeductStockVO);
+
+            // 3. 更新数据库库存【实际场景业务体量较大,可能也会由于MQ消费引起并发,对数据库产生压力,所以如果并发量较大,可以把库存记录缓存中,并使用定时任务进行处理缓存和数据库库存同步,减少对数据库的操作次数】
+            drawExec.updateAwardStock(awardDeductStockVO);
+            ack.acknowledge();
+        } catch (Exception e) {
+            // 削减库存失败
+            log.error("削减库存失败--消费MQ消息,失败 topic:{} message:{}", record.topic(), record.value());
+            throw e;
+        }
+
+    }
+}

+ 17 - 0
lottery-application/src/main/java/com/seamew/lottery/application/mq/producer/KafkaProducer.java

@@ -3,6 +3,7 @@ package com.seamew.lottery.application.mq.producer;
 import com.alibaba.fastjson2.JSON;
 import com.seamew.lottery.domain.activity.model.vo.ActivityPartakeRecordVO;
 import com.seamew.lottery.domain.activity.model.vo.InvoiceVO;
+import com.seamew.lottery.domain.strategy.model.vo.AwardDeductStockVO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.kafka.core.KafkaTemplate;
 import org.springframework.kafka.support.SendResult;
@@ -34,6 +35,11 @@ public class KafkaProducer {
      */
     public static final String TOPIC_ACTIVITY_PARTAKE = "lottery_activity_partake";
 
+    /**
+     * MQ主题:奖品剩余库存记录
+     */
+    public static final String TOPIC_AWARD_SURPLUS_COUNT = "lottery_award_surplus_count";
+
     public static final String GROUP_NAME = "lottery";
 
     /**
@@ -57,4 +63,15 @@ public class KafkaProducer {
         log.info("发送MQ消息(领取活动记录) topic:{} bizId:{} message:{}", TOPIC_ACTIVITY_PARTAKE, activityPartakeRecord.getUId(), objJson);
         kafkaTemplate.send(TOPIC_ACTIVITY_PARTAKE, objJson);
     }
+
+    /**
+     * 发送削减库存记录MQ
+     *
+     * @param awardDeductStock 削减库存记录
+     */
+    public void sendLotteryAwardDeductStock(AwardDeductStockVO awardDeductStock) {
+        String objJson = JSON.toJSONString(awardDeductStock);
+        log.info("发送MQ消息(领取活动记录) topic:{} message:{}", TOPIC_AWARD_SURPLUS_COUNT, objJson);
+        kafkaTemplate.send(TOPIC_AWARD_SURPLUS_COUNT, objJson);
+    }
 }

+ 10 - 3
lottery-application/src/main/java/com/seamew/lottery/application/process/impl/ActivityProcessImpl.java

@@ -18,6 +18,7 @@ import com.seamew.lottery.domain.rule.model.res.EngineResult;
 import com.seamew.lottery.domain.rule.service.engine.EngineFilter;
 import com.seamew.lottery.domain.strategy.model.req.DrawReq;
 import com.seamew.lottery.domain.strategy.model.res.DrawResult;
+import com.seamew.lottery.domain.strategy.model.vo.AwardDeductStockVO;
 import com.seamew.lottery.domain.strategy.model.vo.DrawAwardVO;
 import com.seamew.lottery.domain.strategy.service.draw.IDrawExec;
 import com.seamew.lottery.domain.support.ids.IIdGenerator;
@@ -81,16 +82,22 @@ public class ActivityProcessImpl implements IActivityProcess {
             activityPartake.lockTackActivity(req.getUId(), req.getActivityId(), takeId);
             return new DrawProcessResult(Constants.ResponseCode.LOSING_DRAW.getCode(), Constants.ResponseCode.LOSING_DRAW.getInfo());
         }
+        // 4. 削减奖品库存,发送MQ
         DrawAwardVO drawAwardVO = drawResult.getDrawAwardInfo();
+        AwardDeductStockVO awardDeductStockVO = new AwardDeductStockVO();
+        awardDeductStockVO.setStrategyId(drawResult.getStrategyId());
+        awardDeductStockVO.setAwardSurplusCount(drawAwardVO.getAwardSurplusCount());
+        awardDeductStockVO.setAwardId(drawAwardVO.getAwardId());
+        kafkaProducer.sendLotteryAwardDeductStock(awardDeductStockVO);
 
-        // 4. 结果落库
+        // 5. 结果落库
         DrawOrderVO drawOrderVO = buildDrawOrderVO(req, strategyId, takeId, drawAwardVO);
         Result recordResult = activityPartake.recordDrawOrder(drawOrderVO);
         if (!Constants.ResponseCode.SUCCESS.getCode().equals(recordResult.getCode())) {
             return new DrawProcessResult(recordResult.getCode(), recordResult.getInfo());
         }
 
-        // 5. 发送MQ,触发发奖流程
+        // 6. 发送MQ,触发发奖流程
         InvoiceVO invoiceVO = buildInvoiceVO(drawOrderVO);
         kafkaProducer
                 .sendLotteryInvoice(invoiceVO)
@@ -101,7 +108,7 @@ public class ActivityProcessImpl implements IActivityProcess {
                     // 4.2 MQ 消息发送失败,更新数据库表 user_strategy_export.mq_state = 2 【等待定时任务扫码补偿MQ消息】
                     activityPartake.updateInvoiceMqState(invoiceVO.getUId(), invoiceVO.getOrderId(), Constants.MQState.FAIL.getCode());
                 });
-        // 5. 返回结果
+        // 7. 返回结果
         return new DrawProcessResult(Constants.ResponseCode.SUCCESS.getCode(), Constants.ResponseCode.SUCCESS.getInfo(), drawAwardVO);
     }
 

+ 7 - 0
lottery-common/src/main/java/com/seamew/lottery/common/Constants.java

@@ -67,6 +67,13 @@ public class Constants {
             return LOTTERY_ACTIVITY_STOCK_COUNT_TOKEN + activityId + "_" + stockUsedCount;
         }
 
+        // 奖品key
+        private static final String LOTTERY_AWARD_STOCK_COUNT = "lottery_award_stock_count_";
+
+        public static String KEY_LOTTERY_AWARD_STOCK_COUNT(Long strategyId, String awardId) {
+            return LOTTERY_AWARD_STOCK_COUNT + strategyId + "_" + awardId;
+        }
+
     }
 
     /**

+ 0 - 1
lottery-domain/src/main/java/com/seamew/lottery/domain/activity/service/partake/BaseActivityPartake.java

@@ -46,7 +46,6 @@ public abstract class BaseActivityPartake extends ActivityPartakeSupport impleme
         StockResult subtractionActivityResult = subtractionActivityStockByRedis(req.getUId(), req.getActivityId(), activityBillVO.getStockCount());
 
         if (!Constants.ResponseCode.SUCCESS.getCode().equals(subtractionActivityResult.getCode())) {
-            recoverActivityCacheStockByRedis(req.getActivityId(), subtractionActivityResult.getStockKey(), subtractionActivityResult.getCode());
             return new PartakeResult(subtractionActivityResult.getCode(), subtractionActivityResult.getInfo());
         }
 

+ 27 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/res/DrawAlgorithmResult.java

@@ -0,0 +1,27 @@
+package com.seamew.lottery.domain.strategy.model.res;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Author: seamew
+ * @Title: DrawAlgorithmResult
+ * @CreateTime: 2023年03月03日 22:25:00
+ * @Description: 抽奖算法结果
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class DrawAlgorithmResult {
+    /**
+     * 奖品ID
+     */
+    private String awardId;
+
+    /**
+     * 奖品剩余库存
+     */
+    private Integer awardSurplusCount;
+}

+ 30 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/vo/AwardDeductStockVO.java

@@ -0,0 +1,30 @@
+package com.seamew.lottery.domain.strategy.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Author: seamew
+ * @Title: AwardDeductStockVO
+ * @CreateTime: 2023年03月03日 22:43:00
+ * @Description: 奖品削减库存记录
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class AwardDeductStockVO {
+    /**
+     * 策略ID
+     */
+    private Long strategyId;
+    /**
+     * 奖品ID
+     */
+    private String awardId;
+    /**
+     * 奖品剩余库存
+     */
+    private Integer awardSurplusCount;
+}

+ 4 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/vo/DrawAwardVO.java

@@ -55,6 +55,10 @@ public class DrawAwardVO {
      * 发奖时间
      */
     private Date grantDate;
+    /**
+     * 奖品剩余库存
+     */
+    private Integer awardSurplusCount;
 
     public DrawAwardVO(String uId, String awardId, Integer awardType, String awardName, String awardContent) {
         this.uId = uId;

+ 33 - 3
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/repository/IStrategyRepository.java

@@ -2,6 +2,7 @@ package com.seamew.lottery.domain.strategy.repository;
 
 import com.seamew.lottery.domain.strategy.model.aggregates.StrategyRich;
 import com.seamew.lottery.domain.strategy.model.vo.AwardBriefVO;
+import com.seamew.lottery.domain.strategy.model.vo.AwardDeductStockVO;
 
 import java.util.List;
 
@@ -14,22 +15,51 @@ import java.util.List;
  */
 public interface IStrategyRepository {
     /**
-     * 查询策略详情
+     * 查询策略信息
      *
      * @param strategyId 策略ID
-     * @return 策略详情
+     * @return 策略信息
      */
     StrategyRich queryStrategyRich(Long strategyId);
 
+    /**
+     * 查询奖励配置
+     *
+     * @param awardId 奖励ID
+     * @return 奖励信息
+     */
     AwardBriefVO queryAwardInfo(String awardId);
 
+    /**
+     * 查询无库存奖品
+     *
+     * @param strategyId 策略ID
+     * @return 无库存奖品
+     */
     List<String> queryNoStockStrategyAwardList(Long strategyId);
 
     /**
      * 扣减库存
+     *
      * @param strategyId 策略ID
      * @param awardId    奖品ID
-     * @return           扣减结果
+     * @return 扣减结果
      */
     boolean deductStock(Long strategyId, String awardId);
+
+    /**
+     * 通过redis扣减库存
+     *
+     * @param strategyId 策略ID
+     * @param awardId    奖品ID
+     * @return 剩余库存
+     */
+    int deductStockByRedis(Long strategyId, String awardId);
+
+    /**
+     * 更新奖品库存
+     *
+     * @param awardDeductStockVO 奖品削减库存记录
+     */
+    void updateAwardStock(AwardDeductStockVO awardDeductStockVO);
 }

+ 13 - 11
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/draw/AbstractDrawBase.java

@@ -3,6 +3,7 @@ package com.seamew.lottery.domain.strategy.service.draw;
 import com.seamew.lottery.common.Constants;
 import com.seamew.lottery.domain.strategy.model.aggregates.StrategyRich;
 import com.seamew.lottery.domain.strategy.model.req.DrawReq;
+import com.seamew.lottery.domain.strategy.model.res.DrawAlgorithmResult;
 import com.seamew.lottery.domain.strategy.model.res.DrawResult;
 import com.seamew.lottery.domain.strategy.model.vo.*;
 import com.seamew.lottery.domain.strategy.service.algorithm.IDrawAlgorithm;
@@ -33,10 +34,10 @@ public abstract class AbstractDrawBase extends DrawStrategySupport implements ID
         List<String> excludeAwardIds = queryExcludeAwardIds(req.getStrategyId());
 
         // 4. 执行抽奖算法
-        String awardId = drawAlgorithm(req.getStrategyId(), drawAlgorithmGroup.get(strategy.getStrategyMode()), excludeAwardIds);
+        DrawAlgorithmResult drawAlgorithmResult = drawAlgorithm(req.getStrategyId(), drawAlgorithmGroup.get(strategy.getStrategyMode()), excludeAwardIds);
 
         // 5. 包装中奖结果
-        return buildDrawResult(req.getUId(), req.getStrategyId(), awardId, strategy);
+        return buildDrawResult(req.getUId(), req.getStrategyId(), drawAlgorithmResult, strategy);
     }
 
     /**
@@ -53,9 +54,9 @@ public abstract class AbstractDrawBase extends DrawStrategySupport implements ID
      * @param strategyId      策略ID
      * @param drawAlgorithm   抽奖算法模型
      * @param excludeAwardIds 排除的抽奖ID集合
-     * @return 中奖奖品ID
+     * @return 抽奖算法结果
      */
-    protected abstract String drawAlgorithm(Long strategyId, IDrawAlgorithm drawAlgorithm, List<String> excludeAwardIds);
+    protected abstract DrawAlgorithmResult drawAlgorithm(Long strategyId, IDrawAlgorithm drawAlgorithm, List<String> excludeAwardIds);
 
     /**
      * 校验抽奖策略是否已经初始化到内存
@@ -87,23 +88,24 @@ public abstract class AbstractDrawBase extends DrawStrategySupport implements ID
     /**
      * 包装抽奖结果
      *
-     * @param uId        用户ID
-     * @param strategyId 策略ID
-     * @param awardId    奖品ID,null 情况:并发抽奖情况下,库存临界值1 -> 0,会有用户中奖结果为 null
+     * @param uId                 用户ID
+     * @param strategyId          策略ID
+     * @param drawAlgorithmResult 抽奖算法结果
      * @return 中奖结果
      */
-    private DrawResult buildDrawResult(String uId, Long strategyId, String awardId, StrategyBriefVO strategy) {
-        if (null == awardId) {
+    private DrawResult buildDrawResult(String uId, Long strategyId, DrawAlgorithmResult drawAlgorithmResult, StrategyBriefVO strategy) {
+        if ("-1".equals(drawAlgorithmResult.getAwardId())) {
             log.info("执行策略抽奖完成【未中奖】,用户:{} 策略ID:{}", uId, strategyId);
             return new DrawResult(uId, strategyId, Constants.DrawState.FAIL.getCode());
         }
 
-        AwardBriefVO award = queryAwardInfoByAwardId(awardId);
+        AwardBriefVO award = queryAwardInfoByAwardId(drawAlgorithmResult.getAwardId());
         DrawAwardVO drawAwardInfo = new DrawAwardVO(uId, award.getAwardId(), award.getAwardType(), award.getAwardName(), award.getAwardContent());
         drawAwardInfo.setStrategyMode(strategy.getStrategyMode());
         drawAwardInfo.setGrantType(strategy.getGrantType());
         drawAwardInfo.setGrantDate(strategy.getGrantDate());
-        log.info("执行策略抽奖完成【已中奖】,用户:{} 策略ID:{} 奖品ID:{} 奖品名称:{}", uId, strategyId, awardId, award.getAwardName());
+        drawAwardInfo.setAwardSurplusCount(drawAlgorithmResult.getAwardSurplusCount());
+        log.info("执行策略抽奖完成【已中奖】,用户:{} 策略ID:{} 奖品ID:{} 奖品名称:{}", uId, strategyId, drawAlgorithmResult.getAwardId(), award.getAwardName());
 
         return new DrawResult(uId, strategyId, Constants.DrawState.SUCCESS.getCode(), drawAwardInfo);
     }

+ 14 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/draw/IDrawExec.java

@@ -2,6 +2,7 @@ package com.seamew.lottery.domain.strategy.service.draw;
 
 import com.seamew.lottery.domain.strategy.model.req.DrawReq;
 import com.seamew.lottery.domain.strategy.model.res.DrawResult;
+import com.seamew.lottery.domain.strategy.model.vo.AwardDeductStockVO;
 
 /**
  * @Author: seamew
@@ -11,5 +12,18 @@ import com.seamew.lottery.domain.strategy.model.res.DrawResult;
  * @Version: 1.0
  */
 public interface IDrawExec {
+    /**
+     * 抽奖方法
+     *
+     * @param req 抽奖参数;用户ID、策略ID
+     * @return 中奖结果
+     */
     DrawResult doDrawExec(DrawReq req);
+
+    /**
+     * 更新奖品库存
+     *
+     * @param awardDeductStockVO 奖品削减库存记录
+     */
+    void updateAwardStock(AwardDeductStockVO awardDeductStockVO);
 }

+ 11 - 3
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/draw/impl/DrawExecImpl.java

@@ -1,12 +1,15 @@
 package com.seamew.lottery.domain.strategy.service.draw.impl;
 
 import com.alibaba.fastjson2.JSON;
+import com.seamew.lottery.domain.strategy.model.res.DrawAlgorithmResult;
+import com.seamew.lottery.domain.strategy.model.vo.AwardDeductStockVO;
 import com.seamew.lottery.domain.strategy.service.algorithm.IDrawAlgorithm;
 import com.seamew.lottery.domain.strategy.service.draw.AbstractDrawBase;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.stereotype.Service;
 
+import javax.annotation.Resource;
 import java.util.List;
 
 /**
@@ -28,7 +31,7 @@ public class DrawExecImpl extends AbstractDrawBase {
     }
 
     @Override
-    protected String drawAlgorithm(Long strategyId, IDrawAlgorithm drawAlgorithm, List<String> excludeAwardIds) {
+    protected DrawAlgorithmResult drawAlgorithm(Long strategyId, IDrawAlgorithm drawAlgorithm, List<String> excludeAwardIds) {
         // 执行抽奖
         String awardId = drawAlgorithm.randomDraw(strategyId, excludeAwardIds);
 
@@ -42,9 +45,14 @@ public class DrawExecImpl extends AbstractDrawBase {
          * 注意:通常数据库直接锁行记录的方式并不能支撑较大体量的并发,但此种方式需要了解,
          * 因为在分库分表下的正常数据流量下的个人数据记录中,是可以使用行级锁的,因为他只影响到自己的记录,不会影响到其他人
          */
-        boolean isSuccess = strategyRepository.deductStock(strategyId, awardId);
+        int awardSurplusCount = strategyRepository.deductStockByRedis(strategyId, awardId);
 
         // 返回结果,库存扣减成功返回奖品ID,否则返回NULL 「在实际的业务场景中,如果中奖奖品库存为空,则会发送兜底奖品,比如各类券」
-        return isSuccess ? awardId : null;
+        return new DrawAlgorithmResult(awardSurplusCount >= 0 ? awardId : "-1", awardSurplusCount);
+    }
+
+    @Override
+    public void updateAwardStock(AwardDeductStockVO awardDeductStockVO) {
+        strategyRepository.updateAwardStock(awardDeductStockVO);
     }
 }

+ 12 - 3
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/dao/IStrategyDetailDao.java

@@ -17,22 +17,25 @@ public interface IStrategyDetailDao {
 
     /**
      * 查询策略表详细配置
+     *
      * @param strategyId 策略ID
-     * @return           返回结果
+     * @return 返回结果
      */
     List<StrategyDetail> queryStrategyDetailList(Long strategyId);
 
     /**
      * 查询无库存策略奖品ID
+     *
      * @param strategyId 策略ID
-     * @return           返回结果
+     * @return 返回结果
      */
     List<String> queryNoStockStrategyAwardList(Long strategyId);
 
     /**
      * 扣减库存
+     *
      * @param strategyDetailReq 策略ID、奖品ID
-     * @return                  返回结果
+     * @return 返回结果
      */
     int deductStock(StrategyDetail strategyDetailReq);
 
@@ -43,4 +46,10 @@ public interface IStrategyDetailDao {
      */
     void insertList(List<StrategyDetail> list);
 
+    /**
+     * 更新用户抽到奖品时,奖品库存
+     *
+     * @param strategyDetail 策略明细
+     */
+    void updateAwardStock(StrategyDetail strategyDetail);
 }

+ 29 - 0
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/repository/StrategyRepository.java

@@ -1,7 +1,9 @@
 package com.seamew.lottery.infrastructure.repository;
 
+import com.seamew.lottery.common.Constants;
 import com.seamew.lottery.domain.strategy.model.aggregates.StrategyRich;
 import com.seamew.lottery.domain.strategy.model.vo.AwardBriefVO;
+import com.seamew.lottery.domain.strategy.model.vo.AwardDeductStockVO;
 import com.seamew.lottery.domain.strategy.model.vo.StrategyBriefVO;
 import com.seamew.lottery.domain.strategy.model.vo.StrategyDetailBriefVO;
 import com.seamew.lottery.domain.strategy.repository.IStrategyRepository;
@@ -11,6 +13,8 @@ import com.seamew.lottery.infrastructure.dao.IStrategyDetailDao;
 import com.seamew.lottery.infrastructure.po.Award;
 import com.seamew.lottery.infrastructure.po.Strategy;
 import com.seamew.lottery.infrastructure.po.StrategyDetail;
+import com.seamew.lottery.infrastructure.util.RedisUtil;
+import org.apache.commons.lang3.ObjectUtils;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Repository;
 
@@ -27,6 +31,10 @@ import java.util.List;
  */
 @Repository
 public class StrategyRepository implements IStrategyRepository {
+
+    @Resource
+    private RedisUtil redisUtil;
+
     @Resource
     private IStrategyDao strategyDao;
 
@@ -49,6 +57,10 @@ public class StrategyRepository implements IStrategyRepository {
             StrategyDetailBriefVO strategyDetailBriefVO = new StrategyDetailBriefVO();
             BeanUtils.copyProperties(strategyDetail, strategyDetailBriefVO);
             strategyDetailBriefVOList.add(strategyDetailBriefVO);
+            // 插入redis缓存
+            if (ObjectUtils.isEmpty(redisUtil.get(Constants.RedisKey.KEY_LOTTERY_AWARD_STOCK_COUNT(strategyDetail.getStrategyId(), strategyDetail.getAwardId())))) {
+                redisUtil.set(Constants.RedisKey.KEY_LOTTERY_AWARD_STOCK_COUNT(strategyDetail.getStrategyId(), strategyDetail.getAwardId()), strategyDetail.getAwardSurplusCount());
+            }
         }
 
         return new StrategyRich(strategyId, strategyBriefVO, strategyDetailBriefVOList);
@@ -75,4 +87,21 @@ public class StrategyRepository implements IStrategyRepository {
         int count = strategyDetailDao.deductStock(req);
         return count == 1;
     }
+
+    @Override
+    public int deductStockByRedis(Long strategyId, String awardId) {
+        // 1. 获取奖品的key
+        String stockKey = Constants.RedisKey.KEY_LOTTERY_AWARD_STOCK_COUNT(strategyId, awardId);
+        // 2. 扣减库存
+        return redisUtil.decr(stockKey, 1).intValue();
+    }
+
+    @Override
+    public void updateAwardStock(AwardDeductStockVO awardDeductStockVO) {
+        StrategyDetail strategyDetail = new StrategyDetail();
+        strategyDetail.setStrategyId(awardDeductStockVO.getStrategyId());
+        strategyDetail.setAwardId(awardDeductStockVO.getAwardId());
+        strategyDetail.setAwardSurplusCount(awardDeductStockVO.getAwardSurplusCount());
+        strategyDetailDao.updateAwardStock(strategyDetail);
+    }
 }

+ 2 - 0
lottery-interfaces/src/main/java/com/seamew/lottery/interfaces/facade/LotteryActivityBooth.java

@@ -16,6 +16,7 @@ import com.seamew.lottery.rpc.req.DrawReq;
 import com.seamew.lottery.rpc.req.QuantificationDrawReq;
 import com.seamew.lottery.rpc.res.DrawRes;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang.RandomStringUtils;
 import org.springframework.stereotype.Controller;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
@@ -41,6 +42,7 @@ public class LotteryActivityBooth implements ILotteryActivityBooth {
     @Override
     @RequestMapping("/doDraw")
     public DrawRes doDraw(DrawReq drawReq) {
+        drawReq.setUId(RandomStringUtils.randomAlphabetic(5));
         try {
             log.info("抽奖,开始 uId:{} activityId:{}", drawReq.getUId(), drawReq.getActivityId());
 

+ 8 - 0
lottery-interfaces/src/main/resources/mybatis/mapper/StrategyDetail_Mapper.xml

@@ -49,4 +49,12 @@
         </foreach>
     </insert>
 
+    <update id="updateAwardStock" parameterType="com.seamew.lottery.infrastructure.po.StrategyDetail">
+        UPDATE strategy_detail
+        set award_surplus_count = #{awardSurplusCount}
+        WHERE strategy_id = #{strategyId}
+          AND award_id = #{awardId}
+          AND award_surplus_count > #{awardSurplusCount}
+    </update>
+
 </mapper>