一、前言
前面我们已经把前中置规则过滤完成了优化,前置采用责任链,中置采用决策树,以节点来抽象规则处理的实现,但是在我们最核心的类------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是可以改变的,所以工厂可以造出不同种类的车供司机开。
最后附上整体流程图,是作者亲自画的【狗头】
