一、本章诉求
在我们的流程设计中,用户执行抽奖时会判断是否已经超过N积分,如果超过N积分则可以在限定范围内进行抽奖。同时如果用户是黑名单范围的羊毛党用户,则只返回固定的奖品ID。
二、流程设计

- 整个规则来说,分为抽奖前、抽奖中、抽奖后,三个阶段执行。本节我们先来处理抽奖前的规则。
- 在工程分包上,需要添加 rule 来处理抽奖规则,在添加 raffle 处理抽奖过程。
三、功能实现
1. 工程结构

- 首先,我们需要在 strategy 层下,添加2个包;rule 规则、raffle 抽奖。
- rule 下面是实现的整个规则部分的处理,后续可以更好的扩展添加其他规则。
- raffle 是抽奖功能的实现,抽象类是模板模式,定义出标准的抽奖流程。
2.抽奖入口层(后续还会讲)
java
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 抽奖策略接口
* @create 2024-01-06 09:19
*/
public interface IRaffleStrategy {
/**
* 执行抽奖;用抽奖因子入参,执行抽奖计算,返回奖品信息
*
* @param raffleFactorEntity 抽奖因子实体对象,根据入参信息计算抽奖结果
* @return 抽奖的奖品
*/
RaffleAwardEntity performRaffle(RaffleFactorEntity raffleFactorEntity);
}
新增了统一抽奖接口 performRaffle,它的意义是把"抽奖"从以前零散的装配/调度调用里抽象成一个明确的领域服务入口
java
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 抽奖策略抽象类,定义抽奖的标准流程
* @create 2024-01-06 09:26
*/
@Slf4j
public abstract class AbstractRaffleStrategy implements IRaffleStrategy {
// 策略仓储服务 -> domain层像一个大厨,仓储层提供米面粮油
protected IStrategyRepository repository;
// 策略调度服务 -> 只负责抽奖处理,通过新增接口的方式,隔离职责,不需要使用方关心或者调用抽奖的初始化
protected IStrategyDispatch strategyDispatch;
public AbstractRaffleStrategy(IStrategyRepository repository, IStrategyDispatch strategyDispatch) {
this.repository = repository;
this.strategyDispatch = strategyDispatch;
}
@Override
public RaffleAwardEntity performRaffle(RaffleFactorEntity raffleFactorEntity) {
// 1. 参数校验
String userId = raffleFactorEntity.getUserId();
Long strategyId = raffleFactorEntity.getStrategyId();
if (null == strategyId || StringUtils.isBlank(userId)) {
throw new AppException(ResponseCode.ILLEGAL_PARAMETER.getCode(), ResponseCode.ILLEGAL_PARAMETER.getInfo());
}
// 2. 策略查询
StrategyEntity strategy = repository.queryStrategyEntityByStrategyId(strategyId);
// 3. 抽奖前 - 规则过滤
RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> ruleActionEntity = this.doCheckRaffleBeforeLogic(RaffleFactorEntity.builder().userId(userId).strategyId(strategyId).build(), strategy.ruleModels());
if (RuleLogicCheckTypeVO.TAKE_OVER.getCode().equals(ruleActionEntity.getCode())) {
if (DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode().equals(ruleActionEntity.getRuleModel())) {
// 黑名单返回固定的奖品ID
return RaffleAwardEntity.builder()
.awardId(ruleActionEntity.getData().getAwardId())
.build();
} else if (DefaultLogicFactory.LogicModel.RULE_WIGHT.getCode().equals(ruleActionEntity.getRuleModel())) {
// 权重根据返回的信息进行抽奖
RuleActionEntity.RaffleBeforeEntity raffleBeforeEntity = ruleActionEntity.getData();
String ruleWeightValueKey = raffleBeforeEntity.getRuleWeightValueKey();
Integer awardId = strategyDispatch.getRandomAwardId(strategyId, ruleWeightValueKey);
return RaffleAwardEntity.builder()
.awardId(awardId)
.build();
}
}
// 4. 默认抽奖流程
Integer awardId = strategyDispatch.getRandomAwardId(strategyId);
return RaffleAwardEntity.builder()
.awardId(awardId)
.build();
}
protected abstract RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> doCheckRaffleBeforeLogic(RaffleFactorEntity raffleFactorEntity, String... logics);
}
AbstractRaffleStrategy.java 是这次最重要的类。它定义了标准流程:
- 先校验 userId 和 strategyId
- 通过 repository.queryStrategyEntityByStrategyId(strategyId) 查策略
- 调 doCheckRaffleBeforeLogic(...) 做前置规则过滤
- 如果返回的是 TAKE_OVER,说明规则已经接管
- 黑名单规则就直接返回固定 awardId
- 权重规则就拿到 ruleWeightValueKey,再按限定范围抽奖
- 如果没有规则接管,就走 strategyDispatch.getRandomAwardId(strategyId) 的默认抽奖
这里你可以把它理解成"模板方法模式":
- 父类定流程
- 子类补"前置规则怎么检查"
java
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 默认的抽奖策略实现
* @create 2024-01-06 11:46
*/
@Slf4j
@Service
public class DefaultRaffleStrategy extends AbstractRaffleStrategy {
@Resource
private DefaultLogicFactory logicFactory;
public DefaultRaffleStrategy(IStrategyRepository repository, IStrategyDispatch strategyDispatch) {
super(repository, strategyDispatch);
}
@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());
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: {}", raffleFactorEntity.getUserId(), ruleModel, ruleActionEntity.getCode(), ruleActionEntity.getInfo());
if (!RuleLogicCheckTypeVO.ALLOW.getCode().equals(ruleActionEntity.getCode())) return ruleActionEntity;
}
return ruleActionEntity;
}
}
DefaultRaffleStrategy.java 就是这个模板的默认实现。它主要负责两件事:
- 优先处理黑名单规则
- 再顺序处理其他规则
之所以把黑名单放前面,是因为它是强拦截型规则,一旦命中,就没必要继续算别的规则了。
这里你可能觉得好乱,听不懂,那你就先了解一个大概流程就行:
IRaffleStrategy新增了统一抽奖接口 performRaffle,它的意义是把"抽奖"从以前零散的装配/调度调用里抽象成一个明确的领域服务入口**,AbstractRaffleStrategy** 是抽奖策略抽象类,定义抽奖的标准流程(实现了IRaffleStrategy),DefaultRaffleStrategy****继承AbstractRaffleStrategy,实现前置规则过滤
3.新增的领域对象(entity)
java
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 抽奖因子实体
* @create 2024-01-06 09:20
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RaffleFactorEntity {
/** 用户ID */
private String userId;
/** 策略ID */
private Long strategyId;
}
抽奖入参,目前只放了 userId 和 strategyId。名字叫 "Factor",意思是"影响抽奖的因子",后面要扩展用户积分 、等级 、参与次数时可以继续往里放。
java
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 抽奖奖品实体
* @create 2024-01-06 09:20
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RaffleAwardEntity {
/** 策略ID */
private Long strategyId;
/** 奖品ID */
private Integer awardId;
/** 奖品对接标识 - 每一个都是一个对应的发奖策略 */
private String awardKey;
/** 奖品配置信息 */
private String awardConfig;
/** 奖品内容描述 */
private String awardDesc;
}
**抽奖结果对象,**当前真正用到的核心字段是 awardId,其他像 awardKey、awardConfig、awardDesc 是为后续奖品发放和展示预留的。
java
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 规则物料实体对象,用于过滤规则的必要参数信息。
* @create 2024-01-06 09:56
*/
@Data
public class RuleMatterEntity {
/** 用户ID */
private String userId;
/** 策略ID */
private Long strategyId;
/** 抽奖奖品ID【规则类型为策略,则不需要奖品ID】 */
private Integer awardId;
/** 抽奖规则类型【rule_random - 随机值计算、rule_lock - 抽奖几次后解锁、rule_luck_award - 幸运奖(兜底奖品)】 */
private String ruleModel;
}
可以理解为"规则执行时喂给过滤器的材料"。它和 RaffleFactorEntity 不完全一样,因为规则不一定只关心用户和策略,也可能关心奖品、规则类型,所以专门拆了一个对象。
java
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 规则动作实体
* @create 2024-01-06 09:47
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RuleActionEntity<T extends RuleActionEntity.RaffleEntity> {
private String code = RuleLogicCheckTypeVO.ALLOW.getCode();
private String info = RuleLogicCheckTypeVO.ALLOW.getInfo();
private String ruleModel;
private T data;
static public class RaffleEntity {
}
// 抽奖之前
@EqualsAndHashCode(callSuper = true)
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
static public class RaffleBeforeEntity extends RaffleEntity {
/**
* 策略ID
*/
private Long strategyId;
/**
* 权重值Key;用于抽奖时可以选择权重抽奖。
*/
private String ruleWeightValueKey;
/**
* 奖品ID;
*/
private Integer awardId;
}
// 抽奖之中
static public class RaffleCenterEntity extends RaffleEntity {
}
// 抽奖之后
static public class RaffleAfterEntity extends RaffleEntity {
}
}
RuleActionEntity.java 是"规则执行结果"。
这层设计很关键,它不直接返回 true/false,而是返回:
- code:放行还是接管
- ruleModel:是哪条规则生效
- data:规则附带的数据

