一、本章诉求
在前面的章节中,我们已经结合策略模式、工厂模式,逐步完成了抽奖流程的定义,以及抽奖前、抽奖中、抽奖后各类规则的过滤处理。整体功能虽然已经跑通,但随着规则不断增加,问题也开始显现出来。
尤其是在前置规则这一层,部分规则本身不仅承担了"校验是否放行"的职责,还直接介入了后续抽奖行为的处理。这样一来,规则流程中混入了过多业务动作,导致单个规则节点负责的事情越来越多,代码职责也变得不够单一,整体实现显得有些臃肿。
因此,在这一节中,我们准备继续对抽奖规则的处理结构做一次优化。这里会引入责任链模式,对前置规则的执行流程进行重构,把原本耦合在一起的规则判断与抽奖处理进一步拆开,让每个规则节点只关注自己该负责的判断逻辑,同时也让整个抽奖流程的结构更加清晰、扩展起来更加自然。
二、流程设计
设计前我们需要思考🤔 ,抽奖的前置规则在抽奖中是一个什么行为。其实它可以被抽象为一种策略行为,比如;黑名单抽奖策略、权重抽奖策略、白名单抽奖策略等。而这些策略规则是一种互斥行为,比如走了黑名单规则,就不应该在继续走权重规则了。那么对于这样的情况,责任链的设计就更加合适了。

三、功能实现
1. 工程结构

