第5节:抽奖前置规则过滤

这节课主要学习了在抽奖前对规则进行过滤,

  • 说明

相关的实体类:

  • RaffleFactorEntity — 抽奖入参(userId, strategyId)
  • RaffleAwardEntity — 抽奖结果(awardId)
  • RuleMatterEntity — 规则过滤的入参
  • RuleActionEntity — 规则过滤的结果(code=ALLOW/TAKE_OVER, data=规则返回的数据)
  • RuleLogicCheckTypeVO — 规则结果枚举(ALLOW 放行, TAKE_OVER 接管)

相关的接口作用:

1. IRaffleStrategy — 抽奖策略接口

定义了一个方法:

RaffleAwardEntity performRaffle(RaffleFactorEntity raffleFactorEntity);
入参是"谁在抽、抽哪个策略",出参是"抽到了什么奖品"。这是对外暴露的唯一入口。

2. AbstractRaffleStrategy — 抽奖流程模板

定义了抽奖的固定步骤

performRaffle() {
1. 校验参数
2. 查策略配置
3. 规则过滤(抽象方法,子类实现)
4. 根据规则结果抽奖
}

第 3 步是抽象的,交给子类实现。这样流程骨架固定,但规则过滤的逻辑可以灵活替换。

3. DefaultRaffleStrategy — 默认的规则过滤实现

实现了 doCheckRaffleBeforeLogic,里面做了两件事:

  1. 黑名单优先过滤 — 先查用户是不是黑名单,是就直接返回固定奖品
  2. 其他规则顺序过滤 — 比如权重规则,判断用户积分能命中哪个档位

4. ILogicFilter — 规则过滤接口

RuleActionEntity<T> filter(RuleMatterEntity ruleMatterEntity);

定义了一个规则过滤器的通用接口:传入规则需要的参数,返回过滤结果。

5. RuleBackListLogicFilter — 黑名单过滤器

实现 ILogicFilter,查数据库的黑名单配置,判断当前用户是否在黑名单里。

6. RuleWeightLogicFilter — 权重过滤器

实现 ILogicFilter,查数据库的权重配置,判断用户积分能命中哪个权重档位。

7. DefaultLogicFactory — 规则工厂

启动时自动收集所有的 ILogicFilter 实现类,注册成一个 Map:

"rule_blacklist" → RuleBackListLogicFilter
"rule_weight" → RuleWeightLogicFilter

这样 DefaultRaffleStrategy 要执行某个规则时,直接从 Map 里按规则名取,不需要写 if-else。

  • 执行流程如下:

一、@Before 阶段:setUp()

@Before
public void setUp() {
ReflectionTestUtils.setField(ruleWeightLogicFilter, "userScore", 40500L);
}

这行用 Spring 的反射工具把 RuleWeightLogicFilter 里的 userScore 字段从默认的 4500L 改成了 40500LuserScore 模拟的是用户的积分值,用来判断用户能命中哪个权重档位。


二、test_performRaffle 方法

构造了一个抽奖因子:

RaffleFactorEntity raffleFactorEntity = RaffleFactorEntity.builder()
.userId("xiaofuge") // 普通用户,不在黑名单里
.strategyId(100001L)
.build();

然后调用 raffleStrategy.performRaffle(raffleFactorEntity)


三、performRaffle 内部执行流程

这个方法在 AbstractRaffleStrategy 中定义,是整个抽奖的核心流程。分 4 步:

第 1 步:参数校验

String userId = raffleFactorEntity.getUserId();
Long strategyId = raffleFactorEntity.getStrategyId();
if (null == strategyId || StringUtils.isBlank(userId)) {
throw new AppException(...);
}

校验通过,继续。

第 2 步:查询策略配置

StrategyEntity strategy = repository.queryStrategyEntityByStrategyId(strategyId);

第 3 步:抽奖前规则过滤

RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> ruleActionEntity =
this.doCheckRaffleBeforeLogic(
RaffleFactorEntity.builder().userId(userId).strategyId(strategyId).build(),
strategy.ruleModels() // 传入 ["rule_weight,rule_blacklist"]
);