规定上界,传入的泛型必须是RuleActionEntity.RaffleEntity的子类,防止乱传进来影响最终输出
其中 RaffleBeforeEntity 放的是"抽奖前规则"的返回数据,比如:
- 黑名单场景返回 awardId
- 权重场景返回 ruleWeightValueKey
不只定义了 RaffleBeforeEntity
它还预留了:
- RaffleCenterEntity
- RaffleAfterEntity

这里继承RuleActionEntity.RaffleEntity,可以当做传入的泛型
这样,这个实体就有了code,info,规则类型,策略id,以及对应规则的值
java
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 规则过滤校验类型值对象
* @create 2024-01-06 11:10
*/
@Getter
@AllArgsConstructor
public enum RuleLogicCheckTypeVO {
ALLOW("0000", "放行;执行后续的流程,不受规则引擎影响"),
TAKE_OVER("0001","接管;后续的流程,受规则引擎执行结果影响"),
;
private final String code;
private final String info;
}
定义了两个状态:
- ALLOW:规则放行,继续后面流程
- TAKE_OVER:规则接管,后续结果受规则影响

4.规则扩展机制
java
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 抽奖规则过滤接口
* @create 2024-01-06 09:55
*/
public interface ILogicFilter<T extends RuleActionEntity.RaffleEntity> {
RuleActionEntity<T> filter(RuleMatterEntity ruleMatterEntity);
}
规则过滤器接口。所有前置规则都实现它,这样系统可以统一调度不同规则
java
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 策略自定义枚举
* @create 2023-12-31 11:29
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogicStrategy {
//这是注解里的一个"属性定义"
DefaultLogicFactory.LogicModel logicMode();
}
一个标记注解。它的作用是给每个过滤器贴上"我是哪种规则"的标签。
- @LogicStrategy 这个注解,必须带一个参数
- 这个参数名字叫 logicMode
- 参数类型是 DefaultLogicFactory.LogicModel 这个枚举
5.两个具体规则
java
@Slf4j
@Component
@LogicStrategy(logicMode = DefaultLogicFactory.LogicModel.RULE_BLACKLIST)
public class RuleBackListLogicFilter implements ILogicFilter<RuleActionEntity.RaffleBeforeEntity> {
@Resource
private IStrategyRepository repository;
//100:user001,user002,user003
@Override
public RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> filter(RuleMatterEntity ruleMatterEntity) {
log.info("规则过滤-黑名单 userId:{} strategyId:{} ruleModel:{}", ruleMatterEntity.getUserId(), ruleMatterEntity.getStrategyId(), ruleMatterEntity.getRuleModel());
String userId = ruleMatterEntity.getUserId();
// 查询规则值配置
String ruleValue = repository.queryStrategyRuleValue(ruleMatterEntity.getStrategyId(), ruleMatterEntity.getAwardId(), ruleMatterEntity.getRuleModel());
String[] splitRuleValue = ruleValue.split(Constants.COLON);
Integer awardId = Integer.parseInt(splitRuleValue[0]);
// 过滤其他规则
String[] userBlackIds = splitRuleValue[1].split(Constants.SPLIT);
for (String userBlackId : userBlackIds) {
if (userId.equals(userBlackId)) {
return RuleActionEntity.<RuleActionEntity.RaffleBeforeEntity>builder()
.ruleModel(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode())
.data(RuleActionEntity.RaffleBeforeEntity.builder()
.strategyId(ruleMatterEntity.getStrategyId())
.awardId(awardId)
.build())
.code(RuleLogicCheckTypeVO.TAKE_OVER.getCode())
.info(RuleLogicCheckTypeVO.TAKE_OVER.getInfo())
.build();
}
}
return RuleActionEntity.<RuleActionEntity.RaffleBeforeEntity>builder()
.code(RuleLogicCheckTypeVO.ALLOW.getCode())
.info(RuleLogicCheckTypeVO.ALLOW.getInfo())
.build();
}
}
整体流程
- 这个类是一个"抽奖前规则过滤器"。
- 它专门处理 rule_blacklist 这类规则。
- 系统在抽奖前会把用户信息和策略信息传进来。
- 这个过滤器先去数据库查黑名单规则配置。
- 如果当前用户在黑名单里,就直接接管抽奖结果。
- 如果不在黑名单里,就放行给后面的正常抽奖流程。
java
@Slf4j
@Component
@LogicStrategy(logicMode = DefaultLogicFactory.LogicModel.RULE_WIGHT)
public class RuleWeightLogicFilter implements ILogicFilter<RuleActionEntity.RaffleBeforeEntity> {
@Resource
private IStrategyRepository repository;
public Long userScore = 4500L;
/**
* 权重规则过滤;
* 1. 权重规则格式;4000:102,103,104,105 5000:102,103,104,105,106,107 6000:102,103,104,105,106,107,108,109
* 2. 解析数据格式;判断哪个范围符合用户的特定抽奖范围
*
* @param ruleMatterEntity 规则物料实体对象
* @return 规则过滤结果
*/
@Override
public RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> filter(RuleMatterEntity ruleMatterEntity) {
log.info("规则过滤-权重范围 userId:{} strategyId:{} ruleModel:{}", ruleMatterEntity.getUserId(), ruleMatterEntity.getStrategyId(), ruleMatterEntity.getRuleModel());
String userId = ruleMatterEntity.getUserId();
Long strategyId = ruleMatterEntity.getStrategyId();
String ruleValue = repository.queryStrategyRuleValue(ruleMatterEntity.getStrategyId(), ruleMatterEntity.getAwardId(), ruleMatterEntity.getRuleModel());
// 1. 根据用户ID查询用户抽奖消耗的积分值,本章节我们先写死为固定的值。后续需要从数据库中查询。
Map<Long, String> analyticalValueGroup = getAnalyticalValue(ruleValue);
if (null == analyticalValueGroup || analyticalValueGroup.isEmpty()) {
return RuleActionEntity.<RuleActionEntity.RaffleBeforeEntity>builder()
.code(RuleLogicCheckTypeVO.ALLOW.getCode())
.info(RuleLogicCheckTypeVO.ALLOW.getInfo())
.build();
}
// 2. 转换Keys值,并默认排序
List<Long> analyticalSortedKeys = new ArrayList<>(analyticalValueGroup.keySet());
Collections.sort(analyticalSortedKeys);
// 3. 找出最小符合的值,也就是【4500 积分,能找到 4000:102,103,104,105】、【5000 积分,能找到 5000:102,103,104,105,106,107】
Long nextValue = analyticalSortedKeys.stream()
.filter(key -> userScore >= key)
.findFirst()
.orElse(null);
if (null != nextValue) {
return RuleActionEntity.<RuleActionEntity.RaffleBeforeEntity>builder()
.data(RuleActionEntity.RaffleBeforeEntity.builder()
.strategyId(strategyId)
.ruleWeightValueKey(analyticalValueGroup.get(nextValue))
.build())
.ruleModel(DefaultLogicFactory.LogicModel.RULE_WIGHT.getCode())
.code(RuleLogicCheckTypeVO.TAKE_OVER.getCode())
.info(RuleLogicCheckTypeVO.TAKE_OVER.getInfo())
.build();
}
return RuleActionEntity.<RuleActionEntity.RaffleBeforeEntity>builder()
.code(RuleLogicCheckTypeVO.ALLOW.getCode())
.info(RuleLogicCheckTypeVO.ALLOW.getInfo())
.build();
}
private Map<Long, String> getAnalyticalValue(String ruleValue) {
String[] ruleValueGroups = ruleValue.split(Constants.SPACE);
Map<Long, String> ruleValueMap = new HashMap<>();
for (String ruleValueKey : ruleValueGroups) {
// 检查输入是否为空
if (ruleValueKey == null || ruleValueKey.isEmpty()) {
return ruleValueMap;
}
// 分割字符串以获取键和值
String[] parts = ruleValueKey.split(Constants.COLON);
if (parts.length != 2) {
throw new IllegalArgumentException("rule_weight rule_rule invalid input format" + ruleValueKey);
}
ruleValueMap.put(Long.parseLong(parts[0]), ruleValueKey);
}
return ruleValueMap;
}
}
整体流程
- 这是一个"抽奖前规则过滤器"。
- 它专门处理 rule_weight 规则。
- 系统先根据策略 ID 查出权重规则配置。
- 再根据用户积分判断他应该落到哪个权重区间。
- 如果命中了某个区间,就返回这个区间对应的抽奖范围。
- 后面的抽奖流程只会在这个范围里随机,不再用默认全量奖池。
6.仓储和 SQL
java
public interface IStrategyRepository {
List<StrategyAwardEntity> queryStrategyAwardList(Long strategyId);
void storeStrategyAwardSearchRateTable(String key, Integer rateRange, Map<Integer, Integer> strategyAwardSearchRateTable);
Integer getStrategyAwardAssemble(String key, Integer rateKey);
int getRateRange(Long strategyId);
int getRateRange(String key);
StrategyEntity queryStrategyEntityByStrategyId(Long strategyId);
StrategyRuleEntity queryStrategyRule(Long strategyId, String ruleModel);
String queryStrategyRuleValue(Long strategyId, Integer awardId, String ruleModel);
}
新增了 queryStrategyRuleValue,因为规则过滤器只关心规则值本身,不一定需要整条规则对象

