瀏覽代碼

feat: 规则引擎量化人群参与活动

seamew 2 年之前
父節點
當前提交
61a8a2bc30
共有 34 個文件被更改,包括 1006 次插入10 次删除
  1. 54 0
      lottery-common/src/main/java/com/seamew/lottery/common/Constants.java
  2. 1 1
      lottery-domain/src/main/java/com/seamew/lottery/domain/activity/model/res/PartakeResult.java
  3. 1 1
      lottery-domain/src/main/java/com/seamew/lottery/domain/award/model/vo/ShippingAddress.java
  4. 30 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/rule/model/aggregates/TreeRuleRich.java
  5. 32 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/rule/model/req/DecisionMatterReq.java
  6. 46 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/rule/model/res/EngineResult.java
  7. 34 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/rule/model/vo/TreeNodeLineVO.java
  8. 48 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/rule/model/vo/TreeNodeVO.java
  9. 30 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/rule/model/vo/TreeRootVO.java
  10. 22 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/rule/repository/IRuleRepository.java
  11. 50 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/rule/service/engine/EngineBase.java
  12. 33 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/rule/service/engine/EngineConfig.java
  13. 21 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/rule/service/engine/EngineFilter.java
  14. 41 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/rule/service/engine/impl/RuleEngineHandle.java
  15. 52 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/rule/service/logic/BaseLogic.java
  16. 32 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/rule/service/logic/LogicFilter.java
  17. 20 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/rule/service/logic/impl/UserAgeFilter.java
  18. 21 0
      lottery-domain/src/main/java/com/seamew/lottery/domain/rule/service/logic/impl/UserGenderFilter.java
  19. 1 1
      lottery-domain/src/main/java/com/seamew/lottery/domain/support/ids/annotation/IdMode.java
  20. 32 0
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/dao/IRuleTreeDao.java
  21. 40 0
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/dao/IRuleTreeNodeDao.java
  22. 32 0
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/dao/IRuleTreeNodeLineDao.java
  23. 44 0
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/po/RuleTree.java
  24. 43 0
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/po/RuleTreeNode.java
  25. 43 0
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/po/RuleTreeNodeLine.java
  26. 2 2
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/repository/ActivityRepository.java
  27. 2 2
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/repository/AwardRepository.java
  28. 90 0
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/repository/RuleRepository.java
  29. 2 2
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/repository/StrategyRepository.java
  30. 1 1
      lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/repository/UserTakeActivityRepository.java
  31. 18 0
      lottery-interfaces/src/main/resources/mybatis/mapper/RuleTreeNodeLine_Mapper.xml
  32. 24 0
      lottery-interfaces/src/main/resources/mybatis/mapper/RuleTreeNode_Mapper.xml
  33. 16 0
      lottery-interfaces/src/main/resources/mybatis/mapper/RuleTree_Mapper.xml
  34. 48 0
      lottery-interfaces/src/test/java/com/seamew/lottery/test/domain/RuleTest.java

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

@@ -34,6 +34,60 @@ public class Constants {
 
     }
 
+    /**
+     * 全局属性
+     */
+    public static final class Global {
+        /**
+         * 空节点值
+         */
+        public static final Long TREE_NULL_NODE = 0L;
+    }
+
+    /**
+     * 决策树节点
+     */
+    public static final class NodeType {
+        /**
+         * 树茎
+         */
+        public static final Integer STEM = 1;
+        /**
+         * 果实
+         */
+        public static final Integer FRUIT = 2;
+    }
+
+    /**
+     * 规则限定类型
+     */
+    public static final class RuleLimitType {
+        /**
+         * 等于
+         */
+        public static final int EQUAL = 1;
+        /**
+         * 大于
+         */
+        public static final int GT = 2;
+        /**
+         * 小于
+         */
+        public static final int LT = 3;
+        /**
+         * 大于&等于
+         */
+        public static final int GE = 4;
+        /**
+         * 小于&等于
+         */
+        public static final int LE = 5;
+        /**
+         * 枚举
+         */
+        public static final int ENUM = 6;
+    }
+
     /**
      * 活动状态:1编辑、2提审、3撤审、4通过、5运行(审核通过后worker扫描状态)、6拒绝、7关闭、8开启
      */

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

@@ -6,7 +6,7 @@ import com.seamew.lottery.common.Result;
  * @Author: seamew
  * @Title: PartakeResult
  * @CreateTime: 2023年02月22日 16:47:00