- rule 规则包下,拆2部分。一部分规则过滤,一部分新增的 chain 责任链,用于抽奖使用。
- 迁移 AbstractRaffleStrategy 抽象类到 strategy 包下,形成一个上下调用的结构。从抽象类调用到下面各个模块功能。「这块的方式不影响编码实现,只是做规整处理」
- 后续再有抽奖的不同策略,都可以在 chain 下做具体的责任节点实现。
2.. 抽奖主流程重构
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;
// 抽奖的责任链 -> 从抽奖的规则中,解耦出前置规则为责任链处理
private final DefaultChainFactory defaultChainFactory;
public AbstractRaffleStrategy(IStrategyRepository repository, IStrategyDispatch strategyDispatch, DefaultChainFactory defaultChainFactory) {
this.repository = repository;
this.strategyDispatch = strategyDispatch;
this.defaultChainFactory = defaultChainFactory;
}
@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. 获取抽奖责任链 - 前置规则的责任链处理
ILogicChain logicChain = defaultChainFactory.openLogicChain(strategyId);
// 3. 通过责任链获得,奖品ID
Integer awardId = logicChain.logic(userId, strategyId);
// 4. 查询奖品规则「抽奖中(拿到奖品ID时,过滤规则)、抽奖后(扣减完奖品库存后过滤,抽奖中拦截和无库存则走兜底)」
StrategyAwardRuleModelVO strategyAwardRuleModelVO = repository.queryStrategyAwardRuleModelVO(strategyId, awardId);
// 5. 抽奖中 - 规则过滤
RuleActionEntity<RuleActionEntity.RaffleCenterEntity> ruleActionCenterEntity = this.doCheckRaffleCenterLogic(RaffleFactorEntity.builder()
.userId(userId)
.strategyId(strategyId)
.awardId(awardId)
.build(), strategyAwardRuleModelVO.raffleCenterRuleModelList());
if (RuleLogicCheckTypeVO.TAKE_OVER.getCode().equals(ruleActionCenterEntity.getCode())){
log.info("【临时日志】中奖中规则拦截,通过抽奖后规则 rule_luck_award 走兜底奖励。");
return RaffleAwardEntity.builder()
.awardDesc("中奖中规则拦截,通过抽奖后规则 rule_luck_award 走兜底奖励。")
.build();
}
return RaffleAwardEntity.builder()
.awardId(awardId)
.build();
}
protected abstract RuleActionEntity<RuleActionEntity.RaffleCenterEntity> doCheckRaffleCenterLogic(RaffleFactorEntity raffleFactorEntity, String... logics);
}
这个类从原来的 service/raffle 包挪到了 service 包,本质上是在强调它已经不只是"raffle 下的一个实现细节",而是整个抽奖流程的抽象骨架。
这里最大的变化有 3 个:
- 新增了 DefaultChainFactory 依赖。说明前置规则不再在当前类里手工判断,而是先交给责任链工厂组装好一条链。
- performRaffle(...) 里不再先查策略 、再调用 doCheckRaffleBeforeLogic(...)。现在改成:
- 先通过 defaultChainFactory.openLogicChain(strategyId) 获取责任链
- 再调用 logicChain.logic(userId, strategyId) 直接拿到 awardId
- 抽奖前规则的抽象方法被删掉了,只保留 doCheckRaffleCenterLogic(...)。这说明"前置规则"已经彻底从模板方法里拆出去,由责任链统一接管;而"中置规则"还继续保留在过滤器模型下。
这一步的设计意义很大:
以前 AbstractRaffleStrategy 既要管理抽奖流程,又要关心前置规则执行细节。现在它只负责"调度责任链并拿结果",职责明显更清晰。
3.为什么引入责任链
引入责任链模式,核心是为了解决一个问题:
前置规则已经不只是"校验规则",而是在"决定抽奖该怎么继续执行"了。
在前一个版本里,前置规则放在 DefaultRaffleStrategy 里顺序遍历。这样做在规则少的时候没问题,但随着规则复杂度提升,会慢慢暴露出几个明显问题。
首先,前置规则和抽奖行为耦合得太紧 。
像黑名单规则,不只是判断用户是否命中,还会直接决定返回哪个奖品;权重规则,也不只是校验条件,还会决定用户应该落到哪个权重抽奖范围里。也就是说,这些规则已经不是单纯的"过滤器",而是在接管后续抽奖流程。
其次,流程控制写在策略实现类里,会让主流程越来越臃肿 。
原来的做法需要在 DefaultRaffleStrategy 中手动处理很多细节:
- 哪个规则优先执行
- 哪个规则命中后直接返回
- 哪些规则继续往后放行
- 默认抽奖什么时候兜底执行
这样一来,规则越多,DefaultRaffleStrategy 就越像一个"大总管",既要负责任务编排,又要关心每条规则的执行顺序和中断时机,职责会越来越重。
再次,前置规则天然符合"链式传递"的特征 。
前置规则的本质就是:
- 当前规则先判断自己是否接管
- 如果接管,就直接返回结果
- 如果不接管,就交给下一个规则
- 直到最后落到默认抽奖逻辑
这其实和责任链模式的结构是完全一致的。责任链最适合处理的,就是这种"逐个节点判断,命中就终止,否则继续传递"的场景。
另外,引入责任链之后,规则扩展会更自然 。
每个前置规则都可以单独做成一个责任链节点,比如:
- 黑名单责任链
- 权重责任链
- 默认责任链
以后如果还要新增别的前置规则,比如白名单、用户标签、活动门槛,也只需要新增一个链节点,再按配置装配进链路,而不用再去不断修改 DefaultRaffleStrategy 里的大段判断逻辑。
所以可以把这件事理解成:
- 过滤器模式更适合"判断一下,返回一个规则结果"
- 责任链模式更适合"当前规则决定是否接管后续流程,不接管就交给下一个节点"
而前置规则已经明显属于第二种场景了,所以这里引入责任链模式,是为了让:
- 规则职责更单一
- 主流程更简洁
- 扩展方式更清晰
- 代码结构更符合业务语义
4.默认抽奖策略实现被瘦身
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, DefaultChainFactory defaultChainFactory) {
super(repository, strategyDispatch, defaultChainFactory);
}
@Override
protected RuleActionEntity<RuleActionEntity.RaffleCenterEntity> doCheckRaffleCenterLogic(RaffleFactorEntity raffleFactorEntity, String... logics) {
if (logics == null || 0 == logics.length) return RuleActionEntity.<RuleActionEntity.RaffleCenterEntity>builder()
.code(RuleLogicCheckTypeVO.ALLOW.getCode())
.info(RuleLogicCheckTypeVO.ALLOW.getInfo())
.build();
Map<String, ILogicFilter<RuleActionEntity.RaffleCenterEntity>> logicFilterGroup = logicFactory.openLogicFilter();
RuleActionEntity<RuleActionEntity.RaffleCenterEntity> ruleActionEntity = null;
for (String ruleModel : logics) {
ILogicFilter<RuleActionEntity.RaffleCenterEntity> logicFilter = logicFilterGroup.get(ruleModel);
RuleMatterEntity ruleMatterEntity = new RuleMatterEntity();
ruleMatterEntity.setUserId(raffleFactorEntity.getUserId());
ruleMatterEntity.setAwardId(raffleFactorEntity.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;
}
}
它做了两件事:
- 构造函数增加了 DefaultChainFactory 参数,并传给父类
- 原本那一大段 doCheckRaffleBeforeLogic(...) 的实现被整段删掉
也就是说,分支二里前置规则的这些动作:
- 空规则保护
- 黑名单优先
- 遍历剩余规则
- 封装 RuleMatterEntity
- 执行过滤器
- 返回 RuleActionEntity
现在都不由 DefaultRaffleStrategy 亲自做了,而是交给责任链体系。
当前类只剩下"抽奖中规则"这一块还在本类中继续处理。这个结果说明在做一件很明确的事情:
把"前置规则"从策略实现类里剥离出去,让 DefaultRaffleStrategy 只保留真正还没抽象掉的那部分逻辑。
5.新增责任链顶层接口
java
public interface ILogicChain extends ILogicChainArmory{
/**
* 责任链接口
*
* @param userId 用户ID
* @param strategyId 策略ID
* @return 奖品ID
*/
Integer logic(String userId, Long strategyId);
}
java
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 责任链装配
* @create 2024-01-20 11:53
*/
public interface ILogicChainArmory {
ILogicChain next();
ILogicChain appendNext(ILogicChain next);
}
java
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 抽奖策略责任链,判断走那种抽奖策略。如;默认抽象、权重抽奖、黑名单抽奖
* @create 2024-01-20 09:37
*/
@Slf4j
public abstract class AbstractLogicChain implements ILogicChain {
private ILogicChain next;
@Override
public ILogicChain next() {
return next;
}
@Override
public ILogicChain appendNext(ILogicChain next) {
this.next = next;
return next;
}
protected abstract String ruleModel();
}
这一组文件是这次重构的基础设施
- ILogicChain 定义了责任链节点统一入口:logic(userId, strategyId),返回值是 awardId
- ILogicChainArmory 定义了链式装配能力:next() 和 appendNext(...)
- AbstractLogicChain 作为抽象基类,内部持有 next 指针,并统一实现链路拼接逻辑
这套设计意味着:
- 每一个前置规则节点都可以"自己决定是否接管"
- 如果不接管,就把请求交给下一个节点
- 最终一定会落到一个默认节点,确保总能产出结果
6.新增责任链工厂
java
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 工厂
* @create 2024-01-20 10:54
*/
@Service
public class DefaultChainFactory {
private final Map<String, ILogicChain> logicChainGroup;
protected IStrategyRepository repository;
public DefaultChainFactory(Map<String, ILogicChain> logicChainGroup, IStrategyRepository repository) {
this.logicChainGroup = logicChainGroup;
this.repository = repository;
}
/**
* 通过策略ID,构建责任链
*
* @param strategyId 策略ID
* @return LogicChain
*/
public ILogicChain openLogicChain(Long strategyId) {
StrategyEntity strategy = repository.queryStrategyEntityByStrategyId(strategyId);
String[] ruleModels = strategy.ruleModels();
// 如果未配置策略规则,则只装填一个默认责任链
if (null == ruleModels || 0 == ruleModels.length) return logicChainGroup.get("default");
// 按照配置顺序装填用户配置的责任链;rule_blacklist、rule_weight 「注意此数据从Redis缓存中获取,如果更新库表,记得在测试阶段手动处理缓存」
ILogicChain logicChain = logicChainGroup.get(ruleModels[0]);
ILogicChain current = logicChain;
for (int i = 1; i < ruleModels.length; i++) {
ILogicChain nextChain = logicChainGroup.get(ruleModels[i]);
current = current.appendNext(nextChain);
}
// 责任链的最后装填默认责任链
current.appendNext(logicChainGroup.get("default"));
return logicChain;
}
}
这是前置规则重构后的核心调度类
它做的事情是:
- 先根据 strategyId 查出当前策略配置的 ruleModels
- 如果没有配置规则,直接返回 default 责任链
- 如果有配置规则,就按数据库里 ruleModels 的顺序,把对应链节点一个个拼起来
- 最后再统一挂上 default 责任链作为兜底
这一步有两个重要设计点:
- 责任链顺序完全由策略配置决定,不再写死在代码里
- 无论前面配了什么规则,最后都保证有默认抽奖节点,不会断链
这比之前 DefaultRaffleStrategy 里先找黑名单、再过滤其他规则的方式更灵活。
原来的优先级是代码写死的;现在优先级来自策略配置本身。
7.黑名单规则从 Filter 变成 Chain
java
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 黑名单责任链
* @create 2024-01-20 10:23
*/
@Slf4j
@Component("rule_blacklist")
public class BackListLogicChain extends AbstractLogicChain {
@Resource
private IStrategyRepository repository;
@Override
public Integer logic(String userId, Long strategyId) {
log.info("抽奖责任链-黑名单开始 userId: {} strategyId: {} ruleModel: {}", userId, strategyId, ruleModel());
// 查询规则值配置
String ruleValue = repository.queryStrategyRuleValue(strategyId, ruleModel());
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)) {
log.info("抽奖责任链-黑名单接管 userId: {} strategyId: {} ruleModel: {} awardId: {}", userId, strategyId, ruleModel(), awardId);
return awardId;
}
}
// 过滤其他责任链
log.info("抽奖责任链-黑名单放行 userId: {} strategyId: {} ruleModel: {}", userId, strategyId, ruleModel());
return next().logic(userId, strategyId);
}
@Override
protected String ruleModel() {
return "rule_blacklist";
}
}
这个类本质上是原来 RuleBackListLogicFilter 的责任链版替身。
它的逻辑是:
- 根据 strategyId + rule_blacklist 查规则值
- 解析出黑名单奖品 ID 和黑名单用户集合
- 如果当前用户命中黑名单,当前节点直接接管,返回固定 awardId
- 如果没命中,就调用 next().logic(...) 放给下一个节点
和旧版相比,最大的变化是:
- 旧版返回 RuleActionEntity<TAKE_OVER + awardId>
- 新版直接返回 awardId
也就是说,原来外层还要判断"是不是接管";现在黑名单节点自己接管并结束链路,调用方拿到的已经是最终结果。
另外,这个类用了 @Component("rule_blacklist"),名字正好和策略配置里的规则模型一致,这样工厂才能按名字从 Spring 容器里取出正确节点。
8.权重规则从 Filter 变成 Chain
java
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 权重抽奖责任链
* @create 2024-01-20 10:38
*/
@Slf4j
@Component("rule_weight")
public class RuleWeightLogicChain extends AbstractLogicChain {
@Resource
private IStrategyRepository repository;
@Resource
protected IStrategyDispatch strategyDispatch;
// 根据用户ID查询用户抽奖消耗的积分值,本章节我们先写死为固定的值。后续需要从数据库中查询。
public Long userScore = 0L;
/**
* 权重责任链过滤;
* 1. 权重规则格式;4000:102,103,104,105 5000:102,103,104,105,106,107 6000:102,103,104,105,106,107,108,109
* 2. 解析数据格式;判断哪个范围符合用户的特定抽奖范围
*/
@Override
public Integer logic(String userId, Long strategyId) {
log.info("抽奖责任链-权重开始 userId: {} strategyId: {} ruleModel: {}", userId, strategyId, ruleModel());
String ruleValue = repository.queryStrategyRuleValue(strategyId, ruleModel());
// 1. 根据用户ID查询用户抽奖消耗的积分值,本章节我们先写死为固定的值。后续需要从数据库中查询。
Map<Long, String> analyticalValueGroup = getAnalyticalValue(ruleValue);
if (null == analyticalValueGroup || analyticalValueGroup.isEmpty()) return null;
// 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】
/* 找到最后一个符合的值[如用户传了一个 5900 应该返回正确结果为 5000],如果使用 Lambda findFirst 需要注意使用 sorted 反转结果
* Long nextValue = null;
* for (Long analyticalSortedKeyValue : analyticalSortedKeys) {
* if (userScore >= analyticalSortedKeyValue){
* nextValue = analyticalSortedKeyValue;
* }
* }
* 星球伙伴 @慢慢来 ID 6267 提供
* Long nextValue = analyticalSortedKeys.stream()
* .filter(key -> userScore >= key)
* .max(Comparator.naturalOrder())
* .orElse(null);
*/
Long nextValue = analyticalSortedKeys.stream()
.sorted(Comparator.reverseOrder())
.filter(analyticalSortedKeyValue -> userScore >= analyticalSortedKeyValue)
.findFirst()
.orElse(null);
// 4. 权重抽奖
if (null != nextValue) {
Integer awardId = strategyDispatch.getRandomAwardId(strategyId, analyticalValueGroup.get(nextValue));
log.info("抽奖责任链-权重接管 userId: {} strategyId: {} ruleModel: {} awardId: {}", userId, strategyId, ruleModel(), awardId);
return awardId;
}
// 5. 过滤其他责任链
log.info("抽奖责任链-权重放行 userId: {} strategyId: {} ruleModel: {}", userId, strategyId, ruleModel());
return next().logic(userId, strategyId);
}
@Override
protected String ruleModel() {
return "rule_weight";
}
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;
}
}
现在从 @LogicStrategy(...) 换成 @Component("rule_weight"),本质上就是在表达一件事:
这类规则已经不再交给"规则过滤器工厂"管理,而是改成交给"责任链工厂"管理了。
可以分开理解。
以前的方式是这样的:
- 类上用@ LogicStrategy(logicMode = ...)
- DefaultLogicFactory 在启动时扫描所有 ILogicFilter
- 读取类上的 @LogicStrategy
- 按 logicMode().getCode() 放进 logicFilterMap
- 后面通过 ruleModel 去工厂里拿对应过滤器
这套机制服务的是 过滤器模式。
现在 rule_weight、rule_blacklist 被改成责任链节点之后,它们已经不再实现 ILogicFilter,而是实现了 ILogicChain,所以自然也就不该再交给 DefaultLogicFactory 管。
现在的方式变成:
- 类上直接写 @Component("rule_weight")
- Spring 启动时把它注册成一个 Bean,Bean 名就是 "rule_weight"
- DefaultChainFactory 通过构造器注入 Map<String, ILogicChain> logicChainGroup
- Spring 会自动把所有实现了 ILogicChain 的 Bean 收集进这个 Map
- Map 的 key 就是 Bean 名,也就是 "rule_weight"、"rule_blacklist"、"default"
然后 DefaultChainFactory 再根据策略配置里的 ruleModels,按名字把这些链节点拼起来。
所以现在确实是:
- rule_weight、rule_blacklist 交给 责任链工厂 管
- rule_lock 这种中置规则,仍然交给 过滤器工厂 管
你可以把它记成两套机制:
前置规则
- 用 @Component("rule_weight")
- 由 Spring 注册 Bean
- 由 DefaultChainFactory 通过 Map<String, ILogicChain> 自动收集和装配
- 不再经过 DefaultLogicFactory
中置/后置规则
- 用 LogicStrategy(...)
- 由 DefaultLogicFactory 扫描并放入 logicFilterMap
- 继续走过滤器工厂模式
Map<String, ILogicChain> logicChainGroup和之前过滤器模式里构造器注入 List<ILogicFilter<?>> 是同一类机制,只不过这次注入的不是 List,而是 Map
9.新增默认责任链
java
/**
* @author Fuzhengwei bugstack.cn @小傅哥
* @description 默认的责任链「作为最后一个链」
* @create 2024-01-20 10:06
*/
@Slf4j
@Component("default")
public class DefaultLogicChain extends AbstractLogicChain {
@Resource
protected IStrategyDispatch strategyDispatch;
@Override
public Integer logic(String userId, Long strategyId) {
Integer awardId = strategyDispatch.getRandomAwardId(strategyId);
log.info("抽奖责任链-默认处理 userId: {} strategyId: {} ruleModel: {} awardId: {}", userId, strategyId, ruleModel(), awardId);
return awardId;
}
@Override
protected String ruleModel() {
return "default";
}
}
这个类是责任链模式里非常关键的兜底节点。
它的职责很单纯:
- 不判断任何规则
- 直接走默认概率抽奖 strategyDispatch.getRandomAwardId(strategyId)
也就是说,只要前面的黑名单节点、权重节点都没有接管,请求最终一定会落到这里。
这保证了责任链的结果闭环:
链条前面是"规则决策",链条末尾是"默认抽奖"。
10.仓储接口变更
java
String queryStrategyRuleValue(Long strategyId, String ruleModel)
它内部实际上还是复用原来的三参方法,只是把 awardId 传 null。
这个改动的原因很清楚:
- 黑名单、权重这类前置规则是"策略级规则"
- 它们查询规则值时不需要 awardId
- 所以责任链节点里直接调用两参版本更自然