7.规则工厂
java
@Service
public class DefaultLogicFactory {
public Map<String, ILogicFilter<?>> logicFilterMap = new ConcurrentHashMap<>();
public DefaultLogicFactory(List<ILogicFilter<?>> logicFilters) {
logicFilters.forEach(logic -> {
LogicStrategy strategy = AnnotationUtils.findAnnotation(logic.getClass(), LogicStrategy.class);
if (null != strategy) {
logicFilterMap.put(strategy.logicMode().getCode(), logic);
}
});
}
public <T extends RuleActionEntity.RaffleEntity> Map<String, ILogicFilter<T>> openLogicFilter() {
return (Map<String, ILogicFilter<T>>) (Map<?, ?>) logicFilterMap;
}
@Getter
@AllArgsConstructor
public enum LogicModel {
RULE_WIGHT("rule_weight","【抽奖前规则】根据抽奖权重返回可抽奖范围KEY"),
RULE_BLACKLIST("rule_blacklist","【抽奖前规则】黑名单规则过滤,命中黑名单则直接返回"),
;
private final String code;
private final String info;
}
}
整体作用
在这套代码里,后面会有很多规则类,比如:
- RuleBackListLogicFilter
- RuleWeightLogicFilter
如果没有这个工厂,主流程就只能手写很多判断:
java
if (ruleModel.equals("rule_blacklist")) { ... }
if (ruleModel.equals("rule_weight")) { ... }
这样规则一多,代码就会越来越乱。
所以这里专门做了一个工厂,统一管理"规则编码 -> 规则实现类"的映射关系。
这个 Map 是干什么的
java
public Map<String, ILogicFilter<?>> logicFilterMap = new ConcurrentHashMap<>();
它是这个工厂最核心的数据结构。
含义是:
- key:规则编码,比如 rule_blacklist
- value:对应的规则过滤器实现,比如 RuleBackListLogicFilter
注册完之后,大概会变成这样:
java
{
"rule_blacklist" -> RuleBackListLogicFilter,
"rule_weight" -> RuleWeightLogicFilter
}
构造函数为什么能拿到 List<ILogicFilter<?>>
这是 Spring 的自动注入能力
意思是:
- Spring 会扫描容器里所有实现了 ILogicFilter 的 Bean
- 然后把它们全部装进一个 List
- 再传给这个构造函数
也就是说,只要你写了一个类:
java
@Component
public class XxxLogicFilter implements ILogicFilter<...>
Spring 就会把它自动收集进来。
在你这个项目里,目前会被收进来的就是:
- RuleBackListLogicFilter
- RuleWeightLogicFilter
这一步非常关键,它让"新增规则"变得很轻量:
- 新写一个过滤器类
- 实现接口
- 加注解
- 工厂就能自动发现
forEach 这段在做什么
java
logicFilters.forEach(logic -> {
LogicStrategy strategy = AnnotationUtils.findAnnotation(logic.getClass(), LogicStrategy.class);
if (null != strategy) {
logicFilterMap.put(strategy.logicMode().getCode(), logic);
}
});
这段就是"自动注册"的核心
它分 3 步:
-
遍历所有 ILogicFilter
Spring 已经把所有规则过滤器类都传进来了,所以这里逐个处理。
-
读取类上的 @LogicStrategy
比如:
- RuleBackListLogicFilter 上有
@LogicStrategy(logicMode = RULE_BLACKLIST) - RuleWeightLogicFilter 上有
@LogicStrategy(logicMode = RULE_WIGHT)
- RuleBackListLogicFilter 上有
AnnotationUtils.findAnnotation(...) 就是在反射读取这个标签。
- 把规则编码和实现类塞进 Map
比如:- rule_blacklist -> RuleBackListLogicFilter
- rule_weight -> RuleWeightLogicFilter
这样后面主流程只需要传一个规则编码,就能找到对应实现。
openLogicFilter() 是什么作用
java
public <T extends RuleActionEntity.RaffleEntity> Map<String, ILogicFilter<T>> openLogicFilter() {
return (Map<String, ILogicFilter<T>>) (Map<?, ?>) logicFilterMap;
}
这个方法就是把内部注册表暴露出去。
调用方拿到后,就可以这样按规则名取:
java
Map<String, ILogicFilter<RuleActionEntity.RaffleBeforeEntity>> logicFilterGroup = logicFactory.openLogicFilter();
ILogicFilter<RuleActionEntity.RaffleBeforeEntity> logicFilter = logicFilterGroup.get("rule_blacklist");
这样就能动态找到某个规则过滤器。
这里的泛型 <T extends RuleActionEntity.RaffleEntity> 是为了让不同阶段的规则都能复用这套工厂。
因为 RuleActionEntity 里预留了:
- RaffleBeforeEntity
- RaffleCenterEntity
- RaffleAfterEntity
也就是说,这个工厂不只服务"抽奖前规则",理论上后面"抽奖中规则""抽奖后规则"也能继续用。
整体总结
1. 先理解旧流程在干什么
旧版本的核心能力只有两块:
- IStrategyArmory:装配抽奖策略,把概率表算出来放 Redis
- IStrategyDispatch:根据策略从 Redis 的概率表里随机拿一个奖品
也就是说,之前更像:
- 先准备好概率表
- 抽奖时直接随机取结果
这时候系统还没有"统一抽奖入口",也没有"抽奖前规则判断"。
所以这次改动的出发点就是:
- 以前只有"怎么抽"
- 现在要补上"抽之前先判断能不能这么抽"
2. 再看新加的抽奖入口层
这一步是最大的结构变化。
新增了:
- IRaffleStrategy
- AbstractRaffleStrategy
- DefaultRaffleStrategy
它们的职责是:
- IRaffleStrategy:定义统一抽奖能力,对外暴露 performRaffle
- AbstractRaffleStrategy:定义抽奖标准流程
- DefaultRaffleStrategy:实现"抽奖前规则过滤"这一步
所以现在抽奖不再是外面自己去调 getRandomAwardId(...),而是统一走:
java
raffleStrategy.performRaffle(raffleFactorEntity)
这个入口把整个流程串起来了。
3. 把主流程背下来,这是整章主线
主流程在 AbstractRaffleStrategy.performRaffle(),可以直接记成 4 步:
-
参数校验
检查 userId、strategyId -
查询策略
根据 strategyId 查当前策略配置
-
抽奖前规则过滤
调用 doCheckRaffleBeforeLogic(...)【子类进行具体实现】
-
根据规则结果决定怎么走
- 如果规则 TAKE_OVER:规则接管
- 如果规则 ALLOW:走默认随机抽奖
这里是全流程的核心分叉点。
你可以把它理解成:
- ALLOW = 放行,继续正常抽
- TAKE_OVER = 接管,不按默认方式抽
4. 新增领域对象是为了把"输入、规则、输出"拆开
这次还补了一组领域对象,不是为了凑类,而是为了让流程清楚。
- RaffleFactorEntity
作用:抽奖入参
当前只有: - userId
- strategyId
它表示"触发一次抽奖时,外部传进来的因子"。
-
RaffleAwardEntity
作用:抽奖结果
核心是 awardId
-
RuleMatterEntity
作用:规则执行时的入参
给规则过滤器看的"规则物料"
-
RuleActionEntity
作用:规则执行后的返回值
里面会告诉主流程:
-
是 ALLOW 还是 TAKE_OVER
-
如果接管,带什么数据回来
这一组类的意义是:
- 抽奖入参和规则入参分开
- 规则返回结果和最终抽奖结果分开
这样后面规则扩展就不会把参数搞乱
5. 规则扩展机制是为了以后能继续加规则
这一层是本次设计里最像"框架"的地方。
涉及:
- ILogicFilter
- LogicStrategy
- DefaultLogicFactory
先说目标:
系统以后不可能只有黑名单和权重规则,肯定还会继续加。
所以不能把所有规则都写死在主流程里。
于是这里做了一个扩展机制:
-
ILogicFilter
定义统一规则接口,所有规则都实现它
-
@LogicStrategy
给规则类打标签,说明"我处理哪种规则"
-
DefaultLogicFactory
启动时扫描所有规则过滤器,把它们注册进 Map
这样以后要新增规则时,只要:
- 写一个新过滤器类
- 实现 ILogicFilter
- 加上 @LogicStrategy
就能接入
6. 规则工厂在整条链路里扮演什么角色
你可以把 DefaultLogicFactory 单独当成"规则注册中心"。
它启动时做的事是:
- Spring 把所有实现了 ILogicFilter 的 Bean 收集起来
- 工厂逐个读取它们类上的 @LogicStrategy
- 拿到规则编码,比如:
- rule_blacklist
- rule_weight
- 注册成:
- rule_blacklist -> RuleBackListLogicFilter
- rule_weight -> RuleWeightLogicFilter
后面 DefaultRaffleStrategy 在做规则过滤时,就不用写死 if else 找类了,而是直接按规则名去工厂拿过滤器。
7. 两个具体规则分别干什么
这一步是把规则扩展机制真正落地到业务。
黑名单规则 RuleBackListLogicFilter
作用:
- 先查黑名单规则配置
- 判断当前用户是不是黑名单
- 如果命中,直接返回固定奖品
- 主流程收到 TAKE_OVER 后,不再走默认抽奖
它处理的规则值格式是:
100:user001,user002,user003
含义:
- 100:命中后直接返回奖品 100
- 后面是黑名单用户列表
这是"强接管型规则"。
权重规则 RuleWeightLogicFilter
作用:
- 查权重规则配置
- 根据用户积分命中某个积分门槛
- 返回一个可抽奖范围 key
- 主流程收到 TAKE_OVER 后,不是直接给奖品,而是在指定范围里再抽一次
规则值格式是:
4000:102,103,104,105 5000:102,103,104,105,106,107
含义:
- 不同积分对应不同奖品池范围
这是"限制抽奖范围型规则"。
现在回头去想想我们之前遗留的抽奖入口层,是不是就可以明白为什么这样设计了,这样设计的功能流程是什么,每一步都是在干什么