- * @Description:
+ * @Description: 活动参与结果
  * @Version: 1.0
  */
 public class PartakeResult extends Result {

+ 1 - 1
lottery-domain/src/main/java/com/seamew/lottery/domain/award/model/vo/ShippingAddress.java

@@ -8,7 +8,7 @@ import lombok.NoArgsConstructor;
  * @Author: seamew
  * @Title: ShippingAddress
  * @CreateTime: 2023年02月15日 10:33:00
- * @Description:
+ * @Description: 实物商品送货四级地址
  * @Version: 1.0
  */
 @Data

+ 30 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/rule/model/aggregates/TreeRuleRich.java

@@ -0,0 +1,30 @@
+package com.seamew.lottery.domain.rule.model.aggregates;
+
+import com.seamew.lottery.domain.rule.model.vo.TreeNodeVO;
+import com.seamew.lottery.domain.rule.model.vo.TreeRootVO;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * @Author: seamew
+ * @Title: TreeRuleRich
+ * @CreateTime: 2023年02月25日 16:50:00
+ * @Description: 规则树聚合
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TreeRuleRich {
+    /**
+     * 树根信息
+     */
+    private TreeRootVO treeRoot;
+    /**
+     * 树节点ID -> 子节点
+     */
+    private Map<Long, TreeNodeVO> treeNodeMap;
+}

+ 32 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/rule/model/req/DecisionMatterReq.java

@@ -0,0 +1,32 @@
+package com.seamew.lottery.domain.rule.model.req;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Map;
+
+/**
+ * @Author: seamew
+ * @Title: DecisionMatterReq
+ * @CreateTime: 2023年02月25日 16:53:00
+ * @Description: 决策物料
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class DecisionMatterReq {
+    /**
+     * 规则树ID
+     */
+    private Long treeId;
+    /**
+     * 用户ID
+     */
+    private String userId;
+    /**
+     * 决策值
+     */
+    private Map<String, Object> valMap;
+}

+ 46 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/rule/model/res/EngineResult.java

@@ -0,0 +1,46 @@
+package com.seamew.lottery.domain.rule.model.res;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Author: seamew
+ * @Title: EngineResult
+ * @CreateTime: 2023年02月25日 16:54:00
+ * @Description: 决策结果
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class EngineResult {
+    /**
+     * 执行结果
+     */
+    private boolean isSuccess;
+    /**
+     * 用户ID
+     */
+    private String userId;
+    /**
+     * 规则树ID
+     */
+    private Long treeId;
+    /**
+     * 果实节点ID
+     */
+    private Long nodeId;
+    /**
+     * 果实节点值
+     */
+    private String nodeValue;
+
+    public EngineResult(String userId, Long treeId, Long nodeId, String nodeValue) {
+        this.isSuccess = true;
+        this.userId = userId;
+        this.treeId = treeId;
+        this.nodeId = nodeId;
+        this.nodeValue = nodeValue;
+    }
+}

+ 34 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/rule/model/vo/TreeNodeLineVO.java

@@ -0,0 +1,34 @@
+package com.seamew.lottery.domain.rule.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Author: seamew
+ * @Title: TreeNodeLineVO
+ * @CreateTime: 2023年02月25日 16:51:00
+ * @Description: 规则树线信息
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TreeNodeLineVO {
+    /**
+     * 节点From
+     */
+    private Long nodeIdFrom;
+    /**
+     * 节点To
+     */
+    private Long nodeIdTo;
+    /**
+     * 限定类型;1:=;2:>;3:<;4:>=;5<=;6:enum[枚举范围]
+     */
+    private Integer ruleLimitType;
+    /**
+     * 限定值
+     */
+    private String ruleLimitValue;
+}

+ 48 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/rule/model/vo/TreeNodeVO.java

@@ -0,0 +1,48 @@
+package com.seamew.lottery.domain.rule.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @Author: seamew
+ * @Title: TreeNodeVO
+ * @CreateTime: 2023年02月25日 16:51:00
+ * @Description: 规则树节点信息
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TreeNodeVO {
+    /**
+     * 规则树ID
+     */
+    private Long treeId;
+    /**
+     * 规则树节点ID
+     */
+    private Long treeNodeId;
+    /**
+     * 节点类型;1 = 子叶、2 = 果实
+     */
+    private Integer nodeType;
+    /**
+     * 节点值[nodeType=2];果实值
+     */
+    private String nodeValue;
+    /**
+     * 规则Key
+     */
+    private String ruleKey;
+    /**
+     * 规则描述
+     */
+    private String ruleDesc;
+    /**
+     * 节点链路
+     */
+    private List<TreeNodeLineVO> treeNodeLineInfoList;
+}

+ 30 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/rule/model/vo/TreeRootVO.java

@@ -0,0 +1,30 @@
+package com.seamew.lottery.domain.rule.model.vo;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Author: seamew
+ * @Title: TreeRootVO
+ * @CreateTime: 2023年02月25日 16:52:00
+ * @Description: 规则树根配置
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class TreeRootVO {
+    /**
+     * 规则树ID
+     */
+    private Long treeId;
+    /**
+     * 规则树根ID
+     */
+    private Long treeRootNodeId;
+    /**
+     * 规则树名称
+     */
+    private String treeName;
+}

+ 22 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/rule/repository/IRuleRepository.java

