大营销平台 —— 模板方法串联前中置抽奖规则

一、前言

前面我们已经把前中置规则过滤完成了优化,前置采用责任链,中置采用决策树,以节点来抽象规则处理的实现,但是在我们最核心的类------AbstractRaffleStrategy中,我们会发现在核心流程方法中我们调用这两个模型(链和树)是没有按照一个统一的格式的,所以这一节我们将去串联这两个方式,并找到一套通用的流程体系,便于后续扩展。

二、模板方法类

在核心类的核心流程方法中,我们不再按照前置和中置来表示过滤的顺序,我们直接调用抽奖责任链或者抽奖决策树来过滤 ,所以下面的抽象方法也就改成了抽奖责任链和抽奖决策树的了,我们将在子类(具体模板类)装填这个链和树 ,后续会提到,这里我们直接调用装填好了的链和树,按照先链后树的顺序过滤

而这个核心流程方法,说专业一点其实就是设计模式中的模板方法 ,而这整个抽象类就是模板方法类,充当整个业务流程的骨架。

实现这个抽象类的子类其实叫做具体模板类,后续我们将用专业名称直接称呼这两个类。

java 复制代码
    @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. 责任链抽奖计算【这步拿到的是初步的抽奖ID,之后需要根据ID处理抽奖】注意;黑名单、权重等非默认抽奖的直接返回抽奖结果
        DefaultChainFactory.StrategyAwardVO chainStrategyAwardVO = raffleLogicChain(userId, strategyId);
        log.info("抽奖策略计算-责任链 {} {} {} {}", userId, strategyId, chainStrategyAwardVO.getAwardId(), chainStrategyAwardVO.getLogicModel());
        if (!DefaultChainFactory.LogicModel.RULE_DEFAULT.getCode().equals(chainStrategyAwardVO.getLogicModel())) {
            return RaffleAwardEntity.builder()
                    .awardId(chainStrategyAwardVO.getAwardId())
                    .build();
        }

        // 3. 规则树抽奖过滤【奖品ID,会根据抽奖次数判断、库存判断、兜底兜里返回最终的可获得奖品信息】
        DefaultTreeFactory.StrategyAwardVO treeStrategyAwardVO = raffleLogicTree(userId, strategyId, chainStrategyAwardVO.getAwardId());
        log.info("抽奖策略计算-规则树 {} {} {} {}", userId, strategyId, treeStrategyAwardVO.getAwardId(), treeStrategyAwardVO.getAwardRuleValue());

        return RaffleAwardEntity.builder()
                .awardId(treeStrategyAwardVO.getAwardId())
                .awardConfig(treeStrategyAwardVO.getAwardRuleValue())
                .build();

    }

     /**
     * 抽奖计算,责任链抽象方法
     *
     * @param userId     用户ID
     * @param strategyId 策略ID
     * @return 奖品ID
     */
    public abstract DefaultChainFactory.StrategyAwardVO raffleLogicChain(String userId, Long strategyId);

    /**
     * 抽奖结果过滤,决策树抽象方法
     *
     * @param userId     用户ID
     * @param strategyId 策略ID
     * @param awardId    奖品ID
     * @return 过滤结果【奖品ID,会根据抽奖次数判断、库存判断、兜底兜里返回最终的可获得奖品信息】
     */
    public abstract DefaultTreeFactory.StrategyAwardVO raffleLogicTree(String userId, Long strategyId, Integer awardId);
    

这里我就不赘述模板方法类了,里面的代码经过封装过后已经很容易看懂了。

三、具体模板类

具体模板类要实现模板方法类里面的抽象方法,以供模板方法使用。

所以这个类在这里的主要作用只有两个:

1.装填模型

2.执行模型

java 复制代码
@Service
@Slf4j
public class DefaultRaffleStrategy extends AbstractRaffleStrategy {

    public DefaultRaffleStrategy(DefaultTreeFactory defaultTreeFactory, DefaultChainFactory defaultChainFactory, IStrategyDispatch strategyDispatch, IStrategyRepository repository) {
        super(defaultTreeFactory, defaultChainFactory, strategyDispatch, repository);
    }

