浏览代码

feat: 在应用层编排抽奖过程

seamew 2 年之前
父节点
当前提交
a646ca30a3
共有 30 个文件被更改,包括 758 次插入57 次删除
  1. 2 0
      .idea/encodings.xml
  2. 23 0
      lottery-application/pom.xml
  3. 22 0
      lottery-application/src/main/java/com/seamew/lottery/application/process/IActivityProcess.java
  4. 83 0
      lottery-application/src/main/java/com/seamew/lottery/application/process/impl/ActivityProcessImpl.java
  5. 26 0
      lottery-application/src/main/java/com/seamew/lottery/application/process/req/DrawProcessReq.java
  6. 33 0
      lottery-application/src/main/java/com/seamew/lottery/application/process/res/DrawProcessResult.java
  7. 67 4
      lottery-common/src/main/java/com/seamew/lottery/common/Constants.java
  8. 13 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/activity/model/res/PartakeResult.java
  9. 73 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/activity/model/vo/DrawOrderVO.java
  10. 38 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/activity/model/vo/UserTakeActivityVO.java
  11. 31 1
      lottery-domain/src/main/java/com/seamew/lottery/domain/activity/repository/IUserTakeActivityRepository.java
  12. 45 8
      lottery-domain/src/main/java/com/seamew/lottery/domain/activity/service/partake/BaseActivityPartake.java
  13. 9 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/activity/service/partake/IActivityPartake.java
  14. 40 5
      lottery-domain/src/main/java/com/seamew/lottery/domain/activity/service/partake/impl/ActivityPartakeImpl.java
  15. 13 4
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/aggregates/StrategyRich.java
  16. 12 3
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/req/DrawReq.java
  17. 1 1
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/res/DrawResult.java
  18. 19 1
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/vo/DrawAwardInfo.java
  19. 16 6
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/algorithm/BaseAlgorithm.java
  20. 6 6
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/algorithm/IDrawAlgorithm.java
  21. 10 12
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/draw/AbstractDrawBase.java
  22. 19 0
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/dao/IUserTakeActivityDao.java
  23. 12 0
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/po/UserTakeActivity.java
  24. 62 1
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/repository/UserTakeActivityRepository.java
  25. 6 0
      lottery-interfaces/pom.xml
  26. 21 2
      lottery-interfaces/src/main/resources/mybatis/mapper/UserTakeActivity_Mapper.xml
  27. 2 2
      lottery-interfaces/src/test/java/com/seamew/lottery/test/DrawAlgorithmTest.java
  28. 52 0
      lottery-interfaces/src/test/java/com/seamew/lottery/test/application/ActivityProcessTest.java
  29. 1 1
      lottery-interfaces/src/test/java/com/seamew/lottery/test/dao/ActivityDaoTest.java
  30. 1 0
      pom.xml

+ 2 - 0
.idea/encodings.xml

@@ -1,6 +1,8 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
+    <file url="file://$PROJECT_DIR$/lottery-application/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/lottery-application/src/main/resources" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/lottery-common/src/main/java" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/lottery-common/src/main/resources" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/lottery-domain/src/main/java" charset="UTF-8" />

+ 23 - 0
lottery-application/pom.xml

@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.seamew</groupId>
+        <artifactId>Lottery</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>lottery-application</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.seamew</groupId>
+            <artifactId>lottery-domain</artifactId>
+            <version>1.0-SNAPSHOT</version>
+            <scope>compile</scope>
+        </dependency>
+    </dependencies>
+
+</project>

+ 22 - 0
lottery-application/src/main/java/com/seamew/lottery/application/process/IActivityProcess.java

@@ -0,0 +1,22 @@
+package com.seamew.lottery.application.process;
+
+import com.seamew.lottery.application.process.req.DrawProcessReq;
+import com.seamew.lottery.application.process.res.DrawProcessResult;
+
+/**
+ * @Author: seamew
+ * @Title: IActivityProcess
+ * @CreateTime: 2023年02月24日 16:17:00
+ * @Description: 活动抽奖流程编排接口
+ * @Version: 1.0
+ */
+public interface IActivityProcess {
+
+    /**
+     * 执行抽奖流程
+     * @param req 抽奖请求
+     * @return    抽奖结果
+     */
+    DrawProcessResult doDrawProcess(DrawProcessReq req);
+
+}

+ 83 - 0
lottery-application/src/main/java/com/seamew/lottery/application/process/impl/ActivityProcessImpl.java