@@ -0,0 +1,22 @@
+package com.seamew.lottery.domain.rule.repository;
+
+import com.seamew.lottery.domain.rule.model.aggregates.TreeRuleRich;
+
+/**
+ * @Author: seamew
+ * @Title: IRuleRepository
+ * @CreateTime: 2023年02月25日 17:16:00
+ * @Description: 规则信息仓储服务接口
+ * @Version: 1.0
+ */
+public interface IRuleRepository {
+
+    /**
+     * 查询规则决策树配置
+     *
+     * @param treeId    决策树ID
+     * @return          决策树配置
+     */
+    TreeRuleRich queryTreeRuleRich(Long treeId);
+
+}

+ 50 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/rule/service/engine/EngineBase.java

@@ -0,0 +1,50 @@
+package com.seamew.lottery.domain.rule.service.engine;
+
+import com.seamew.lottery.common.Constants;
+import com.seamew.lottery.domain.rule.model.aggregates.TreeRuleRich;
+import com.seamew.lottery.domain.rule.model.req.DecisionMatterReq;
+import com.seamew.lottery.domain.rule.model.res.EngineResult;
+import com.seamew.lottery.domain.rule.model.vo.TreeNodeVO;
+import com.seamew.lottery.domain.rule.model.vo.TreeRootVO;
+import com.seamew.lottery.domain.rule.service.engine.EngineConfig;
+import com.seamew.lottery.domain.rule.service.engine.EngineFilter;
+import com.seamew.lottery.domain.rule.service.logic.LogicFilter;
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+
+/**
+ * @Author: seamew
+ * @Title: EngineBase
+ * @CreateTime: 2023年02月25日 17:08:00
+ * @Description: 规则引擎基础类
+ * @Version: 1.0
+ */
+@Slf4j
+public abstract class EngineBase extends EngineConfig implements EngineFilter {
+    @Override
+    public EngineResult process(DecisionMatterReq matter) {
+        throw new RuntimeException("未实现规则引擎服务");
+    }
+
+    protected TreeNodeVO engineDecisionMaker(TreeRuleRich treeRuleRich, DecisionMatterReq matter) {
+        TreeRootVO treeRoot = treeRuleRich.getTreeRoot();
+        Map<Long, TreeNodeVO> treeNodeMap = treeRuleRich.getTreeNodeMap();
+
+        // 规则树根ID
+        Long rootNodeId = treeRoot.getTreeRootNodeId();
+        TreeNodeVO treeNodeInfo = treeNodeMap.get(rootNodeId);
+
+        // 节点类型[NodeType];1子叶、2果实
+        while (Constants.NodeType.STEM.equals(treeNodeInfo.getNodeType())) {
+            String ruleKey = treeNodeInfo.getRuleKey();
+            LogicFilter logicFilter = logicFilterMap.get(ruleKey);
+            String matterValue = logicFilter.matterValue(matter);
+            Long nextNode = logicFilter.filter(matterValue, treeNodeInfo.getTreeNodeLineInfoList());
+            treeNodeInfo = treeNodeMap.get(nextNode);
+            log.info("决策树引擎=>{} userId:{} treeId:{} treeNode:{} ruleKey:{} matterValue:{}", treeRoot.getTreeName(), matter.getUserId(), matter.getTreeId(), treeNodeInfo.getTreeNodeId(), ruleKey, matterValue);
+        }
+
+        return treeNodeInfo;
+    }
+}

+ 33 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/rule/service/engine/EngineConfig.java

@@ -0,0 +1,33 @@
+package com.seamew.lottery.domain.rule.service.engine;
+
+import com.seamew.lottery.domain.rule.service.logic.LogicFilter;
+import com.seamew.lottery.domain.rule.service.logic.impl.UserAgeFilter;
+import com.seamew.lottery.domain.rule.service.logic.impl.UserGenderFilter;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.Resource;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @Author: seamew
+ * @Title: EngineConfig
+ * @CreateTime: 2023年02月25日 17:08:00
+ * @Description: 规则配置
+ * @Version: 1.0
+ */
+public class EngineConfig {
+
+    protected static Map<String, LogicFilter> logicFilterMap = new ConcurrentHashMap<>();
+
+    @Resource
+    private UserAgeFilter userAgeFilter;
+    @Resource
+    private UserGenderFilter userGenderFilter;
+
+    @PostConstruct
+    public void init() {
+        logicFilterMap.put("userAge", userAgeFilter);
+        logicFilterMap.put("userGender", userGenderFilter);
+    }
+}

+ 21 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/rule/service/engine/EngineFilter.java