    @Override
    public DefaultChainFactory.StrategyAwardVO raffleLogicChain(String userId, Long strategyId) {
        //装填责任链
        ILogicChain logicChain = defaultChainFactory.openLogicChain(strategyId);
        return logicChain.logic(userId,strategyId);
    }

    @Override
    public DefaultTreeFactory.StrategyAwardVO raffleLogicTree(String userId, Long strategyId, Integer awardId) {
        //装填决策树
        StrategyAwardRuleModelVO strategyAwardRuleModelVO = repository.queryStrategyAwardRuleModelVO(strategyId, awardId);
        if(null == strategyAwardRuleModelVO){
            return DefaultTreeFactory.StrategyAwardVO.builder().awardId(awardId).build();
        }
        RuleTreeVO ruleTreeVO = repository.queryRuleTreeVOByTreeId(strategyAwardRuleModelVO.getRuleModels());
        if (null == ruleTreeVO) {
            throw new RuntimeException("存在抽奖策略配置的规则模型 Key,未在库表 rule_tree、rule_tree_node、rule_tree_line 配置对应的规则树信息 " + strategyAwardRuleModelVO.getRuleModels());
        }
        IDecisionTreeEngine treeEngine = defaultTreeFactory.openLogicTree(ruleTreeVO);
        return treeEngine.process(userId, strategyId, awardId);

    }
}

但是我们注意,在前面的课程中我们还没有去实现决策树的装填,当时我们是通过测试方法中直接装填的,很麻烦,所以在这里我们不能再像测试方法那样去做了,我们应该把繁琐的、需要间接调数据库的步骤都交给仓储来做。(而直接调用数据库的步骤就应该交给Dao来做了)

因此我们需要补全仓储和Dao,同时还应该补一下策略树的实体类(因为Dao需要用一个和数据库完全一样的实体对象来接收数据)

四、补全基础层

1.仓储

这里看着非常复杂,但是干的事情其实和先前的测试类的是一样的,只不过数据都是从数据库中拉出来的了,因此需要使用Dao来获取数据库的对象(RuleTree、RuleNodeTree、RuleNodeTreeLine)。做这个的时候不要忘了,我们只是想装填一个RuleTreeVO,因此我们在这里做的所有步骤仅仅是填充RuleTreeVO的属性而已。

java 复制代码
@Override
    public RuleTreeVO queryRuleTreeVOByTreeId(String treeId) {

        // 优先从缓存获取
        String cacheKey = Constants.RedisKey.RULE_TREE_VO_KEY + treeId;
        RuleTreeVO ruleTreeVOCache = redisService.getValue(cacheKey);
        if (null != ruleTreeVOCache) return ruleTreeVOCache;

        // 从数据库获取
        RuleTree ruleTree = ruleTreeDao.queryRuleTreeByTreeId(treeId);
        List<RuleTreeNode> ruleTreeNodes = ruleTreeNodeDao.queryRuleTreeNodeListByTreeId(treeId);
        List<RuleTreeNodeLine> ruleTreeNodeLines = ruleTreeNodeLineDao.queryRuleTreeNodeLineListByTreeId(treeId);

        // 1. tree node line 转换Map结构
        Map<String, List<RuleTreeNodeLineVO>> ruleTreeNodeLineMap = new HashMap<>();
        for (RuleTreeNodeLine ruleTreeNodeLine : ruleTreeNodeLines) {
            RuleTreeNodeLineVO ruleTreeNodeLineVO = RuleTreeNodeLineVO.builder()
                    .treeId(ruleTreeNodeLine.getTreeId())
                    .ruleNodeFrom(ruleTreeNodeLine.getRuleNodeFrom())
                    .ruleNodeTo(ruleTreeNodeLine.getRuleNodeTo())
                    .ruleLimitType(RuleLimitTypeVO.valueOf(ruleTreeNodeLine.getRuleLimitType()))
                    .ruleLimitValue(RuleLogicCheckTypeVO.valueOf(ruleTreeNodeLine.getRuleLimitValue()))
                    .build();

            List<RuleTreeNodeLineVO> ruleTreeNodeLineVOList = ruleTreeNodeLineMap.computeIfAbsent(ruleTreeNodeLine.getRuleNodeFrom(), k -> new ArrayList<>());
            ruleTreeNodeLineVOList.add(ruleTreeNodeLineVO);
        }

        // 2. tree node 转换为Map结构
        Map<String, RuleTreeNodeVO> treeNodeMap = new HashMap<>();
        for (RuleTreeNode ruleTreeNode : ruleTreeNodes) {
            RuleTreeNodeVO ruleTreeNodeVO = RuleTreeNodeVO.builder()
                    .treeId(ruleTreeNode.getTreeId())
                    .ruleKey(ruleTreeNode.getRuleKey())
                    .ruleDesc(ruleTreeNode.getRuleDesc())
                    .ruleValue(ruleTreeNode.getRuleValue())
                    .treeNodeLineVOList(ruleTreeNodeLineMap.get(ruleTreeNode.getRuleKey()))
                    .build();
            treeNodeMap.put(ruleTreeNode.getRuleKey(), ruleTreeNodeVO);
        }

        // 3. 构建 Rule Tree
        RuleTreeVO ruleTreeVODB = RuleTreeVO.builder()
                .treeId(ruleTree.getTreeId())
                .treeName(ruleTree.getTreeName())
                .treeDesc(ruleTree.getTreeDesc())
                .treeRootRuleNode(ruleTree.getTreeRootRuleKey())
                .treeNodeMap(treeNodeMap)
                .build();

        redisService.setValue(cacheKey, ruleTreeVODB);
        return ruleTreeVODB;
    }

