背景
上文說到BizManager的實現婿脸,其實整體看讯屈,這塊東西跟spring statemachine并沒有什么關系亡嫌,純粹是個人寫的收不住了蛔垢,把近期的一些東西整理下击孩。對這部分不感興趣的可以跳過,直接看下一章節(jié)鹏漆,那塊是從外部調用狀態(tài)機引擎的實現巩梢,還更有用一些。
BaseBizManager定義
BaseBizManager統一規(guī)范業(yè)務處理的接口艺玲,其定義如下:
import org.springframework.statemachine.StateMachine;
/**
* 定義寫服務的入口process模板方法
*
* @param <T>
* @param <R>
*/
@FunctionalInterface
public interface BaseBizManager<T, R> {
/**
* process模板括蝠,用于處理通用寫服務相關方法,包括處理冪等饭聚、記錄日志忌警、事務保證等
*
* @param request
* @return
* @throws BusinessException
*/
R process(T request, StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>... stateMachines) throws BusinessException;
}
注意這里用了個可選參數stateMachines,有些場景在處理邏輯內部是不需要狀態(tài)機的秒梳,可不傳法绵。
抽象類實現
抽象類中使用到了spring的反射工具類箕速,封裝了一層,先提供出來:
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.util.Objects;
public class ReflectionUtil {
/**
* 對Spring的ReflectionUtils中getValue方法做簡單封裝
*
* @param object
* @param key
* @param defaultVal
* @return
*/
public static Object getValue(Object object, String key, Object defaultVal) {
Field field = ReflectionUtils.findField(object.getClass(), key);
if (Objects.isNull(field)) {
return defaultVal;
}
field.setAccessible(true);
return ReflectionUtils.getField(field, object);
}
}
然后是對應的抽象類實現
import com.vipfins.finance.middleplatform.order.util.ReflectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.statemachine.StateMachine;
import org.springframework.transaction.annotation.Transactional;
/**
* 處理訂單更新模板類朋譬, 寫DB操作盐茎、日志、處理統一事務\冪等操作
*/
@Slf4j
public abstract class AbstractBizManagerImpl<T,R> implements BaseBizManager<T,R>{
@Autowired
private BizOrderIdemRepository orderIdemRepository;
@Autowired
private BizOrderLogEventPublisher bizOrderLogEventPublisher;
@Override
@Transactional(value = "finOrderocTransactionManager", rollbackFor = {BusinessException.class, Exception.class})
public R process(T request, StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>... stateMachines) throws BusinessException {
try {
// 冪等控制
if (checkIdem(request)) {
log.info("check idempotence bingo,request={}", request);
throw new BusinessException(BizOrderErrorCode.SUCCESS, "冪等操作徙赢,本次請求忽略");
}
// 實際業(yè)務處理
R resp = doProcess(request, stateMachines);
log.info("response = {}", resp);
return resp;
} catch (BusinessException e) {
log.error("process Business Exception = {}", e);
throw new BusinessException(e.getErrorCode(), ExceptionUtil.getErrorMsg(e));
} catch (Exception e) {
log.error("process Exception = {}", e);
throw new BusinessException(BizOrderErrorCode.ORDER_GENERIC_EXCEPTION, ExceptionUtil.getErrorMsg(e));
}
}
/**
* 實際的業(yè)務操作
*
* @param request 業(yè)務請求
* @param stateMachines 將上游處理后的stateMachine傳遞進來字柠,后續(xù)持久化,可選參數
* @return 業(yè)務結果
*/
public abstract R doProcess(T request, StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>... stateMachines) throws Exception;
/**
* 判斷是否冪等
* 冪等 ==> 返回true
*
* @param request
* @return
*/
private boolean checkIdem(T request) {
boolean result = false;
// 反射獲取請求中基礎數據
String bizOrderId = (String) ReflectionUtil.getValue(request, "bizCode", "");
String operationType = (String) ReflectionUtil.getValue(request, "operationType", "");
String sourceId = (String) ReflectionUtil.getValue(request, "sourceId", "");
String idemNo = bizOrderId + operationType + sourceId;
BizOrderIdem idem = new BizOrderIdem(idemNo, bizOrderId);
// 違反唯一性約束
try {
orderIdemRepository.insert(idem);
} catch (DuplicateKeyException e) {
result = true;
log.error("接口重復消費, idemNo = {}, orderCode = {}, exception = {}", idemNo, bizOrderId, e);
} catch (Exception e) {
log.error("未知異常狡赐,exception={}", e);
}
return result;
}
}
不同場景的BizManager實現
綜合來看窑业,有三種不同的bizManager實現:
- 創(chuàng)建訂單,創(chuàng)建邏輯自成一系枕屉,與其他業(yè)務邏輯思路均不相同
- 訂單狀態(tài)變化数冬,這塊主要關注訂單狀態(tài)的變化,當前是是什么搀庶,之后是什么拐纱,基本上所有訂單狀態(tài)變化的服務實現思路都一致
- 訂單狀態(tài)自動遷移,比如訂單從審核拒絕自動遷移到關閉狀態(tài)哥倔,這種需要主動觸發(fā)spring statemachine event秸架。
下面分別針對這三種實現方式說明:
訂單創(chuàng)建
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Map;
@Component("bizOrderCreateBizManager")
@Slf4j
public class BizOrderCreateBizManagerImpl extends AbstractBizManagerImpl<BizOrderCreateRequest, BizOrderCreateResponse> {
@Resource
private BaseConvertor<BizOrderCreateModel, BizOrder> createBizOrderConvertor;
@Resource
private BaseConvertor<BizOrderExtendsCreateModel, BizOrderExtends> createBizOrderExtendsConvertor;
@Resource
private BaseConvertor<BizOrderChannelCreateModel, BizOrderChannel> createBizOrderChannelConvertor;
@Resource
private BaseConvertor<BizOrderActivityCreateModel, BizOrderActivity> createBizOrderActivityConvertor;
@Autowired
private BizOrderRepository bizOrderRepository;
@Autowired
private BizOrderExtendsRepository bizOrderExtendsRepository;
@Autowired
private BizOrderChannelRepository bizOrderChannelRepository;
@Autowired
private BizOrderActivityRepository bizOrderActivityRepository;
@Autowired
private BizOrderLogEventPublisher bizOrderLogEventPublisher;
@Autowired
private FinMerchantContractQueryService contractQueryService;
/**
* 實際的業(yè)務操作
*
* @param request 業(yè)務請求
* @return 結果
*/
@Override
public BizOrderCreateResponse doProcess(BizOrderCreateRequest request, StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>... stateMachines) throws Exception {
BizOrderCreateModel createModel = request.getBizOrderCreateModel();
...... // 校驗邏輯
// 構造對應的訂單模型. 同時修改
BizOrder bizOrder = createBizOrderConvertor.modelToEntityConvertor(createModel);
bizOrder.setCallSystem(request.getCallSystem());
// MDC日志埋點
LogUtil.setBizId(bizOrder.getBizOrderId());
// 對于XXX業(yè)務,在創(chuàng)建訂單時由于沒有詳細金額咆蒿,而是在簽約是才把資金等信息傳遞過來东抹,前期對于傳遞過來的數據不做處理(無效)
if (!BizOrderBizTypeEnum.isIn(bizOrder.getBizType(), BizOrderBizTypeEnum.EMPLOAN) &&
CollectionUtils.isNotEmpty(request.getSubBizOrderCreateModels())) {
// 判斷主訂單中金額是否等于所有子訂單金額
BigDecimal totalAmount = bizOrder.getRealAmount();
BigDecimal totalSumFromSub = request.getSubBizOrderCreateModels().parallelStream().map(model ->
BigDecimal.valueOf(model.getRealAmount()))
.reduce(BigDecimal.ZERO, BigDecimal::add);
if(!totalAmount.equals(totalSumFromSub)){
throw new BusinessException(BizOrderErrorCode.ORDER_AMOUNT_NOT_MATCH,"主子訂單金額不匹配");
}
bizOrder.setOrderLevel(BizOrderLevelEnum.MAIN.getOrderLevel()); // 主訂單
request.getSubBizOrderCreateModels().parallelStream().forEach(subOrderModel -> {
BizOrder subBizOrder = createBizOrderConvertor.modelToEntityConvertor(subOrderModel);
// 重新設置parentId及orderLevel
subBizOrder.setOrderLevel(BizOrderLevelEnum.DETAIL.getOrderLevel());
subBizOrder.setParentId(bizOrder.getBizOrderId());
bizOrderRepository.insertSelective(subBizOrder);
});
}
bizOrderRepository.insertSelective(bizOrder);
......
BizOrderCreateResponse createResponse = new BizOrderCreateResponse();
createResponse.setBizOrderId(bizOrder.getBizOrderId());
// send log event
Map attrMap = com.google.common.collect.Maps.newHashMap();
attrMap.put(AttributesKeyEnum.TARGET_STATUS.getShortKeyName(), createModel.getOrderStatus());
attrMap.put(AttributesKeyEnum.CALL_SYSTEM.getShortKeyName(), request.getCallSystem());
// 發(fā)送記錄日志的event
bizOrderLogEventPublisher.bizOrderEventPublish(bizOrder,
request.getOperationType(),
BizOrderStatusEnum.CREATE.getStatus(),
attrMap);
return createResponse;
}
}
這里的convertor都是基于Orika的,有興趣的可以度娘下了解沃测。
可以看到缭黔,其實處理很簡單,就是將請求參數構造成訂單對象蒂破,然后入庫馏谨,加入了一部分數據校驗。
訂單狀態(tài)變遷—有明確目標
訂單狀態(tài)變遷附迷,由于重復代碼比較多惧互,所以這里抽象出來了另外一個模板類,如下:
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachine;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.time.Instant;
import java.util.Date;
import java.util.Map;
/**
* 主要處理訂單狀態(tài)變更喇伯,變更到穩(wěn)定節(jié)點狀態(tài)喊儡,而非審核中Auditing這種中間狀態(tài)
*
* 直接落庫,發(fā)送log event稻据,不需要其他操作
*/
@Slf4j
public abstract class BaseStatusSimpleChangeBizManagerImpl<T, R> extends AbstractBizManagerImpl<T, R> {
@Autowired
private BizOrderRepository bizOrderRepository;
@Autowired
private BizOrderLogEventPublisher bizOrderLogEventPublisher;
/**
* 實際的業(yè)務操作
*
* @param request 業(yè)務請求
* @return 業(yè)務結果
*/
public OrderBaseResponse doProcess(BizOrderStatusRequest request, StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>... stateMachines) throws Exception {
// 重新獲取訂單信息,肯定不是空艾猜,不然就在上層攔截了
BizOrder bizOrder = bizOrderRepository.selectByBizPrimaryKey(request.getBizCode());
BizOrderStatusModel statusModel = request.getBizOrderStatusModel();
BizOrder newBizOrder = new BizOrder();
newBizOrder.setOrderStatus(wrapTargetStatus(statusModel)); // 前面已經處理對應的狀態(tài)設置
newBizOrder.setUpdateTime(Date.from(Instant.now()));
newBizOrder.setFinishReason(wrapFinishReason(statusModel)); // 需要子類處理
// 判斷是否需要處理attributes 及 effectMoney
if (null != statusModel.getAttributesMap() && statusModel.getAttributesMap().size() > 0) {
Map<String, String> curAttributes = AttributeUtil.fromString(bizOrder.getAttributesStr());
curAttributes.putAll(statusModel.getAttributesMap());
newBizOrder.setAttributesStr(AttributeUtil.toString(curAttributes));
}
newBizOrder.setBizOrderId(bizOrder.getBizOrderId());
// 訂單信息保存
int updateCount = bizOrderRepository.updateByPrimaryKeySelective(newBizOrder);
if (1 != updateCount) {
throw new BusinessException(BizOrderErrorCode.ORDER_UPDATE_ERROR, "訂單狀態(tài)變更失敗");
}
// send log event
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCommit() {
// 如果effectMoney不為空,則記錄到log中
Map<String,String> attributesMap = Maps.newHashMap();
if (null != statusModel.getEffectAmount()) {
attributesMap.put(AttributesKeyEnum.EFFECT_MONEY_AMOUNT.getShortKeyName(), statusModel.getEffectAmount().toString());
}
if(null != statusModel.getAttributesMap()) {
attributesMap.putAll(statusModel.getAttributesMap());
}
attributesMap.put(AttributesKeyEnum.CALL_SYSTEM.getShortKeyName(), request.getCallSystem());
bizOrderLogEventPublisher.bizOrderEventPublish(newBizOrder, request.getOperationType(),
bizOrder.getOrderStatus(), attributesMap);
}
});
return new OrderBaseResponse(); // 返回值不會用到
}
/**
* 構造目標狀態(tài)
*
* @param statusModel 狀態(tài)模型
* @return 結果
*/
public abstract String wrapTargetStatus(BizOrderStatusModel statusModel);
/**
* 構造關閉原因,僅需要在close匆赃、success淤毛、cancel場景下處理
* @param statusModel 狀態(tài)模型
* @return 結果
*/
public abstract String wrapFinishReason(BizOrderStatusModel statusModel);
}
比如訂單狀態(tài)變?yōu)槿∠涂梢院喕瘜崿F成這個樣子(實現BaseStatusSimpleChangeBizManagerImpl):
import org.apache.commons.lang3.StringUtils;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Component;
@Component("bizOrderCancelBizManager")
public class BizOrderCancelBizManagerImpl extends BaseStatusSimpleChangeBizManagerImpl<BizOrderStatusRequest, OrderBaseResponse> {
/**
* 實際的業(yè)務操作
*
* @param request 業(yè)務請求
* @return 業(yè)務結果
*/
@Override
public OrderBaseResponse doProcess(BizOrderStatusRequest request, StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>... stateMachines) throws Exception {
return super.doProcess(request,stateMachines);
}
/**
* 構造目標狀態(tài)
*
* @param statusModel 狀態(tài)模型
* @return 結果
*/
@Override
public String wrapTargetStatus(BizOrderStatusModel statusModel) {
return BizOrderStatusEnum.CANCEL.getStatus();
}
/**
* 關閉原因炸庞,僅需要在close、success荚斯、cancel場景下處理
*
* @param statusModel
* @return
*/
@Override
public String wrapFinishReason(BizOrderStatusModel statusModel) {
if (StringUtils.isBlank(statusModel.getFinishReason())) {
return "CANCEL_FROM_" + StringUtils.upperCase(statusModel.getCurrentOrderStatus());
}
return statusModel.getFinishReason();
}
}
基本上每個簡單的狀態(tài)變化邏輯埠居,都是如此實現。
訂單狀態(tài)變遷--需要再次發(fā)送消息
這種同樣抽象出了一個模板類事期,如下:
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Maps;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import java.time.Instant;
import java.util.Date;
/**
* 主要主動處理訂單到達中間狀態(tài)的變更處理滥壕,比如toXXXAction對應的基類
*
* 需要發(fā)送statemachine event 用來串聯下一步操作,并寫db兽泣,發(fā)log event
*/
@Slf4j
public abstract class BaseStatusToUnstableTargetBizManagerImpl<T, R> extends AbstractBizManagerImpl<T, R> {
@Autowired
private BizOrderRepository bizOrderRepository;
@Autowired
private BizOrderLogEventPublisher bizOrderLogEventPublisher;
@Autowired
private BeanMapper beanMapper;
/**
* 實際的業(yè)務操作
*
* @param request 業(yè)務請求
* @param stateMachines 將上游處理后的stateMachine傳遞進來绎橘,后續(xù)持久化
* @return 業(yè)務結果
*/
OrderBaseResponse doProcess(BizOrderStatusRequest request, StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>... stateMachines) throws Exception {
// 修改狀態(tài)
BizOrderStatusModel statusModel = request.getBizOrderStatusModel();
BizOrder bizOrder = bizOrderRepository.selectByBizPrimaryKey(statusModel.getBizOrderId());
String currentStatus = bizOrder.getOrderStatus();
BizOrder newBizOrder = new BizOrder();
newBizOrder.setOrderStatus(statusModel.getTargetOrderStatus());
newBizOrder.setBizOrderId(bizOrder.getBizOrderId());
newBizOrder.setUpdateTime(Date.from(Instant.now()));
int updateCount = bizOrderRepository.updateByPrimaryKeySelective(newBizOrder);
if (1 != updateCount) {
throw new BusinessException(BizOrderErrorCode.ORDER_UPDATE_ERROR, "更新訂單狀態(tài)失敗");
}
// send spring statemachine event
if (null != stateMachines && stateMachines.length > 0) {
StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum> stateMachine = stateMachines[0];
// 重新構造request
statusModel.setFinishReason(wrapFinishReason(statusModel.getTargetOrderStatus()) + statusModel.getFinishReason());
statusModel.setCurrentOrderStatus(statusModel.getTargetOrderStatus());
statusModel.setTargetOrderStatus(wrapTargetOrderStatus());
request.setBizOrderStatusModel(statusModel); // need or not
Message<BizOrderStatusChangeEventEnum> eventMsg = MessageBuilder.
withPayload(wrapToEvent())
.setHeader(BizOrderConstants.BIZORDER_CONTEXT_KEY, request)
.build();
stateMachine.sendEvent(eventMsg);
}
// send log event
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
// @Retryable(maxAttempts = 5)
public void afterCommit() {
// 發(fā)送記錄日志的event
bizOrderLogEventPublisher.bizOrderEventPublish(bizOrder,
request.getOperationType(), currentStatus,
Maps.newHashMap(AttributesKeyEnum.CALL_SYSTEM.getShortKeyName(), request.getCallSystem()));
}
});
return new OrderBaseResponse();
}
/**
* 構造待發(fā)送的statemachine event
*
* @return 對應的event
*/
public abstract BizOrderStatusChangeEventEnum wrapToEvent();
/**
* 構造對應的狀態(tài)
*
* @return 狀態(tài)
*/
public abstract String wrapTargetOrderStatus();
/**
* 構造對應的結束原因,只有finish態(tài)才需要返回唠倦,否則直接return null即可
*
* @param curOrderStatus 當前訂單狀態(tài)
* @return 訂單結束原因
*/
public abstract String wrapFinishReason(String curOrderStatus);
}
同樣的称鳞,比如訂單需要自動跳轉到關單情況就可以實現如下:
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Component;
/**
* 跳轉到close狀態(tài) 前一節(jié)點的邏輯處理
*/
@Component("bizOrderToCloseBizManager")
public class BizOrderToCloseBizManagerImpl extends BaseStatusToUnstableTargetBizManagerImpl<BizOrderStatusRequest, OrderBaseResponse> {
/**
* 實際的業(yè)務操作
*
* @param request 業(yè)務請求
* @param stateMachines 將上游處理后的stateMachine傳遞進來,后續(xù)持久化
* @return 業(yè)務結果
*/
@Override
public OrderBaseResponse doProcess(BizOrderStatusRequest request, StateMachine<BizOrderStatusEnum, BizOrderStatusChangeEventEnum>... stateMachines) throws Exception {
return super.doProcess(request, stateMachines);
}
/**
* 構造待發(fā)送的statemachine event
*
* @return 對應的event
*/
@Override
public BizOrderStatusChangeEventEnum wrapToEvent() {
return BizOrderStatusChangeEventEnum.EVT_SYS_CLOSE;
}
/**
* 構造對應的狀態(tài)
*
* @return 狀態(tài)
*/
@Override
public String wrapTargetOrderStatus() {
return BizOrderStatusEnum.CLOSE.getStatus() + "";
}
/**
* 構造對應的結束原因稠鼻,只有finish態(tài)才需要返回冈止,否則直接return null即可
*
* @param curOrderStatus 當前訂單狀態(tài)
* @return 訂單結束原因
*/
@Override
public String wrapFinishReason(String curOrderStatus) {
return "CLOSE_FROM_" + curOrderStatus + "||";
}
}