@@ -0,0 +1,21 @@
+package com.seamew.lottery.domain.rule.service.engine;
+
+import com.seamew.lottery.domain.rule.model.req.DecisionMatterReq;
+import com.seamew.lottery.domain.rule.model.res.EngineResult;
+
+/**
+ * @Author: seamew
+ * @Title: EngineFilter
+ * @CreateTime: 2023年02月25日 17:07:00
+ * @Description: 规则过滤器引擎
+ * @Version: 1.0
+ */
+public interface EngineFilter {
+    /**
+     * 规则过滤器接口
+     *
+     * @param matter 规则决策物料
+     * @return 规则决策结果
+     */
+    EngineResult process(final DecisionMatterReq matter);
+}

+ 41 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/rule/service/engine/impl/RuleEngineHandle.java

@@ -0,0 +1,41 @@
+package com.seamew.lottery.domain.rule.service.engine.impl;
+
+import com.seamew.lottery.domain.rule.model.aggregates.TreeRuleRich;
+import com.seamew.lottery.domain.rule.model.req.DecisionMatterReq;
+import com.seamew.lottery.domain.rule.model.res.EngineResult;
+import com.seamew.lottery.domain.rule.model.vo.TreeNodeVO;
+import com.seamew.lottery.domain.rule.repository.IRuleRepository;
+import com.seamew.lottery.domain.rule.service.engine.EngineBase;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+
+/**
+ * @Author: seamew
+ * @Title: RuleEngineHandle
+ * @CreateTime: 2023年02月25日 17:15:00
+ * @Description:
+ * @Version: 1.0
+ */
+@Service
+public class RuleEngineHandle extends EngineBase {
+
+    @Resource
+    private IRuleRepository ruleRepository;
+
+    @Override
+    public EngineResult process(DecisionMatterReq matter) {
+        // 决策规则树
+        TreeRuleRich treeRuleRich = ruleRepository.queryTreeRuleRich(matter.getTreeId());
+        if (null == treeRuleRich) {
+            throw new RuntimeException("Tree Rule is null!");
+        }
+
+        // 决策节点
+        TreeNodeVO treeNodeInfo = engineDecisionMaker(treeRuleRich, matter);
+
+        // 决策结果
+        return new EngineResult(matter.getUserId(), treeNodeInfo.getTreeId(), treeNodeInfo.getTreeNodeId(), treeNodeInfo.getNodeValue());
+    }
+
+}

+ 52 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/rule/service/logic/BaseLogic.java

@@ -0,0 +1,52 @@
+package com.seamew.lottery.domain.rule.service.logic;
+
+import com.seamew.lottery.common.Constants;
+import com.seamew.lottery.domain.rule.model.req.DecisionMatterReq;
+import com.seamew.lottery.domain.rule.model.vo.TreeNodeLineVO;
+
+import java.util.List;
+
+/**
+ * @Author: seamew
+ * @Title: BaseLogic
+ * @CreateTime: 2023年02月25日 16:55:00
+ * @Description: 规则基础抽象类
+ * @Version: 1.0
+ */
+public abstract class BaseLogic implements LogicFilter {
+    @Override
+    public Long filter(String matterValue, List<TreeNodeLineVO> treeNodeLineInfoList) {
+        for (TreeNodeLineVO nodeLine : treeNodeLineInfoList) {
+            if (decisionLogic(matterValue, nodeLine)) {
+                return nodeLine.getNodeIdTo();
+            }
+        }
+        return Constants.Global.TREE_NULL_NODE;
+    }
+
+    /**
+     * 获取规则比对值
+     *
+     * @param decisionMatter 决策物料
+     * @return 比对值
+     */
+    @Override
+    public abstract String matterValue(DecisionMatterReq decisionMatter);
+
+    private boolean decisionLogic(String matterValue, TreeNodeLineVO nodeLine) {
+        switch (nodeLine.getRuleLimitType()) {
+            case Constants.RuleLimitType.EQUAL:
+                return matterValue.equals(nodeLine.getRuleLimitValue());
+            case Constants.RuleLimitType.GT:
+                return Double.parseDouble(matterValue) > Double.parseDouble(nodeLine.getRuleLimitValue());
+            case Constants.RuleLimitType.LT:
+                return Double.parseDouble(matterValue) < Double.parseDouble(nodeLine.getRuleLimitValue());
+            case Constants.RuleLimitType.GE:
+                return Double.parseDouble(matterValue) >= Double.parseDouble(nodeLine.getRuleLimitValue());
+            case Constants.RuleLimitType.LE:
+                return Double.parseDouble(matterValue) <= Double.parseDouble(nodeLine.getRuleLimitValue());
+            default:
+                return false;
+        }
+    }
+}

+ 32 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/rule/service/logic/LogicFilter.java

