在2019年的时候,由于组织架构变更,我接手了交易板块。接手后开始找之前的产品、开发沟通了解下这块有什么问题。
综合了下有以下问题:
本着,耳听为虚,眼见为实的原则,我开始和产品对交易结构和扒拉代码和数据;
发现的问题:
梳理了下各业务线和各机构的元素,大致是这个样子的(真实的业务逻辑比这个复杂多了,因为业务问题不方便透露)。
梳理完以后脑子里有两个问题:
花了几天的时间和产品碰,以及细化规则点。确认如下流程。
业务结构清晰了,剩下的问题一个个的解决呗:
和业务沟通利弊,让业务指定了一个主产品(要不然各提各的需求,互不关心,底层一套,容易打架)
准入哪些是必须? 不看过去,只看业务到底想要什么,然后再把过去的拿出来,对比,防止有遗漏;
差异化的额度加载应该如何处理?通过策略模式来适配各业务场景;
链路跟踪转化怎么处理?漏斗转化怎么实现?入口生成唯一标识,前端缓存,调用所有的交易接口,必须携带这个唯一标识;
哪些规则可以延后?
怎么认定是通用规则?怎么认定是特有规则?
问题流程怎么方便排查?
其实,在这个过程中,我们分析发现,一次交易发起基本99.99%上10分钟内能全部结束。
在这个节点,我们只保留了最核心的校验,
在准入分发里:
同时,针对每一步,进行接口抽取,然后进行默认实现,差异性的根据策略实现
用户输入金额,试算,对相关结果无异议,就点击同意协议进行下一步了;
在这里,把所有的规则都集中到这一处:
所以这块重点有两块,一块是规则的组装,一块是规则的抽取。
规则组装:
CREATE TABLE `t_flow_strategy_config` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键Id',
`capital_name` varchar(40) DEFAULT NULL COMMENT '机构:all,等',
`flow` varchar(40) DEFAULT 'all' COMMENT '流程标识:all, 其他为具体流程',
`scene` varchar(10) default null comment '场景,all 全场景,其他为指定场景',
`position` varchar(10) default null comment '位置,具体在哪执行',
`category` varchar(20) DEFAULT NULL COMMENT '策略类型block,guide',
`strategy_name` varchar(20) DEFAULT NULL COMMENT '策略名称',
`start_time` datetime DEFAULT NULL COMMENT '开始时间,临时策略生效时间',
`end_time` datetime DEFAULT NULL COMMENT '结束时间,临时策略失效时间',
`sort` int(2) DEFAULT NULL COMMENT '策略排序',
`enable_state` varchar(10) DEFAULT NULL COMMENT '策略可用状态:1:启用,0:禁用',
`creator` varchar(20) DEFAULT NULL COMMENT '创建人',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updator` varchar(20) DEFAULT NULL COMMENT '更新人',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='机构对应的策略配置';
规则抽取,这里抽出两种类型,一种是阻塞型的规则,一种是引导型的规则,类关系如下:
BaseStrategy 只最基本的
/**
* 〈公共策略〉
*
* @author yxkong
* @create 2019-05-12
* @since 1.0.0
*/
public interface BaseStrategy {
/**
* 服务调用走这个方法
*
* @param context 策略上下文对象
* @return
*/
boolean execute(StrategyContext context);
/**
* 策略执行前,返回true代表成功,无需再次引导
*
* @param context 策略上下文对象
* @return
*/
boolean before(StrategyContext context);
/**
* 策略执行
*
* @param strategyRuleDto 策略上下文对象
* @return true 代表策略执行成功,false代表策略执行失败
*/
boolean strategy(StrategyContext context);
/**
* 策略执行后
*
* @param context 策略上下文对象
* @param result 策略执行结果
* @return
*/
void after(StrategyContext context, boolean result);
/**
* 返回策略失败的特定标识,以及提示
*/
ResultBean<ValidateVo> getFailResult();
}
/**
* 〈基础策略抽象实现〉
*
* @author yxkong
* @create 2019-05-12
* @since 1.0.0
*/
public abstract class BaseStrategyImpl implements BaseStrategy {
protected Logger logger = LoggerFactory.getLogger(BaseStrategyImpl.class);
/**
* 执行策略抽象实现
* 1,before 策略执行前的通用处理,
* 如果缓存中有,可以直接通过,防止重复验证
* 不同的策略类型,实现不一样
* 2,strategy 真实策略执行
* 3,after 策略执行后的通用处理,
* 包含日志的记录,以及缓存的时效
* @param context 策略上下文对象
* @return
*/
@Override
public boolean execute(StrategyContext context) {
if (this.before(context)) {
return Boolean.TRUE;
}
boolean result = false;
try {
result = this.strategy(context);
} catch (Exception e) {
// 设置异常状态
context.setStatus(StrategyResultStatusEnum.EXCEPTION.status);
String message = e.getMessage();
if (StringUtils.isNotNull(message) && message.length() > 500) {
message = message.substring(0, 500);
}
// 设置异常信息
context.setMessage(message);
logger.error("执行strategy:{} 失败", e);
}
this.after(context, result);
return result;
}
@Override
public void after(StrategyContext context, boolean result) {
CapitalStrategyConfigEntity capitalStrategyConfig = context.getFlowStrategyConfigEntity();
String strategyName = capitalStrategyConfig.getStrategyName();
String category = capitalStrategyConfig.getCategory();
if (logger.isInfoEnabled()) {
logger.info("执行{}:{}的结果:{} after ", category, strategyName, result);
}
if (result) {
//这里相对简单,以人为维护,缓存规则通过,1个小时 ,这也会记录入库记录log
successHandle(context);
} else {
//这里会记录埋点日志,并会入库记录log,哪一条规则,未过
failHandle(context);
}
}
}
阻塞策略抽象实现
/**
* 〈阻塞规则策略抽象实现〉
*
* @author yxkong
* @create 2019-05-14
* @since 1.0.0
*/
public abstract class BaseBlockStrategyImpl extends BaseStrategyImpl implements BlockStrategy {
@Override
public boolean before(StrategyContext context) {
// 阻塞策略在此不做校验
if (logger.isInfoEnabled()) {
logger.info("执行blockStrategy :{} before",context.getFlowStrategyConfigEntity().getStrategyName());
}
return false;
}
}
引导策略抽象实现:
/**
* 〈引导策略抽象实现〉
*
* @author yxkong
* @create 2019-05-14
* @since 1.0.0
*/
public abstract class BaseGuideStrategyImpl extends BaseStrategyImpl implements GuideStrategy {
public static final String GUIDE_CAPITAL_KEY_FORMAT = "%s";
@Override
public boolean before(StrategyContext context) {
/***
* 查询缓存是否有通过的数据
* 只缓存成功的,因为规则依赖外部查询,失败的重新去查,看是否成功
*/
return true;
}
@Override
public boolean after(StrategyContext context) {
/***
* 查询缓存是否有通过的数据
* 只缓存成功的,因为规则依赖外部查询,失败的重新去查,看是否成功
*/
return true;
}
}
一个具体的策略实现
/**
* 〈个人信息引导规则实现〉
*
* @author yxkong
* @create 2019-05-12
* @since 1.0.0
*/
@Service("personGuide")
public class PersonGuideStrategyImpl extends BaseGuideStrategyImpl {
@Autowired
private UserSystemService userSystemService;
@Override
public boolean strategy(StrategyContext context) {
ValidateDto validateDto = context.getValidateDto();
String capitalName = validateDto.getCapitalName();
capitalName = handleCapitalName(validateDto, capitalName);
// 查询用户信息,用户信息,在底层通过feign拦截器做了处理
ResultBean<PersonGuideVo> resultBean = userSystemService.verifyOCRPersonlInfo( capitalName);
if (ResultBeanUtil.isSuccess(resultBean)) {
return Boolean.TRUE;
}
context.getValidateVo().setPerson(resultBean.getData());
return false;
}
@Override
public ResultBean getFailResult() {
//自定义返回状态码和message
return ResultBeanUtil.result(StrategyStatusMessageEnum.PERSON_GUIDE);
}
}
策略执行
/**
* 执行接口定义
*/
public interface StrategyService {
ResultBean validate(StrategyContext context,String strategyName) ;
}
阻塞策略执行
/**
* 〈阻塞策略路由实现〉
*
* @author yxkong
* @create 2019-05-13
* @since 1.0.0
*/
@Service
public class BlockStrategyService implements StrategyService{
//利用spring的特性,省去了工厂和if的判断
@Autowired
private Map<String, BlockStrategy> blockStrategyMap;
/**
* 阻塞策略执行
*
* @param strategyRuleDto 策略执行时的上下文对象
*/
@Override
public ResultBean validate(StrategyContext context,String strategyName) {
BlockStrategy strategy= blockStrategyMap.get(strategyName);
boolean rst =strategy.execute( context);
if(rst){
//阻塞策略,拦截,表示失败
return strategy.getFailResult();
}
return ResultBeanUtil.sucess();
}
}
引导策略执行:
/**
* 〈引导策略路由实现〉
*
* @author ducongcong
* @create 2019-05-13
* @since 1.0.0
*/
@Service
public class GuideStrategyService implements StrategyService{
@Autowired
private Map<String, GuideStrategy> guideStrategyMap;
/**
* 引导策略执行
*
* @param strategyRuleDto 策略执行时的上下文对象
* @return true 表示策略通过,false表示策略失败
*/
@Override
public ResultBean validate(StrategyContext context,String strategyName) {
GuideStrategy strategy= guideStrategyMap.get(strategyName);
boolean rst =strategy.execute(context);
if(rst){
return ResultBeanUtil.sucess();
}
//引导用户去做对应的事
return strategy.getFailResult();
}
}
策略编排
t_flow_strategy_config
表中
t_flow_strategy_config
表获取所有的执行策略(原子方法)public interface StrategyBuilder {
ResultBean validate(LoginContext loginContext, BizContext bizContext);
}
@Service
public class StrategyBuilderImpl implements StrategyBuilder{
@Resource
private BlockStrategyService blockStrategyService;
@Resource
private GuideStrategyService guideStrategyService;
@Override
public ResultBean validate(LoginContext loginContext, BizContext bizContext) {
/**
* 根据场景+机构+业务流程+位置 获取所有的策略
* 旁路缓存查的,基本上不变动,变动以后对一致性的要求没那么高,缓存了10分钟
*/
List<FlowStrategyConfigEntity> strategies = getScenePosition(bizContext.getScene(),bizContext.getPosition(),bizContext.getFlow(),bizContext.getCapitalName());
List<FlowStrategyConfigEntity> blockStrategies = getBlockStrategy(strategies);
StrategyContext strategyContext = buildContenxt(loginContext,bizContext);
for (FlowStrategyConfigEntity entity:blockStrategies){
ResultBean validate = blockStrategyService.validate(strategyContext,entity.getStrategyName());
if(!validate.isSuc()){
return validate;
}
}
List<FlowStrategyConfigEntity> guideStrategies = getBlockStrategy(strategies);
for (FlowStrategyConfigEntity entity:guideStrategies){
ResultBean validate = guideStrategyService.validate(strategyContext,entity.getStrategyName());
if(!validate.isSuc()){
return validate;
}
}
return ResultBeanUtil.success();
}
}
在整个重构的过程中,这里就用到以下一些方法:
我觉的最重重重要的是对业务的理解,怎么通过系统引导用户,怎么通过体系化的建设来将整个业务串联起来。