Forráskód Böngészése

feat: 抽奖策略领域模块开发

seamew 2 éve
szülő
commit
62bc2bf26d
36 módosított fájl, 948 hozzáadás és 31 törlés
  1. 1 0
      .idea/encodings.xml
  2. 15 0
      .idea/git_toolbox_prj.xml
  3. 2 2
      lottery-common/src/main/java/com/seamew/lottery/common/Constants.java
  4. 40 0
      lottery-common/src/main/java/com/seamew/lottery/common/enumeration/StrategyModeEnum.java
  5. 57 0
      lottery-domain/pom.xml
  6. 22 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/annotation/StrategyMode.java
  7. 28 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/aggregates/StrategyRich.java
  8. 23 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/req/DrawReq.java
  9. 29 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/res/DrawResult.java
  10. 25 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/vo/AwardRateInfo.java
  11. 17 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/repository/IStrategyRepository.java
  12. 45 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/repository/impl/StrategyRepository.java
  13. 71 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/algorithm/BaseAlgorithm.java
  14. 53 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/algorithm/IDrawAlgorithm.java
  15. 69 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/algorithm/impl/DefaultRateRandomDrawAlgorithm.java
  16. 42 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/algorithm/impl/SingleRateRandomDrawAlgorithm.java
  17. 35 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/draw/DrawBase.java
  18. 36 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/draw/DrawConfig.java
  19. 15 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/draw/IDrawExec.java
  20. 58 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/draw/impl/DrawExecImpl.java
  21. 18 0
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/dao/IAwardDao.java
  22. 18 0
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/dao/IStrategyDao.java
  23. 20 0
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/dao/IStrategyDetailDao.java
  24. 43 0
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/po/Award.java
  25. 47 0
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/po/Strategy.java
  26. 40 0
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/po/StrategyDetail.java
  27. 4 6
      lottery-interfaces/pom.xml
  28. 1 1
      lottery-interfaces/src/main/java/com/seamew/lottery/interfaces/ActivityBooth.java
  29. 3 0
      lottery-interfaces/src/main/resources/application.yaml
  30. 8 7
      lottery-interfaces/src/main/resources/mybatis/mapper/Activity_Mapper.xml
  31. 12 0
      lottery-interfaces/src/main/resources/mybatis/mapper/Award_Mapper.xml
  32. 11 0
      lottery-interfaces/src/main/resources/mybatis/mapper/StrategyDetail_Mapper.xml
  33. 13 0
      lottery-interfaces/src/main/resources/mybatis/mapper/Strategy_Mapper.xml
  34. 14 14
      lottery-interfaces/src/test/java/com/seamew/lottery/test/ApiTest.java
  35. 0 1
      lottery-rpc/src/main/java/com/seamew/lottery/rpc/res/ActivityRes.java
  36. 13 0
      pom.xml

+ 1 - 0
.idea/encodings.xml

@@ -2,6 +2,7 @@
 <project version="4">
   <component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
     <file url="file://$PROJECT_DIR$/lottery-common/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/lottery-domain/src/main/java" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/lottery-infrastructure/src/main/java" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/lottery-interfaces/src/main/java" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/lottery-rpc/src/main/java" charset="UTF-8" />

+ 15 - 0
.idea/git_toolbox_prj.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GitToolBoxProjectSettings">
+    <option name="commitMessageIssueKeyValidationOverride">
+      <BoolValueOverride>
+        <option name="enabled" value="true" />
+      </BoolValueOverride>
+    </option>
+    <option name="commitMessageValidationEnabledOverride">
+      <BoolValueOverride>
+        <option name="enabled" value="true" />
+      </BoolValueOverride>
+    </option>
+  </component>
+</project>

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