@@ -0,0 +1,32 @@
+package com.seamew.lottery.domain.rule.service.logic;
+
+import com.seamew.lottery.domain.rule.model.req.DecisionMatterReq;
+import com.seamew.lottery.domain.rule.model.vo.TreeNodeLineVO;
+
+import java.util.List;
+
+/**
+ * @Author: seamew
+ * @Title: LogicFilter
+ * @CreateTime: 2023年02月25日 16:39:00
+ * @Description: 规则过滤器接口
+ * @Version: 1.0
+ */
+public interface LogicFilter {
+    /**
+     * 逻辑决策器
+     *
+     * @param matterValue          决策值
+     * @param treeNodeLineInfoList 决策节点
+     * @return 下一个节点Id
+     */
+    Long filter(String matterValue, List<TreeNodeLineVO> treeNodeLineInfoList);
+
+    /**
+     * 获取决策值
+     *
+     * @param decisionMatter 决策物料
+     * @return 决策值
+     */
+    String matterValue(DecisionMatterReq decisionMatter);
+}

+ 20 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/rule/service/logic/impl/UserAgeFilter.java

@@ -0,0 +1,20 @@
+package com.seamew.lottery.domain.rule.service.logic.impl;
+
+import com.seamew.lottery.domain.rule.model.req.DecisionMatterReq;
+import com.seamew.lottery.domain.rule.service.logic.BaseLogic;
+import org.springframework.stereotype.Component;
+
+/**
+ * @Author: seamew
+ * @Title: UserAgeFilter
+ * @CreateTime: 2023年02月25日 17:04:00
+ * @Description: 年龄规则
+ * @Version: 1.0
+ */
+@Component
+public class UserAgeFilter extends BaseLogic {
+    @Override
+    public String matterValue(DecisionMatterReq decisionMatter) {
+        return decisionMatter.getValMap().get("age").toString();
+    }
+}

+ 21 - 0
lottery-domain/src/main/java/com/seamew/lottery/domain/rule/service/logic/impl/UserGenderFilter.java

@@ -0,0 +1,21 @@
+package com.seamew.lottery.domain.rule.service.logic.impl;
+
+import com.seamew.lottery.domain.rule.model.req.DecisionMatterReq;
+import com.seamew.lottery.domain.rule.service.logic.BaseLogic;
+import org.springframework.stereotype.Component;
+
+/**
+ * @Author: seamew
+ * @Title: UserGenderFilter
+ * @CreateTime: 2023年02月25日 17:05:00
+ * @Description: 性别规则
+ * @Version: 1.0
+ */
+@Component
+public class UserGenderFilter extends BaseLogic {
+
+    @Override
+    public String matterValue(DecisionMatterReq decisionMatter) {
+        return decisionMatter.getValMap().get("gender").toString();
+    }
+}

+ 1 - 1
lottery-domain/src/main/java/com/seamew/lottery/domain/support/ids/annotation/IdMode.java

@@ -11,7 +11,7 @@ import java.lang.annotation.Target;
  * @Author: seamew
  * @Title: IdMode
  * @CreateTime: 2023年02月16日 10:38:00
- * @Description:
+ * @Description: ID生成策略模型
  * @Version: 1.0
  */
 @Target({ElementType.TYPE})

+ 32 - 0
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/dao/IRuleTreeDao.java

@@ -0,0 +1,32 @@
+package com.seamew.lottery.infrastructure.dao;
+
+import com.seamew.lottery.infrastructure.po.RuleTree;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @Author: seamew
+ * @Title: IRuleTreeDao
+ * @CreateTime: 2023年02月25日 17:19:00
+ * @Description: 规则树配置DAO
+ * @Version: 1.0
+ */
+@Mapper
+public interface IRuleTreeDao {
+
+    /**
+     * 规则树查询
+     *
+     * @param id ID
+     * @return 规则树
+     */
+    RuleTree queryRuleTreeByTreeId(Long id);
+
+    /**
+     * 规则树简要信息查询
+     *
+     * @param treeId 规则树ID
+     * @return 规则树
+     */
+    RuleTree queryTreeSummaryInfo(Long treeId);
+
+}

+ 40 - 0
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/dao/IRuleTreeNodeDao.java

@@ -0,0 +1,40 @@
+package com.seamew.lottery.infrastructure.dao;
+
+import com.seamew.lottery.infrastructure.po.RuleTreeNode;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * @Author: seamew
+ * @Title: IRuleTreeNodeDao
+ * @CreateTime: 2023年02月25日 17:20:00
+ * @Description:
+ * @Version: 1.0
+ */
+@Mapper
+public interface IRuleTreeNodeDao {
+    /**
+     * 查询规则树节点
+     *
+     * @param treeId 规则树ID
+     * @return 规则树节点集合
+     */
+    List<RuleTreeNode> queryRuleTreeNodeList(Long treeId);
+
+    /**
+     * 查询规则树节点数量
+     *
+     * @param treeId 规则树ID
+     * @return 节点数量
+     */
+    int queryTreeNodeCount(Long treeId);
+
+    /**
+     * 查询规则树节点
+     *
+     * @param treeId 规则树ID
+     * @return 节点集合
+     */
+    List<RuleTreeNode> queryTreeRulePoint(Long treeId);
+}

