一、前言
上一期我们实现了权重配置的装配,这一期我们将实现抽奖业务的前置规则过滤,这里主要涉及了两个规则,第一个是黑名单(要求这些用户100积分只能抽到1积分),第二个是权重抽奖(在幸运值达到指定分数时,触发保底机制),为什么叫做前置过滤呢,因为黑名单和权重都需要在每次抽奖前就判断好,也就类似于一个拦截器。
我认为在实现这些复杂业务的时候应该遵循“找核心”的思维,从整体推到局部,并且应该先确定整体的核心类,然后再确定核心类中的核心方法,接着确定核心方法中的核心流程,最后再去看想要实现这个流程需要补充哪些实体/工厂/实现类。
二、核心流程抽象类
这里的基本准备很重要,后面的规则过滤都需要通过这些准备内容来实现,我们在这里需要准备两部分内容:
1.过滤器的整体框架(接口)
2.各种实体类
1. 核心类AbstractRaffleStrategy
我们以往写对请求的过滤器时我们看到的preHandle/postHandle这些方法其实就是前置/后置过滤,当然,我们这里不能使用这个,因为我们不是对请求拦截,只是在抽奖业务内部对业务规则进行拦截,所以我们在这里选择创建一个抽象类来充当一个过滤器框架。
public interface HandlerInterceptor { // 在 Controller 方法执行前调用 boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; // 在 Controller 方法执行后,视图渲染前调用 void postHandle(...); // 在整个请求结束后调用 void afterCompletion(...); }首先要明确整体流程,这个是最重要的,视频中在这部分就讲得不好,如果结构没有理清楚,后面基本上都是在抄代码。
整体流程:
1.开始抽奖 —— 从抽象类AbstractRaffleStrategy中开始
2.校验参数
3.过滤
4.在过滤时检验userId是否在黑名单,在的话就返回一个固定的奖品
5.如果不在就再去检验有没有权重规则(检查幸运值)
搞清楚流程过后就可以开始写代码了,首先应该先写出刚刚我们看到的类似于请求拦截器的过滤结构框架,然后再在其中的抽奖前过滤方法中增加过滤逻辑:
首先要注意,我们是要创建一个抽象类,确实很久没有写过抽象类了,一直以来都是通过实现接口的方式来规定方法,在这里回顾一下抽象类的作用:
抽象类的存在就是为了让子类继承的,子类会继承抽象父类的所有非私有方法。
这里的这个抽奖策略抽象类是本小节的核心类,它规定了整个抽奖的流程,也就是建立了一个抽奖框架。
/** * @author 印东升 * @description 抽奖策略抽象类,定义抽奖的标准流程 * @create 2026-04-10 11:19 */ @Slf4j public abstract class AbstractRaffleStrategy implements IRaffleStrategy { //策略仓储服务 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); }2.核心方法performRaffle()
我们必须好好分析这个类,必须理解它每一部分在干什么,因为这个类是核心。
首先,performRaffle()是这个抽象类中最核心的方法,是用于进行抽奖前置过滤的方法,我们需要传入userId和strategyId(封装到了一个RaffleFactorEntity实体类),用于校验参数,因为我们需要知道当前的userId才能知道其是否在黑名单之中,同时我们需要知道当前的strategyId,用于获取当前策略的所有规则(比如这里的100001号策略里面就有两个规则,一个是黑名单,一个是权重规则,后续要一个一个地去过滤它们)
public RaffleAwardEntity performRaffle(RaffleFactorEntity raffleFactorEntity)然后我们来看看这个核心方法里面的流程:
1.参数校验,从传入的raffleFactorEntity对象中获取我们想要的userId和strategyId,判空:
//抽奖因子实体 @Data @Builder @AllArgsConstructor @NoArgsConstructor public class RaffleFactorEntity { /** * 用户id */ private String userId; /** * 策略id */ private Long strategyId; }// 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.查询策略,这里查询策略实体是为了获取策略,本质还是通过从表中获取策略规则ruleModels
(这里调用仓储,然后仓储再调用Dao,最后Dao查表)
// 2. 策略查询 StrategyEntity strategy = repository.queryStrategyEntityByStrategyId(strategyId);3.核心流程:规则过滤
// 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(); } }这里就是核心方法中的核心流程了,接下来一步一步来拆开理解;
核心流程分为四步:
RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> ruleActionEntity = this.doCheckRaffleBeforeLogic(RaffleFactorEntity.builder().userId(userId).strategyId(strategyId).build(), strategy.ruleModels());1.核心流程的第一步中我们定义了一个新的实体类RuleActionEntity,这个实体类结构有点复杂,但是目的其实就是通过泛型和内部静态类来做到一个大实体类中含多个用途实体类。
再说详细一点:也就是这一个实体类中有三个用途,分别是前中后过滤,那么我们就写三个内部静态类,通过泛型我们就可以知道当前是用的哪个实体类,这样的好处是可以将这三个用途的实体类放在一起统一管理,并且它们三个用途实体类的一些共有的属性可以不用重复写了,直接写在大实体类中就行了。
而这个前置用途实体类里面放什么呢?就放我们前置过滤需要用的属性就行了,所以有7个属性:code、info、ruleModel、data(共有)和strategyId、ruleWeightValueKey、awardId(前置)
这里调用了doCheckRaffleBeforeLogic()方法,暂时按下不表。
//规则动作实体 @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 {} }if (RuleLogicCheckTypeVO.TAKE_OVER.getCode().equals(ruleActionEntity.getCode()))2.核心流程第第二步时我们已经拿到了规则动作实体对象了,现在要做的是判断是放行还是接管,如果放行,就直接走默认抽奖了,如果接管就要开始过滤规则了。
//规则过滤校验类型值对象 @Getter @AllArgsConstructor public enum RuleLogicCheckTypeVO { ALLOW("0000", "放行:执行后续的流程,不受规则引擎影响"), TAKE_OVER("0001", "接管:后续的流程,受规则引擎执行结果影响"), ; private final String code; private final String info; }-----------------------------------------分割线-------------------------------------------------if (DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode().equals(ruleActionEntity.getRuleModel())) { // 黑名单返回固定的奖品ID return RaffleAwardEntity.builder() .awardId(ruleActionEntity.getData().getAwardId()) .build(); }3.核心流程第三步:进行黑名单过滤,这里涉及到了规则工厂的事情了,这里面的代码看着很复杂,还涉及到了注解,其实不要想复杂了,这个工厂的核心作用是映射,我们创建了一个注解LogicStrategy,这个注解专门标记规则的实现类(标记后让Spring放到容器里面),这个工厂就是将容器中的规则过滤实现类整理到一个map中(比如黑名单和权重规则)。
那么显然的,当命中了黑名单规则,我们就写死一个奖品返回给用户。
//规则工厂 @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; } }//策略自定义枚举 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface LogicStrategy { DefaultLogicFactory.LogicModel logicMode(); }-----------------------------------------分割线-----------------------------------------------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.核心流程第四步:权重规则过滤,这里比较好理解,就是拿到指定权重的权重值,比如:4000:101,102,103,然后调用前面一节课的权重概率装配,获取权重配置中的随机奖品,最后返回给用户。
-----------------------------------------分割线-----------------------------------------------最后就是规则都没有被命中,就直接走默认的抽奖流程了,很好理解。
// 4. 默认抽奖流程 Integer awardId = strategyDispatch.getRandomAwardId(strategyId); return RaffleAwardEntity.builder() .awardId(awardId) .build();别忘了在核心类里面还有一个方法,这个方法我们放下一个板块讲:
protected abstract RuleActionEntity<RuleActionEntity.RaffleBeforeEntity> doCheckRaffleBeforeLogic(RaffleFactorEntity raffleFactorEntity, String... logics);三、规则过滤的实现类
1.核心子类DefaultRaffleStrategy
接下来的两个类就是刚刚提到的被注解标记的过滤规则的实现类了,你可能会有疑惑,我怎么感觉没用到这个实现类呢?其实是因为我们还没有讲刚刚提到的doCheckRaffleBeforeLogic()方法,这个方法是protected的,也就是是供子类重写的,所以我们真正执行这个流程的不是刚刚的核心抽象类,而是核心抽象类的子类——DefaultRaffleStrategy 。
@Service @Slf4j 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; } }这里面就可以很容易地看到它实现了doCheckRaffleBeforeLogic()方法,这里面就把刚刚注解那里讲的——被Spring放到容器中的实现类map给取出来了,取出来了然后就可以按照优先级顺序处理了。
两个规则过滤实现类如下:
2.黑名单过滤规则实现类
/** * @author 印东升 * @description 【抽奖前规则】黑名单用户过滤规则 * @create 2026-04-10 11:53 */ @Slf4j @Component @LogicStrategy(logicMode = DefaultLogicFactory.LogicModel.RULE_BLACKLIST) public class RuleBlackListLogicFilter implements ILogicFilter<RuleActionEntity.RaffleBeforeEntity> { @Resource private IStrategyRepository repository; @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]); //100:user001,user002,user003 // 过滤其他规则 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(); } }3.权重过滤规则的实现类
/** * @author 印东升 * @description 【抽奖前规则】根据抽奖权重返回可抽奖范围KEY * @create 2026-04-10 12:30 */ @Slf4j @Component @LogicStrategy(logicMode = DefaultLogicFactory.LogicModel.RULE_WIGHT) public class RuleWeightLogicFilter implements ILogicFilter<RuleActionEntity.RaffleBeforeEntity> { @Resource private IStrategyRepository repository; private 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()); //查询表中的规则值,比如4000:102,103,104,105 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; } }四、测试
注意哈,我们刚刚的大抽象类是实现了IRaffleStrategy接口的哦,所以它的子类也是实现了的,这就是专门为了后续直接调用接口来实现整个规则过滤业务,面向接口编程。这里注入这个接口实际上是注入了子类,也就是那个完整实现了doCheckRaffleBeforeLogic和performRaffle的DefaultRaffleStrategy 。
public interface IRaffleStrategy { RaffleAwardEntity performRaffle(RaffleFactorEntity raffleFactorEntity); }//抽奖策略测试 @Slf4j @RunWith(SpringRunner.class) @SpringBootTest public class RaffleStrategyTest { @Resource private IRaffleStrategy raffleStrategy; @Resource private RuleWeightLogicFilter ruleWeightLogicFilter; @Before public void setUp() { ReflectionTestUtils.setField(ruleWeightLogicFilter, "userScore", 4050L); } @Test public void test_performRaffle() { RaffleFactorEntity raffleFactorEntity = RaffleFactorEntity.builder() .userId("yds") .strategyId(100001L) .build(); RaffleAwardEntity raffleAwardEntity = raffleStrategy.performRaffle(raffleFactorEntity); log.info("请求参数:{}", JSON.toJSONString(raffleFactorEntity)); log.info("测试结果:{}", JSON.toJSONString(raffleAwardEntity)); } @Test public void test_performRaffle_blacklist() { RaffleFactorEntity raffleFactorEntity = RaffleFactorEntity.builder() .userId("user003") // 黑名单用户 user001,user002,user003 .strategyId(100001L) .build(); RaffleAwardEntity raffleAwardEntity = raffleStrategy.performRaffle(raffleFactorEntity); log.info("请求参数:{}", JSON.toJSONString(raffleFactorEntity)); log.info("测试结果:{}", JSON.toJSONString(raffleAwardEntity)); } }测试结果如下:
五、本节的设计模式
1.模板方法模式
读到这里你可能会有一些疑惑,比如:
我们在其他地方都是使用的DefaultRaffleStrategy这个流程子类,那为啥不把AbstructRaffleStrategy中的performRaffle()直接写到DefaultRaffleStrategy中呢?
答:这是一种设计模式——模板方法模式
定义:在一个方法中定义一个算法的骨架,将某些步骤延迟到子类中实现。子类可以在不改变算法结构的情况下,重新定义算法的某些特定步骤。
这里我们将doCheckRaffleBeforeLogic()方法延迟到子类中实现,但是在父类中的performRaffle()方法中使用,doCheckRaffleBeforeLogic()方法是用于规定规则过滤的顺序的,如果后续我们想改变这个顺序,或者想用另外一个顺序,那就直接更改子类或者创建一个新子类重写新顺序方法即可,而不会去动父类的骨架。
优点:
1.代码复用:AbstructRaffleStrategy抽象类中的骨架方法是每个过滤流程必定要走的,并且是固定的。
2.反向控制:父类控制流程,在合适的时候调用子类的方法
3.防止子类破坏算法结构:确保所有子类的抽奖流程都遵循相同的步骤顺序。
另外围绕模板方法模式补充,模板方法模式中包含了三种方法:
1.模板方法——我们代码中的performRaffle()
2.抽象方法——我们代码中的doCheckRaffleBeforeLogic()
3.钩子方法——在我们的代码中没有体现,其实就是抽象模板类里面的非抽象方法,子类不一定要实现,但是可以选择性重写。(用钩子把自定义的逻辑挂到子类上去)
2.工厂模式
显然的,我们这一节中的工厂就是DefaultLogicFactory规则工厂了。
这个工厂的作用体现在将被注解标记的过滤实现类整理到一个map中,所以这个工厂制造的就是这个map对象,供我们在流程类DefaultRaffleStrategy中调用。
好处:解耦对象的创建和使用:我们不需要自己去拿这两个过滤实现类了,而是统一地在工厂中拿生产好的map。
3.策略模式
策略模式的核心思想是:定义一系列算法,将每个算法封装起来,让它们可以互相替换。
在我们的项目中的ILogicFilter是策略模式的体现,我们的两个过滤实现类(黑名单和权重)都实现了ILogicFilter接口,在流程类DefaultRaffleStrategy的doCheckRaffleBeforeLogic()方法中我们获取了工厂生产的map,然后从map中分别拿出了这两种策略(根据策略名的key)。
这里就符合策略模式的核心思想了,我们将每种规则过滤的实现类都封装起来了,然后放进一个map中,在处理不同的过滤规则时分别被调用(相当于替换策略)。
ILogicFilter<RuleActionEntity.RaffleBeforeEntity> logicFilter = logicFilterGroup.get(DefaultLogicFactory.LogicModel.RULE_BLACKLIST.getCode()); ILogicFilter<RuleActionEntity.RaffleBeforeEntity> logicFilter = logicFilterGroup.get(ruleModel);所以事实上这里是也是工厂模式和策略模式的混合运用。
六、总结
这种业务极度复杂的项目真的太难啃了,我觉得按照“找核心”的方式去剖析才能理清楚关系,这一节其实是很开阔眼界的,当然,我认为这里面最厉害的还是那个抽象类,之所以认为它是核心,就是因为它起到了承上启下的作用,上承工厂、接口,下启子类、注解实现类,它本身又是设计模式中模板方法模式的体现。
其实我觉得这一节的具体实现逻辑我后面很快就会忘,但是这样的业务结构非常值得去深度理解,确实是大开眼界了。