
一、本章诉求在前面的章节中我们已经结合策略模式、工厂模式逐步完成了抽奖流程的定义以及抽奖前、抽奖中、抽奖后各类规则的过滤处理。整体功能虽然已经跑通但随着规则不断增加问题也开始显现出来。尤其是在前置规则这一层部分规则本身不仅承担了“校验是否放行”的职责还直接介入了后续抽奖行为的处理。这样一来规则流程中混入了过多业务动作导致单个规则节点负责的事情越来越多代码职责也变得不够单一整体实现显得有些臃肿。因此在这一节中我们准备继续对抽奖规则的处理结构做一次优化。这里会引入责任链模式对前置规则的执行流程进行重构把原本耦合在一起的规则判断与抽奖处理进一步拆开让每个规则节点只关注自己该负责的判断逻辑同时也让整个抽奖流程的结构更加清晰、扩展起来更加自然。二、流程设计设计前我们需要思考 抽奖的前置规则在抽奖中是一个什么行为。其实它可以被抽象为一种策略行为比如黑名单抽奖策略、权重抽奖策略、白名单抽奖策略等。而这些策略规则是一种互斥行为比如走了黑名单规则就不应该在继续走权重规则了。那么对于这样的情况责任链的设计就更加合适了。三、功能实现1. 工程结构rule 规则包下拆2部分。一部分规则过滤一部分新增的 chain 责任链用于抽奖使用。迁移 AbstractRaffleStrategy 抽象类到 strategy 包下形成一个上下调用的结构。从抽象类调用到下面各个模块功能。「这块的方式不影响编码实现只是做规整处理」后续再有抽奖的不同策略都可以在 chain 下做具体的责任节点实现。2.. 抽奖主流程重构/** * 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. 抽奖中 - 规则过滤 RuleActionEntityRuleActionEntity.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 RuleActionEntityRuleActionEntity.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.默认抽奖策略实现被瘦身/** * 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 RuleActionEntityRuleActionEntity.RaffleCenterEntity doCheckRaffleCenterLogic(RaffleFactorEntity raffleFactorEntity, String... logics) { if (logics null || 0 logics.length) return RuleActionEntity.RuleActionEntity.RaffleCenterEntitybuilder() .code(RuleLogicCheckTypeVO.ALLOW.getCode()) .info(RuleLogicCheckTypeVO.ALLOW.getInfo()) .build(); MapString, ILogicFilterRuleActionEntity.RaffleCenterEntity logicFilterGroup logicFactory.openLogicFilter(); RuleActionEntityRuleActionEntity.RaffleCenterEntity ruleActionEntity null; for (String ruleModel : logics) { ILogicFilterRuleActionEntity.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.新增责任链顶层接口public interface ILogicChain extends ILogicChainArmory{ /** * 责任链接口 * * param userId 用户ID * param strategyId 策略ID * return 奖品ID */ Integer logic(String userId, Long strategyId); }/** * author Fuzhengwei bugstack.cn 小傅哥 * description 责任链装配 * create 2024-01-20 11:53 */ public interface ILogicChainArmory { ILogicChain next(); ILogicChain appendNext(ILogicChain next); }/** * 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)返回值是 awardIdILogicChainArmory 定义了链式装配能力next() 和 appendNext(...)AbstractLogicChain 作为抽象基类内部持有 next 指针并统一实现链路拼接逻辑这套设计意味着每一个前置规则节点都可以“自己决定是否接管”如果不接管就把请求交给下一个节点最终一定会落到一个默认节点确保总能产出结果6.新增责任链工厂/** * author Fuzhengwei bugstack.cn 小傅哥 * description 工厂 * create 2024-01-20 10:54 */ Service public class DefaultChainFactory { private final MapString, ILogicChain logicChainGroup; protected IStrategyRepository repository; public DefaultChainFactory(MapString, 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/** * 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(...) 放给下一个节点和旧版相比最大的变化是旧版返回 RuleActionEntityTAKE_OVER awardId新版直接返回 awardId也就是说原来外层还要判断“是不是接管”现在黑名单节点自己接管并结束链路调用方拿到的已经是最终结果。另外这个类用了 Component(rule_blacklist)名字正好和策略配置里的规则模型一致这样工厂才能按名字从 Spring 容器里取出正确节点。8.权重规则从 Filter 变成 Chain/** * 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查询用户抽奖消耗的积分值本章节我们先写死为固定的值。后续需要从数据库中查询。 MapLong, String analyticalValueGroup getAnalyticalValue(ruleValue); if (null analyticalValueGroup || analyticalValueGroup.isEmpty()) return null; // 2. 转换Keys值并默认排序 ListLong 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 MapLong, String getAnalyticalValue(String ruleValue) { String[] ruleValueGroups ruleValue.split(Constants.SPACE); MapLong, 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 启动时把它注册成一个 BeanBean 名就是 rule_weightDefaultChainFactory 通过构造器注入 MapString, ILogicChain logicChainGroupSpring 会自动把所有实现了 ILogicChain 的 Bean 收集进这个 MapMap 的 key 就是 Bean 名也就是 rule_weight、rule_blacklist、default然后 DefaultChainFactory 再根据策略配置里的 ruleModels按名字把这些链节点拼起来。所以现在确实是rule_weight、rule_blacklist 交给责任链工厂管rule_lock 这种中置规则仍然交给过滤器工厂管你可以把它记成两套机制前置规则用 Component(rule_weight)由 Spring 注册 Bean由 DefaultChainFactory 通过 MapString, ILogicChain 自动收集和装配不再经过 DefaultLogicFactory中置/后置规则用 LogicStrategy(...)由 DefaultLogicFactory 扫描并放入 logicFilterMap继续走过滤器工厂模式MapString, ILogicChain logicChainGroup和之前过滤器模式里构造器注入 ListILogicFilter? 是同一类机制只不过这次注入的不是 List而是 Map9.新增默认责任链/** * 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.仓储接口变更String queryStrategyRuleValue(Long strategyId, String ruleModel)它内部实际上还是复用原来的三参方法只是把 awardId 传 null。这个改动的原因很清楚黑名单、权重这类前置规则是“策略级规则”它们查询规则值时不需要 awardId所以责任链节点里直接调用两参版本更自然