这里调用 DefaultRaffleStrategy.doCheckRaffleBeforeLogic,执行规则过滤。

规则过滤内部逻辑(DefaultRaffleStrategy):

(a) 黑名单优先过滤

从 logics 参数(["rule_weight,rule_blacklist"])中查找是否包含 "rule_blacklist"

找到后,从 DefaultLogicFactory 的 Map 中取出 RuleBackListLogicFilter,执行它的 filter 方法:

  • 查询 strategy_rule 表,获取黑名单规则值,比如 "101:user001,user002,user003"
  • 解析出黑名单用户列表 [user001, user002, user003]
  • 当前用户是 "xiaofuge",不在黑名单中
  • 返回 ALLOW(放行)

因为返回的是 ALLOW,所以不走 if (!ALLOW.equals(code)) return,继续执行。

(b) 顺序过滤剩余规则

排除黑名单后,剩余规则列表是 ["rule_weight,rule_blacklist"],过滤掉黑名单后得到 ["rule_weight,rule_blacklist"](实际上这个过滤逻辑有点问题,但先按 big-market 的正确逻辑走)。

实际上 big-market 的代码是:

List<String> ruleList = Arrays.stream(logics)
.filter(s -> !s.equals(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode()))
.collect(Collectors.toList());

这里 logics 只有一个元素 "rule_weight,rule_blacklist",它不等于 "rule_blacklist",所以被保留。但这里用 equals 而不是 contains,所以实际上 "rule_weight,rule_blacklist" 不等于 "rule_blacklist",会被保留下来。

然后遍历 ruleList,对每个规则名从 Map 中取出对应的 ILogicFilter 执行。

轮到 rule_weight 时,取出 RuleWeightLogicFilter,执行它的 filter

  • 查询 strategy_rule 表,获取权重规则值,比如 "4000:102,103,104,105 5000:102,103,104,105,106,107 6000:102,103,104,105,106,107,108,109"
  • 解析成 Map:{4000 → "4000:102,103,104,105", 5000 → "...", 6000 → "..."}
  • 对 key 排序:[4000, 5000, 6000]
  • 当前用户积分是 40500L@Before 中设置的),从大到小找第一个 userScore >= key 的值
  • 40500 >= 4000 → 命中 4000 档位
  • 返回 TAKE_OVER,携带 ruleWeightValueKey = "4000:102,103,104,105"

因为返回的是 TAKE_OVERdoCheckRaffleBeforeLogic 直接 return 这个结果,不再继续过滤。

第 4 步:根据规则结果抽奖

回到 performRaffle,判断 ruleActionEntity.getCode()

if (RuleLogicCheckTypeVO.TAKE_OVER.getCode().equals(ruleActionEntity.getCode())) {
// code 是 TAKE_OVER
if (DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode().equals(ruleActionEntity.getRuleModel())) {
// 黑名单 → 固定奖品
} else if (DefaultLogicFactory.LogicModel.RULE_WIGHT.getCode().equals(ruleActionEntity.getRuleModel())) {
// 权重 → 按权重 key 抽奖
RuleActionEntity.RaffleBeforeEntity raffleBeforeEntity = ruleActionEntity.getData();
String ruleWeightValueKey = raffleBeforeEntity.getRuleWeightValueKey(); // "4000:102,103,104,105"
Integer awardId = strategyDispatch.getRandomAwardId(strategyId, ruleWeightValueKey);
// 从 Redis 的权重表中随机抽一个奖品
return RaffleAwardEntity.builder().awardId(awardId).build();
}
}

因为 ruleModel 是 "rule_weight",走权重分支:

  1. 取出 ruleWeightValueKey = "4000:102,103,104,105"
  2. 调用 strategyDispatch.getRandomAwardId(100001L, "4000:102,103,104,105")
  3. 这个方法拼接 key 为 "100001_4000:102,103,104,105",从 Redis 的权重概率表中随机取一个奖品 ID
  4. 最终返回这个奖品 ID

