一、前言
上面一节我们尝试使用了责任链模式使前置规则过滤解耦,通过调用责任链来进行顺序过滤。但是这样的过滤依旧不够完美,因为责任链的设定就是 "一条道走到黑" ,无论中间的过滤结果怎么样,都会按照顺序走下去,直到最后兜底尾节点进行默认处理。这样的顺序过滤对于前置规则过滤来说是合理的,但是在中置规则过滤就不太适配了,因为中置过滤规则中会涉及到库存扣减,存在多种情况,自然也就有多种不同的走向了,用单一的链表很难处理:


所以这里我们要引入组合模式来解决这个问题,组合模式本身就是一个树形结构,具体的概念后面阐述,总之,这里只需要知道我们需要通过树结构来对中置规则过滤进行处理,和责任链一样,需要把这些处理类当作一个节点。
二、抽奖规则决策树
老样子,我们还是 "找核心",显然的,和责任链一样,这里我们的核心类一定是规定树结构的那个类。
java
/**
* @author 印东升
* @description 决策树引擎
* @create 2026-04-15 11:38
*/
@Slf4j
public class DecisionTreeEngine implements IDecisionTreeEngine {
private final Map<String, ILogicTreeNode> logicTreeNodeGroup;
private final RuleTreeVO ruleTreeVO;
public DecisionTreeEngine(Map<String, ILogicTreeNode> logicTreeNodeGroup, RuleTreeVO ruleTreeVO) {
this.logicTreeNodeGroup = logicTreeNodeGroup;
this.ruleTreeVO = ruleTreeVO;
}
@Override
public DefaultTreeFactory.StrategyAwardData process(String userId, Long strategy, Integer awardId) {
DefaultTreeFactory.StrategyAwardData strategyAwardData = null;
//获取基础信息
String nextNode = ruleTreeVO.getTreeRootRuleNode();
Map<String, RuleTreeNodeVO> treeNodeMap = ruleTreeVO.getTreeNodeMap();
RuleTreeNodeVO ruleTreeNode = treeNodeMap.get(nextNode);
while (null != nextNode) {
ILogicTreeNode logicTreeNode = logicTreeNodeGroup.get(ruleTreeNode.getRuleKey());
DefaultTreeFactory.TreeActionEntity logicEntity = logicTreeNode.logic(userId, strategy, awardId);
RuleLogicCheckTypeVO ruleLogicCheckTypeVO = logicEntity.getRuleLogicCheckType();
strategyAwardData = logicEntity.getStrategyAwardData();
log.info("决策树引擎【{}】treeId:{} node:{} code:{}", ruleTreeVO.getTreeName(), ruleTreeVO.getTreeId(), nextNode, ruleLogicCheckTypeVO.getCode());
nextNode = nextNode(ruleLogicCheckTypeVO.getCode(), ruleTreeNode.getTreeNodeLineVOList());
ruleTreeNode = treeNodeMap.get(nextNode);
}
//返回最终结果
return strategyAwardData;
}
private String nextNode(String matterValue, List<RuleTreeNodeLineVO> ruleTreeNodeLineVOList) {
if (null == ruleTreeNodeLineVOList || ruleTreeNodeLineVOList.isEmpty()) return null;
for (RuleTreeNodeLineVO nodeLine : ruleTreeNodeLineVOList) {
if (decisionLogic(matterValue, nodeLine)) {
return nodeLine.getRuleNodeTo();
}
}
throw new RuntimeException("决策树引擎,nextNode 计算失败,未找到可执行节点");
}
public boolean decisionLogic(String matterValue, RuleTreeNodeLineVO nodeLine) {
switch (nodeLine.getRuleLimitType()) {
case EQUAL:
return matterValue.equals(nodeLine.getRuleLimitValue().getCode());
// 以下规则暂时不需要实现
case GT:
case LT:
case GE:
case LE:
default:
return false;
}
}
}
DecisionTreeEngine 这个规则树引擎就是核心类了,接下来我们一步一步分析它的流程:
1.注入叶子节点
和责任链一样,我们首先要注入这三个处理节点的Bean(次数锁、库存处理、兜底奖励),还是通过Map注入,Spring会自动装配:
java
private final Map<String, ILogicTreeNode> logicTreeNodeGroup;
2.核心流程方法
然后就是整体的流程方法**process()**了,在里面我们要去定义节点的处理顺序链:
java
@Override
public DefaultTreeFactory.StrategyAwardData process(String userId, Long strategy, Integer awardId) {
DefaultTreeFactory.StrategyAwardData strategyAwardData = null;
//获取基础信息
String nextNode = ruleTreeVO.getTreeRootRuleNode();
Map<String, RuleTreeNodeVO> treeNodeMap = ruleTreeVO.getTreeNodeMap();
RuleTreeNodeVO ruleTreeNode = treeNodeMap.get(nextNode);
while (null != nextNode) {
ILogicTreeNode logicTreeNode = logicTreeNodeGroup.get(ruleTreeNode.getRuleKey());
DefaultTreeFactory.TreeActionEntity logicEntity = logicTreeNode.logic(userId, strategy, awardId);
RuleLogicCheckTypeVO ruleLogicCheckTypeVO = logicEntity.getRuleLogicCheckType();
strategyAwardData = logicEntity.getStrategyAwardData();
log.info("决策树引擎【{}】treeId:{} node:{} code:{}", ruleTreeVO.getTreeName(), ruleTreeVO.getTreeId(), nextNode, ruleLogicCheckTypeVO.getCode());
nextNode = nextNode(ruleLogicCheckTypeVO.getCode(), ruleTreeNode.getTreeNodeLineVOList());
ruleTreeNode = treeNodeMap.get(nextNode);
}
//返回最终结果
return strategyAwardData;
}
我们一步一步分析:
(1)值对象
在正式开始前先介绍一下和规则树相关的节点属性类:
树的值对象:这里面包含了整个树的属性
java
//规则树对象【注意;不具有唯一ID,不需要改变数据库结果的对象,可以被定义为值对象】
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RuleTreeVO {
/** 规则树ID */
private Integer treeId;
/** 规则树名称 */
private String treeName;
/** 规则树描述 */
private String treeDesc;
/** 规则根节点 */
private String treeRootRuleNode;
/** 规则节点 */
private Map<String, RuleTreeNodeVO> treeNodeMap;
}
这是每个节点对象:
java
//规则树节点对象
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RuleTreeNodeVO {
/** 规则树ID */
private Integer treeId;
/** 规则Key */
private String ruleKey;
/** 规则描述 */
private String ruleDesc;
/** 规则比值 */
private String ruleValue;
/** 规则连线 */
private List<RuleTreeNodeLineVO> treeNodeLineVOList;
}
这是连接对象:
java
//规则树节点指向线对象。用于衔接 from->to 节点链路关系
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RuleTreeNodeLineVO {
/** 规则树ID */
private Integer treeId;
/** 规则Key节点 From */
private String ruleNodeFrom;
/** 规则Key节点 To */
private String ruleNodeTo;
/** 限定类型;1:=;2:>;3:<;4:>=;5<=;6:enum[枚举范围] */
private RuleLimitTypeVO ruleLimitType;
/** 限定值(到下个节点) */
private RuleLogicCheckTypeVO ruleLimitValue;
}
(2)获取基础信息
java
//获取根节点名
String nextNode = ruleTreeVO.getTreeRootRuleNode();
//从接口map中拿出所有的节点
Map<String, RuleTreeNodeVO> treeNodeMap = ruleTreeVO.getTreeNodeMap();
//根据根节点名获取根节点属性
RuleTreeNodeVO ruleTreeNode = treeNodeMap.get(nextNode);
(3)树型处理(核心方法)
我已经把每一步都写上注释了,应该很容易就能看懂了:
java
while (null != nextNode) {
//1.根据节点的规则名找到map中注入的规则处理节点
ILogicTreeNode logicTreeNode = logicTreeNodeGroup.get(ruleTreeNode.getRuleKey());
//2.规则处理,得到一个处理结果------封装到TreeActionEntity中
DefaultTreeFactory.TreeActionEntity logicEntity = logicTreeNode.logic(userId, strategy, awardId);
//3.查看是放行还是接管(用于选择后续的路由)
RuleLogicCheckTypeVO ruleLogicCheckTypeVO = logicEntity.getRuleLogicCheckType();
//4.更新处理结果
strategyAwardData = logicEntity.getStrategyAwardData();
log.info("决策树引擎【{}】treeId:{} node:{} code:{}", ruleTreeVO.getTreeName(), ruleTreeVO.getTreeId(), nextNode, ruleLogicCheckTypeVO.getCode());
//5.节点迭代
nextNode = nextNode(ruleLogicCheckTypeVO.getCode(), ruleTreeNode.getTreeNodeLineVOList());
ruleTreeNode = treeNodeMap.get(nextNode);
}
(4)返回结果
java
//返回最终结果
return strategyAwardData;
(5)关于节点迭代
nextNode是节点与节点之间的连线,只有满足特定条件才会走这一条,这里注意,在抽奖开始前,这一整棵规则树一定是创建好了的,包括里面各个节点、连线的顺序都是全部确定好了的,所有这个选择路由其实是固定指向了一个固定节点的。
java
//寻找下一个节点(选择路由)
private String nextNode(String matterValue, List<RuleTreeNodeLineVO> ruleTreeNodeLineVOList) {
//1.判断是否为叶子节点,如果是叶子节点,那么就没有连线了
if (null == ruleTreeNodeLineVOList || ruleTreeNodeLineVOList.isEmpty()) return null;
//2.遍历所有连线,找到第一根条件匹配的连线,走匹配的连线
for (RuleTreeNodeLineVO nodeLine : ruleTreeNodeLineVOList) {
if (decisionLogic(matterValue, nodeLine)) {
return nodeLine.getRuleNodeTo();
}
}
throw new RuntimeException("决策树引擎,nextNode 计算失败,未找到可执行节点");
}
//条件选择器,规定走当前连线的条件,对比条件后返回一个布尔值来表示 走or不走
public boolean decisionLogic(String matterValue, RuleTreeNodeLineVO nodeLine) {
switch (nodeLine.getRuleLimitType()) {
//对应枚举类中的数
case EQUAL:
return matterValue.equals(nodeLine.getRuleLimitValue().getCode());
// 以下规则暂时不需要实现
case GT:
case LT:
case GE:
case LE:
default:
return false;
}
}
3.节点处理实现
树中的三种节点实现:
次数锁:
java
//次数锁节点
@Slf4j
@Component("rule_lock")
public class RuleLockLogicTreeNode implements ILogicTreeNode {
@Override
public DefaultTreeFactory.TreeActionEntity logic(String userId, Long strategyId, Integer awardId) {
return DefaultTreeFactory.TreeActionEntity.builder()
.ruleLogicCheckType(RuleLogicCheckTypeVO.ALLOW)
.build();
}
}
兜底奖励:
java
//兜底奖励节点
@Slf4j
@Component("rule_luck_award")
public class RuleLuckAwardLogicTreeNode implements ILogicTreeNode {
@Override
public DefaultTreeFactory.TreeActionEntity logic(String userId, Long strategyId, Integer awardId) {
return DefaultTreeFactory.TreeActionEntity.builder()
.ruleLogicCheckType(RuleLogicCheckTypeVO.TAKE_OVER)
.strategyAwardData(DefaultTreeFactory.StrategyAwardData.builder()
.awardId(101)
.awardRuleValue("1,100")
.build())
.build();
}
}
库存:
java
//库存节点
@Slf4j
@Component("rule_stock")
public class RuleStockLogicTreeNode implements ILogicTreeNode {
@Override
public DefaultTreeFactory.TreeActionEntity logic(String userId, Long strategyId, Integer awardId) {
return DefaultTreeFactory.TreeActionEntity.builder()
.ruleLogicCheckType(RuleLogicCheckTypeVO.TAKE_OVER)
.build();
}
}
4.规则树工厂
刚刚我们主要是在讲核心规则树引擎类,而这个规则树工厂是用来生产规则树引擎的,同时提供了规则树引擎需要使用的数据类型。
为什么要把这些数据类型放在这个规则工厂?
因为这样可以告诉其他类:"这些数据类型是专门为这个工厂及其产出的引擎服务的,不要在其他地方乱用。"
同时当我们需要使用这些静态内部类时,就必须写DefaultTreeFactory.TreeActionEntity,使这些静态类的从属关系一目了然。
java
/**
* @author 印东升
* @description 规则树工厂
* @create 2026-04-15 11:34
*/
@Service
public class DefaultTreeFactory {
private final Map<String, ILogicTreeNode> logicTreeNodeMap;
public DefaultTreeFactory(Map<String, ILogicTreeNode> logicTreeNodeMap) {
this.logicTreeNodeMap = logicTreeNodeMap;
}
public IDecisionTreeEngine openLogicTree(RuleTreeVO ruleTreeVO) {
return new DecisionTreeEngine(logicTreeNodeMap, ruleTreeVO);
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class TreeActionEntity {
private RuleLogicCheckTypeVO ruleLogicCheckType;
private StrategyAwardData strategyAwardData;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class StrategyAwardData {
/**
* 抽奖奖品ID - 内部流转使用
*/
private Integer awardId;
/**
* 抽奖奖品规则
*/
private String awardRuleValue;
}
}
三、测试
先构建树的流程,然后构建树的属性,最后测试:
java
/**
* @author 印东升
* @description 规则树测试
* @create 2026-04-15 13:00
*/
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class LogicTreeTest {
@Resource
private DefaultTreeFactory defaultTreeFactory;
/**
* rule_lock --左--> rule_luck_award
* --右--> rule_stock --右--> rule_luck_award
*/
@Test
public void test_tree_rule() {
// 构建规则树整体流程
RuleTreeNodeVO rule_lock = RuleTreeNodeVO.builder()
.treeId(100000001)
.ruleKey("rule_lock")
.ruleDesc("限定用户已完成N次抽奖后解锁")
.ruleValue("1")
.treeNodeLineVOList(new ArrayList<RuleTreeNodeLineVO>() {{
add(RuleTreeNodeLineVO.builder()
.treeId(100000001)
.ruleNodeFrom("rule_lock")
.ruleNodeTo("rule_luck_award")
.ruleLimitType(RuleLimitTypeVO.EQUAL)
.ruleLimitValue(RuleLogicCheckTypeVO.TAKE_OVER)
.build());
add(RuleTreeNodeLineVO.builder()
.treeId(100000001)
.ruleNodeFrom("rule_lock")
.ruleNodeTo("rule_stock")
.ruleLimitType(RuleLimitTypeVO.EQUAL)
.ruleLimitValue(RuleLogicCheckTypeVO.ALLOW)
.build());
}})
.build();
RuleTreeNodeVO rule_luck_award = RuleTreeNodeVO.builder()
.treeId(100000001)
.ruleKey("rule_luck_award")
.ruleDesc("限定用户已完成N次抽奖后解锁")
.ruleValue("1")
.treeNodeLineVOList(null)
.build();
RuleTreeNodeVO rule_stock = RuleTreeNodeVO.builder()
.treeId(100000001)
.ruleKey("rule_stock")
.ruleDesc("库存处理规则")
.ruleValue(null)
.treeNodeLineVOList(new ArrayList<RuleTreeNodeLineVO>() {{
add(RuleTreeNodeLineVO.builder()
.treeId(100000001)
.ruleNodeFrom("rule_lock")
.ruleNodeTo("rule_luck_award")
.ruleLimitType(RuleLimitTypeVO.EQUAL)
.ruleLimitValue(RuleLogicCheckTypeVO.TAKE_OVER)
.build());
}})
.build();
//构建规则树属性
RuleTreeVO ruleTreeVO = new RuleTreeVO();
ruleTreeVO.setTreeId(100000001);
ruleTreeVO.setTreeName("决策树规则;增加dall-e-3画图模型");
ruleTreeVO.setTreeDesc("决策树规则;增加dall-e-3画图模型");
ruleTreeVO.setTreeRootRuleNode("rule_lock");
ruleTreeVO.setTreeNodeMap(new HashMap<String, RuleTreeNodeVO>() {{
put("rule_lock", rule_lock);
put("rule_stock", rule_stock);
put("rule_luck_award", rule_luck_award);
}});
IDecisionTreeEngine treeEngine = defaultTreeFactory.openLogicTree(ruleTreeVO);
DefaultTreeFactory.StrategyAwardData data = treeEngine.process("yds", 100001L, 100);
log.info("测试结果:{}", JSON.toJSONString(data));
}
}