第9节:模板模式串联抽奖规则

-
本节课对之前知识点进行串联
performRaffle(RaffleFactorEntity{userId="xiaofuge", strategyId=100006})
│
├─ 📦 阶段一:责任链抽奖
│
├─ raffleLogicChain("xiaofuge", 100006)
│ │
│ ├─ defaultChainFactory.openLogicChain(100006)
│ │ ├─ repository.queryStrategyEntityByStrategyId(100006)
│ │ │ └─ SELECT * FROM strategy WHERE strategy_id=100006
│ │ │ └─ ruleModels = NULL(策略100006未配置责任链规则)
│ │ ├─ ruleModels 为空 → 直接取默认链
│ │ └─ return logicChainGroup.get("default") → DefaultLogicChain
│ │
│ └─ DefaultLogicChain.logic("xiaofuge", 100006)
│ ├─ strategyDispatch.getRandomAwardId(100006)
│ │ └─ 从策略100006的奖品池中按概率随机抽取 → 返回 102
│ └─ return StrategyAwardVO{awardId=102, logicModel="rule_default"}
│
├─ chainStrategyAwardVo = {awardId=102, logicModel="rule_default"}
├─ 判断:logicModel == "rule_default" → 是默认结果,不提前返回,进入规则树
│
│
├─ 🌲 阶段二:规则树(决策树)抽奖
│
├─ raffleLogicTree("xiaofuge", 100006, 102)
│ │
│ ├─ repository.queryStrategyAwardRuleModel(100006, 102)
│ │ └─ SELECT rule_models FROM strategy_award
│ │ WHERE strategy_id=100006 AND award_id=102
│ │ └─ ruleModels = "tree_lock"
│ │
│ ├─ repository.queryRuleTreeVOByTreeId("tree_lock")
│ │ ├─ 查 rule_tree → tree_id=tree_lock, tree_node_rule_key=rule_lock
│ │ ├─ 查 rule_tree_node → 3个节点(rule_lock / rule_stock / rule_luck_award)
│ │ ├─ 查 rule_tree_node_line → 3条连线:
│ │ │ ├─ rule_lock ──[EQUAL:ALLOW]──▶ rule_stock 当前在 rule_lock,如果它的 logic() 返回 ALLOW(放行),就走到 rule_stock
│ │ │ ├─ rule_lock ──[EQUAL:TAKE_OVER]──▶ rule_luck_award 当前在 rule_lock,如果返回 TAKE_OVER(接管/拦截),就走到 rule_luck_award
│ │ │ └─ rule_stock ──[EQUAL:TAKE_OVER]──▶ rule_luck_award 当前在 rule_stock,如果返回 TAKE_OVER,就走到 rule_luck_award
│ │ └─ 组装成 RuleTreeVO{treeRootRuleNode="rule_lock", treeNodeMap={...}}
│ │
│ ├─ defaultTreeFactory.openLogicTree(ruleTreeVO)
│ │ └─ return new DecisionTreeEngine(logicTreeNodeGroup, ruleTreeVO)
│ │
│ └─ treeEngine.process("xiaofuge", 100006, 102)
│ │
│ ├─ nextNode = "rule_lock"(根节点)
│ ├─ ruleTreeNode = treeNodeMap.get("rule_lock")
│ │
│ ├─ 🔄 第1轮循环:node = rule_lock
│ │ ├─ logicTreeNode = logicTreeNodeGroup.get("rule_lock")
│ │ │ └─ RuleLockLogicTreeNode
│ │ ├─ logicTreeNode.logic() → {ruleLogicCheckType=ALLOW, strategyAwardVO=null}
│ │ ├─ strategyAwardData = null(次数锁节点不设置奖品)
│ │ ├─ nextNode("ALLOW", [①→rule_stock(EQUAL:ALLOW), ②→rule_luck_award(EQUAL:TAKE_OVER)])
│ │ │ ├─ 遍历第1条线:rule_limit_type=EQUAL, rule_limit_value=ALLOW
│ │ │ │ └─ decisionLogic("ALLOW", 这条线)
│ │ │ │ └─ "ALLOW".equals("ALLOW") → true → return "rule_stock"
│ │ │ └─ nextNode = "rule_stock"
│ │ └─ ruleTreeNode = treeNodeMap.get("rule_stock")
│ │
│ ├─ 🔄 第2轮循环:node = rule_stock
│ │ ├─ logicTreeNode = logicTreeNodeGroup.get("rule_stock")
│ │ │ └─ RuleStockLogicTreeNode
│ │ ├─ logicTreeNode.logic() → {ruleLogicCheckType=TAKE_OVER, strategyAwardVO=null}
│ │ ├─ strategyAwardData = null(库存节点也不设置奖品)
│ │ ├─ nextNode("TAKE_OVER", [③→rule_luck_award(EQUAL:TAKE_OVER)])
│ │ │ ├─ 遍历第3条线:rule_limit_type=EQUAL, rule_limit_value=TAKE_OVER
│ │ │ │ └─ decisionLogic("TAKE_OVER", 这条线)
│ │ │ │ └─ "TAKE_OVER".equals("TAKE_OVER") → true → return "rule_luck_award"
│ │ │ └─ nextNode = "rule_luck_award"
│ │ └─ ruleTreeNode = treeNodeMap.get("rule_luck_award")
│ │
│ ├─ 🔄 第3轮循环:node = rule_luck_award
│ │ ├─ logicTreeNode = logicTreeNodeGroup.get("rule_luck_award")
│ │ │ └─ RuleLuckAwardLogicTreeNode
│ │ ├─ logicTreeNode.logic() → {ruleLogicCheckType=TAKE_OVER, strategyAwardVO={awardId=101, awardRuleValue="1,100"}}
│ │ ├─ strategyAwardData = {awardId=101, awardRuleValue="1,100"}(兜底节点写入最终奖品)
│ │ ├─ nextNode("TAKE_OVER", null/空列表)
│ │ │ └─ rule_luck_award 无后续连线 → return null
│ │ │ └─ nextNode = null
│ │ └─ ruleTreeNode = treeNodeMap.get(null) = null
│ │
│ └─ while(null != null) → false → 退出循环
│ └─ return strategyAwardData → {awardId=101, awardRuleValue="1,100"}
│
├─ treeStrategyAwardVo = {awardId=101, awardRuleValue="1,100"}
│
└─ return RaffleAwardEntity{awardId=101, awardConfig="1,100"} ✅
-
什么时候会进入规则树 什么时候不进入规则树
答:
从 AbstractRaffleStrategy.performRaffle() 的代码直接就能看出来:
责任链抽奖 → 拿到 chainStrategyAwardVo
│
├─ logicModel != "rule_default" → return 直接返回 → ❌ 不进入规则树
│ (被黑名单/权重等责任链"接管"了,提前结束)
│
└─ logicModel == "rule_default" → 继续往下走 → ✅ 进入规则树
(责任链没拦截,走默认链随机抽了个奖品,需要规则树来审核)
具体场景
| 场景 | 责任链结果 | 是否进入规则树 | 原因 |
|---|---|---|---|
| 黑名单用户(user003 抽 100001) | {logicModel="rule_blacklist"} |
❌ 不进入 | 黑名单链直接拦截,返回固定奖品 |
| 权重命中(xiaofuge 分数够 抽 100001) | {logicModel="rule_weight"} |
❌ 不进入 | 权重链接管,返回权重范围内的奖品 |
| 普通用户 + 策略无链规则(100006) | {logicModel="rule_default"} |
✅ 进入 | 没配责任链,默认链随机抽,进规则树审核 |
| 普通用户 + 链都未命中(无黑名单/权重) | {logicModel="rule_default"} |
✅ 进入 | 链走到底,默认链兜底,进规则树审核 |
一句话:责任链是"拦截器",拦截到了就当场返回,拦截不到才交给规则树做"事后审核"。
对应的代码逻辑
// ① 责任链先跑
chainStrategyAwardVo = raffleLogicChain(userId, strategyId);
// ② 被拦截(黑名单/权重)→ 直接返回,不走规则树
if (logicModel != "rule_default") {
return RaffleAwardEntity{...}; // ← 这里就结束了
}
// ③ 没被拦截 → 进规则树审核拿到的奖品
treeStrategyAwardVo = raffleLogicTree(userId, strategyId, chainStrategyAwardVo.getAwardId());
return RaffleAwardEntity{...};
-
使用抽象父类的好处是什么 ?使用接口,实现了接口 最后定义了时候定义接口比如 @Resource private IRaffleStrategy raffleStrategy;的好处是什么?
一、抽象父类(AbstractRaffleStrategy)的好处
以你项目里 performRaffle 为例:
// AbstractRaffleStrategy(抽象父类)
public RaffleAwardEntity performRaffle(RaffleFactorEntity raffleFactorEntity) {
// ① 责任链抽奖
DefaultChainFactory.StrategyAwardVO chainVo = raffleLogicChain(...);
// ② 判断要不要进规则树
if (逻辑不是默认) return ...;
// ③ 规则树抽奖
DefaultTreeFactory.StrategyAwardVO treeVo = raffleLogicTree(...);
// ④ 组装返回
return RaffleAwardEntity.builder()...;
}
// 这两个交给子类做
public abstract raffleLogicChain(...);
public abstract raffleLogicTree(...);
好处 1:模板方法模式 —— 固定"流程骨架",开放"可变步骤"
performRaffle 的 4 步顺序是固定的(先责任链、再判断、再规则树、最后组装)。如果每次抽奖都重写一遍,10 个子类就有 10 份重复代码。抽象父类把"不变的部分"写死,“会变的部分”(链路怎么组装、树怎么查)留成 abstract 让子类填。
好处 2:约束子类必须实现某些方法
abstract 方法强制子类(DefaultRaffleStrategy)必须提供 raffleLogicChain 和 raffleLogicTree 的实现,否则编译不过。等于父类给子类定了"契约"。
好处 3:代码复用 + 唯一修改点
流程逻辑只在父类一处。哪天你想在返回前加个日志、加个兜底,只改 AbstractRaffleStrategy 一处,所有子类生效。
这和你之前问过的
AbstractLogicChain是同一个套路:父类管"next 指针串联"这种公共逻辑,子类只管各自的logic()业务。JDK 的AbstractList也是这么干的(骨架实现模式)。
二、面向接口编程(@Resource private IRaffleStrategy raffleStrategy)的好处
// 调用方(trigger / test)
@Resource
private IRaffleStrategy raffleStrategy; // ← 类型是接口,不是具体类
raffleStrategy.performRaffle(raffleFactorEntity);
好处 1:解耦 —— 调用方不依赖具体实现
调用方只认 IRaffleStrategy 这个"能力契约",不认 DefaultRaffleStrategy。哪天你写个新实现(比如 MockRaffleStrategy 用于测试、V2RaffleStrategy 换算法),调用方一行都不用改。
好处 2:可替换 / 可扩展
Spring 注入时,只要容器里有个 IRaffleStrategy 的实现类就行。换实现 = 换 @Component 类,业务逻辑层无感。
好处 3:便于测试(Mock)
测试时你可以轻易用 Mock 对象替换掉真实实现,只测调用方的逻辑,不用真的跑一遍责任链+规则树。
好处 4:多态
IRaffleStrategy 可以有很多实现,运行时根据注入的是哪个,表现不同。和 ILogicChain、ILogicTreeNode 一模一样 —— 你项目里 Map<String, ILogicTreeNode> logicTreeNodeGroup,key 是 rule_lock/rule_stock,value 是各自的实现类,工厂按 key 取不同实现,这就是接口多态的威力。
三、抽象类 vs 接口,怎么选?
| 抽象类(AbstractRaffleStrategy / AbstractLogicChain) | 接口(IRaffleStrategy / ILogicChain) | |
|---|---|---|
| 核心作用 | 复用代码 —— 父类写公共逻辑 | 定义契约 —— 只规定"能做什么" |
| 关系 | is-a(DefaultRaffleStrategy 是一个抽奖策略) | like-a / can-do(能执行抽奖、能处理逻辑) |
| 典型用法 | 模板方法:父类定流程,子类填空 | 面向接口注入:调用方依赖抽象不依赖实现 |
| 你项目里的例子 | 父类 performRaffle 复用,子类只填 2 个方法 |
@Resource IRaffleStrategy 注入、Map<String, ILogicTreeNode> 多态分发 |
一句话总结:
- 抽象类解决"多个子类有相同流程/公共代码,但某些步骤不同" → 省代码、定骨架
- 接口解决"调用方不该知道具体是谁,只该知道能干什么" → 解耦、可替换、好测试
-
tree比较的逻辑?
// DecisionTreeEngine.nextNode()
// matterValue = ruleLogicCheckType.getCode()(logic() 返回的"结果值")
// 再用这个结果值和每一条"线"上规定的 rule_limit_value 比对
if (decisionLogic(matterValue, nodeLine)) {
return nodeLine.getRuleNodeTo(); // 比对上了就走这条线
}
// decisionLogic()
case EQUAL:
return matterValue.equals(nodeLineVO.getRuleLimitValue().getCode());
// 逻辑结果 == 线上规定的值?
所以每一条线的含义是:
rule_lock ──[EQUAL:ALLOW]──▶ rule_stock
↑ ↑
源节点:当前在 rule_lock 目的节点:匹配上了就去 rule_stock
↑
线上规定的:如果 logic() 返回的结果 == ALLOW,就走这条线
逐条解释:
| 连线 | 含义 |
|---|---|
rule_lock ──[EQUAL:ALLOW]──▶ rule_stock |
当前在 rule_lock,如果它的 logic() 返回 ALLOW(放行),就走到 rule_stock |
rule_lock ──[EQUAL:TAKE_OVER]──▶ rule_luck_award |
当前在 rule_lock,如果返回 TAKE_OVER(接管/拦截),就走到 rule_luck_award |
rule_stock ──[EQUAL:TAKE_OVER]──▶ rule_luck_award |
当前在 rule_stock,如果返回 TAKE_OVER,就走到 rule_luck_award |
在你的测试场景里实际执行:
第1轮:rule_lock.logic() → ALLOW
match 第1条线 EQUAL:ALLOW → 走到 rule_stock ✅
第2轮:rule_stock.logic() → TAKE_OVER
match 第3条线 EQUAL:TAKE_OVER → 走到 rule_luck_award ✅
第3轮:rule_luck_award.logic() → TAKE_OVER → 无后续连线 → 结束 ✅
核心理解:每一条线的写法是 源节点 ──[比对方式:线上规定的值]──▶ 目标节点,线上左边的 EQUAL(等于)是比对方式,右边的 ALLOW/TAKE_OVER 是线上规定的值。用源节点 logic() 返回的实际结果去和线上规定的值比对,匹配上了就沿这条线走到目标节点。

浙公网安备 33010602011771号