+ 32 - 0
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/dao/IRuleTreeNodeLineDao.java

@@ -0,0 +1,32 @@
+package com.seamew.lottery.infrastructure.dao;
+
+import com.seamew.lottery.infrastructure.po.RuleTreeNodeLine;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.List;
+
+/**
+ * @Author: seamew
+ * @Title: IRuleTreeNodeLineDao
+ * @CreateTime: 2023年02月25日 17:20:00
+ * @Description: 规则树节点连线DAO
+ * @Version: 1.0
+ */
+@Mapper
+public interface IRuleTreeNodeLineDao {
+    /**
+     * 查询规则树节点连线集合
+     *
+     * @param req 入参
+     * @return 规则树节点连线集合
+     */
+    List<RuleTreeNodeLine> queryRuleTreeNodeLineList(RuleTreeNodeLine req);
+
+    /**
+     * 查询规则树连线数量
+     *
+     * @param treeId 规则树ID
+     * @return 规则树连线数量
+     */
+    int queryTreeNodeLineCount(Long treeId);
+}

+ 44 - 0
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/po/RuleTree.java

@@ -0,0 +1,44 @@
+package com.seamew.lottery.infrastructure.po;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+/**
+ * @Author: seamew
+ * @Title: RuleTree
+ * @CreateTime: 2023年02月25日 17:18:00
+ * @Description: 规则树
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class RuleTree {
+    /**
+     * 主键ID
+     */
+    private Long id;
+    /**
+     * 规则树名称
+     */
+    private String treeName;
+    /**
+     * 规则树描述
+     */
+    private String treeDesc;
+    /**
+     * 规则树根ID
+     */
+    private Long treeRootNodeId;
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+    /**
+     * 更新时间
+     */
+    private Date updateTime;
+}

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

@@ -0,0 +1,43 @@
+package com.seamew.lottery.infrastructure.po;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Author: seamew
+ * @Title: RuleTreeNode
+ * @CreateTime: 2023年02月25日 17:18:00
+ * @Description: 规则树节点
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class RuleTreeNode {
+    /**
+     * 主键ID
+     */
+    private Long id;
+    /**
+     * 规则树ID
+     */
+    private Long treeId;
+    /**
+     * 节点类型;1 = 子叶、2 = 果实
+     */
+    private Integer nodeType;
+    /**
+     * 节点值[nodeType=2];果实值
+     */
+    private String nodeValue;
+    /**
+     * 规则Key
+     */
+    private String ruleKey;
+    /**
+     * 规则描述
+     */
+    private String ruleDesc;
+
+}

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

@@ -0,0 +1,43 @@
+package com.seamew.lottery.infrastructure.po;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @Author: seamew
+ * @Title: RuleTreeNodeLine
+ * @CreateTime: 2023年02月25日 17:18:00
+ * @Description: 规则树节点连线
+ * @Version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class RuleTreeNodeLine {
+    /**
+     * 主键ID
+     */
+    private Long id;
+    /**
+     * 规则树ID
+     */
+    private Long treeId;
+    /**
+     * 节点From
+     */
+    private Long nodeIdFrom;
+    /**
+     * 节点To
+     */
+    private Long nodeIdTo;
+    /**
+     * 限定类型;1:=;2:>;3:<;4:>=;5<=;6:enum[枚举范围]
+     */
+    private Integer ruleLimitType;
+    /**
+     * 限定值
+     */
+    private String ruleLimitValue;
+
+}

+ 2 - 2
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/repository/ActivityRepository.java

@@ -7,7 +7,7 @@ import com.seamew.lottery.domain.activity.repository.IActivityRepository;
 import com.seamew.lottery.infrastructure.dao.*;
 import com.seamew.lottery.infrastructure.po.*;
 import org.springframework.beans.BeanUtils;
-import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Repository;
 
 import javax.annotation.Resource;
 import java.util.ArrayList;
@@ -20,7 +20,7 @@ import java.util.List;
  * @Description:
  * @Version: 1.0
  */