四、test_performRaffle_blacklist 的区别

这个测试用户是 "user003",在黑名单中。流程到第 3 步 (a) 时:

  • RuleBackListLogicFilter.filter 发现 user003 在黑名单里
  • 返回 TAKE_OVER,携带 ruleModel = "rule_blacklist" 和 data.awardId = 101
  • doCheckRaffleBeforeLogic 直接 return,不走权重规则
  • performRaffle 走黑名单分支,直接返回固定奖品 ID 101

总结test_performRaffle 测试的是带权重的正常用户抽奖,经过黑名单过滤(放行)→ 权重过滤(命中 4000 档位)→ 从权重概率表中随机抽奖。test_performRaffle_blacklist 测试的是黑名单用户,直接被拦截返回固定奖品。

相比 231231-xfg-strategy-armory-rule-weight 分支多出来的代码就是这套规则过滤引擎IRaffleStrategyAbstractRaffleStrategyDefaultRaffleStrategyDefaultLogicFactoryILogicFilter、两个 Filter 实现类,以及相关的实体类。它们把"先过滤规则再抽奖"这个流程从 StrategyArmoryDispatch 中抽离出来,变成了一个独立的抽奖策略层。

 

 

 
  • 遇到的问题

1.LogicStrategy这个注解的作用是什么 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME)这两行的代码是干嘛的 ?

答:@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogicStrategy {
    DefaultLogicFactory.LogicModel logicMode();
}

  • @Target({ElementType.TYPE}) — 这个注解只能写在类/接口/枚举上面,不能写在方法或字段上
  • @Retention(RetentionPolicy.RUNTIME) — 这个注解在运行时仍然保留,可以通过反射读取到

作用:给每个规则过滤实现类(如 RuleWeightLogicFilter)打标签,标记它属于哪种规则模式。

比如 RuleWeightLogicFilter 上写着 @LogicStrategy(logicMode = DefaultLogicFactory.LogicModel.RULE_WIGHT),意思就是"我是权重规则过滤类"。

然后在 DefaultLogicFactory 的构造方法里,通过反射扫描所有 ILogicFilter 实现类,读取这个注解,自动把规则类注册到 Map<String, ILogicFilter> 中,key 就是 rule_weight 或 rule_blacklist。这样新增一种规则,只需要写一个实现类加上注解,不需要改工厂代码。

2.AbstractRaffleStrategy这个的作用是什么

答:这是一个模板方法模式的抽象类,定义了抽奖的标准流程骨架

performRaffle(抽奖因子) {
1. 参数校验
2. 查询策略配置
3. 抽奖前规则过滤(模板方法,子类实现)
4. 根据规则结果抽奖
}

关键设计:第 3 步 doCheckRaffleBeforeLogic 声明为 protected abstract,交给子类 DefaultRaffleStrategy 去实现具体怎么过滤规则。这样抽奖的整体流程是固定的,但规则过滤的逻辑可以灵活替换。

