状态模式背景
一般来说,使用状态模式都是有很多的状态转换,如果在代码中直接转换,状态特别多的情况下回特别的凌乱,不太方便维护也是不太好理解!从设计模式的使用原则来说,一个类的职责最好单一,各种状态转换搞得云里雾里的,PD给你增加一个新的需求估计你就要炸了。
笔者需求遇到了啥事
需求简单的就是当短信发送失败了,我们要根据第三方提供的能力,通过身份证号码,姓名,之前使用过的手机号码获取到他可能现在还在使用的号码,然后再次的继续发送短信。这里发现了没有,这个是状态之间转换先兆。具体的步骤就是
1、根据条件信息发送处理请求
2、过一段时间根据API标识获取结果(可能是直接给你电话信息,或者唯一标识能够通过标识发送短信)
3、然后就是成功还是失败啦。
如果可以在一个线程中直接处理完成,责任链模式或者是一个不错的选择哦!但是请求第三方的数据都是异步处理的,没有办法一口气完成啊!
之前的实现的逻辑为:(每个定时器都是根据某种特定的状态进行获取相应的数据)
1、(失败定时任务)定时任务扫描发送失败的数据,然后进行发送处理请求
2、(获取结果定时器)定时任务获取之前发送过处理请求的结果信息的获取
3、(发送短信息)定时任务,根据上一个任务处理完成的状态获取结果,然后进行发送信息;
看上去能够完成任务,简单的一个API的接口提供商感觉使用起来还是可以的!现在的需求就是增加一个接口提供商,且接口提供商的处理逻辑还有些不一样,之前的是能够直接获取真实的号码,新的接口提供商没有真实的号码,只给你中间标识信息,能够通过标识处理数据。
新需求的一些思考
1、肯定不会在将所有的状态的转换放置在不同的定时器中处理,新的员工来了分布在不同的地方不方便熟悉业务以及理解业务(反正我是看了一天左右才懂的)
2、状态的转换的处理一定要集中处理,通过状态模式进行抽象,方便扩展新的状态(比如有个人的号码经常发送失败,但是反查后又发送成功了,我们可以在处理之前添加一个状态,能够扩展之前发送成功的例子,减少外部API的调用,省钱)
3、抽象后的方法处理起来一定是非常的灵活的!能够在不同的状态下处理自己应该做的事情,不去干涩其他状态下的任务。
设计方案一
状态转换的设计流程
简单的说一下:第一个接口提供商无法满足业务的要求,也就是最终获取备案请求成功,不发送短信;或者备案请求成功且发送短信成功,才不用切换接口提供商,否则切换下一个接口提供商
这个方案:状态比较多,最处理失败的原因就是和短信的发送耦合在一起了,短信发送的成功失败可以交给具体的短信发送处理服务去处理相应的逻辑,当接收到后通知处理查询发送短信失败的结果服务就好了!这个是两个服务应该减少耦合性处理。
设计方案二
状态转换设计流程
虽然根据第三方的API获取到具体的联系人目前使用的手机信息之后,发送短信处理逻辑对于整个流程的成功失败(切换供应商)是有影响的!但是我们应该抽象一下,减少两个系统之间的耦合,反查身份的流程其实就是获取到反查的结果就完了,发送短信是下一个能力,且切换接口提供商和当前的服务没有特别多的联系哦!
实现
状态类实现
状态转换Context,根据状态标识获取具体的状态处理类
@Component
public class AssetsStateProcessContext {
public static final Logger logger = LoggerFactory.getLogger(AssetsStateProcessContext.class);
/**
* 所有的状态的仓库信息
*/
@Autowired
private List<AbstractAssetsState> sateRepositorys;
/**
* 空状态处理器
*/
@Autowired
private AbstractAssetsState assetsDoNothingState;
/**
* 根据当前的反查请求的状态{@link AssetsRepairRequestDO#getStatus()},获取当前处理当前状态的{@link AbstractAssetsState}
* 根据当前的状态标识,获取到具体的状态的处理类
* @param assetsRepairRequestDO
* @return
*/
public AbstractAssetsState getRepairRequestAssetsState(AssetsRepairRequestDO assetsRepairRequestDO) {
AbstractAssetsState resultAssetsState = this.assetsDoNothingState;
if (CollectionUtils.isNotEmpty(this.sateRepositorys) && assetsRepairRequestDO != null
&& StringUtils.isNotEmpty(assetsRepairRequestDO.getStatus())) {
for (AbstractAssetsState assetsState : this.sateRepositorys) {
//获取处理当前状态的AssetsState
if (assetsState.isSupport(assetsRepairRequestDO.getStatus())) {
resultAssetsState = assetsState;
break;
}
}
logger.debug("AssetsRepairRequestDO get AbstractAssetsState,id:{},AssetsState:{}",
assetsRepairRequestDO.getId(), resultAssetsState.getStatusEnum().toString());
}
return resultAssetsState;
}
}
定时任务处理
@Component
public class AssetsStateTransitionJob {
private static Logger logger = LoggerFactory.getLogger(AssetsStateTransitionJob.class);
/**
* 失联信息处理类
*/
@Autowired
private AssetsRepairRequestBO assetsRepairRequestBO;
@Autowired
private AssetsStateProcessContext assetsStateProcessContext;
/**
* 状态调度核处理,将查询需要处理的状态{@link AssetsRepairRequestStatusEnum}
* 然后执行具体状态下的处理策略{@link AbstractAssetsState} 具体查看其实现类
*
* @param status
* @param beginSize
* @param pageSize
*/
public void handlerTimingschedulerAssetsRepairRequest(List<String> status, Integer beginSize, Integer pageSize) {
List<AssetsRepairRequestDO> assetsRepairRequestDOS = assetsRepairRequestBO
.queryRepairRequestByStatusAndEndTimeIsNull(status, beginSize, pageSize);
if (CollectionUtils.isNotEmpty(assetsRepairRequestDOS)) {
for (AssetsRepairRequestDO repairRequestItem : assetsRepairRequestDOS) {
//1、获取当前资产反查请求对应的状态处理类
AbstractAssetsState abstractAssetsState = assetsStateProcessContext.getRepairRequestAssetsState(
repairRequestItem);
try {
//2、执行当前状态下需要处理的业务
abstractAssetsState.processState(repairRequestItem);
} catch (StateTransitionException e) {
//3、当前的状态与数据库中的状态不一致,更新状态时失败,进行数据回滚
logger.info("repair request status has changed, rollback,id:{},message:{}",
repairRequestItem.getId(), e.getMessage());
} catch (Exception e) {
logger.error("repair request deal error ,id:{}", repairRequestItem.getId(), e);
}
}
}
}
}
其他发送短信
对于处理不同状态下不同的接口提供商处理不同的备案逻辑(API接口,是不是返回真实号码等等),最土的方式就是if else 搞定,或者使用策略模式+工厂处理模式
发送策略
public abstract class AbstractAssetsSendMessageStrategy {
public AbstractAssetsSendMessageStrategy() {
initChannle();
}
private AssetsRepairChannelEnum channelEnum = AssetsRepairChannelEnum.EMPTY;
public AssetsRepairChannelEnum getChannelEnum() {
return channelEnum;
}
/**
* 初始化设置当前的channel
*/
public void initChannle(){
}
/**
* 设置当前接口提供商的名称
* @param channelEnum
*/
protected void setChannelEnum(AssetsRepairChannelEnum channelEnum) {
this.channelEnum = channelEnum;
}
/**
* 是否支持 当前的接口提供商
* @param channelName
* @return
*/
public boolean isSupport(String channelName){
return this.channelEnum.getValue().equals(channelName);
}
/**
* 发送消息
* @param assetsRepairResultDO
* @return
*/
public abstract boolean sendAssetsMessage(AssetsRepairResultDO assetsRepairResultDO);
}
策略工厂 ,外部直接调用策略工厂就可以处理啦,在添加新的接口提供商也没有关系啦
/**
* 发送反查信息抽象策略工厂
*
* @author wangji
*/
@Component
public class AssetsSendMessageFactory {
public static final Logger logger = LoggerFactory.getLogger(AssetsSendMessageFactory.class);
@Autowired
private List<AbstractAssetsSendMessageStrategy> sendMessageStrategies;
@Autowired
private AbstractAssetsSendMessageStrategy assetsEmptySendMessageStategy;
/**
* 发送短信
*
* @param assetsRepairResultDO
* @return
*/
public boolean sendAssetsMessage(AssetsRepairResultDO assetsRepairResultDO) {
boolean result = false;
if (assetsRepairResultDO != null) {
AbstractAssetsSendMessageStrategy assetsSendMessageStrategy = getAssetsSendMessageStrategy(
assetsRepairResultDO);
result = assetsSendMessageStrategy.sendAssetsMessage(assetsRepairResultDO);
logger.info("send assets message reuslt;resultId:{},result:{}", assetsRepairResultDO.getId(), result);
}
return result;
}
public void batchSendAssetsMessages(List<AssetsRepairResultDO> assetsRepairResultDOS) {
Map<Long, Boolean> resultMap = new HashMap<>(1);
if (assetsRepairResultDOS != null) {
resultMap = new HashMap<>(assetsRepairResultDOS.size());
if (CollectionUtils.isNotEmpty(assetsRepairResultDOS)) {
for (AssetsRepairResultDO assetsRepairResultDO : assetsRepairResultDOS) {
resultMap.put(assetsRepairResultDO.getId(), sendAssetsMessage(assetsRepairResultDO));
}
}
}
logger.info("batchSendAssetsMessages result:{}", resultMap.toString());
}
/**
* 获取当前发送资产反查接口提供商的发送策略
*
* @param assetsRepairResultDO
* @return
*/
private AbstractAssetsSendMessageStrategy getAssetsSendMessageStrategy(AssetsRepairResultDO assetsRepairResultDO) {
AbstractAssetsSendMessageStrategy result = assetsEmptySendMessageStategy;
if (assetsRepairResultDO != null && CollectionUtils.isNotEmpty(this.sendMessageStrategies)
&& StringUtils.isNotEmpty(assetsRepairResultDO.getRepairChannel())) {
for (AbstractAssetsSendMessageStrategy assetsSendMessageStrategy : sendMessageStrategies) {
//找到合适的策略 根据接口提供商的类型
if (assetsSendMessageStrategy.isSupport(assetsRepairResultDO.getRepairChannel())) {
result = assetsSendMessageStrategy;
}
}
logger.debug("get AbstractAssetsSendMessageStrategy ,asseRepairtResult id:{},repairChannle:{},Strategy:{}",
assetsRepairResultDO.getId(), assetsRepairResultDO.getRepairChannel(),
result.getChannelEnum().toString());
}
return result;
}
}