@@ -0,0 +1,83 @@
+package com.seamew.lottery.application.process.impl;
+
+import com.seamew.lottery.application.process.IActivityProcess;
+import com.seamew.lottery.application.process.req.DrawProcessReq;
+import com.seamew.lottery.application.process.res.DrawProcessResult;
+import com.seamew.lottery.common.Constants;
+import com.seamew.lottery.domain.activity.model.req.PartakeReq;
+import com.seamew.lottery.domain.activity.model.res.PartakeResult;
+import com.seamew.lottery.domain.activity.model.vo.DrawOrderVO;
+import com.seamew.lottery.domain.activity.service.partake.IActivityPartake;
+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.DrawAwardInfo;
+import com.seamew.lottery.domain.strategy.service.draw.IDrawExec;
+import com.seamew.lottery.domain.support.ids.IIdGenerator;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.Map;
+
+/**
+ * @Author: seamew
+ * @Title: ActivityProcessImpl
+ * @CreateTime: 2023年02月24日 16:20:00
+ * @Description: 活动抽奖流程编排
+ * @Version: 1.0
+ */
+@Service
+public class ActivityProcessImpl implements IActivityProcess {
+    @Resource
+    private IActivityPartake activityPartake;
+
+    @Resource
+    private IDrawExec drawExec;
+
+    @Resource
+    private Map<Constants.Ids, IIdGenerator> idGeneratorMap;
+
+    @Override
+    public DrawProcessResult doDrawProcess(DrawProcessReq req) {
+        // 1. 领取活动
+        PartakeResult partakeResult = activityPartake.doPartake(new PartakeReq(req.getUId(), req.getActivityId()));
+        if (!Constants.ResponseCode.SUCCESS.getCode().equals(partakeResult.getCode())) {
+            return new DrawProcessResult(partakeResult.getCode(), partakeResult.getInfo());
+        }
+        Long strategyId = partakeResult.getStrategyId();
+        Long takeId = partakeResult.getTakeId();
+
+        // 2. 执行抽奖
+        DrawResult drawResult = drawExec.doDrawExec(new DrawReq(req.getUId(), strategyId, String.valueOf(takeId)));
+        if (Constants.DrawState.FAIL.getCode().equals(drawResult.getDrawState())) {
+            return new DrawProcessResult(Constants.ResponseCode.LOSING_DRAW.getCode(), Constants.ResponseCode.LOSING_DRAW.getInfo());
+        }
+        DrawAwardInfo drawAwardInfo = drawResult.getDrawAwardInfo();
+
+        // 3. 结果落库
+        activityPartake.recordDrawOrder(buildDrawOrderVO(req, strategyId, takeId, drawAwardInfo));
+
+        // 4. 发送MQ,触发发奖流程
+
+        // 5. 返回结果
+        return new DrawProcessResult(Constants.ResponseCode.SUCCESS.getCode(), Constants.ResponseCode.SUCCESS.getInfo(), drawAwardInfo);
+    }
+
+    private DrawOrderVO buildDrawOrderVO(DrawProcessReq req, Long strategyId, Long takeId, DrawAwardInfo drawAwardInfo) {
+        long orderId = idGeneratorMap.get(Constants.Ids.SnowFlake).nextId();
+        DrawOrderVO drawOrderVO = new DrawOrderVO();
+        drawOrderVO.setUId(req.getUId());
+        drawOrderVO.setTakeId(takeId);
+        drawOrderVO.setActivityId(req.getActivityId());
+        drawOrderVO.setOrderId(orderId);
+        drawOrderVO.setStrategyId(strategyId);
+        drawOrderVO.setStrategyMode(drawAwardInfo.getStrategyMode());
+        drawOrderVO.setGrantType(drawAwardInfo.getGrantType());
+        drawOrderVO.setGrantDate(drawAwardInfo.getGrantDate());
+        drawOrderVO.setGrantState(Constants.GrantState.INIT.getCode());
+        drawOrderVO.setAwardId(drawAwardInfo.getAwardId());
+        drawOrderVO.setAwardType(drawAwardInfo.getAwardType());
+        drawOrderVO.setAwardName(drawAwardInfo.getAwardName());
+        drawOrderVO.setAwardContent(drawAwardInfo.getAwardContent());
+        return drawOrderVO;
+    }
+}

+ 26 - 0
lottery-application/src/main/java/com/seamew/lottery/application/process/req/DrawProcessReq.java

@@ -0,0 +1,26 @@
+package com.seamew.lottery.application.process.req;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Author: seamew
+ * @Title: DrawProcessReq
+ * @CreateTime: 2023年02月24日 16:19:00
+ * @Description: 抽奖请求
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class DrawProcessReq {
+    /**
+     * 用户ID
+     */
+    private String uId;
+    /**
+     * 活动ID
+     */
+    private Long activityId;
+}

+ 33 - 0
lottery-application/src/main/java/com/seamew/lottery/application/process/res/DrawProcessResult.java

@@ -0,0 +1,33 @@
+package com.seamew.lottery.application.process.res;
+
+import com.seamew.lottery.common.Result;
+import com.seamew.lottery.domain.strategy.model.vo.DrawAwardInfo;
+
+/**
+ * @Author: seamew
+ * @Title: DrawProcessResult
+ * @CreateTime: 2023年02月24日 16:18:00
+ * @Description: 活动抽奖结果
+ * @Version: 1.0
+ */
+public class DrawProcessResult extends Result {
+
+    private DrawAwardInfo drawAwardInfo;
+
+    public DrawProcessResult(String code, String info) {
+        super(code, info);
+    }
+
+    public DrawProcessResult(String code, String info, DrawAwardInfo drawAwardInfo) {
+        super(code, info);
+        this.drawAwardInfo = drawAwardInfo;
+    }
+
+    public DrawAwardInfo getDrawAwardInfo() {
+        return drawAwardInfo;
+    }
+
+    public void setDrawAwardInfo(DrawAwardInfo drawAwardInfo) {
+        this.drawAwardInfo = drawAwardInfo;
+    }
+}

+ 67 - 4
lottery-common/src/main/java/com/seamew/lottery/common/Constants.java

@@ -13,7 +13,8 @@ public class Constants {
         UN_ERROR("0001", "未知失败"),
         ILLEGAL_PARAMETER("0002", "非法参数"),
         INDEX_DUP("0003", "主键冲突"),
-        NO_UPDATE("0004","SQL操作无更新");
+        NO_UPDATE("0004", "SQL操作无更新"),
+        LOSING_DRAW("D001", "未中奖");
 
         private final String code;
         private final String info;
@@ -32,6 +33,7 @@ public class Constants {
         }
 
     }
+
     /**
      * 活动状态:1编辑、2提审、3撤审、4通过、5运行(审核通过后worker扫描状态)、6拒绝、7关闭、8开启
      */
@@ -239,11 +241,72 @@ public class Constants {
      * Ids 生成策略枚举
      */
     public enum Ids {
-        /** 雪花算法 */
+        /**
+         * 雪花算法
+         */
         SnowFlake,
-        /** 日期算法 */
+        /**
+         * 日期算法
+         */
         ShortCode,
-        /** 随机算法 */
+        /**
+         * 随机算法
+         */
         RandomNumeric;
     }
+
+    /**
+     * 活动单使用状态 0未使用、1已使用
+     */
+    public enum TaskState {
+
+        NO_USED(0, "未使用"),
+        USED(1, "已使用");
+
+        private final Integer code;
+        private final String info;
+
+        TaskState(Integer code, String info) {
+            this.code = code;
+            this.info = info;
+        }
+
+        public Integer getCode() {
+            return code;
+        }
+
+
+        public String getInfo() {
+            return info;
+        }
+
+    }
+
+    /**
+     * 发奖状态 0初始、1完成、2失败
+     */
+    public enum GrantState {
+
+        INIT(0, "初始"),
+        COMPLETE(1, "完成"),
+        FAIL(2, "失败");
+
+        private final Integer code;
+        private final String info;
+
+        GrantState(Integer code, String info) {
+            this.code = code;
+            this.info = info;
+        }
+
+        public Integer getCode() {
+            return code;
+        }
+
+
+        public String getInfo() {
+            return info;
+        }
+
+    }
 }

+ 13 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/activity/model/res/PartakeResult.java

