日常开发中可能遇到这样的业务场景:请求从入口进来,需要经过层层的校验,通过校验后才会执行业务操作(写操作、RPC、异步消息...);这里前置的多层校验流程中,从类型上看,部分是基本参数校验,部分是包含业务逻辑的校验,并且部分校验是可以并行,部分是有依赖关系的;此外,从代码的通用性来说,对于流程相对固定的业务逻辑,完全可以提前定义好多个校验器,通过配置,可动态的拔插这些预制的校验器以及其顺序;本篇介绍用户下单前业务订单多层校验场景的代码示例;
1. 业务场景
本篇介绍一个业务场景:支付流程中,从端发起,一般会先到业务订单模块,通过一系列的校验和业务订单处理后,才会真正的与订单中台交互,而订单中台屏蔽对各种支付方(蚂蚁金服、微信支付、网上银行等)的交互;这里给出下单前业务侧前置校验(支付限购、实名制、未成年限制等)的方案及代码示例;
2. 代码示例
代码主要包括:校验器接口、职责链入参和返参(上下文)、执行器(顺序/并行);校验器抽象模板(汗基本校验逻辑)和其实现类;
(1)代码结构
(2)校验器接口IChecker
/**
* 校验器
*/
public interface IChecker {
/**
* 校验行为
* @param checkParam
* @param checkContext
*/
void action(CheckParam checkParam, CheckContext checkContext);
/**
* 校验器code
* @return
*/
String getCheckCode();
}
(3)IChecker的参数(校验器入参、校验结果和职责链的上下文)
校验器入参
/**
* 订单校验参数
*/
@Slf4j
@Data
public class CheckParam {
/**
* uid
*/
private String uid;
/**
* 设备号
*/
private String deviceId;
/**
* 支付插件版本
*/
private String sdkVersion;
/**
* appId
*/
private String appId;
/**
* 订单金额
*/
private Long orderAmount;
/**
* 交易类型
*/
private Integer tradeType;
/**
* 商品id
*/
private String productId;
/**
* 周期内限购次数(0代表不限购)
*/
private Integer limitNum;
/**
* 限购开始时间
*/
private Date cycleStart;
/**
* 限购结束时间
*/
private Date cycleEnd;
/* 省略 */
}
校验结果
/**
* 校验结果
*/
@Data
public class CheckResult {
private String code;
private String msg;
public boolean isSuccess() {
return TradeLimitCodeEnum.SUCCESS.getCode().equals(this.getCode());
}
public static CheckResult success() {
return CheckResult.builder().code(TradeLimitCodeEnum.SUCCESS.getCode()).msg(TradeLimitCodeEnum.SUCCESS.getMsg()).build();
}
public static CheckResult fail(TradeLimitCodeEnum tradeLimitCodeEnum, String msg) {
return CheckResult.builder().code(tradeLimitCodeEnum.getCode()).msg(msg).build();
}
/* 省略 */
}
职责链上下文
/**
* 校验上下文
*/
@Data
public class CheckContext {
/**
* 存储各Checker的校验结果 key:check_code
*/
private Map<String, CheckResult> checkResultMap = Maps.newConcurrentMap();
public void setResult(String checkerName, CheckResult checkResult) {
checkResultMap.put(checkerName, checkResult);
}
}
(4)校验器配置-初始化几种校验链路
/**
* 校验器配置
*/
@Configuration
public class CheckerConfig {
// 校验器code
public static final String ORDER_ACCOUNT_CHECKER_CODE = "order_account_checker";
public static final String PAY_ACCOUNT_CHECKER_CODE = "pay_account_checker";
public static final String ORDER_LIMIT_CHECKER_CODE = "order_limit_checker";
// 配置下单校验器链
public static final List<String> ORDER_CHECKER_PIPELINE = Lists.newArrayList(CheckerConfig.ORDER_ACCOUNT_CHECKER_CODE, CheckerConfig.ORDER_LIMIT_CHECKER_CODE);
// 配置支付校验器链
public static final List<String> PAY_CHECKER_PIPELINE = Lists.newArrayList(CheckerConfig.PAY_ACCOUNT_CHECKER_CODE);
// 所有的校验器会自动注入这个List
@Resource
private List<IChecker> iCheckers;
// 订单校验器自动注入
@Bean
public List<IChecker> orderCheckers() {
return buildCheckFilterBase(CheckerConfig.ORDER_CHECKER_PIPELINE);
}
// 支付校验器自动注入
@Bean
public List<IChecker> payCheckers() {
return buildCheckFilterBase(CheckerConfig.PAY_CHECKER_PIPELINE);
}
// 根据配置 取各自检验器code列表下对应的校验器Bean列表
private List<IChecker> buildCheckFilterBase(List<String> checkerPipeline) {
List<IChecker> orderChecks = Lists.newArrayList();
if (CollectionUtils.isEmpty(iCheckers)) {
return orderChecks;
}
Map<String, IChecker> map = iCheckers.stream().collect(Collectors.toMap(IChecker::getCheckCode, iChecker -> iChecker));
checkerPipeline.forEach(code -> {
if (map.get(code) != null) {
orderChecks.add(map.get(code));
}
});
return orderChecks;
}
}
(5)校验控制器/执行器
/**
* 校验控制器
*/
@Slf4j
@Service
public class CheckManager {
@Resource
private List<IChecker> orderCheckers;
@Resource
private List<IChecker> payCheckers;
private static final ThreadPoolExecutor ORDER_POOL = new ThreadPoolExecutor(20, 256,
15L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
(ThreadFactory) Thread::new);
private static final ThreadPoolExecutor PAY_POOL = new ThreadPoolExecutor(20, 128,
15L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
(ThreadFactory) Thread::new);
/**
* 下单校验
* @param checkParam
* @return
*/
public CheckResult checkOrder(CheckParam checkParam) {
return checkBase(checkParam, orderCheckers, ORDER_POOL, true);
}
/**
* 支付校验
* @param checkParam
* @return
*/
public CheckResult checkPay(CheckParam checkParam) {
return checkBase(checkParam, payCheckers, PAY_POOL, false);
}
private CheckResult checkBase(CheckParam checkParam, List<IChecker> checkers, Executor executor, boolean isOrderCheck) {
// 后续可以根据需要在上下文暂存各校验器公共依赖的基础数据
CheckContext checkContext = new CheckContext();
if (CollectionUtils.isNotEmpty(checkers)) {
CompletableFuture[] futures = new CompletableFuture[checkers.size()];
for (int i = 0; i < checkers.size(); i ++) {
IChecker checker = checkers.get(i);
try {
futures[i] = CompletableFuture.runAsync(() -> checker.action(checkParam, checkContext), executor);
} catch (Exception e) {
log.error("check_action_error.", e);
}
}
try {
// 同步获取多个异步结果 超时1500ms
CompletableFuture.allOf(futures).get(ConfigManager.getInteger("trade.limit.timeout", 1500), TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
log.error("check_timeout");
} catch (Exception e) {
log.error("CheckManager_error", e);
}
}
return getCheckResult(checkContext, isOrderCheck);
}
private CheckResult getCheckResult(CheckContext checkContext, boolean isOrderCheck) {
List<String> checkerPipeLine = isOrderCheck ? CheckerConfig.ORDER_CHECKER_PIPELINE : CheckerConfig.PAY_CHECKER_PIPELINE;
for (String checkerCode : checkerPipeLine) {
CheckResult checkResult = checkContext.getCheckResultMap().get(checkerCode);
// 返回第一个被拦截的校验结果
if (checkResult != null && !TradeLimitCodeEnum.SUCCESS.getCode().equals(checkResult.getCode())) {
return checkResult;
}
}
return CheckResult.success();
}
}
Tips:链式校验若先后关系无依赖,可以并行异步执行校验逻辑,同步获取校验结果上下文,从而提升执行速度;
(6)校验器模板及校验器实现类
抽象模板
/**
* 账号域校验器
*/
@Slf4j
public abstract class AbstractChecker {
@Resource
private PayRecordService payRecordService;
protected boolean preCheck(CheckParam checkParam) {
if (StringUtils.isBlank(checkParam.getUid()) && StringUtils.isEmpty(checkParam.getDeviceId())) {
log.info("preCheck_without_uid or deviceId]");
return true;
}
return false;
}
protected PayInfo buildPayInfo(CheckParam checkParam) {/*...*/}
protected PayHistory buildPayHistory(CheckParam checkParam) {/*...*/}
protected void buildResult(String checkerCode, Result accountResult, CheckContext checkContext) {
// 账号限购、实名制...
}
}
模板实现类——支付环节账号校验
/**
* 支付环节账号校验---实名制、未成年
*/
@Slf4j
@Component
public class PayByAccountChecker extends AbstractChecker implements IChecker {
@Resource
private SubAccountService subAccountService;
// 标识当前校验器的code
@Override
public String getCheckCode() {
return CheckerConfig.PAY_ACCOUNT_CHECKER_CODE;
}
@Override
public void action(CheckParam checkParam, CheckContext checkContext) {
Result result = null;
try {
// 校验开关
if (!ConfigManager.getBoolean("pay.account.check.switch", true)) {
return;
}
// 基本参数校验
if (preCheck(checkParam)) {
return;
}
// 支付插件版本判断
if (StringUtils.isNotBlank(checkParam.getSdkVersion()) && APKVersionComparetor.compareVersion(checkParam.getSdkVersion(), "4.8.0") < 0
&& ConfigManager.get("pay.account.check.lowapk.apptype", "5").contains(checkParam.getAppBigType())) {
checkContext.setResult(getCheckCode(), CheckResult.fail(TradeLimitCodeEnum.FORBID_PAY, "支付安全插件版本较低,请前往应用商店升级"));
return;
}
// 对支付环节账号的校验逻辑
result = subAccountService.checkPayLimit(buildPayInfo(checkParam), buildPayHistory(checkParam));
} catch (Exception e) {
log.error("PayByAccountChecker_action_error.", e);
}
buildResult(getCheckCode(), result, checkContext);
}
}
模板实现类——支付限购校验
/**
* 支付限购
*/
@Slf4j
@Component
public class OrderBuyLimitChecker extends AbstractChecker implements IChecker {
@Resource
private PayRecordService payRecordService;
// 校验器对应的code
@Override
public String getCheckCode() {
return CheckerConfig.ORDER_LIMIT_CHECKER_CODE;
}
@Override
public void action(CheckParam checkParam, CheckContext checkContext) {
String result = TradeLimitCodeEnum.SUCCESS.getCode();
try {
// 功能开关
if (!ConfigManager.getBoolean("buy.limit.check.switch", true) || isInBuyLimitWhiteList(checkParam.getAppId())) {
return;
}
// 前置校验
if (checkParam(checkParam)) {
return;
}
// 支付限购校验逻辑
if (payRecordService.queryNumberOfPay(checkParam) >= checkParam.getLimitNum()) {
result = TradeLimitCodeEnum.BUY_LIMIT.getCode();
}
} catch (Exception e) {
log.error("PayLimitChecker_action_error.", e);
}
buildResult(getCheckCode(), result, checkContext);
}
/* ... */
}
以上便是相关的所有代码;还有优化的空间,如:校验器加上order顺序属性、动态配置校验器的数量和顺序等;