第5节:抽奖前置规则过滤
这节课主要学习了在抽奖前对规则进行过滤,
-
说明
相关的实体类:
RaffleFactorEntity— 抽奖入参(userId, strategyId)RaffleAwardEntity— 抽奖结果(awardId)RuleMatterEntity— 规则过滤的入参RuleActionEntity— 规则过滤的结果(code=ALLOW/TAKE_OVER, data=规则返回的数据)RuleLogicCheckTypeVO— 规则结果枚举(ALLOW 放行, TAKE_OVER 接管)
相关的接口作用:
1. IRaffleStrategy — 抽奖策略接口
定义了一个方法:
2. AbstractRaffleStrategy — 抽奖流程模板
定义了抽奖的固定步骤:
第 3 步是抽象的,交给子类实现。这样流程骨架固定,但规则过滤的逻辑可以灵活替换。
3. DefaultRaffleStrategy — 默认的规则过滤实现
实现了 doCheckRaffleBeforeLogic,里面做了两件事:
- 黑名单优先过滤 — 先查用户是不是黑名单,是就直接返回固定奖品
- 其他规则顺序过滤 — 比如权重规则,判断用户积分能命中哪个档位
4. ILogicFilter — 规则过滤接口
定义了一个规则过滤器的通用接口:传入规则需要的参数,返回过滤结果。
5. RuleBackListLogicFilter — 黑名单过滤器
实现 ILogicFilter,查数据库的黑名单配置,判断当前用户是否在黑名单里。
6. RuleWeightLogicFilter — 权重过滤器
实现 ILogicFilter,查数据库的权重配置,判断用户积分能命中哪个权重档位。
7. DefaultLogicFactory — 规则工厂
启动时自动收集所有的 ILogicFilter 实现类,注册成一个 Map:
这样 DefaultRaffleStrategy 要执行某个规则时,直接从 Map 里按规则名取,不需要写 if-else。
-
执行流程如下:
一、@Before 阶段:setUp()
这行用 Spring 的反射工具把 RuleWeightLogicFilter 里的 userScore 字段从默认的 4500L 改成了 40500L。userScore 模拟的是用户的积分值,用来判断用户能命中哪个权重档位。
二、test_performRaffle 方法
构造了一个抽奖因子:
然后调用 raffleStrategy.performRaffle(raffleFactorEntity)。
三、performRaffle 内部执行流程
这个方法在 AbstractRaffleStrategy 中定义,是整个抽奖的核心流程。分 4 步:
第 1 步:参数校验
校验通过,继续。
第 2 步:查询策略配置
第 3 步:抽奖前规则过滤
这里调用 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 的代码是:
这里 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_OVER,doCheckRaffleBeforeLogic 直接 return 这个结果,不再继续过滤。
第 4 步:根据规则结果抽奖
回到 performRaffle,判断 ruleActionEntity.getCode():
因为 ruleModel 是 "rule_weight",走权重分支:
- 取出
ruleWeightValueKey = "4000:102,103,104,105" - 调用
strategyDispatch.getRandomAwardId(100001L, "4000:102,103,104,105") - 这个方法拼接 key 为
"100001_4000:102,103,104,105",从 Redis 的权重概率表中随机取一个奖品 ID - 最终返回这个奖品 ID
四、test_performRaffle_blacklist 的区别
这个测试用户是 "user003",在黑名单中。流程到第 3 步 (a) 时:
RuleBackListLogicFilter.filter发现user003在黑名单里- 返回
TAKE_OVER,携带ruleModel = "rule_blacklist"和data.awardId = 101 doCheckRaffleBeforeLogic直接 return,不走权重规则performRaffle走黑名单分支,直接返回固定奖品 ID101
总结:test_performRaffle 测试的是带权重的正常用户抽奖,经过黑名单过滤(放行)→ 权重过滤(命中 4000 档位)→ 从权重概率表中随机抽奖。test_performRaffle_blacklist 测试的是黑名单用户,直接被拦截返回固定奖品。
相比 231231-xfg-strategy-armory-rule-weight 分支多出来的代码就是这套规则过滤引擎:IRaffleStrategy、AbstractRaffleStrategy、DefaultRaffleStrategy、DefaultLogicFactory、ILogicFilter、两个 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这个的作用是什么
答:这是一个模板方法模式的抽象类,定义了抽奖的标准流程骨架:
关键设计:第 3 步 doCheckRaffleBeforeLogic 声明为 protected abstract,交给子类 DefaultRaffleStrategy 去实现具体怎么过滤规则。这样抽奖的整体流程是固定的,但规则过滤的逻辑可以灵活替换。
流程拆解(performRaffle 方法内):
- 校验 userId、strategyId
- 查策略实体(获取
ruleModels) - 执行规则过滤
doCheckRaffleBeforeLogic,返回RuleActionEntity - 根据过滤结果判断:
- 如果被黑名单接管 → 直接返回固定奖品
- 如果被权重接管 → 按权重 key 去 Redis 权重表中抽奖
- 如果放行(ALLOW)→ 走基础概率表抽奖
3.DefaultRaffleStrategy是干嘛的?
并且我不知道 String ruleBackList = Arrays.stream(logics) .filter(str -> str.contains(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode())) .findFirst() .orElse(null); 这里的str是哪里来的
答:
先看 doCheckRaffleBeforeLogic 的完整代码,
String... logics 是可变参数,本质是个数组。调用它的地方在 AbstractRaffleStrategy.performRaffle:
strategy.ruleModels() 是从数据库 strategy 表查出来的 rule_models 字段值,比如 "rule_weight,rule_blacklist"。
所以 logics 就是一个包含一个元素的数组:["rule_weight,rule_blacklist"]。
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的过滤逻辑?
第一步:黑名单优先
- 在
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()),原因:
- 结果只需要一个 — 代码只是想判断"有没有黑名单规则",有就行,不需要知道有几个
- 返回类型是
Optional<String>—findFirst()返回 Optional,后面.orElse(null)表示没找到就返回 null。这样if (StringUtils.isNotBlank(ruleBackList))就能区分"有黑名单"和"没有黑名单"
本质上这行代码等价于:
findFirst() 就相当于那个 break——找到一个就停,不继续遍历
6.if (!RuleLogicCheckTypeVO.ALLOW.getCode().equals(ruleActionEntity.getCode())) {
return ruleActionEntity;
}这个是如果不是allow的话 就把这个返回吗?返回到哪里 ?TAKE_OVER是不是在过滤器里 ?TAKE_OVER在过滤器里 那这里的allow又是否的话 这个是啥意思?返回的是什么东西有什么用?
第一个问题:!ALLOW.equals(code) 返回到哪里?
返回到 AbstractRaffleStrategy.performRaffle 方法的这里:
所以 doCheckRaffleBeforeLogic 返回的 RuleActionEntity 回到 performRaffle,performRaffle 根据 code 判断走哪个分支。
第二个问题:TAKE_OVER 在哪里定义的?
在 RuleLogicCheckTypeVO 枚举里:
TAKE_OVER 在过滤器的 filter 方法里被设置成返回值。比如 RuleWeightLogicFilter 里:
没命中权重时:
第三个问题:!ALLOW.equals(code) 这段判断到底是什么意思?
翻译成人话:"如果规则没放行(不是 ALLOW),就说明被接管了,直接把这个接管结果返回出去,后面的规则不用再执行了。"
doCheckRaffleBeforeLogic 里是顺序过滤多个规则。假设有 rule_blacklist 和 rule_weight 两个规则:
- 先执行黑名单过滤 → 如果返回 TAKE_OVER → 直接 return,权重规则就不执行了
- 如果黑名单返回 ALLOW → 继续执行权重过滤 → 如果返回 TAKE_OVER → 直接 return
- 如果权重也返回 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:
父类 RaffleEntity 是空的,所以这里加不加 callSuper = true 效果一样。但设计上是一个好习惯——如果以后父类加了字段,equals 和 hashCode 会自动把父类字段也考虑进去,不会漏掉。
如果不加 callSuper = true(默认 false),生成的 equals 和 hashCode 只比较子类自己的字段,父类的字段不参与比较。两个子类对象即使父类字段不同,也可能被判定为相等。

浙公网安备 33010602011771号