@@ -15,6 +15,11 @@ public class PartakeResult extends Result {
      */
     private Long strategyId;
 
+    /**
+     * 活动领取ID
+     */
+    private Long takeId;
+
     public PartakeResult(String code, String info) {
         super(code, info);
     }
@@ -26,4 +31,12 @@ public class PartakeResult extends Result {
     public void setStrategyId(Long strategyId) {
         this.strategyId = strategyId;
     }
+
+    public Long getTakeId() {
+        return takeId;
+    }
+
+    public void setTakeId(Long takeId) {
+        this.takeId = takeId;
+    }
 }

+ 73 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/activity/model/vo/DrawOrderVO.java

@@ -0,0 +1,73 @@
+package com.seamew.lottery.domain.activity.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+/**
+ * @Author: seamew
+ * @Title: DrawOrderVO
+ * @CreateTime: 2023年02月24日 15:54:00
+ * @Description: 奖品单
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class DrawOrderVO {
+    /**
+     * 用户ID
+     */
+    private String uId;
+
+    /**
+     * 活动领取ID
+     */
+    private Long takeId;
+    /**
+     * 活动ID
+     */
+    private Long activityId;
+    /**
+     * 订单ID
+     */
+    private Long orderId;
+    /**
+     * 策略ID
+     */
+    private Long strategyId;
+    /**
+     * 策略方式(1:单项概率、2:总体概率)
+     */
+    private Integer strategyMode;
+    /**
+     * 发放奖品方式(1:即时、2:定时[含活动结束]、3:人工)
+     */
+    private Integer grantType;
+    /**
+     * 发奖时间
+     */
+    private Date grantDate;
+    /**
+     * 发奖状态
+     */
+    private Integer grantState;
+    /**
+     * 发奖ID
+     */
+    private String awardId;
+    /**
+     * 奖品类型(1:文字描述、2:兑换码、3:优惠券、4:实物奖品)
+     */
+    private Integer awardType;
+    /**
+     * 奖品名称
+     */
+    private String awardName;
+    /**
+     * 奖品内容「文字描述、Key、码」
+     */
+    private String awardContent;
+}

+ 38 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/activity/model/vo/UserTakeActivityVO.java

@@ -0,0 +1,38 @@
+package com.seamew.lottery.domain.activity.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Author: seamew
+ * @Title: UserTakeActivityVO
+ * @CreateTime: 2023年02月24日 15:07:00
+ * @Description: 用户领取活动记录
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserTakeActivityVO {
+    /**
+     * 活动ID
+     */
+    private Long activityId;
+
+    /**
+     * 活动领取ID
+     */
+    private Long takeId;
+
+    /**
+     * 策略ID
+     */
+    private Long strategyId;
+
+    /**
+     * 活动单使用状态 0未使用、1已使用
+     * Constants.TaskState
+     */
+    private Integer state;
+}

+ 31 - 1
lottery-domain/src/main/java/com/seamew/lottery/domain/activity/repository/IUserTakeActivityRepository.java

@@ -1,5 +1,8 @@
 package com.seamew.lottery.domain.activity.repository;
 
+import com.seamew.lottery.domain.activity.model.vo.DrawOrderVO;
+import com.seamew.lottery.domain.activity.model.vo.UserTakeActivityVO;
+
 import java.util.Date;
 
 /**
@@ -24,17 +27,44 @@ public interface IUserTakeActivityRepository {
      */
     int subtractionLeftCount(Long activityId, String activityName, Integer takeCount, Integer userTakeLeftCount, String uId, Date partakeDate);
 
+
     /**
      * 领取活动
      *
      * @param activityId        活动ID
      * @param activityName      活动名称
+     * @param strategyId        抽奖策略ID
      * @param takeCount         活动个人可领取次数
      * @param userTakeLeftCount 活动个人剩余领取次数
      * @param uId               用户ID
      * @param takeDate          领取时间
      * @param takeId            领取ID
      */
-    void takeActivity(Long activityId, String activityName, Integer takeCount, Integer userTakeLeftCount, String uId, Date takeDate, Long takeId);
+    void takeActivity(Long activityId, String activityName, Long strategyId, Integer takeCount, Integer userTakeLeftCount, String uId, Date takeDate, Long takeId);
+
+    /**
+     * 锁定活动领取记录
+     *
+     * @param uId        用户ID
+     * @param activityId 活动ID
+     * @param takeId     领取ID
+     * @return 更新结果
+     */
+    int lockTackActivity(String uId, Long activityId, Long takeId);
 
+    /**
+     * 保存抽奖信息
+     *
+     * @param drawOrder 中奖单
+     */
+    void saveUserStrategyExport(DrawOrderVO drawOrder);
+
+    /**
+     * 查询是否存在未执行抽奖领取活动单【user_take_activity 存在 state = 0,领取了但抽奖过程失败的,可以直接返回领取结果继续抽奖】
+     *
+     * @param activityId 活动ID
+     * @param uId        用户ID
+     * @return 领取单
+     */
+    UserTakeActivityVO queryNoConsumedTakeActivityOrder(Long activityId, String uId);
 }

+ 45 - 8
lottery-domain/src/main/java/com/seamew/lottery/domain/activity/service/partake/BaseActivityPartake.java

@@ -5,6 +5,11 @@ import com.seamew.lottery.common.Result;
 import com.seamew.lottery.domain.activity.model.req.PartakeReq;
 import com.seamew.lottery.domain.activity.model.res.PartakeResult;
 import com.seamew.lottery.domain.activity.model.vo.ActivityBillVO;