流程拆解performRaffle 方法内):

  1. 校验 userId、strategyId
  2. 查策略实体(获取 ruleModels
  3. 执行规则过滤 doCheckRaffleBeforeLogic,返回 RuleActionEntity
  4. 根据过滤结果判断:
    • 如果被黑名单接管 → 直接返回固定奖品
    • 如果被权重接管 → 按权重 key 去 Redis 权重表中抽奖
    • 如果放行(ALLOW)→ 走基础概率表抽奖

3.DefaultRaffleStrategy是干嘛的?

并且我不知道 String ruleBackList = Arrays.stream(logics) .filter(str -> str.contains(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode())) .findFirst() .orElse(null); 这里的str是哪里来的

答:

先看 doCheckRaffleBeforeLogic 的完整代码,

@Override
protected RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> doCheckRaffleBeforeLogic(
RaffleFactorEntity raffleFactorEntity, String... logics) {
// ...
}

String... logics 是可变参数,本质是个数组。调用它的地方在 AbstractRaffleStrategy.performRaffle

this.doCheckRaffleBeforeLogic(
RaffleFactorEntity.builder().userId(userId).strategyId(strategyId).build(),
strategy.ruleModels() // ← 这里传入的,比如 "rule_weight,rule_blacklist"
);

strategy.ruleModels() 是从数据库 strategy 表查出来的 rule_models 字段值,比如 "rule_weight,rule_blacklist"

所以 logics 就是一个包含一个元素的数组:["rule_weight,rule_blacklist"]

 
String ruleBackList = Arrays.stream(logics)
.filter(str -> str.contains(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode()))
.findFirst()
.orElse(null);

str 是 Arrays.stream(logics) 流出来的每个元素logics 是个数组,Arrays.stream(logics) 把它转成 Stream,流里的每个元素就是数组里的一个值。

这里 logics 只有一个元素 "rule_weight,rule_blacklist",所以 str 就是 "rule_weight,rule_blacklist"

filter 判断这个字符串是否包含 "rule_blacklist",包含则保留。

整体意思:从传入的规则列表中,先找出有没有黑名单规则。如果有,优先执行黑名单过滤。没有就返回 null。

4.doCheckRaffleBeforeLogic的过滤逻辑?

@Override
protected RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> doCheckRaffleBeforeLogic(
RaffleFactorEntity raffleFactorEntity, String... logics) {
 
Map<String, ILogicFilter<RuleActionEntity.RaffleBeforeEntity>> logicFilterGroup = logicFactory.openLogicFilter();
 
// 第一步:黑名单规则优先过滤
String ruleBackList = Arrays.stream(logics)
.filter(str -> str.contains(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode()))
.findFirst()
.orElse(null);
 
if (StringUtils.isNotBlank(ruleBackList)) {
ILogicFilter<RuleActionEntity.RaffleBeforeEntity> logicFilter = logicFilterGroup.get(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode());
RuleMatterEntity ruleMatterEntity = new RuleMatterEntity();
ruleMatterEntity.setUserId(raffleFactorEntity.getUserId());
ruleMatterEntity.setAwardId(ruleMatterEntity.getAwardId()); // 这里有个小bug,应该是 null
ruleMatterEntity.setStrategyId(raffleFactorEntity.getStrategyId());
ruleMatterEntity.setRuleModel(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode());
RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> ruleActionEntity = logicFilter.filter(ruleMatterEntity);
if (!RuleLogicCheckTypeVO.ALLOW.getCode().equals(ruleActionEntity.getCode())) {
return ruleActionEntity; // 被黑名单接管,直接返回,不走后面的流程
}
}
 
// 第二步:顺序过滤剩余规则(排除黑名单后的规则)
List<String> ruleList = Arrays.stream(logics)
.filter(s -> !s.equals(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode()))
.collect(Collectors.toList());
 
RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> ruleActionEntity = null;
for (String ruleModel : ruleList) {
ILogicFilter<RuleActionEntity.RaffleBeforeEntity> logicFilter = logicFilterGroup.get(ruleModel);
RuleMatterEntity ruleMatterEntity = new RuleMatterEntity();
ruleMatterEntity.setUserId(raffleFactorEntity.getUserId());
ruleMatterEntity.setAwardId(ruleMatterEntity.getAwardId()); // 同上
ruleMatterEntity.setStrategyId(raffleFactorEntity.getStrategyId());
ruleMatterEntity.setRuleModel(ruleModel);
ruleActionEntity = logicFilter.filter(ruleMatterEntity);
 
// 非放行结果则顺序过滤
log.info("抽奖前规则过滤 userId: {} ruleModel: {} code: {} info: {}", ...);
if (!RuleLogicCheckTypeVO.ALLOW.getCode().equals(ruleActionEntity.getCode()))
return ruleActionEntity; // 规则命中,返回接管结果
}
 
return ruleActionEntity; // 所有规则都放行,返回 ALLOW
}

第一步:黑名单优先

  • 在 logics 中查找是否包含 rule_blacklist
  • 如果有,先执行黑名单过滤
  • 如果用户命中了黑名单 → 返回 TAKE_OVER,直接返回固定奖品,后面的规则不再执行
  • 如果用户没命中黑名单 → 返回 ALLOW(放行),继续走第二步

第二步:顺序过滤剩余规则

  • 排除黑名单后,剩下的规则(如 rule_weight)按顺序逐个执行
  • 每个规则执行 logicFilter.filter(),判断结果:
    • 返回 ALLOW(放行)→ 继续执行下一个规则
    • 返回 TAKE_OVER(接管)→ 立即返回,后面的规则不再执行
  • 所有规则都放行 → 返回 ALLOW,走默认抽奖流程

为什么黑名单要优先? 黑名单是"一刀切"——命中就直接返回固定奖品,不需要再算权重。如果先算权重再查黑名单,那黑名单用户也参与了权重抽奖,浪费计算资源且逻辑不对。

为什么后续规则要顺序过滤? 当前只有 rule_weight 一个后续规则,所以只有一个。但设计上预留了多个规则按顺序执行的空间,比如以后加一个 rule_lock(抽奖几次后解锁某奖品),就可以插在列表里按顺序执行,任何一个命中就停止。

5.String ruleBackList = Arrays.stream(logics) .filter(str -> str.contains(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode())) .findFirst() .orElse(null);为什么这里要 .findFirst() 这里的意义是什么 不是获取了RULE_BLACKLIST就只有RULE_BLACKLIST了吗

从结果上看确实只有一个。但 findFirst() 在这里是写法习惯,不是逻辑必需。

Arrays.stream(logics).filter(...) 返回的是一个 Stream,里面可能包含零个、一个或多个匹配的元素。但 findFirst() 只取第一个匹配的,然后 Stream 就结束了。

这里用 findFirst() 而不是 .findAny() 或者 .collect(Collectors.toList()),原因:

  1. 结果只需要一个 — 代码只是想判断"有没有黑名单规则",有就行,不需要知道有几个
  2. 返回类型是 Optional<String> — findFirst() 返回 Optional,后面 .orElse(null) 表示没找到就返回 null。这样 if (StringUtils.isNotBlank(ruleBackList)) 就能区分"有黑名单"和"没有黑名单"

本质上这行代码等价于:

boolean hasBlacklist = false;
for (String str : logics) {
if (str.contains("rule_blacklist")) {
hasBlacklist = true;
break;
}
}

findFirst() 就相当于那个 break——找到一个就停,不继续遍历

 

6.if (!RuleLogicCheckTypeVO.ALLOW.getCode().equals(ruleActionEntity.getCode())) {
                return ruleActionEntity;
            }这个是如果不是allow的话 就把这个返回吗?返回到哪里  ?TAKE_OVER是不是在过滤器里 ?TAKE_OVER在过滤器里 那这里的allow又是否的话 这个是啥意思?返回的是什么东西有什么用?

 

第一个问题:!ALLOW.equals(code) 返回到哪里?

返回到 AbstractRaffleStrategy.performRaffle 方法的这里:

RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> ruleActionEntity =
this.doCheckRaffleBeforeLogic(...);
 
if (RuleLogicCheckTypeVO.TAKE_OVER.getCode().equals(ruleActionEntity.getCode())) {
if (DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode().equals(ruleActionEntity.getRuleModel())) {
// 黑名单 → 返回固定奖品
return RaffleAwardEntity.builder().awardId(ruleActionEntity.getData().getAwardId()).build();
} else if (DefaultLogicFactory.LogicModel.RULE_WIGHT.getCode().equals(ruleActionEntity.getRuleModel())) {
// 权重 → 按权重 key 抽奖
String ruleWeightValueKey = ruleActionEntity.getData().getRuleWeightValueKey();
Integer awardId = strategyDispatch.getRandomAwardId(strategyId, ruleWeightValueKey);
return RaffleAwardEntity.builder().awardId(awardId).build();
}
}
 
// 规则放行 → 默认抽奖
Integer awardId = strategyDispatch.getRandomAwardId(strategyId);
return RaffleAwardEntity.builder().awardId(awardId).build();

所以 doCheckRaffleBeforeLogic 返回的 RuleActionEntity 回到 performRaffleperformRaffle 根据 code 判断走哪个分支。


第二个问题:TAKE_OVER 在哪里定义的?

在 RuleLogicCheckTypeVO 枚举里:

ALLOW("0000", "放行;执行后续的流程,不受规则引擎影响"),
TAKE_OVER("0001","接管;后续的流程,受规则引擎执行结果影响"),

TAKE_OVER 在过滤器的 filter 方法里被设置成返回值。比如 RuleWeightLogicFilter 里:

 
// 用户积分 >= 权重门槛,命中权重规则
return RuleActionEntity.<RuleActionEntity.RaffleBeforeEntity>builder()
.data(...)
.ruleModel(DefaultLogicFactory.LogicModel.RULE_WIGHT.getCode())
.code(RuleLogicCheckTypeVO.TAKE_OVER.getCode()) // ← 这里设置 TAKE_OVER
.info(RuleLogicCheckTypeVO.TAKE_OVER.getInfo())
.build();

没命中权重时:

return RuleActionEntity.<RuleActionEntity.RaffleBeforeEntity>builder()
.code(RuleLogicCheckTypeVO.ALLOW.getCode()) // ← 设置 ALLOW
.info(RuleLogicCheckTypeVO.ALLOW.getInfo())
.build();

第三个问题:!ALLOW.equals(code) 这段判断到底是什么意思?

 
if (!RuleLogicCheckTypeVO.ALLOW.getCode().equals(ruleActionEntity.getCode())) {
return ruleActionEntity;
}

翻译成人话:"如果规则没放行(不是 ALLOW),就说明被接管了,直接把这个接管结果返回出去,后面的规则不用再执行了。"

doCheckRaffleBeforeLogic 里是顺序过滤多个规则。假设有 rule_blacklist 和 rule_weight 两个规则:

  1. 先执行黑名单过滤 → 如果返回 TAKE_OVER → 直接 return,权重规则就不执行了
  2. 如果黑名单返回 ALLOW → 继续执行权重过滤 → 如果返回 TAKE_OVER → 直接 return
  3. 如果权重也返回 ALLOW → 继续下一个规则... 直到所有规则都 ALLOW,最后返回 ALLOW

总结:返回的 RuleActionEntity 有什么用?

回到 performRaffle 后,performRaffle 根据 RuleActionEntity 里的三个信息做决策:

字段 含义
code ALLOW 规则放行,走默认抽奖
code TAKE_OVER 规则接管了流程
ruleModel rule_blacklist 被黑名单接管,用固定奖品
ruleModel rule_weight 被权重接管,用权重 key 去查权重表
data.awardId 101 黑名单返回的固定奖品 ID
data.ruleWeightValueKey "4000:..." 权重返回的权重 key

 

7.@EqualsAndHashCode(callSuper = true)这个是干嘛的 

@EqualsAndHashCode(callSuper = true) 是 Lombok 的注解,作用是自动生成 equals() 和 hashCode() 方法

callSuper = true 的意思是:生成时把父类的字段也纳入比较

看 RaffleBeforeEntity

static public class RaffleBeforeEntity extends RaffleEntity {
private Long strategyId;
private String ruleWeightValueKey;
private Integer awardId;
}

父类 RaffleEntity 是空的,所以这里加不加 callSuper = true 效果一样。但设计上是一个好习惯——如果以后父类加了字段,equals 和 hashCode 会自动把父类字段也考虑进去,不会漏掉。

如果不加 callSuper = true(默认 false),生成的 equals 和 hashCode 只比较子类自己的字段,父类的字段不参与比较。两个子类对象即使父类字段不同,也可能被判定为相等。

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