RuleTreeVO :

java 复制代码
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RuleTreeVO {

    /** 规则树ID */
    private String treeId;
    /** 规则树名称 */
    private String treeName;
    /** 规则树描述 */
    private String treeDesc;
    /** 规则根节点 */
    private String treeRootRuleNode;

    /** 规则节点 */
    private Map<String, RuleTreeNodeVO> treeNodeMap;

}

Dao的部分我就不展示了,没啥特殊的,就是从数据库中把对应表的数据拉出来而已。

五、总结

别忘了把之前的filter部分的代码删了,同时对应的相关代码也应该删了,因为我们现在都是走责任链和决策树了,不会再单独使用filter了。

这一节其实难度不是很大,主要就是在优化模板方法类和具体模板类,可以看到,我们这里的整体思路还是找核心,只要抓住核心不放,其他的部分其实都只是缺啥补啥而已。

这里需要辨析一下决策树工厂、引擎、装填方法三者之间有什么区别?

组件 职责 类比
工厂 (DefaultTreeFactory) 创建引擎实例 汽车制造厂(负责生产汽车)
引擎 (DecisionTreeEngine) 执行规则树逻辑 汽车(负责运行)
装填方法 (raffleLogicTree) 协调流程:查询配置 → 调用工厂 → 调用引擎 司机(负责开车)

这里面,工厂需要logicTreeNodeMap 这个零件和RuleTreeVO 这个图纸才能生产汽车,生产出来的汽车给司机开,由于logicTreeNodeMap是可以改变的,所以工厂可以造出不同种类的车供司机开。

最后附上整体流程图,是作者亲自画的【狗头】

相关推荐
.柒宇.1 小时前
Java八股之== 与 equals 区别
java·开发语言
时间静止不是简史1 小时前
当MyBatis-Plus的like遇上SQL通配符:下划线翻车记
java·sql·mybatis
喜欢流萤吖~1 小时前
SpringBoot 性能优化实战
spring boot·后端·性能优化
两年半的个人练习生^_^2 小时前
每日一学:设计模式之建造者模式
java·开发语言·设计模式
我登哥MVP2 小时前
【SpringMVC笔记】 - 6 - RESTFul编程风格
java·spring boot·spring·servlet·tomcat·maven·restful
yhole2 小时前
spring security 超详细使用教程(接入springboot、前后端分离)
java·spring boot·spring
zjjsctcdl2 小时前
SpringBoot3.3.0集成Knife4j4.5.0实战
java
常利兵2 小时前
Spring Boot 搭建邮件发送系统:开启你的邮件自动化之旅
spring boot·后端·自动化
彭于晏Yan2 小时前
Spring Boot 集成邮件服务实现发送邮件功能
java·spring boot·后端