+import com.seamew.lottery.domain.activity.model.vo.UserTakeActivityVO;
+import com.seamew.lottery.domain.support.ids.IIdGenerator;
+
+import javax.annotation.Resource;
+import java.util.Map;
 
 /**
  * @Author: seamew
@@ -15,35 +20,66 @@ import com.seamew.lottery.domain.activity.model.vo.ActivityBillVO;
  */
 public abstract class BaseActivityPartake extends ActivityPartakeSupport implements IActivityPartake {
 
+    @Resource
+    private Map<Constants.Ids, IIdGenerator> idGeneratorMap;
+
     @Override
     public PartakeResult doPartake(PartakeReq req) {
-        // 查询活动账单
+
+        // 1. 查询是否存在未执行抽奖领取活动单【user_take_activity 存在 state = 0,领取了但抽奖过程失败的,可以直接返回领取结果继续抽奖】
+        UserTakeActivityVO userTakeActivityVO = queryNoConsumedTakeActivityOrder(req.getActivityId(), req.getUId());
+        if (null != userTakeActivityVO) {
+            return buildPartakeResult(userTakeActivityVO.getStrategyId(), userTakeActivityVO.getTakeId());
+        }
+
+        // 2. 查询活动账单
         ActivityBillVO activityBillVO = queryActivityBill(req);
 
-        // 活动信息校验处理【活动库存、状态、日期、个人参与次数】
+        // 3. 活动信息校验处理【活动库存、状态、日期、个人参与次数】
         Result checkResult = checkActivityBill(req, activityBillVO);
         if (!Constants.ResponseCode.SUCCESS.getCode().equals(checkResult.getCode())) {
             return new PartakeResult(checkResult.getCode(), checkResult.getInfo());
         }
 
-        // 扣减活动库存【目前为直接对配置库中的 lottery.activity 直接操作表扣减库存,后续优化为Redis扣减】
+        // 4. 扣减活动库存【目前为直接对配置库中的 lottery.activity 直接操作表扣减库存,后续优化为Redis扣减】
         Result subtractionActivityResult = subtractionActivityStock(req);
         if (!Constants.ResponseCode.SUCCESS.getCode().equals(subtractionActivityResult.getCode())) {
             return new PartakeResult(subtractionActivityResult.getCode(), subtractionActivityResult.getInfo());
         }
 
-        // 领取活动信息【个人用户把活动信息写入到用户表】
-        Result grabResult = grabActivity(req, activityBillVO);
+        // 5. 插入领取活动信息【个人用户把活动信息写入到用户表】
+        Long takeId = idGeneratorMap.get(Constants.Ids.SnowFlake).nextId();
+        Result grabResult = grabActivity(req, activityBillVO, takeId);
         if (!Constants.ResponseCode.SUCCESS.getCode().equals(grabResult.getCode())) {
             return new PartakeResult(grabResult.getCode(), grabResult.getInfo());
         }
 
-        // 封装结果【返回的策略ID,用于继续完成抽奖步骤】
+        return buildPartakeResult(activityBillVO.getStrategyId(), takeId);
+    }
+
+    /**
+     * 封装结果【返回的策略ID,用于继续完成抽奖步骤】
+     *
+     * @param strategyId 策略ID
+     * @param takeId     领取ID
+     * @return 封装结果
+     */
+    private PartakeResult buildPartakeResult(Long strategyId, Long takeId) {
         PartakeResult partakeResult = new PartakeResult(Constants.ResponseCode.SUCCESS.getCode(), Constants.ResponseCode.SUCCESS.getInfo());
-        partakeResult.setStrategyId(activityBillVO.getStrategyId());
+        partakeResult.setStrategyId(strategyId);
+        partakeResult.setTakeId(takeId);
         return partakeResult;
     }
 
+    /**
+     * 查询是否存在未执行抽奖领取活动单【user_take_activity 存在 state = 0,领取了但抽奖过程失败的,可以直接返回领取结果继续抽奖】
+     *
+     * @param activityId 活动ID
+     * @param uId        用户ID
+     * @return 领取单
+     */
+    protected abstract UserTakeActivityVO queryNoConsumedTakeActivityOrder(Long activityId, String uId);
+
     /**
      * 活动信息校验处理,把活动库存、状态、日期、个人参与次数
      *
@@ -66,8 +102,9 @@ public abstract class BaseActivityPartake extends ActivityPartakeSupport impleme
      *
      * @param partake 参与活动请求
      * @param bill    活动账单
+     * @param takeId  领取活动ID
      * @return 领取结果
      */
-    protected abstract Result grabActivity(PartakeReq partake, ActivityBillVO bill);
+    protected abstract Result grabActivity(PartakeReq partake, ActivityBillVO bill, Long takeId);
 
 }

+ 9 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/activity/service/partake/IActivityPartake.java

@@ -1,7 +1,9 @@
 package com.seamew.lottery.domain.activity.service.partake;
 
+import com.seamew.lottery.common.Result;
 import com.seamew.lottery.domain.activity.model.req.PartakeReq;
 import com.seamew.lottery.domain.activity.model.res.PartakeResult;
+import com.seamew.lottery.domain.activity.model.vo.DrawOrderVO;
 
 /**
  * @Author: seamew
@@ -17,4 +19,11 @@ public interface IActivityPartake {
      * @return    领取结果
      */
     PartakeResult doPartake(PartakeReq req);
+
+    /**
+     * 保存奖品单
+     * @param drawOrder 奖品单
+     * @return          保存结果
+     */
+    Result recordDrawOrder(DrawOrderVO drawOrder);
 }

+ 40 - 5
lottery-domain/src/main/java/com/seamew/lottery/domain/activity/service/partake/impl/ActivityPartakeImpl.java

@@ -4,6 +4,8 @@ import com.seamew.lottery.common.Constants;
 import com.seamew.lottery.common.Result;
 import com.seamew.lottery.domain.activity.model.req.PartakeReq;
 import com.seamew.lottery.domain.activity.model.vo.ActivityBillVO;
+import com.seamew.lottery.domain.activity.model.vo.DrawOrderVO;
+import com.seamew.lottery.domain.activity.model.vo.UserTakeActivityVO;
 import com.seamew.lottery.domain.activity.repository.IUserTakeActivityRepository;
 import com.seamew.lottery.domain.activity.service.partake.BaseActivityPartake;
 import com.seamew.lottery.domain.support.ids.IIdGenerator;
