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

image

 

  • 本节课对之前知识点进行串联

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 可以有很多实现,运行时根据注入的是哪个,表现不同。和 ILogicChainILogicTreeNode 一模一样 —— 你项目里 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() 返回的实际结果去和线上规定的值比对,匹配上了就沿这条线走到目标节点。

posted @ 2026-07-24 11:36  超级无敌大帅比  阅读(0)  评论(0)    收藏  举报