@@ -14,8 +14,8 @@ public class Constants {
         ILLEGAL_PARAMETER("0002","非法参数"),
         INDEX_DUP("0003","主键冲突");
 
-        private String code;
-        private String info;
+        private final String code;
+        private final String info;
 
         ResponseCode(String code, String info) {
             this.code = code;

+ 40 - 0
lottery-common/src/main/java/com/seamew/lottery/common/enumeration/StrategyModeEnum.java

@@ -0,0 +1,40 @@
+package com.seamew.lottery.common.enumeration;
+
+/**
+ * @Author: seamew
+ * @Title: StrategyModeEnum
+ * @CreateTime: 2023年02月13日 21:23:00
+ * @Description: 抽奖策略枚举
+ * @Version: 1.0
+ */
+public enum StrategyModeEnum {
+    DEFAULT_RATE_RANDOM_DRAW_ALGORITHM(1, "必中奖策略抽奖,排掉已经中奖的概率,重新计算中奖范围"),
+
+    SINGLE_RATE_RANDOM_DRAW_ALGORITHM(2, "单项随机概率抽奖,抽到一个已经排掉的奖品则未中奖"),
+    ;
+
+    StrategyModeEnum(Integer id, String description) {
+        this.id = id;
+        this.description = description;
+    }
+
+    /**
+     * 策略id
+     */
+    private final Integer id;
+
+    /**
+     * 策略描述
+     */
+    private final String description;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+
+}

+ 57 - 0
lottery-domain/pom.xml

@@ -0,0 +1,57 @@
+<?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-domain</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.seamew</groupId>
+            <artifactId>lottery-infrastructure</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>com.seamew</groupId>
+            <artifactId>lottery-common</artifactId>
+            <version>1.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>lottery-domain</finalName>
+        <plugins>
+            <!-- 编译plugin -->
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>${jdk.version}</source>
+                    <target>${jdk.version}</target>
+                    <compilerVersion>1.8</compilerVersion>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

+ 22 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/annotation/StrategyMode.java

@@ -0,0 +1,22 @@
+package com.seamew.lottery.domain.strategy.annotation;
+
+import com.seamew.lottery.common.enumeration.StrategyModeEnum;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @Author: seamew
+ * @Title: StrategyMode
+ * @CreateTime: 2023年02月13日 21:19:00
+ * @Description: 抽奖策略模型枚举
+ * @Version: 1.0
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface StrategyMode {
+
+    StrategyModeEnum strategyMode();
+}

+ 28 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/aggregates/StrategyRich.java

@@ -0,0 +1,28 @@
+package com.seamew.lottery.domain.strategy.model.aggregates;
+
+import com.seamew.lottery.infrastructure.po.Strategy;
+import com.seamew.lottery.infrastructure.po.StrategyDetail;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @Author: seamew
+ * @Title: StrategyRich
+ * @CreateTime: 2023年02月13日 20:56:00
+ * @Description: 策略详情
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class StrategyRich {
+    // 策略ID
+    private Long strategyId;
+    // 策略配置
+    private Strategy strategy;
+    // 策略明细
+    private List<StrategyDetail> strategyDetailList;
+}

+ 23 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/req/DrawReq.java

@@ -0,0 +1,23 @@
+package com.seamew.lottery.domain.strategy.model.req;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Author: seamew
+ * @Title: DrawReq
+ * @CreateTime: 2023年02月13日 20:56:00
+ * @Description:
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class DrawReq {
+    // 用户ID
+    private String uId;
+
+    // 策略ID
+    private Long strategyId;
+}

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

@@ -0,0 +1,29 @@
+package com.seamew.lottery.domain.strategy.model.res;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Author: seamew
+ * @Title: DrawResult
+ * @CreateTime: 2023年02月13日 20:41:00
+ * @Description:
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class DrawResult {
+    // 用户ID
+    private String uId;
+
+    // 策略ID
+    private Long strategyId;
+
+    // 奖品ID
+    private String rewardId;
+
+    // 奖品名称
+    private String awardName;
+}

+ 25 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/model/vo/AwardRateInfo.java

@@ -0,0 +1,25 @@
+package com.seamew.lottery.domain.strategy.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+/**
+ * @Author: seamew
+ * @Title: AwardRateInfo
+ * @CreateTime: 2023年02月13日 14:27:00
+ * @Description: 奖品概率信息,奖品编号、库存、概率
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class AwardRateInfo {
+    // 奖品ID
+    private String awardId;
+
+    // 中奖概率
+    private BigDecimal awardRate;
+}

+ 17 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/repository/IStrategyRepository.java

@@ -0,0 +1,17 @@
+package com.seamew.lottery.domain.strategy.repository;
+
+import com.seamew.lottery.domain.strategy.model.aggregates.StrategyRich;
+import com.seamew.lottery.infrastructure.po.Award;
+
+/**
+ * @Author: seamew
+ * @Title: IStrategyRepository
+ * @CreateTime: 2023年02月13日 21:02:00
+ * @Description: 策略数据存储
+ * @Version: 1.0
+ */
+public interface IStrategyRepository {
+    StrategyRich queryStrategyRich(Long strategyId);
+
+    Award queryAwardInfo(String awardId);
+}

+ 45 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/repository/impl/StrategyRepository.java

@@ -0,0 +1,45 @@
+package com.seamew.lottery.domain.strategy.repository.impl;
+
+import com.seamew.lottery.domain.strategy.model.aggregates.StrategyRich;
+import com.seamew.lottery.domain.strategy.repository.IStrategyRepository;
+import com.seamew.lottery.infrastructure.dao.IAwardDao;
+import com.seamew.lottery.infrastructure.dao.IStrategyDao;
+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 org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * @Author: seamew
+ * @Title: StrategyRepository
+ * @CreateTime: 2023年02月13日 21:02:00
+ * @Description:
+ * @Version: 1.0
+ */
+@Component
+public class StrategyRepository implements IStrategyRepository {
+    @Resource
+    private IStrategyDao strategyDao;
+
+    @Resource
+    private IStrategyDetailDao strategyDetailDao;
+
+    @Resource
+    private IAwardDao awardDao;
+
+    @Override
+    public StrategyRich queryStrategyRich(Long strategyId) {
+        Strategy strategy = strategyDao.queryStrategy(strategyId);
+        List<StrategyDetail> strategyDetailList = strategyDetailDao.queryStrategyDetailList(strategyId);
+        return new StrategyRich(strategyId, strategy, strategyDetailList);
+    }
+
+    @Override
+    public Award queryAwardInfo(String awardId) {
+        return awardDao.queryAwardInfo(awardId);
+    }
+}

+ 71 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/algorithm/BaseAlgorithm.java

@@ -0,0 +1,71 @@
+package com.seamew.lottery.domain.strategy.service.algorithm;
+
+import com.seamew.lottery.domain.strategy.model.vo.AwardRateInfo;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @Author: seamew
+ * @Title: BaseAlgorithm
+ * @CreateTime: 2023年02月13日 14:25:00
+ * @Description: 共用的算法逻辑
+ * @Version: 1.0
+ */
+public abstract class BaseAlgorithm implements IDrawAlgorithm {
+    // 斐波那契散列增量,逻辑:黄金分割点:(√5 - 1) / 2 = 0.6180339887,Math.pow(2, 32) * 0.6180339887 = 0x61c88647
+    private final int HASH_INCREMENT = 0x61c88647;
+
+    // 数组初始化长度,2的整数幂用来减少哈希冲突
+    private final int RATE_TUPLE_LENGTH = 128;
+
+    // 默认倍率
+    private final int DEFAULT_RATE = 100;
+
+    // 存放概率与奖品对应的散列结果,strategyId -> rateTuple
+    protected Map<Long, String[]> rateTupleMap = new ConcurrentHashMap<>();
+
+    // 奖品区间概率值,strategyId -> [awardId->0.3、awardId->0.7]
+    protected Map<Long, List<AwardRateInfo>> awardRateInfoMap = new ConcurrentHashMap<>();
+
+    @Override
+    public void initRateTuple(Long strategyId, List<AwardRateInfo> awardRateInfoList) {
+        // 保存奖品概率信息
+        awardRateInfoMap.put(strategyId, awardRateInfoList);
+
+        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();
+
+            // 循环填充概率范围值
+            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);
+    }
+
+    /**
+     * 斐波那契(Fibonacci)散列法,计算哈希索引下标值
+     *
+     * @param value 值
+     * @return 索引
+     */
+    protected int hashIdx(int value) {
+        int hashCode = value * HASH_INCREMENT + HASH_INCREMENT;
+        return hashCode & (RATE_TUPLE_LENGTH - 1);
+    }
+
+}

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

@@ -0,0 +1,53 @@
+package com.seamew.lottery.domain.strategy.service.algorithm;
+
+import com.seamew.lottery.domain.strategy.model.vo.AwardRateInfo;
+
+import java.util.List;
+
+/**
+ * @Author: seamew
+ * @Title: IDrawAlgorithm
+ * @CreateTime: 2023年02月13日 14:19:00
+ * @Description: 抽奖算法接口
+ * @Version: 1.0
+ */
+public interface IDrawAlgorithm {
+    /**
+     * 程序启动时初始化概率元祖,在初始化完成后使用过程中不允许修改元祖数据
+     * <p>
+     * 元祖数据作用在于将百分比内(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 (计算方式同上)
+     * <p>
+     * 通过数据拆分为整条后,再根据0-100中各个区间的奖品信息,使用斐波那契散列计算出索引位置,把奖品数据存放到元祖中。比如:
+     * <p>
+     * 1. 把 0.2 转换为 20
+     * 2. 20 对应的斐波那契值哈希值:(20 * HASH_INCREMENT + HASH_INCREMENT)= -1549107828 HASH_INCREMENT = 0x61c88647
+     * 3. 再通过哈希值计算索引位置:hashCode & (rateTuple.length - 1) = 12
+     * 4. 那么tup[14] = 0.2 中奖概率对应的奖品
+     * 5. 当后续通过随机数获取到1-100的值后,可以直接定位到对应的奖品信息,通过这样的方式把轮训算奖的时间复杂度从O(n) 降低到 0(1)
+     *
+     * @param strategyId        策略ID
+     * @param awardRateInfoList 奖品概率配置集合 「值示例:AwardRateInfo.awardRate = 0.04」
+     */
+    void initRateTuple(Long strategyId, List<AwardRateInfo> awardRateInfoList);
+
+    /**
+     * 判断是否已经,做了数据初始化
+     *
+     * @param strategyId 策略ID
+     * @return 判断结果
+     */
+    boolean isExistRateTuple(Long strategyId);
+
+    /**
+     * SecureRandom 生成随机数,索引到对应的奖品信息返回结果
+     *
+     * @param strategyId      策略ID
+     * @param excludeAwardIds 排除掉已经不能作为抽奖的奖品ID,留给风控和空库存使用
+     * @return 中奖结果
+     */
+    String randomDraw(Long strategyId, List<String> excludeAwardIds);
+
+}

+ 69 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/algorithm/impl/DefaultRateRandomDrawAlgorithm.java

@@ -0,0 +1,69 @@
+package com.seamew.lottery.domain.strategy.service.algorithm.impl;
+
+import com.seamew.lottery.common.enumeration.StrategyModeEnum;
+import com.seamew.lottery.domain.strategy.annotation.StrategyMode;
+import com.seamew.lottery.domain.strategy.model.vo.AwardRateInfo;
+import com.seamew.lottery.domain.strategy.service.algorithm.BaseAlgorithm;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Author: seamew
+ * @Title: DefaultRateRandomDrawAlgorithm
+ * @CreateTime: 2023年02月13日 16:00:00
+ * @Description: 必中奖策略抽奖,排掉已经中奖的概率,重新计算中奖范围
+ * @Version: 1.0
+ */
+@Component
+@StrategyMode(strategyMode = StrategyModeEnum.DEFAULT_RATE_RANDOM_DRAW_ALGORITHM)
+public class DefaultRateRandomDrawAlgorithm extends BaseAlgorithm {
+    @Override
+    public String randomDraw(Long strategyId, List<String> excludeAwardIds) {
+
+        BigDecimal differenceDenominator = BigDecimal.ZERO;
+
+        // 排除掉不在抽奖范围的奖品ID集合
+        List<AwardRateInfo> differenceAwardRateList = new ArrayList<>();
+        List<AwardRateInfo> awardRateIntervalValList = awardRateInfoMap.get(strategyId);
+        for (AwardRateInfo awardRateInfo : awardRateIntervalValList) {
+            String awardId = awardRateInfo.getAwardId();
+            if (excludeAwardIds.contains(awardId)) {
+                continue;
+            }
+            differenceAwardRateList.add(awardRateInfo);
+            differenceDenominator = differenceDenominator.add(awardRateInfo.getAwardRate());
+        }
+
+        // 前置判断
+        if (differenceAwardRateList.size() == 0) return "";
+        if (differenceAwardRateList.size() == 1) return differenceAwardRateList.get(0).getAwardId();
+
+        // 获取随机概率值
+        SecureRandom secureRandom = new SecureRandom();
+        int randomVal = secureRandom.nextInt(100) + 1;
+
+        // 循环获取奖品
+        String awardId = "";
+        int cursorVal = 0;
+        for (AwardRateInfo awardRateInfo : differenceAwardRateList) {
+            int rateVal = awardRateInfo
+                    .getAwardRate()
+                    .divide(differenceDenominator, 2, RoundingMode.UP)
+                    .multiply(new BigDecimal(100))
+                    .intValue();
+            if (randomVal <= (cursorVal + rateVal)) {
+                awardId = awardRateInfo.getAwardId();
+                break;
+            }
+            cursorVal += rateVal;
+        }
+
+        // 返回中奖结果
+        return awardId;
+    }
+}

+ 42 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/algorithm/impl/SingleRateRandomDrawAlgorithm.java

@@ -0,0 +1,42 @@
+package com.seamew.lottery.domain.strategy.service.algorithm.impl;
+
+import com.seamew.lottery.common.enumeration.StrategyModeEnum;
+import com.seamew.lottery.domain.strategy.annotation.StrategyMode;
+import com.seamew.lottery.domain.strategy.model.vo.AwardRateInfo;
+import com.seamew.lottery.domain.strategy.service.algorithm.BaseAlgorithm;
+import org.springframework.stereotype.Component;
+
+import java.math.BigDecimal;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Author: seamew
+ * @Title: SingleRateRandomDrawAlgorithm
+ * @CreateTime: 2023年02月13日 17:03:00
+ * @Description:
+ * @Version: 1.0
+ */
+@Component
+@StrategyMode(strategyMode = StrategyModeEnum.SINGLE_RATE_RANDOM_DRAW_ALGORITHM)
+public class SingleRateRandomDrawAlgorithm extends BaseAlgorithm {
+    @Override
+    public String randomDraw(Long strategyId, List<String> excludeAwardIds) {
+
+        // 获取策略对应的元祖
+        String[] rateTuple = rateTupleMap.get(strategyId);
+
+        // 随机索引
+        SecureRandom secureRandom = new SecureRandom();
+        int randomVal = secureRandom.nextInt(100) + 1;
+        int index = hashIdx(randomVal);
+
+        // 返回结果
+        String awardId = rateTuple[index];
+        if (excludeAwardIds.contains(awardId)) return "未中奖";
+
+        return awardId == null ? "未中奖" : awardId;
+    }
+
+}

+ 35 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/draw/DrawBase.java

@@ -0,0 +1,35 @@
+package com.seamew.lottery.domain.strategy.service.draw;
+
+import com.seamew.lottery.domain.strategy.model.vo.AwardRateInfo;
+import com.seamew.lottery.domain.strategy.service.algorithm.IDrawAlgorithm;
+import com.seamew.lottery.infrastructure.po.StrategyDetail;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Author: seamew
+ * @Title: DrawBase
+ * @CreateTime: 2023年02月13日 21:00:00
+ * @Description:
+ * @Version: 1.0
+ */
+public class DrawBase extends DrawConfig {
+    public void checkAndInitRateData(Long strategyId, Integer strategyMode, List<StrategyDetail> strategyDetailList) {
+        // 单体概率才需要初始化
+        if (1 != strategyMode) return;
+        IDrawAlgorithm drawAlgorithm = drawAlgorithmMap.get(strategyMode);
+
+        // 防止重复初始化
+        boolean existRateTuple = drawAlgorithm.isExistRateTuple(strategyId);
+        if (existRateTuple) return;
+
+        List<AwardRateInfo> awardRateInfoList = new ArrayList<>(strategyDetailList.size());
+        for (StrategyDetail strategyDetail : strategyDetailList) {
+            awardRateInfoList.add(new AwardRateInfo(strategyDetail.getAwardId(), strategyDetail.getAwardRate()));
+        }
+
+        drawAlgorithm.initRateTuple(strategyId, awardRateInfoList);
+
+    }
+}

+ 36 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/strategy/service/draw/DrawConfig.java

@@ -0,0 +1,36 @@
+package com.seamew.lottery.domain.strategy.service.draw;
+
+import com.seamew.lottery.domain.strategy.annotation.StrategyMode;
+import com.seamew.lottery.domain.strategy.service.algorithm.IDrawAlgorithm;
+import org.springframework.core.annotation.AnnotationUtils;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @Author: seamew
+ * @Title: DrawConfig
+ * @CreateTime: 2023年02月13日 21:00:00
+ * @Description:
+ * @Version: 1.0
+ */
+public class DrawConfig {
+
+    protected static Map<Integer, IDrawAlgorithm> drawAlgorithmMap = new ConcurrentHashMap<>();
+
+    @Resource
+    private List<IDrawAlgorithm> algorithmList = new ArrayList<>();
+
+
+    @PostConstruct
+    public void init() {
+        algorithmList.forEach(r -> {
+            StrategyMode strategyMode = AnnotationUtils.findAnnotation(r.getClass(), StrategyMode.class);
+            drawAlgorithmMap.put(strategyMode.strategyMode().getId(), r);
+        });
+    }
+}

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

@@ -0,0 +1,15 @@
+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;
+
+/**
+ * @Author: seamew
+ * @Title: IDrawExec
+ * @CreateTime: 2023年02月13日 20:39:00
+ * @Description: 策略包装
+ * @Version: 1.0
+ */
+public interface IDrawExec {
+    DrawResult doDrawExec(DrawReq req);
+}

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

@@ -0,0 +1,58 @@
+package com.seamew.lottery.domain.strategy.service.draw.impl;
+
+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.DrawResult;
+import com.seamew.lottery.domain.strategy.repository.IStrategyRepository;
+import com.seamew.lottery.domain.strategy.service.algorithm.IDrawAlgorithm;
+import com.seamew.lottery.domain.strategy.service.draw.DrawBase;
+import com.seamew.lottery.domain.strategy.service.draw.IDrawExec;
+import com.seamew.lottery.infrastructure.po.Award;
+import com.seamew.lottery.infrastructure.po.Strategy;
+import com.seamew.lottery.infrastructure.po.StrategyDetail;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @Author: seamew
+ * @Title: DrawExecImpl
+ * @CreateTime: 2023年02月13日 21:00:00
+ * @Description:
+ * @Version: 1.0
+ */
+@Service("drawExec")
+@Slf4j
+public class DrawExecImpl extends DrawBase implements IDrawExec {
+
+    @Resource
+    private IStrategyRepository strategyRepository;
+
+    @Override
+    public DrawResult doDrawExec(DrawReq req) {
+        log.info("执行策略抽奖开始,strategyId:{}", req.getStrategyId());
+
+        // 获取抽奖策略配置数据
+        StrategyRich strategyRich = strategyRepository.queryStrategyRich(req.getStrategyId());
+        Strategy strategy = strategyRich.getStrategy();
+        List<StrategyDetail> strategyDetailList = strategyRich.getStrategyDetailList();
+
+        // 校验和初始化数据
+        checkAndInitRateData(req.getStrategyId(), strategy.getStrategyMode(), strategyDetailList);
+
+        // 根据策略方式抽奖
+        IDrawAlgorithm drawAlgorithm = drawAlgorithmMap.get(strategy.getStrategyMode());
+        String awardId = drawAlgorithm.randomDraw(req.getStrategyId(), new ArrayList<>());
+
+        // 获取奖品信息
+        Award award = strategyRepository.queryAwardInfo(awardId);
+
+        log.info("执行策略抽奖完成,中奖用户:{} 奖品ID:{} 奖品名称:{}", req.getUId(), awardId, award.getAwardName());
+
+        // 封装结果
+        return new DrawResult(req.getUId(), req.getStrategyId(), awardId, award.getAwardName());
+    }
+}

+ 18 - 0
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/dao/IAwardDao.java

@@ -0,0 +1,18 @@
+package com.seamew.lottery.infrastructure.dao;
+
+import com.seamew.lottery.infrastructure.po.Award;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @Author: seamew
+ * @Title: IAwardDao
+ * @CreateTime: 2023年02月13日 21:03:00
+ * @Description:
+ * @Version: 1.0
+ */
+@Mapper
+public interface IAwardDao {
+
+    Award queryAwardInfo(String awardId);
+
+}

+ 18 - 0
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/dao/IStrategyDao.java

@@ -0,0 +1,18 @@
+package com.seamew.lottery.infrastructure.dao;
+
+import com.seamew.lottery.infrastructure.po.Strategy;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @Author: seamew
+ * @Title: IStrategyDao
+ * @CreateTime: 2023年02月13日 21:03:00
+ * @Description:
+ * @Version: 1.0
+ */
+@Mapper
+public interface IStrategyDao {
+
+    Strategy queryStrategy(Long strategyId);
+
+}

+ 20 - 0
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/dao/IStrategyDetailDao.java

@@ -0,0 +1,20 @@
+package com.seamew.lottery.infrastructure.dao;
+
+import com.seamew.lottery.infrastructure.po.StrategyDetail;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * @Author: seamew
+ * @Title: IStrategyDetailDao
+ * @CreateTime: 2023年02月13日 21:04:00
+ * @Description:
+ * @Version: 1.0
+ */
+@Mapper
+public interface IStrategyDetailDao {
+
+    List<StrategyDetail> queryStrategyDetailList(Long strategyId);
+
+}

+ 43 - 0
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/po/Award.java

@@ -0,0 +1,43 @@
+package com.seamew.lottery.infrastructure.po;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+/**
+ * @Author: seamew
+ * @Title: Award
+ * @CreateTime: 2023年02月13日 20:58:00
+ * @Description: 奖品配置
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class Award {
+    // 自增ID
+    private Long id;
+
+    // 奖品ID
+    private String awardId;
+
+    // 奖品类型(文字描述、兑换码、优惠券、实物奖品暂无)
+    private Integer awardType;
+
+    // 奖品数量
+    private Integer awardCount;
+
+    // 奖品名称
+    private String awardName;
+
+    // 奖品内容「文字描述、Key、码」
+    private String awardContent;
+
+    // 创建时间
+    private Date createTime;
+
+    // 修改时间
+    private Date updateTime;
+}

+ 47 - 0
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/po/Strategy.java

@@ -0,0 +1,47 @@
+package com.seamew.lottery.infrastructure.po;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+/**
+ * @Author: seamew
+ * @Title: Strategy
+ * @CreateTime: 2023年02月13日 20:57:00
+ * @Description: 策略配置
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class Strategy {
+
+    // 自增ID
+    private Long id;
+
+    // 策略ID
+    private Long strategyId;
+
+    // 策略描述
+    private String strategyDesc;
+
+    // 策略方式「1:单项概率、2:总体概率」
+    private Integer strategyMode;
+
+    // 发放奖品方式「1:即时、2:定时[含活动结束]、3:人工」
+    private Integer grantType;
+
+    // 发放奖品时间
+    private Date grantDate;
+
+    // 扩展信息
+    private String extInfo;
+
+    // 创建时间
+    private Date createTime;
+
+    // 修改时间
+    private Date updateTime;
+}

+ 40 - 0
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/po/StrategyDetail.java

@@ -0,0 +1,40 @@
+package com.seamew.lottery.infrastructure.po;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+/**
+ * @Author: seamew
+ * @Title: StrategyDetail
+ * @CreateTime: 2023年02月13日 20:58:00
+ * @Description: 策略明细
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class StrategyDetail {
+    // 自增ID
+    private String id;
+
+    // 策略ID
+    private Long strategyId;
+
+    // 奖品ID
+    private String awardId;
+
+    // 奖品数量
+    private String awardCount;
+
+    // 中奖概率
+    private BigDecimal awardRate;
+
+    // 创建时间
+    private String createTime;
+
+    // 修改时间
+    private String updateTime;
+}

+ 4 - 6
lottery-interfaces/pom.xml

@@ -11,8 +11,6 @@
 
     <artifactId>lottery-interfaces</artifactId>
 
-    <packaging>war</packaging>
-
     <properties>
         <maven.compiler.source>8</maven.compiler.source>
         <maven.compiler.target>8</maven.compiler.target>
@@ -50,22 +48,22 @@
         <dependency>
             <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo</artifactId>
-            <version>2.7.1</version>
+            <version>2.7.21</version>
         </dependency>
         <dependency>
             <groupId>org.apache.dubbo</groupId>
             <artifactId>dubbo-spring-boot-starter</artifactId>
-            <version>2.7.1</version>
+            <version>2.7.21</version>
         </dependency>
         <dependency>
             <groupId>org.apache.curator</groupId>
             <artifactId>curator-framework</artifactId>
-            <version>2.8.0</version>
+            <version>4.2.0</version>
         </dependency>
         <dependency>
             <groupId>org.apache.curator</groupId>
             <artifactId>curator-recipes</artifactId>
-            <version>2.8.0</version>
+            <version>4.2.0</version>
         </dependency>
         <dependency>
             <groupId>com.alibaba</groupId>

+ 1 - 1
lottery-interfaces/src/main/java/com/seamew/lottery/interfaces/ActivityBooth.java

@@ -23,7 +23,7 @@ import javax.annotation.Resource;
 @Service
 public class ActivityBooth implements IActivityBooth {
 
-    @Autowired
+    @Resource
     private IActivityDao activityDao;
 
     @Override

+ 3 - 0
lottery-interfaces/src/main/resources/application.yaml

@@ -19,6 +19,9 @@ dubbo:
     version: 1.0.0
   registry:
     address: zookeeper://180.76.231.231:2181
+    timeout: 60000
+  metadata-report:
+    address: zookeeper://180.76.231.231:2181
   protocol:
     name: dubbo
     port: 20880

+ 8 - 7
lottery-interfaces/src/main/resources/mybatis/mapper/Activity_Mapper.xml

@@ -4,17 +4,18 @@
 
     <insert id="insert" parameterType="com.seamew.lottery.infrastructure.po.Activity">
         INSERT INTO activity
-        (activity_id, activity_name, activity_desc, begin_date_time, end_date_time,
-         stock_count, take_count, state, creator, create_time, update_time)
-        VALUES (#{activityId}, #{activityName}, #{activityDesc}, #{beginDateTime}, #{endDateTime},
-                #{stockCount}, #{takeCount}, #{state}, #{creator}, now(), now())
+        (activityId, activityName, activityDesc,beginDateTime, endDateTime,
+         stockCount, takeCount, state, creator, createTime, updateTime)
+        VALUES
+            (#{activityId}, #{activityName}, #{activityDesc},#{beginDateTime}, #{endDateTime},
+             #{stockCount}, #{takeCount}, #{state}, #{creator}, now(), now())
     </insert>
 
     <select id="queryActivityById" parameterType="java.lang.Long" resultType="com.seamew.lottery.infrastructure.po.Activity">
-        SELECT activity_id, activity_name, activity_desc, begin_date_time, end_date_time,
-               stock_count, take_count, state, creator, create_time, update_time
+        SELECT activityId, activityName, activityDesc,beginDateTime, endDateTime,
+               stockCount, takeCount, state, creator, createTime, updateTime
         FROM activity
-        WHERE activity_id = #{activityId}
+        WHERE activityId = #{activityId}
     </select>
 
 

+ 12 - 0
lottery-interfaces/src/main/resources/mybatis/mapper/Award_Mapper.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.seamew.lottery.infrastructure.dao.IAwardDao">
+
+    <select id="queryAwardInfo" parameterType="java.lang.String" resultType="com.seamew.lottery.infrastructure.po.Award">
+        SELECT
+            id, awardId, awardType, awardCount, awardName, awardContent, createTime, updateTime
+        FROM award
+        WHERE awardId = #{awardId}
+    </select>
+
+</mapper>

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

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.seamew.lottery.infrastructure.dao.IStrategyDetailDao">
+
+    <select id="queryStrategyDetailList" parameterType="java.lang.Long" resultType="com.seamew.lottery.infrastructure.po.StrategyDetail">
+        SELECT id, strategyId, awardId, awardCount, awardRate, createTime, updateTime
+        FROM strategy_detail
+        WHERE strategyId = #{strategyId}
+    </select>
+
+</mapper>

+ 13 - 0
lottery-interfaces/src/main/resources/mybatis/mapper/Strategy_Mapper.xml

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.seamew.lottery.infrastructure.dao.IStrategyDao">
+
+    <select id="queryStrategy" parameterType="java.lang.Long" resultType="com.seamew.lottery.infrastructure.po.Strategy">
+        SELECT
+            id, strategyId, strategyDesc, strategyMode, grantType,
+            grantDate, extInfo , createTime, updateTime
+        FROM strategy
+        WHERE strategyId = #{strategyId}
+    </select>
+
+</mapper>

+ 14 - 14
lottery-interfaces/src/test/java/com/seamew/lottery/test/ApiTest.java

@@ -24,21 +24,21 @@ public class ApiTest {
     IActivityDao activityDao;
     @Test
     public void context() {
-        Activity activity = new Activity();
-        activity.setActivityId(100002l);
-        activity.setActivityName("测试活动");
-        activity.setActivityDesc("仅用于插入数据测试");
-        activity.setBeginDateTime(new Date());
-        activity.setEndDateTime(new Date());
-        activity.setStockCount(10);
-        activity.setTakeCount(10);
-        activity.setState(0);
-        activity.setCreator("admin");
-
-        activityDao.insert(activity);
+        // Activity activity = new Activity();
+        // activity.setActivityId(100002l);
+        // activity.setActivityName("测试活动");
+        // activity.setActivityDesc("仅用于插入数据测试");
+        // activity.setBeginDateTime(new Date());
+        // activity.setEndDateTime(new Date());
+        // activity.setStockCount(10);
+        // activity.setTakeCount(10);
+        // activity.setState(0);
+        // activity.setCreator("admin");
         //
-        // Activity activity = activityDao.queryActivityById(100002l);
-        // System.out.println(activity);
+        // activityDao.insert(activity);
+
+        Activity activity = activityDao.queryActivityById(100002l);
+        System.out.println(activity);
     }
 }
 

+ 0 - 1
lottery-rpc/src/main/java/com/seamew/lottery/rpc/res/ActivityRes.java

@@ -22,5 +22,4 @@ public class ActivityRes implements Serializable {
     private Result result;
     private ActivityDto activity;
 
-
 }

+ 13 - 0
pom.xml

@@ -13,6 +13,7 @@
         <module>lottery-interfaces</module>
         <module>lottery-common</module>
         <module>lottery-infrastructure</module>
+        <module>lottery-domain</module>
     </modules>
 
     <properties>
@@ -30,6 +31,7 @@
         <version>2.3.5.RELEASE</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
+
     <dependencies>
         <dependency>
             <groupId>org.projectlombok</groupId>
@@ -37,11 +39,22 @@
         </dependency>
     </dependencies>
 
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-all</artifactId>
+                <version>5.5.0</version>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+
     <build>
         <plugins>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
+                <version>2.20</version>
                 <configuration>
                     <skipTests>true</skipTests>
                 </configuration>