@@ -39,6 +41,11 @@ public class ActivityPartakeImpl extends BaseActivityPartake {
     @Resource
     private IDBRouterStrategy dbRouter;
 
+    @Override
+    protected UserTakeActivityVO queryNoConsumedTakeActivityOrder(Long activityId, String uId) {
+        return userTakeActivityRepository.queryNoConsumedTakeActivityOrder(activityId, uId);
+    }
+
     @Override
     protected Result checkActivityBill(PartakeReq partake, ActivityBillVO bill) {
         // 校验:活动状态
@@ -60,7 +67,7 @@ public class ActivityPartakeImpl extends BaseActivityPartake {
         }
 
         // 校验:个人库存 - 个人活动剩余可领取次数
-        if (ObjectUtils.isNotEmpty(bill.getUserTakeLeftCount()) && bill.getUserTakeLeftCount() <= 0) {
+        if (null != bill.getUserTakeLeftCount() && bill.getUserTakeLeftCount() <= 0) {
             log.warn("个人领取次数非可用 userTakeLeftCount:{}", bill.getUserTakeLeftCount());
             return Result.buildResult(Constants.ResponseCode.UN_ERROR, "个人领取次数非可用");
         }
@@ -79,7 +86,7 @@ public class ActivityPartakeImpl extends BaseActivityPartake {
     }
 
     @Override
-    protected Result grabActivity(PartakeReq partake, ActivityBillVO bill) {
+    protected Result grabActivity(PartakeReq partake, ActivityBillVO bill, Long takeId) {
         try {
             dbRouter.doRouter(partake.getUId());
             return transactionTemplate.execute(status -> {
@@ -92,9 +99,8 @@ public class ActivityPartakeImpl extends BaseActivityPartake {
                         return Result.buildResult(Constants.ResponseCode.NO_UPDATE);
                     }
 
-                    // 插入领取活动信息
-                    Long takeId = idGeneratorMap.get(Constants.Ids.SnowFlake).nextId();
-                    userTakeActivityRepository.takeActivity(bill.getActivityId(), bill.getActivityName(), bill.getTakeCount(), bill.getUserTakeLeftCount(), partake.getUId(), partake.getPartakeDate(), takeId);
+                    // 写入领取活动记录
+                    userTakeActivityRepository.takeActivity(bill.getActivityId(), bill.getActivityName(), bill.getStrategyId(), bill.getTakeCount(), bill.getUserTakeLeftCount(), partake.getUId(), partake.getPartakeDate(), takeId);
                 } catch (DuplicateKeyException e) {
                     status.setRollbackOnly();
                     log.error("领取活动,唯一索引冲突 activityId:{} uId:{}", partake.getActivityId(), partake.getUId(), e);
@@ -106,4 +112,33 @@ public class ActivityPartakeImpl extends BaseActivityPartake {
             dbRouter.clear();
         }
     }
+
+    @Override
+    public Result recordDrawOrder(DrawOrderVO drawOrder) {
+        try {
+            dbRouter.doRouter(drawOrder.getUId());
+            return transactionTemplate.execute(status -> {
+                try {
+                    // 锁定活动领取记录
+                    int lockCount = userTakeActivityRepository.lockTackActivity(drawOrder.getUId(), drawOrder.getActivityId(), drawOrder.getTakeId());
+                    if (0 == lockCount) {
+                        status.setRollbackOnly();
+                        log.error("记录中奖单,个人参与活动抽奖已消耗完 activityId:{} uId:{}", drawOrder.getActivityId(), drawOrder.getUId());
+                        return Result.buildResult(Constants.ResponseCode.NO_UPDATE);
+                    }
+
+                    // 保存抽奖信息
+                    userTakeActivityRepository.saveUserStrategyExport(drawOrder);
+                } catch (DuplicateKeyException e) {
+                    status.setRollbackOnly();
+                    log.error("记录中奖单,唯一索引冲突 activityId:{} uId:{}", drawOrder.getActivityId(), drawOrder.getUId(), e);
+                    return Result.buildResult(Constants.ResponseCode.INDEX_DUP);
+                }
+                return Result.buildSuccessResult();
+            });
+        } finally {
+            dbRouter.clear();
+        }
+
+    }
 }

+ 13 - 4
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/aggregates/StrategyRich.java

@@ -12,17 +12,26 @@ import java.util.List;
  * @Author: seamew
  * @Title: StrategyRich
  * @CreateTime: 2023年02月13日 20:56:00
- * @Description: 策略详情
+ * @Description: 抽奖策略聚合对象
  * @Version: 1.0
  */
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 public class StrategyRich {
-    // 策略ID
+
+    /**
+     * 策略ID
+     */
     private Long strategyId;
-    // 策略配置
+
+    /**
+     * 策略配置
+     */
     private StrategyBriefVO strategy;
-    // 策略明细
+
+    /**
+     * 策略明细
+     */
     private List<StrategyDetailBriefVO> strategyDetailList;
 }

+ 12 - 3
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/req/DrawReq.java

@@ -8,16 +8,25 @@ import lombok.NoArgsConstructor;
  * @Author: seamew
  * @Title: DrawReq
  * @CreateTime: 2023年02月13日 20:56:00
- * @Description:
+ * @Description: 抽奖请求对象
  * @Version: 1.0
  */
 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 public class DrawReq {
-    // 用户ID
+    /**
+     * 用户ID
+     */
     private String uId;
 
-    // 策略ID
+    /**
+     * 策略ID
+     */
     private Long strategyId;
+
+    /**
+     * 防重ID
+     */
+    private String uuid;
 }

+ 1 - 1
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/res/DrawResult.java

@@ -10,7 +10,7 @@ import lombok.NoArgsConstructor;
  * @Author: seamew
  * @Title: DrawResult
  * @CreateTime: 2023年02月13日 20:41:00
- * @Description:
+ * @Description: 抽奖结果
  * @Version: 1.0
  */
 @Data

+ 19 - 1
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/vo/DrawAwardInfo.java

@@ -4,6 +4,8 @@ import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+import java.util.Date;
+
 /**
  * @Author: seamew
  * @Title: DrawAwardInfo
@@ -35,8 +37,24 @@ public class DrawAwardInfo {
      */
     private String awardContent;
 
-    public DrawAwardInfo(String awardId, String awardName) {
+    /**
+     * 策略方式(1:单项概率、2:总体概率)
+     */
+    private Integer strategyMode;
+
+    /**
+     * 发放奖品方式(1:即时、2:定时[含活动结束]、3:人工)
+     */
+    private Integer grantType;
+    /**
+     * 发奖时间
+     */
+    private Date grantDate;
+
+    public DrawAwardInfo(String awardId, Integer awardType, String awardName, String awardContent) {
         this.awardId = awardId;
+        this.awardType = awardType;
         this.awardName = awardName;
+        this.awardContent = awardContent;
     }
 }

+ 16 - 6
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/algorithm/BaseAlgorithm.java

@@ -1,5 +1,6 @@
 package com.seamew.lottery.domain.strategy.service.algorithm;
 
+import com.seamew.lottery.common.Constants;
 import com.seamew.lottery.domain.strategy.model.vo.AwardRateInfo;
 
 import java.math.BigDecimal;
@@ -32,30 +33,39 @@ public abstract class BaseAlgorithm implements IDrawAlgorithm {
     protected Map<Long, List<AwardRateInfo>> awardRateInfoMap = new ConcurrentHashMap<>();
 
     @Override
-    public void initRateTuple(Long strategyId, List<AwardRateInfo> awardRateInfoList) {
+    public synchronized void initRateTuple(Long strategyId, Integer strategyMode, List<AwardRateInfo> awardRateInfoList) {
+        // 前置判断
+        if (isExist(strategyId)) {
+            return;
+        }
+
         // 保存奖品概率信息
         awardRateInfoMap.put(strategyId, awardRateInfoList);
 
+        // 非单项概率,不必存入缓存,因为这部分抽奖算法需要实时处理中奖概率。
+        if (!Constants.StrategyMode.SINGLE.getCode().equals(strategyMode)) {
+            return;
+        }
+
         String[] rateTuple = rateTupleMap.computeIfAbsent(strategyId, k -> new String[RATE_TUPLE_LENGTH]);
 
         int cursorVal = 0;
         for (AwardRateInfo awardRateInfo : awardRateInfoList) {
-            int rateVal = awardRateInfo.getAwardRate().multiply(new BigDecimal(DEFAULT_RATE)).intValue();
+            int rateVal = awardRateInfo.getAwardRate().multiply(new BigDecimal(100)).intValue();
 
             // 循环填充概率范围值
             for (int i = cursorVal + 1; i <= (rateVal + cursorVal); i++) {
                 rateTuple[hashIdx(i)] = awardRateInfo.getAwardId();
             }
 
-            // 循环增加区间
             cursorVal += rateVal;
 
         }
     }
 
     @Override
-    public boolean isExistRateTuple(Long strategyId) {
-        return rateTupleMap.containsKey(strategyId);
+    public boolean isExist(Long strategyId) {
+        return awardRateInfoMap.containsKey(strategyId);
     }
 
     /**
@@ -74,7 +84,7 @@ public abstract class BaseAlgorithm implements IDrawAlgorithm {
      *
      * @return 随机值
      */
-    protected int generateSecureRandomIntCode(int bound){
+    protected int generateSecureRandomIntCode(int bound) {
         return new SecureRandom().nextInt(bound) + 1;
     }
 

+ 6 - 6
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/algorithm/IDrawAlgorithm.java

@@ -15,7 +15,7 @@ public interface IDrawAlgorithm {
     /**
      * 程序启动时初始化概率元祖,在初始化完成后使用过程中不允许修改元祖数据
      * <p>
-     * 元祖数据作用在于百分比内(0.2、0.3、0.5)的数据,转换为一整条数组上分区数据,如下;
+     * 元祖数据作用在于百分比内(0.2、0.3、0.5)的数据,转换为一整条数组上分区数据,如下;
      * 0.2 = 0 ~ 0.2
      * 0.3 = 0 + 0.2 ~ 0.2 + 0.3 = 0.2 ~ 0.5
      * 0.5 = 0.5 ~ 1 (计算方式同上)
@@ -29,17 +29,17 @@ public interface IDrawAlgorithm {
      * 5. 当后续通过随机数获取到1-100的值后,可以直接定位到对应的奖品信息,通过这样的方式把轮训算奖的时间复杂度从O(n) 降低到 0(1)
      *
      * @param strategyId        策略ID
+     * @param strategyMode      抽奖策略模式
      * @param awardRateInfoList 奖品概率配置集合 「值示例:AwardRateInfo.awardRate = 0.04」
      */
-    void initRateTuple(Long strategyId, List<AwardRateInfo> awardRateInfoList);
+    void initRateTuple(Long strategyId,Integer strategyMode, List<AwardRateInfo> awardRateInfoList);
 
     /**
      * 判断是否已经,做了数据初始化
-     *
-     * @param strategyId 策略ID
-     * @return 判断结果
+     * @param strategyId    策略ID
+     * @return              判断结果
      */
-    boolean isExistRateTuple(Long strategyId);
+    boolean isExist(Long strategyId);
 
     /**
      * SecureRandom 生成随机数,索引到对应的奖品信息返回结果

+ 10 - 12
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/draw/AbstractDrawBase.java

@@ -36,7 +36,7 @@ public abstract class AbstractDrawBase extends DrawStrategySupport implements ID
         String awardId = drawAlgorithm(req.getStrategyId(), drawAlgorithmGroup.get(strategy.getStrategyMode()), excludeAwardIds);
 
         // 5. 包装中奖结果
-        return buildDrawResult(req.getUId(), req.getStrategyId(), awardId);
+        return buildDrawResult(req.getUId(), req.getStrategyId(), awardId, strategy);
     }
 
     /**
@@ -66,16 +66,11 @@ public abstract class AbstractDrawBase extends DrawStrategySupport implements ID
      */
     private void checkAndInitRateData(Long strategyId, Integer strategyMode, List<StrategyDetailBriefVO> strategyDetailList) {
 
-        // 非单项概率,不必存入缓存
-        // 暂时注释有BUG
-        // if (!Constants.StrategyMode.SINGLE.getCode().equals(strategyMode)) {
-        //     return;
-        // }
-
+        // 根据抽奖策略模式,获取对应的抽奖服务
         IDrawAlgorithm drawAlgorithm = drawAlgorithmGroup.get(strategyMode);
 
-        // 已初始化过的数据,不必重复初始化
-        if (drawAlgorithm.isExistRateTuple(strategyId)) {
+        // 判断已处理过的的数据
+        if (drawAlgorithm.isExist(strategyId)) {
             return;
         }
 
@@ -85,7 +80,7 @@ public abstract class AbstractDrawBase extends DrawStrategySupport implements ID
             awardRateInfoList.add(new AwardRateInfo(strategyDetail.getAwardId(), strategyDetail.getAwardRate()));
         }
 
-        drawAlgorithm.initRateTuple(strategyId, awardRateInfoList);
+        drawAlgorithm.initRateTuple(strategyId, strategyMode, awardRateInfoList);
 
     }
 
@@ -97,14 +92,17 @@ public abstract class AbstractDrawBase extends DrawStrategySupport implements ID
      * @param awardId    奖品ID,null 情况:并发抽奖情况下,库存临界值1 -> 0,会有用户中奖结果为 null
      * @return 中奖结果
      */
-    private DrawResult buildDrawResult(String uId, Long strategyId, String awardId) {
+    private DrawResult buildDrawResult(String uId, Long strategyId, String awardId, StrategyBriefVO strategy) {
         if (null == awardId) {
             log.info("执行策略抽奖完成【未中奖】,用户:{} 策略ID:{}", uId, strategyId);
             return new DrawResult(uId, strategyId, Constants.DrawState.FAIL.getCode());
         }
 
-        AwardBriefVO award = super.queryAwardInfoByAwardId(awardId);
+        AwardBriefVO award = queryAwardInfoByAwardId(awardId);
         DrawAwardInfo drawAwardInfo = new DrawAwardInfo(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());
 
         return new DrawResult(uId, strategyId, Constants.DrawState.SUCCESS.getCode(), drawAwardInfo);

+ 19 - 0
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/dao/IUserTakeActivityDao.java

@@ -20,4 +20,23 @@ public interface IUserTakeActivityDao {
      */
     @DBRouter
     void insert(UserTakeActivity userTakeActivity);
+
+    /**
+     * 锁定活动领取记录
+     *
+     * @param userTakeActivity  入参
+     * @return                  更新结果
+     */
+    @DBRouter
+    int lockTackActivity(UserTakeActivity userTakeActivity);
+
+    /**
+     * 查询是否存在未执行抽奖领取活动单【user_take_activity 存在 state = 0,领取了但抽奖过程失败的,可以直接返回领取结果继续抽奖】
+     * 查询此活动ID,用户最早领取但未消费的一条记录【这部分一般会有业务流程限制,比如是否处理最先还是最新领取单,要根据自己的业务实际场景进行处理】
+     *
+     * @param userTakeActivity 请求入参
+     * @return                 领取结果
+     */
+    @DBRouter
+    UserTakeActivity queryNoConsumedTakeActivityOrder(UserTakeActivity userTakeActivity);
 }

+ 12 - 0
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/po/UserTakeActivity.java

@@ -45,6 +45,18 @@ public class UserTakeActivity {
      * 领取次数
      */
     private Integer takeCount;
+
+    /**
+     * 策略ID
+     */
+    private Long strategyId;
+
+    /**
+     * 活动单使用状态 0未使用、1已使用
+     * Constants.TaskState
+     */
+    private Integer state;
+
     /**
      * 防重ID
      */

+ 62 - 1
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/repository/UserTakeActivityRepository.java

@@ -1,8 +1,13 @@
 package com.seamew.lottery.infrastructure.repository;
 
+import com.seamew.lottery.common.Constants;
+import com.seamew.lottery.domain.activity.model.vo.DrawOrderVO;
+import com.seamew.lottery.domain.activity.model.vo.UserTakeActivityVO;
 import com.seamew.lottery.domain.activity.repository.IUserTakeActivityRepository;
+import com.seamew.lottery.infrastructure.dao.IUserStrategyExportDao;
 import com.seamew.lottery.infrastructure.dao.IUserTakeActivityCountDao;
 import com.seamew.lottery.infrastructure.dao.IUserTakeActivityDao;
+import com.seamew.lottery.infrastructure.po.UserStrategyExport;
 import com.seamew.lottery.infrastructure.po.UserTakeActivity;
 import com.seamew.lottery.infrastructure.po.UserTakeActivityCount;
 import org.springframework.stereotype.Component;
@@ -26,6 +31,9 @@ public class UserTakeActivityRepository implements IUserTakeActivityRepository {
     @Resource
     private IUserTakeActivityDao userTakeActivityDao;
 
+    @Resource
+    private IUserStrategyExportDao userStrategyExportDao;
+
 
     @Override
     public int subtractionLeftCount(Long activityId, String activityName, Integer takeCount, Integer userTakeLeftCount, String uId, Date partakeDate) {
@@ -46,7 +54,7 @@ public class UserTakeActivityRepository implements IUserTakeActivityRepository {
     }
 
     @Override
-    public void takeActivity(Long activityId, String activityName, Integer takeCount, Integer userTakeLeftCount, String uId, Date takeDate, Long takeId) {
+    public void takeActivity(Long activityId, String activityName, Long strategyId, Integer takeCount, Integer userTakeLeftCount, String uId, Date takeDate, Long takeId) {
         UserTakeActivity userTakeActivity = new UserTakeActivity();
         userTakeActivity.setUId(uId);
         userTakeActivity.setTakeId(takeId);
@@ -58,10 +66,63 @@ public class UserTakeActivityRepository implements IUserTakeActivityRepository {
         } else {
             userTakeActivity.setTakeCount(takeCount - userTakeLeftCount + 1);
         }
+        userTakeActivity.setStrategyId(strategyId);
+        userTakeActivity.setState(Constants.TaskState.NO_USED.getCode());
         String uuid = uId + "_" + activityId + "_" + userTakeActivity.getTakeCount();
         userTakeActivity.setUuid(uuid);
 
         userTakeActivityDao.insert(userTakeActivity);
     }
 
+    @Override
+    public int lockTackActivity(String uId, Long activityId, Long takeId) {
+        UserTakeActivity userTakeActivity = new UserTakeActivity();
+        userTakeActivity.setUId(uId);
+        userTakeActivity.setActivityId(activityId);
+        userTakeActivity.setTakeId(takeId);
+        return userTakeActivityDao.lockTackActivity(userTakeActivity);
+    }
+
+    @Override
+    public void saveUserStrategyExport(DrawOrderVO drawOrder) {
+        UserStrategyExport userStrategyExport = new UserStrategyExport();
+        userStrategyExport.setUId(drawOrder.getUId());
+        userStrategyExport.setActivityId(drawOrder.getActivityId());
+        userStrategyExport.setOrderId(drawOrder.getOrderId());
+        userStrategyExport.setStrategyId(drawOrder.getStrategyId());
+        userStrategyExport.setStrategyMode(drawOrder.getStrategyMode());
+        userStrategyExport.setGrantType(drawOrder.getGrantType());
+        userStrategyExport.setGrantDate(drawOrder.getGrantDate());
+        userStrategyExport.setGrantState(drawOrder.getGrantState());
+        userStrategyExport.setAwardId(drawOrder.getAwardId());
+        userStrategyExport.setAwardType(drawOrder.getAwardType());
+        userStrategyExport.setAwardName(drawOrder.getAwardName());
+        userStrategyExport.setAwardContent(drawOrder.getAwardContent());
+        userStrategyExport.setUuid(String.valueOf(drawOrder.getOrderId()));
+
+        userStrategyExportDao.insert(userStrategyExport);
+    }
+
+    @Override
+    public UserTakeActivityVO queryNoConsumedTakeActivityOrder(Long activityId, String uId) {
+
+        UserTakeActivity userTakeActivity = new UserTakeActivity();
+        userTakeActivity.setUId(uId);
+        userTakeActivity.setActivityId(activityId);
+        UserTakeActivity noConsumedTakeActivityOrder = userTakeActivityDao.queryNoConsumedTakeActivityOrder(userTakeActivity);
+
+        // 未查询到符合的领取单,直接返回 NULL
+        if (null == noConsumedTakeActivityOrder) {
+            return null;
+        }
+
+        UserTakeActivityVO userTakeActivityVO = new UserTakeActivityVO();
+        userTakeActivityVO.setActivityId(noConsumedTakeActivityOrder.getActivityId());
+        userTakeActivityVO.setTakeId(noConsumedTakeActivityOrder.getTakeId());
+        userTakeActivityVO.setStrategyId(noConsumedTakeActivityOrder.getStrategyId());
+        userTakeActivityVO.setState(noConsumedTakeActivityOrder.getState());
+
+        return userTakeActivityVO;
+    }
+
 }

+ 6 - 0
lottery-interfaces/pom.xml

@@ -106,6 +106,12 @@
             <artifactId>lottery-domain</artifactId>
             <version>1.0-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>com.seamew</groupId>
+            <artifactId>lottery-application</artifactId>
+            <version>1.0-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
 

+ 21 - 2
lottery-interfaces/src/main/resources/mybatis/mapper/UserTakeActivity_Mapper.xml

@@ -6,8 +6,27 @@
     <insert id="insert" parameterType="com.seamew.lottery.infrastructure.po.UserTakeActivity">
         INSERT INTO user_take_activity
         (u_id, take_id, activity_id, activity_name, take_date,
-         take_count, uuid, create_time, update_time)
+         take_count, strategy_id, state, uuid, create_time, update_time)
         VALUES (#{uId}, #{takeId}, #{activityId}, #{activityName}, #{takeDate},
-                #{takeCount}, #{uuid}, now(), now())
+                #{takeCount}, #{strategyId}, #{state}, #{uuid}, now(), now())
     </insert>
+
+    <update id="lockTackActivity" parameterType="com.seamew.lottery.infrastructure.po.UserTakeActivity">
+        UPDATE user_take_activity
+        SET state = 1
+        WHERE u_id = #{uId}
+          AND activity_id = #{activityId}
+          AND state = 0
+    </update>
+
+    <select id="queryNoConsumedTakeActivityOrder" parameterType="com.seamew.lottery.infrastructure.po.UserTakeActivity"
+            resultType="com.seamew.lottery.infrastructure.po.UserTakeActivity">
+        SELECT activity_id, take_id, strategy_id, state
+        FROM user_take_activity
+        WHERE u_id = #{uId}
+          AND activity_id = #{activityId}
+          AND state = 0
+        ORDER BY id DESC LIMIT 1
+    </select>
+
 </mapper>

+ 2 - 2
lottery-interfaces/src/test/java/com/seamew/lottery/test/DrawAlgorithmTest.java

@@ -37,13 +37,13 @@ public class DrawAlgorithmTest {
 
     @Test
     public void test_drawExec() {
-        drawExec.doDrawExec(new DrawReq("小佳佳", 10001L));
+        drawExec.doDrawExec(new DrawReq("小佳佳", 10001L, "123"));
     }
 
     @Test
     public void test_award() {
         // 执行抽奖
-        DrawResult drawResult = drawExec.doDrawExec(new DrawReq("admin", 10001L));
+        DrawResult drawResult = drawExec.doDrawExec(new DrawReq("admin", 10001L, "123"));
 
         // 判断抽奖结果
         Integer drawState = drawResult.getDrawState();

+ 52 - 0
lottery-interfaces/src/test/java/com/seamew/lottery/test/application/ActivityProcessTest.java

@@ -0,0 +1,52 @@
+package com.seamew.lottery.test.application;
+
+import com.alibaba.fastjson2.JSON;
+import com.seamew.lottery.application.process.IActivityProcess;
+import com.seamew.lottery.application.process.req.DrawProcessReq;
+import com.seamew.lottery.application.process.res.DrawProcessResult;
+import com.seamew.middleware.db.router.strategy.IDBRouterStrategy;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import javax.annotation.Resource;
+
+/**
+ * @Author: seamew
+ * @Title: ActivityProcessTest
+ * @CreateTime: 2023年02月24日 16:33:00
+ * @Description:
+ * @Version: 1.0
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@Slf4j
+public class ActivityProcessTest {
+    @Resource
+    private IActivityProcess activityProcess;
+
+    @Resource
+    IDBRouterStrategy dbRouter;
+
+    @Test
+    public void test_doDrawProcess() {
+        DrawProcessReq req = new DrawProcessReq();
+        req.setUId("fustack");
+        req.setActivityId(100001L);
+
+        DrawProcessResult drawProcessResult = activityProcess.doDrawProcess(req);
+        log.info("请求入参:{}", JSON.toJSONString(req));
+        log.info("测试结果:{}", JSON.toJSONString(drawProcessResult));
+    }
+
+    @Test
+    public void test_router() {
+        DrawProcessReq req = new DrawProcessReq();
+        req.setUId("fustack");
+        req.setActivityId(100001L);
+        dbRouter.doRouter(req.getUId());
+    }
+
+}

+ 1 - 1
lottery-interfaces/src/test/java/com/seamew/lottery/test/dao/ActivityDaoTest.java

@@ -29,7 +29,7 @@ public class ActivityDaoTest {
 
     @Test
     public void test_activityPartake() {
-        PartakeReq req = new PartakeReq("Uhdgkw766120d", 100001L);
+        PartakeReq req = new PartakeReq("fustack", 100001L);
         PartakeResult res = activityPartake.doPartake(req);
         log.info("请求参数:{}", JSON.toJSONString(req));
         log.info("测试结果:{}", JSON.toJSONString(res));

+ 1 - 0
pom.xml

@@ -14,6 +14,7 @@
         <module>lottery-common</module>
         <module>lottery-infrastructure</module>
         <module>lottery-domain</module>
+        <module>lottery-application</module>
     </modules>
 
     <properties>