-@Component
+@Repository
 public class ActivityRepository implements IActivityRepository {
 
     @Resource

+ 2 - 2
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/repository/AwardRepository.java

@@ -1,7 +1,7 @@
 package com.seamew.lottery.infrastructure.repository;
 
 import com.seamew.lottery.domain.award.repository.IAwardRepository;
-import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Repository;
 
 /**
  * @Author: seamew
@@ -10,7 +10,7 @@ import org.springframework.stereotype.Component;
  * @Description: 奖品表仓储服务
  * @Version: 1.0
  */
-@Component
+@Repository
 public class AwardRepository implements IAwardRepository {
 
 }

+ 90 - 0
lottery-infrastructure/src/main/java/com/seamew/lottery/infrastructure/repository/RuleRepository.java

@@ -0,0 +1,90 @@
+package com.seamew.lottery.infrastructure.repository;
+
+import com.seamew.lottery.common.Constants;
+import com.seamew.lottery.domain.rule.model.aggregates.TreeRuleRich;
+import com.seamew.lottery.domain.rule.model.vo.TreeNodeLineVO;
+import com.seamew.lottery.domain.rule.model.vo.TreeNodeVO;
+import com.seamew.lottery.domain.rule.model.vo.TreeRootVO;
+import com.seamew.lottery.domain.rule.repository.IRuleRepository;
+import com.seamew.lottery.infrastructure.dao.IRuleTreeDao;
+import com.seamew.lottery.infrastructure.dao.IRuleTreeNodeDao;
+import com.seamew.lottery.infrastructure.dao.IRuleTreeNodeLineDao;
+import com.seamew.lottery.infrastructure.po.RuleTree;
+import com.seamew.lottery.infrastructure.po.RuleTreeNode;
+import com.seamew.lottery.infrastructure.po.RuleTreeNodeLine;
+import org.springframework.stereotype.Repository;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @Author: seamew
+ * @Title: RuleRepository
+ * @CreateTime: 2023年02月25日 17:17:00
+ * @Description: 规则信息仓储服务
+ * @Version: 1.0
+ */
+@Repository
+public class RuleRepository implements IRuleRepository {
+
+    @Resource
+    private IRuleTreeDao ruleTreeDao;
+    @Resource
+    private IRuleTreeNodeDao ruleTreeNodeDao;
+    @Resource
+    private IRuleTreeNodeLineDao ruleTreeNodeLineDao;
+
+    @Override
+    public TreeRuleRich queryTreeRuleRich(Long treeId) {
+
+        // 规则树
+        RuleTree ruleTree = ruleTreeDao.queryRuleTreeByTreeId(treeId);
+        TreeRootVO treeRoot = new TreeRootVO();
+        treeRoot.setTreeId(ruleTree.getId());
+        treeRoot.setTreeRootNodeId(ruleTree.getTreeRootNodeId());
+        treeRoot.setTreeName(ruleTree.getTreeName());
+
+        // 树节点->树连接线
+        Map<Long, TreeNodeVO> treeNodeMap = new HashMap<>();
+        List<RuleTreeNode> ruleTreeNodeList = ruleTreeNodeDao.queryRuleTreeNodeList(treeId);
+        for (RuleTreeNode treeNode : ruleTreeNodeList) {
+            List<TreeNodeLineVO> treeNodeLineInfoList = new ArrayList<>();
+            if (Constants.NodeType.STEM.equals(treeNode.getNodeType())) {
+
+                RuleTreeNodeLine ruleTreeNodeLineReq = new RuleTreeNodeLine();
+                ruleTreeNodeLineReq.setTreeId(treeId);
+                ruleTreeNodeLineReq.setNodeIdFrom(treeNode.getId());
+                List<RuleTreeNodeLine> ruleTreeNodeLineList = ruleTreeNodeLineDao.queryRuleTreeNodeLineList(ruleTreeNodeLineReq);
+
+                for (RuleTreeNodeLine nodeLine : ruleTreeNodeLineList) {
+                    TreeNodeLineVO treeNodeLineInfo = new TreeNodeLineVO();
+                    treeNodeLineInfo.setNodeIdFrom(nodeLine.getNodeIdFrom());
+                    treeNodeLineInfo.setNodeIdTo(nodeLine.getNodeIdTo());
+                    treeNodeLineInfo.setRuleLimitType(nodeLine.getRuleLimitType());
+                    treeNodeLineInfo.setRuleLimitValue(nodeLine.getRuleLimitValue());
+                    treeNodeLineInfoList.add(treeNodeLineInfo);
+                }
+            }
+            TreeNodeVO treeNodeInfo = new TreeNodeVO();
+            treeNodeInfo.setTreeId(treeNode.getTreeId());
+            treeNodeInfo.setTreeNodeId(treeNode.getId());
+            treeNodeInfo.setNodeType(treeNode.getNodeType());
+            treeNodeInfo.setNodeValue(treeNode.getNodeValue());
+            treeNodeInfo.setRuleKey(treeNode.getRuleKey());
+            treeNodeInfo.setRuleDesc(treeNode.getRuleDesc());
+            treeNodeInfo.setTreeNodeLineInfoList(treeNodeLineInfoList);
+
+            treeNodeMap.put(treeNode.getId(), treeNodeInfo);
+        }
+
+        TreeRuleRich treeRuleRich = new TreeRuleRich();
+        treeRuleRich.setTreeRoot(treeRoot);
+        treeRuleRich.setTreeNodeMap(treeNodeMap);
+
+        return treeRuleRich;
+    }
+
+}

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

@@ -12,7 +12,7 @@ import com.seamew.lottery.infrastructure.po.Award;
 import com.seamew.lottery.infrastructure.po.Strategy;
 import com.seamew.lottery.infrastructure.po.StrategyDetail;
 import org.springframework.beans.BeanUtils;
-import org.springframework.stereotype.Component;
+import org.springframework.stereotype.Repository;
 
 import javax.annotation.Resource;
 import java.util.ArrayList;
@@ -25,7 +25,7 @@ import java.util.List;
  * @Description: 策略表仓储服务
  * @Version: 1.0
  */
-@Component
+@Repository
 public class StrategyRepository implements IStrategyRepository {
     @Resource
     private IStrategyDao strategyDao;

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

@@ -19,7 +19,7 @@ import java.util.Date;
  * @Author: seamew
  * @Title: UserTakeActivityRepository
  * @CreateTime: 2023年02月22日 16:51:00
- * @Description:
+ * @Description: 用户参与活动仓储
  * @Version: 1.0
  */
 @Component

+ 18 - 0
lottery-interfaces/src/main/resources/mybatis/mapper/RuleTreeNodeLine_Mapper.xml

@@ -0,0 +1,18 @@
+<?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.IRuleTreeNodeLineDao">
+
+
+    <select id="queryRuleTreeNodeLineList" resultType="com.seamew.lottery.infrastructure.po.RuleTreeNodeLine">
+        SELECT id, tree_id, node_id_from, node_id_to, rule_limit_type, rule_limit_value
+        FROM rule_tree_node_line
+        where tree_id = #{treeId}
+          and node_id_from = #{nodeIdFrom}
+    </select>
+
+    <select id="queryTreeNodeLineCount" resultType="java.lang.Integer">
+        select count(id)
+        from rule_tree_node_line
+        where tree_id = #{treeId}
+    </select>
+</mapper>

+ 24 - 0
lottery-interfaces/src/main/resources/mybatis/mapper/RuleTreeNode_Mapper.xml

@@ -0,0 +1,24 @@
+<?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.IRuleTreeNodeDao">
+
+    <select id="queryRuleTreeNodeList" resultType="com.seamew.lottery.infrastructure.po.RuleTreeNode">
+        SELECT id, tree_id, node_type, node_value, rule_key, rule_desc
+        FROM rule_tree_node
+        where tree_id = #{treeId}
+    </select>
+
+    <select id="queryTreeNodeCount" resultType="java.lang.Integer">
+        select count(id)
+        from rule_tree_node
+        where tree_id = #{treeId}
+    </select>
+
+    <select id="queryTreeRulePoint" resultType="com.seamew.lottery.infrastructure.po.RuleTreeNode">
+        SELECT distinct (rule_key), rule_desc
+        FROM rule_tree_node
+        where tree_id = #{treeId}
+          and rule_key is not null
+    </select>
+
+</mapper>

+ 16 - 0
lottery-interfaces/src/main/resources/mybatis/mapper/RuleTree_Mapper.xml

@@ -0,0 +1,16 @@
+<?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.IRuleTreeDao">
+    <select id="queryRuleTreeByTreeId" resultType="com.seamew.lottery.infrastructure.po.RuleTree">
+        SELECT id, tree_name, tree_desc, tree_root_node_id, create_time, update_time
+        FROM rule_tree
+        where id = #{id}
+    </select>
+
+    <select id="queryTreeSummaryInfo" resultType="com.seamew.lottery.infrastructure.po.RuleTree">
+        SELECT id, tree_name, tree_desc
+        FROM rule_tree
+        where id = #{id}
+    </select>
+
+</mapper>

+ 48 - 0
lottery-interfaces/src/test/java/com/seamew/lottery/test/domain/RuleTest.java

@@ -0,0 +1,48 @@
+package com.seamew.lottery.test.domain;
+
+import com.alibaba.fastjson2.JSON;
+import com.seamew.lottery.domain.rule.model.req.DecisionMatterReq;
+import com.seamew.lottery.domain.rule.model.res.EngineResult;
+import com.seamew.lottery.domain.rule.repository.IRuleRepository;
+import com.seamew.lottery.domain.rule.service.engine.EngineFilter;
+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;
+import java.util.HashMap;
+
+/**
+ * @Author: seamew
+ * @Title: RuleTest
+ * @CreateTime: 2023年02月25日 17:31:00
+ * @Description: 规则引擎测试
+ * @Version: 1.0
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest
+@Slf4j
+public class RuleTest {
+    @Resource
+    private EngineFilter engineFilter;
+
+    @Test
+    public void test_process() {
+        DecisionMatterReq req = new DecisionMatterReq();
+        req.setTreeId(2110081902L);
+        req.setUserId("fustack");
+        req.setValMap(new HashMap<String, Object>() {{
+            put("gender", "man");
+            put("age", "25");
+        }});
+
+        EngineResult res = engineFilter.process(req);
+
+        log.info("请求参数:{}", JSON.toJSONString(req));
+        log.info("测试结果:{}", JSON.toJSONString(res));
+    }
+
+
+}