原创|关于一次产品需求程序设计及优化的经历

news2024/11/16 5:33:16

文章目录

  • 一、流程梳理
  • 二、设计梳理
  • 三、技术方案
    • 3.1、下单接口扩展
      • 3.3.1、Request类新增deviceType
      • 3.3.2、申请单新增字段产品策略(productStrategy)
      • 3.3.3、下单产品策略的处理逻辑
    • 3.2、询价模块的设计
      • 3.2.1、Context设计
      • 3.2.2、ProductStrategy类设计
        • 3.2.2.1、AbstractProductStrategy类
        • 3.2.2.2、ProxyLiveStandardPriceStrategy
        • 3.2.2.3、ProxyLiveStandardQuantityStrategy
        • 3.2.2.4、HandleDispatcher
    • 3.3、不同场景下的价格计算策略设计
      • 3.3.1、UML类图
        • 3.3.1.1、Calculator UML类图
        • 3.3.1.2、Context UML类图
      • 3.3.2、Calculator
        • 3.3.2.1、AbstractCalculator
        • 3.3.2.2、DeductAmountCalculator
      • 3.3.3、Context
        • 3.3.3.1、AbstractCalculateContext
        • 3.3.3.2、PartialDeductAmountCalculateContext
      • 3.3.4、CalculateMediator
    • 3.4、付款与退款的设计
      • 3.4.1、Context类的设计
      • 3.4.2、TransactionHandler类的设计
      • 3.4.3、PaymentTransactionHandler类的设计
        • 3.4.3.1、StandardPricePaymentTransactionHandler类的设计
        • 3.4.3.2、StandardPricePaymentTransactionHandler类的设计
      • 3.4.4、TransactionHandler类的设计
        • 3.4.4.1、StandardPriceRefundTransactionHandler类的设计
        • 3.4.4.2、StandardQuantityRefundTransactionHandler类的设计
  • 四、优化设计
    • 1、计算模型优化
    • 2、Dispatcher优化
      • 2.1、第一版Dispatcher设计
      • 2.2、第二版Dispatcher设计
        • 2.2.1、Context接口
        • 2.2.2、Handle接口
        • 2.2.3、AbstractDispatcher抽象类
  • 五、总结

由于一次需求变更,由于的业务流程不支持这种新的场景,这也意味着需要设计支持新场景的业务逻辑处理。但是又要考虑后期的扩展性,同时尽可能不影响原来的业务,需要分析利弊寻找一种技术设计方案来解决当前需要解决的问题。通过分析,勾勒出涉及的变动点,这些变动点如果在原有代码新增if else就会造成程序的可维护性很差,而且不利于后期场景复用,因此寻找一种策略+分发的处理机制,通过上下文封装场景,然后通过调度器来分发处理器完成处理,同时把稳定的能力下沉到能力层,跟业务场景有关的通过路由分发完成最终业务逻辑处理。

一、流程梳理

业务流程梳理

通过梳理上图,整个业务流程可以精简如上。通过分析上图可以得出如下结论:

  • 该业务流程需要对接外部资产支付系统,意味着资金交易的生命周期维护(即上述红色流程)。
  • 紫色的块意味着业务逻辑需要变更,原有的策略假设为A,新增一种策略为B,也有意味着相关变动点需要考虑场景选择A或者B,以便处理。
  • 涉及金额处理的,比如付款金额或者退款金额,需要根据场景来选择具体如何计算,场景不同,计算方式也不同,假设两种不同计算金额方式为A、B两种策略。
  • 在交付环节,业务的特殊之处就是在下单的时候,产品类型分为基础班(交付1份)、高级版(交付3份),所以交付完成的处理触发机制不同。与此同时,如果交付数量未达标,还涉及退款处理。
  • 两种场景的不同之处,如果商品总额可以除尽意味着可以按照商品数量,交由资产系统完成付款和退款的处理。但是不能除尽意味着需要业务方自己计算付款和退款金额。
  • 业务订单有自己的状态机(未交付、已交付),交易订单也有自己的状态机(冻结、支付中、支付成功、结算完成)。这两种领域之间有联系,业务订单先于交易订单创建,业务订单触发交付的时候,资金订单才发起付款处理。
  • 业务订单由于不同的产品类型,意味着交付是分阶段付款。这也意味着商品总额如果除以商品数量不尽的情况,需要特殊处理。比如一个商品总额10¥,购买3个商品,通过与产品沟通,交付第一个付款3¥,交付第二个付款3¥,最后一个交付付款4¥,然后结算完成。可以这么处理。

二、设计梳理

不同业务线的产品类型划分,也意味着购买不同产品类型的产品触发的交付条件不同,进而触发付款的条件也不同,付款金额和退款金额也有所区别。
商品类型的分析

退款的处理
假设一订单购买的是高级版的商品,此商品总额10¥,需要交付三份,在指定时间未达成交付,意味着未交付的那几份,需要退款。这里与产品沟通,可以采取四舍五入的金额处理方式。Math.floor(10/3) = 3,最后一份则 buyAmount-paidAmount即 10-6。

在这里插入图片描述
未全交付场景下的自动结算流程,先退款未交付的,再付款已交付的。
在这里插入图片描述
原有的商品定价,对于资产系统而言

在这里插入图片描述

三、技术方案

3.1、下单接口扩展

3.3.1、Request类新增deviceType

    /**
     * 1-iOS、2-android、3-其他
     */
    @ThriftField(5)
    private Integer deviceType;

3.3.2、申请单新增字段产品策略(productStrategy)

该字段是后续付款和退款时,具体选择商品数量(quantity)和商品价格(price)两种策略模型。

新增一个产品策略枚举:

/**
 * 产品策略类型
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/7 8:21 PM
 */
@Getter
public enum ProductStrategyEnum {
    /**
     * 标准产品策略
     */
    UNKNOWN(0, "UnknownProductStrategy", "Unknown"),

    /**
     * 标准产品商品数量确认付款策略
     */
    STANDARD_QUANTITY(1, "StandardQuantityProductStrategy", "quantity"),

    /**
     * 标准产品商品价格确认付款策略
     */
    STANDARD_PRICE(2, "StandardPriceProductStrategy", "price"),

    ;

    /**
     * 策略索引
     */
    private final int value;

    /**
     * 策略名称
     */
    private final String name;

    /**
     * 策略简称
     */
    private final String shortName;

    ProductStrategyEnum(int value, String name, String shortName) {
        this.value = value;
        this.name = name;
        this.shortName = shortName;
    }

    public static ProductStrategyEnum valueOfShortName(String shortName) {
        for (ProductStrategyEnum each : values()) {
            if (Objects.equals(each.getShortName(), shortName)) {
                return each;
            }
        }
        return UNKNOWN;
    }
}

下单BO类和POJO类新增属性:

    /**
     * 产品策略
     * {@link ProductStrategyEnum#getShortName()}
     */
    private String productStrategy;

3.3.3、下单产品策略的处理逻辑

定义一个类:ProxyLiveComponent,新增如下方法:getProductStrategy

    /**
     * 获取产品策略
     *
     * @param requestBO 请求
     * @return 返回
     */
    public ProductStrategyEnum getProductStrategy(JobApplyLiveRequestBO requestBO) {
        if (SideBusinessSubtypeEnum.isSocial(requestBO.getSideBusinessSubtype()) && Objects.equals(requestBO.getDeviceType(), 1)) {
            int deliveryCount = getSocialProxyDeliveryCount(requestBO.getProductType());
            // 基础版、高级版=职位数*投递量 ;至尊版=职位数
            int quantity = requestBO.getJobNumbers().size() * deliveryCount;
            GetOrderPriceRequestBO getOrderPriceRequest = assetPaymentConverter.convertToGetOrderPriceRequestBO(requestBO, quantity);
            //下单时需要向资产系统询价,如果账号资金充足则可以选择商品数量;不足只能选择商品价格。
            PaymentTypeEnum paymentType;
            try {
                paymentType = assetPaymentBusiness.autoPrice(getOrderPriceRequest);
            } catch (Exception e) {
                log.error("[getProductStrategy]下单获取产品策略异常,req={}", JsonUtils.toJson(requestBO), e);
                throw new BusinessException("获取产品策略异常", e);
            }
            if (PaymentTypeEnum.RMB == paymentType) {
                return ProductStrategyEnum.STANDARD_PRICE;
            }
        }
        return ProductStrategyEnum.STANDARD_QUANTITY;
    }

然后再business层,新增如下代码

ProductStrategyEnum productStrategy = proxyLiveComponent.getProductStrategy(requestBO);
jobApplyLiveBO.setProductStrategy(productStrategy.getShortName());
//入库

3.2、询价模块的设计

在这里插入图片描述

3.2.1、Context设计

上下文类的作用在于封装整个业务处理所依赖的相关参数,后续扩展只需要在此类增加成员属性即可。

/**
 * 上下文接口
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2023/2/17 7:14 AM
 */
public interface Context {
}

具体场景Context实现Context接口。

/**
 * 产品策略上下文对象
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/7 8:55 PM
 */
@AllArgsConstructor
@NoArgsConstructor
@SuperBuilder
@Data
public class ProductEnquiryContext implements Context {

    /**
     * 产品策略
     */
    private ProductStrategyEnum productStrategy;

    /**
     * 上下文依赖的请求参数
     */
    private Request request;

    /**
     * 上下文处理结果
     */
    private Response response;

    @AllArgsConstructor
    @NoArgsConstructor
    @SuperBuilder
    @Data
    public static class Request {

        /**
         * 请求参数
         */
        private QualifyProductRequestBO qualifyProductRequest;

        /**
         * 产品
         */
        private LiveProductBO liveProduct;

        /**
         * 申请单
         */
        private JobApplyLiveBO jobApplyLive;
    }

    @AllArgsConstructor
    @NoArgsConstructor
    @SuperBuilder
    @Data
    public static class Response {

        /**
         * 数量
         */
        private Integer quantity;

        /**
         * 询价属性
         */
        private Map<String, String> priceAttributes;
    }
}

3.2.2、ProductStrategy类设计

/**
 * 处理接口
 * 适用于多场景、多策略的处理器
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2023/2/17 6:46 AM
 */
public interface Handle<C extends Context> {
    /**
     * 获取处理器名称
     *
     * @return 处理器名称
     */
    String getName();

    /**
     * 执行处理
     *
     * @param context 上下文对象
     */
    void execute(C context);
}

3.2.2.1、AbstractProductStrategy类

/**
 * 抽象产品策略
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/8 2:28 PM
 */
@Slf4j
public abstract class AbstractProductStrategy implements Handle<ProductEnquiryContext> {

    final static String LOG_PREFIX = "[ProductStrategy]";

    /**
     * 产品策略
     */
    protected ProductStrategyEnum productStrategy;

    @Autowired
    protected ProxyLiveComponent proxyLiveComponent;

    public AbstractProductStrategy(ProductStrategyEnum productStrategy) {
        this.productStrategy = productStrategy;
    }

    /**
     * 执行策略
     *
     * @param context 上下文对象
     */
    @Override
    public void execute(ProductEnquiryContext context) {
        ProductEnquiryContext.Request request = context.getRequest();
        JobApplyLiveBO jobApplyLive = request.getJobApplyLive();
        ProductEnquiryContext.Response response = ProductEnquiryContext
            .Response.builder().quantity(calculateQuantity(context))
            .priceAttributes(getPriceAttributes(context))
            .build();
        log.info("{}|{}|applyId={},response={}", LOG_PREFIX, getName(), jobApplyLive.getId(), JsonUtils.toJson(response));
        context.setResponse(response);
    }

    /**
     * 计算商品数量
     *
     * @param context 上下文对象
     * @return 返回值
     */
    protected abstract Integer calculateQuantity(ProductEnquiryContext context);

    /**
     * 获取询价属性
     *
     * @param context 上下文对象
     * @return 返回值
     */
    protected abstract Map<String, String> getPriceAttributes(ProductEnquiryContext context);

    public ProductStrategyEnum getProductStrategy() {
        return productStrategy;
    }
}

3.2.2.2、ProxyLiveStandardPriceStrategy

商品数量询价策略类

Service
@Slf4j
public class ProxyLiveStandardPriceStrategy extends AbstractProductStrategy {

    final int THRESHOLD = 5;

    public ProxyLiveStandardPriceStrategy() {
        super(ProductStrategyEnum.STANDARD_PRICE);
    }

    @Override
    public String getName() {
        return ProxyLiveStandardPriceStrategy.class.getSimpleName();
    }

    @Override
    protected Integer calculateQuantity(ProductEnquiryContext context) {
        int quantity = 1;
        ProductEnquiryContext.Request request = context.getRequest();
        JobApplyLiveBO jobApplyLive = request.getJobApplyLive();
        List<String> jobNumbers = JSONObject.parseArray(jobApplyLive.getJobInfo(), String.class);
        if (jobNumbers.size() > THRESHOLD) {
            throw new BusinessException("询价失败!");
        }
        return quantity;
    }

    @Override
    protected Map<String, String> getPriceAttributes(ProductEnquiryContext context) {
        ProductEnquiryContext.Request request = context.getRequest();
        JobApplyLiveBO jobApplyLive = request.getJobApplyLive();
        List<String> jobNumbers = JSONObject.parseArray(jobApplyLive.getJobInfo(), String.class);
        Map<String, String> attributes = Maps.newHashMap();
        ProductTypeEnum productType = ProductTypeEnum.valueOf(jobApplyLive.getProductType());
        ProductSKUEnum productSKU = ProductSKUEnum.valueOf(jobNumbers.size(), productType);
        Optional.ofNullable(productSKU).orElseThrow(() -> {
            log.error("{}未查询到产品规格|jobApplyLive={}",  LOG_PREFIX, JsonUtils.toJson(jobApplyLive));
            return new NotExistException("未查询到产品规格");
        });
        attributes.put(MetaToBusinessEnum.PROXY_LIVE.getKey(), String.valueOf(productSKU.getSpecificationIndex()));
        //此字段用于资产支持小额支付
        if (jobNumbers.size() <= THRESHOLD) {
            attributes.put("hideRmb", "true");
        }
        return attributes;
    }
}

3.2.2.3、ProxyLiveStandardQuantityStrategy

商品价格询价策略类

@Service
public class ProxyLiveStandardQuantityStrategy extends AbstractProductStrategy {

    public ProxyLiveStandardQuantityStrategy() {
        super(ProductStrategyEnum.STANDARD_QUANTITY);
    }

    @Override
    public String getName() {
        return ProxyLiveStandardQuantityStrategy.class.getSimpleName();
    }

    @Override
    protected Integer calculateQuantity(ProductEnquiryContext context) {
        ProductEnquiryContext.Request request = context.getRequest();
        JobApplyLiveBO jobApplyLive = request.getJobApplyLive();
        List<String> jobNumbers = JSONObject.parseArray(jobApplyLive.getJobInfo(), String.class);
        int buyCount = proxyLiveComponent.getSocialProxyDeliveryCount(jobApplyLive.getProductType());
        int quantity = jobNumbers.size();
        if (SideBusinessSubtypeEnum.isSocial(jobApplyLive.getSideBusinessSubtype())) {
            quantity = jobNumbers.size() * buyCount;
        }
        return quantity;
    }

    @Override
    protected Map<String, String> getPriceAttributes(ProductEnquiryContext context) {
        Map<String, String> attributes = Maps.newHashMap();
        LiveProductBO liveProduct = context.getRequest().getLiveProduct();
        attributes.put(MetaToBusinessEnum.PROXY_LIVE.getKey(), String.valueOf(liveProduct.getProductType()));
        return attributes;
    }
}

3.2.2.4、HandleDispatcher

该类作用通过场景分发选择何种策略处理,对于业务方不需要具体关注内部策略的实现,只需要通过该类完成处理。

@Slf4j
public abstract class AbstractHandleDispatcher<H extends Handle, C extends Context> {
    /**
     * 获取分发器名称
     *
     * @return 分发器名称
     */
    protected abstract String getName();

    /**
     * 获取处理器集合
     *
     * @return 处理器集合
     */
    protected abstract List<H> getHandlers();

    /**
     * 查找处理器
     *
     * @param context 处理器上下文
     * @return 要分发的处理器
     */
    protected abstract Optional<H> find(C context);

    /**
     * 执行分发
     *
     * @param context 处理器上下文
     */
    public void execute(C context) {
        Optional<H> optional = find(context);
        if (optional.isPresent()) {
            optional.get().execute(context);
        } else {
            log.error("no find handler", JsonUtils.toJson(context));
        }
    }
}

ProductEnquiryDispatcher 具体询价调度器。

@Component
@Slf4j
public class ProductEnquiryDispatcher extends AbstractHandleDispatcher<AbstractProductStrategy, ProductEnquiryContext> {

    @Autowired
    private List<AbstractProductStrategy> strategies;

    @Override
    protected String getName() {
        return ProductEnquiryDispatcher.class.getSimpleName();
    }

    @Override
    protected List<AbstractProductStrategy> getHandlers() {
        return strategies;
    }

    @Override
    protected Optional<AbstractProductStrategy> find(ProductEnquiryContext context) {
        return getHandlers().stream().filter(each -> each.getProductStrategy() == context.getProductStrategy()).findAny();
    }
}

3.3、不同场景下的价格计算策略设计

在这里插入图片描述
业务逻辑处理时,只需要委托给JobApplyLiveCalculateMediator类来完成计算,外部不需要关注具体的计算器,内部计算器业务逻辑扩展修改,不需要业务方修改,做到职责隔离。

3.3.1、UML类图

3.3.1.1、Calculator UML类图

Calculator类图
每一种场景都是一个计算器,如果对场景进行计算调整,只需要找到对应的计算器修改逻辑即可。倘若后续增加一种场景,只需要继承基类即可。

3.3.1.2、Context UML类图

在这里插入图片描述
每一种计算器对应一个Context上下文类,该类封装了计算所需的相关依赖参数。

3.3.2、Calculator

3.3.2.1、AbstractCalculator

该类是一个泛型类,这样便于具体场景扩展,因为计算结果有的需要返回Integer,有的返回BigDecimal,有的是具体一个BO类,所以方便扩展,灵活性强,符合代码松耦合的设计目标。

/**
 * 抽象计算器
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/17 9:59 AM
 */
@Slf4j
public abstract class AbstractCalculator<R, C extends Context> {

    /**
     * 获取计算器名称
     *
     * @return 返回值
     */
    protected abstract String getName();

    /**
     * 执行计算
     *
     * @param ctx
     * @return 返回计算结果
     */
    protected abstract R doExecute(C ctx);

    /**
     * 执行计算
     *
     * @param context
     * @return
     */
    protected R execute(C context) {
        R result = doExecute(context);
        log.info("[{}]|{}|{}", getName(), JsonUtils.toJson(result), JsonUtils.toJson(context));
        return result;
    }
}

3.3.2.2、DeductAmountCalculator

@Service
@Slf4j
public class DeductAmountCalculator extends AbstractProxyLiveCalculator {

    @Override
    protected String getName() {
        return DeductAmountCalculator.class.getSimpleName();
    }

    @Override
    protected Integer doExecute(AbstractCalculateContext ctx) {
        DeductAmountCalculateContext context = (DeductAmountCalculateContext) ctx;
        JobApplyLiveBO jobApplyLive = context.getJobApplyLive();
        JobPoolBO jobPool = context.getJobPool();
        List<String> proxyList = getProxyJobList(jobApplyLive);
        int proxyCount = proxyList.size();
        int finishedCount = jobPool.getDeliveryMatchCount();
        UserOrderInfoBO userOrderInfoBO = findOrder(jobApplyLive.getId());
        int deductAmount = 0;
        int buyAmount = Optional.ofNullable(userOrderInfoBO.getBuyAmount()).orElse(BigDecimal.ZERO).intValue();
        int payAmount = Optional.ofNullable(userOrderInfoBO.getPayAmount()).orElse(BigDecimal.ZERO).intValue();
        if (finishedCount < proxyCount - 1) {
            deductAmount = Math.round((float) buyAmount / proxyCount);
        }
        if (finishedCount == proxyCount - 1) {
            deductAmount = buyAmount - payAmount;
        }
        JSONObject extend = new JSONObject();
        extend.put("applyId", jobApplyLive.getId());
        extend.put("proxyCount", proxyCount);
        extend.put("finishedCount", finishedCount);
        extend.put("deductAmount", deductAmount);
        ctx.setExtend(extend);
        return deductAmount;
    }
}

3.3.3、Context

3.3.3.1、AbstractCalculateContext

该抽象类封装了相关场景所依赖的共同参数,相关子类只需要继承此基类,然后扩展自己的成员属性即可。

/**
 * 抽象计算上下文
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/17 9:52 AM
 */
@Data
@SuperBuilder
public class AbstractCalculateContext implements Context {

    /**
     * 申请单
     */
    private JobApplyLiveBO jobApplyLive;

    /**
     * 职位池
     */
    private JobPoolBO jobPool;

    /**
     * 扩展项
     */
    private JSONObject extend;
}

3.3.3.2、PartialDeductAmountCalculateContext

/**
 * 计算部分划扣金额上下文
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/17 9:52 AM
 */
@Data
@SuperBuilder
public class PartialDeductAmountCalculateContext extends AbstractCalculateContext {

    /**
     * 当前要部分交付完成退款金额
     */
    private int presentRefundAmount;
}

3.3.4、CalculateMediator

相关计算器统一通过Spring @Autowired注入到该类,作为其成员属性。该类相当于一个委托类,并提供一系列成员方法,提供业务场景的金额计算调用,业务场景不需要关注具体选择何种计算器,只需要交由其处理。做到计算器与业务场景的隔离,方便后期扩展维护。

@Component
@Slf4j
public class JobApplyLiveCalculateMediator {

    @Autowired
    private DeductAmountCalculator deductAmountCalculator;

    @Autowired
    private RefundAmountCalculator refundAmountCalculator;

    @Autowired
    private PartialDeductAmountCalculator partialDeductAmountCalculator;

    @Autowired
    private DeliveryDeductAmountCalculator deliveryDeductAmountCalculator;

	//示例代码方法1
    public int calculateDeductAmount(JobApplyLiveBO jobApplyLive, JobPoolBO jobPool) {
        DeductAmountCalculateContext context = DeductAmountCalculateContext.builder().jobApplyLive(jobApplyLive).jobPool(jobPool).build();
        return deductAmountCalculator.execute(context);
    }

   //省略方法2、3、4、5
}

3.4、付款与退款的设计

在这里插入图片描述

3.4.1、Context类的设计

/**
 * 交易处理上下文对象
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 10:24 AM
 */
@AllArgsConstructor
@NoArgsConstructor
@SuperBuilder
@Data
public class TransactionHandleContext implements Context {

    /**
     * 请求参数,需要外部调用实例化
     */
    private Request request;

    /**
     * 计算结果,内部处理使用
     */
    private CalculateResult calculateResult;

    /**
     * 付款请求参数
     */
    private PaymentRequestBO paymentRequest;

    /**
     * 订单信息
     */
    private UserOrderInfoBO userOrderInfo;

    @AllArgsConstructor
    @NoArgsConstructor
    @SuperBuilder
    @Data
    public static class Request {

        /**
         * 产品策略
         */
        protected ProductStrategyEnum productStrategy;

        /**
         * 业务类型
         */
        private BusinessTypeEnum businessType;

        /**
         * 交易类型
         */
        private TransactionTypeEnum transactionType;
    }

    @AllArgsConstructor
    @NoArgsConstructor
    @SuperBuilder
    @Data
    public static class CalculateResult {

        /**
         * 确认
         */
        private BigDecimal confirmed;

        /**
         * 累计付款
         */
        private BigDecimal paid;

        /**
         * 累计退款
         */
        private BigDecimal refunded;

        /**
         * 是否满足结算完成
         */
        private boolean matchSettle;
    }
}

3.4.2、TransactionHandler类的设计

/**
 * 抽象交易处理处理器
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 10:26 AM
 */
@Slf4j
public abstract class AbstractTransactionHandler implements Handle<TransactionHandleContext> {

    protected final static String LOG_PREFIX = "[TransactionHandler]";

    /**
     * 产品策略
     */
    protected ProductStrategyEnum productStrategy;

    /**
     * 交易类型
     */
    private TransactionTypeEnum transactionType;

    public AbstractTransactionHandler(ProductStrategyEnum productStrategy, TransactionTypeEnum transactionType) {
        this.productStrategy = productStrategy;
        this.transactionType = transactionType;
    }

    @Autowired
    protected UserOrderInfoDAO userOrderInfoDAO;

    @Autowired
    protected PaymentBusiness paymentBusiness;

    @Autowired
    protected UserOrderInfoDetailDAO userOrderInfoDetailDAO;

    @Autowired
    protected UserOrderInfoDetailConverter userOrderInfoDetailConverter;

    @Autowired
    private RedisClient redisClient;

    /**
     * 执行订单检查
     *
     * @param context 上下文对象
     * @return 返回校验结果
     */
    protected abstract CheckedResult doCheckOrder(TransactionHandleContext context);

    /**
     * 执行计算
     *
     * @param context 上下文对象
     */
    protected abstract void doCalculate(TransactionHandleContext context);

    /**
     * 执行处理
     *
     * @param context 上下文对象
     */
    protected abstract void doExecute(TransactionHandleContext context);

    /**
     * 获取加锁key
     *
     * @param context 上下文对象
     * @return 返回值
     */
    protected String getLockKey(TransactionHandleContext context) {
        PaymentRequestBO paymentRequestBO = context.getPaymentRequest();
        return "confirmPayment:lock:" + paymentRequestBO.getBusinessId() + "-" + paymentRequestBO.getBusinessType();
    }

    /**
     * 执行数据装载
     *
     * @param context 上下文对象
     */
    protected void doPrepare(TransactionHandleContext context) {
        PaymentRequestBO paymentRequestBO = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfoBO = getUserOrderInfo(paymentRequestBO.getBusinessId(), paymentRequestBO.getBusinessType());
        context.setUserOrderInfo(userOrderInfoBO);
    }

    /**
     * 执行处理
     *
     * @param context 上下文对象
     */
    @Override
    public void execute(TransactionHandleContext context) {
        PaymentRequestBO paymentRequestBO = context.getPaymentRequest();
        String key = getLockKey(context);
        redisClient.lock(() -> {
            try {
                doExecute(context);
            } catch (Exception e) {
                log.error("{}|{}|param={}", LOG_PREFIX, getName(), JsonUtils.toJson(paymentRequestBO), e);
            }
        }, key);
    }

    private UserOrderInfoBO getUserOrderInfo(long businessId, int businessType) {
        UserOrderInfoBO userOrderInfoBO = new UserOrderInfoBO();
        userOrderInfoBO.setBusinessId(businessId);
        userOrderInfoBO.setBusinessType(businessType);
        List<UserOrderInfoBO> list = userOrderInfoDAO.list(userOrderInfoBO);
        if (CollectionUtils.isEmpty(list)) {
            throw new BusinessException("支付订单不存在!");
        }
        return list.get(0);
    }

    protected void addOrderDetail(PaymentRequestBO paymentRequestBO, Long orderId) {
        UserOrderInfoDetailBO bo = userOrderInfoDetailConverter.convertToUserOrderInfoDetailBO(paymentRequestBO, orderId);
        userOrderInfoDetailDAO.insert(bo);
    }

    public ProductStrategyEnum getProductStrategy() {
        return productStrategy;
    }

    public TransactionTypeEnum getTransactionType() {
        return transactionType;
    }

    @AllArgsConstructor
    @NoArgsConstructor
    @Builder
    @Data
    public static class CheckedResult {

        /**
         * 校验成功
         */
        private boolean success;

        /**
         * 校验消息
         */
        private String message;
    }
}

3.4.3、PaymentTransactionHandler类的设计

/**
 * 抽象付款处理器
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 10:26 AM
 */
@Slf4j
public abstract class AbstractPaymentTransactionHandler extends AbstractTransactionHandler {

    public AbstractPaymentTransactionHandler(ProductStrategyEnum productStrategy) {
        super(productStrategy, TransactionTypeEnum.PAYMENT);
    }

    @Override
    public void doExecute(TransactionHandleContext context) {
        PaymentRequestBO paymentRequestBO = context.getPaymentRequest();
        doPrepare(context);
        UserOrderInfoBO userOrderInfoBO = context.getUserOrderInfo();
        String trans = userOrderInfoBO.getPaymentNo();
        Long bizId = userOrderInfoBO.getBusinessId();
        if (userOrderInfoBO.getState() == PayStatusEnum.SUCCESS.getValue()) {
            return;
        }
        if (userOrderInfoBO.getState() != PayStatusEnum.FREEZE.getValue()) {
            throw new BusinessException("订单不是冻结状态,无法确认支付!");
        }
        CheckedResult checked = doCheckOrder(context);
        if (!checked.isSuccess()) {
            throw new BusinessException(checked.getMessage());
        }
        doCalculate(context);
        ConfirmPaymentBO confirmPaymentBO = doBuildConfirmPayment(context);
        log.info("{}|{}|trans={}|bizId={}|Calculate={}|confirmPayment-pay:{}", LOG_PREFIX, getName(),trans, bizId, JsonUtils.toJson(context.getCalculateResult()), JsonUtils.toJson(confirmPaymentBO));
        boolean result = paymentBusiness.confirmPayment(confirmPaymentBO);
        if (!result) {
            throw new BusinessException("订单确认支付失败!");
        }
        UserOrderInfoBO updateUserOrderInfoBO = doBuildUserOrderInfo(context);
        userOrderInfoDAO.update(updateUserOrderInfoBO);
        addOrderDetail(paymentRequestBO, userOrderInfoBO.getId());
    }

    protected ConfirmPaymentBO doBuildConfirmPayment(TransactionHandleContext context) {
        TransactionHandleContext.CalculateResult calculateResult = context.getCalculateResult();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        ConfirmPaymentBO confirmPaymentBO = new ConfirmPaymentBO();
        confirmPaymentBO.setUserOrderInfoBO(context.getUserOrderInfo());
        confirmPaymentBO.setConfirmType(ConfirmTypeEnum.valueOf(userOrderInfo.getConfirmType()));
        confirmPaymentBO.setConfirmed(calculateResult.getConfirmed().longValue());
        return confirmPaymentBO;
    }

    protected UserOrderInfoBO doBuildUserOrderInfo(TransactionHandleContext context) {
        TransactionHandleContext.CalculateResult calculateResult = context.getCalculateResult();
        UserOrderInfoBO updateUserOrderInfo = new UserOrderInfoBO();
        updateUserOrderInfo.setId(context.getUserOrderInfo().getId());
        if (calculateResult.isMatchSettle()) {
            updateUserOrderInfo.setState(PayStatusEnum.FINISH.getValue());
        }
        invokeUserOrderInfoSet(context, updateUserOrderInfo);
        return updateUserOrderInfo;
    }

    /**
     * 设置需要更新订单的相关属性
     *
     * @param context 上下文信息
     * @param orderInfo 订单信息
     */
    protected abstract void invokeUserOrderInfoSet(TransactionHandleContext context, UserOrderInfoBO orderInfo);
}

3.4.3.1、StandardPricePaymentTransactionHandler类的设计

/**
 * (按商品价格)付款交易处理器
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 10:31 AM
 */
@Service
@Slf4j
public class StandardPricePaymentTransactionHandler extends AbstractPaymentTransactionHandler {

    public StandardPricePaymentTransactionHandler() {
        super(ProductStrategyEnum.STANDARD_PRICE);
    }

    @Override
    public String getName() {
        return StandardPricePaymentTransactionHandler.class.getSimpleName();
    }

    @Override
    protected CheckedResult doCheckOrder(TransactionHandleContext context) {
        PaymentRequestBO paymentRequest = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        BigDecimal buyAmount = Optional.ofNullable(userOrderInfo.getBuyAmount()).orElse(BigDecimal.ZERO);
        BigDecimal payAmount = Optional.ofNullable(userOrderInfo.getPayAmount()).orElse(BigDecimal.ZERO);
        BigDecimal cancelAmount = Optional.ofNullable(userOrderInfo.getCancelAmount()).orElse(BigDecimal.ZERO);
        BigDecimal operateAmount = BigDecimal.valueOf(Optional.ofNullable(paymentRequest.getOperateCount()).orElse(0));
        BigDecimal rest = buyAmount.subtract(payAmount).subtract(cancelAmount);
        if (rest.compareTo(operateAmount) == -1) {
            return CheckedResult.builder().success(Boolean.FALSE).message("支付金额大于剩余金额,无法确认支付").build();
        }
        return CheckedResult.builder().success(Boolean.TRUE).build();
    }

    @Override
    protected void doCalculate(TransactionHandleContext context) {
        PaymentRequestBO paymentRequest = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        BigDecimal buyAmount = Optional.ofNullable(userOrderInfo.getBuyAmount()).orElse(BigDecimal.ZERO);
        BigDecimal payAmount = Optional.ofNullable(userOrderInfo.getPayAmount()).orElse(BigDecimal.ZERO);
        BigDecimal cancelAmount = Optional.ofNullable(userOrderInfo.getCancelAmount()).orElse(BigDecimal.ZERO);
        BigDecimal operateAmount = BigDecimal.valueOf(Optional.ofNullable(paymentRequest.getOperateCount()).orElse(0));
        BigDecimal total = payAmount.add(operateAmount).add(cancelAmount);
        boolean matchSettle = total.compareTo(buyAmount) == 0;
        TransactionHandleContext.CalculateResult calculateResult = TransactionHandleContext.CalculateResult.builder()
                .confirmed(payAmount.add(operateAmount))
                .paid(payAmount.add(operateAmount))
                .matchSettle(matchSettle)
                .build();
        context.setCalculateResult(calculateResult);
    }

    @Override
    protected void invokeUserOrderInfoSet(TransactionHandleContext context, UserOrderInfoBO orderInfo) {
        TransactionHandleContext.CalculateResult calculateResult = context.getCalculateResult();
        orderInfo.setPayAmount(calculateResult.getPaid());
    }
}

3.4.3.2、StandardPricePaymentTransactionHandler类的设计

/**
 * (按商品价格)付款交易处理器
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 10:31 AM
 */
@Service
@Slf4j
public class StandardPricePaymentTransactionHandler extends AbstractPaymentTransactionHandler {

    public StandardPricePaymentTransactionHandler() {
        super(ProductStrategyEnum.STANDARD_PRICE);
    }

    @Override
    public String getName() {
        return StandardPricePaymentTransactionHandler.class.getSimpleName();
    }

    @Override
    protected CheckedResult doCheckOrder(TransactionHandleContext context) {
        PaymentRequestBO paymentRequest = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        BigDecimal buyAmount = Optional.ofNullable(userOrderInfo.getBuyAmount()).orElse(BigDecimal.ZERO);
        BigDecimal payAmount = Optional.ofNullable(userOrderInfo.getPayAmount()).orElse(BigDecimal.ZERO);
        BigDecimal cancelAmount = Optional.ofNullable(userOrderInfo.getCancelAmount()).orElse(BigDecimal.ZERO);
        BigDecimal operateAmount = BigDecimal.valueOf(Optional.ofNullable(paymentRequest.getOperateCount()).orElse(0));
        BigDecimal rest = buyAmount.subtract(payAmount).subtract(cancelAmount);
        if (rest.compareTo(operateAmount) == -1) {
            return CheckedResult.builder().success(Boolean.FALSE).message("支付金额大于剩余金额,无法确认支付").build();
        }
        return CheckedResult.builder().success(Boolean.TRUE).build();
    }

    @Override
    protected void doCalculate(TransactionHandleContext context) {
        PaymentRequestBO paymentRequest = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        BigDecimal buyAmount = Optional.ofNullable(userOrderInfo.getBuyAmount()).orElse(BigDecimal.ZERO);
        BigDecimal payAmount = Optional.ofNullable(userOrderInfo.getPayAmount()).orElse(BigDecimal.ZERO);
        BigDecimal cancelAmount = Optional.ofNullable(userOrderInfo.getCancelAmount()).orElse(BigDecimal.ZERO);
        BigDecimal operateAmount = BigDecimal.valueOf(Optional.ofNullable(paymentRequest.getOperateCount()).orElse(0));
        BigDecimal total = payAmount.add(operateAmount).add(cancelAmount);
        boolean matchSettle = total.compareTo(buyAmount) == 0;
        TransactionHandleContext.CalculateResult calculateResult = TransactionHandleContext.CalculateResult.builder()
                .confirmed(payAmount.add(operateAmount))
                .paid(payAmount.add(operateAmount))
                .matchSettle(matchSettle)
                .build();
        context.setCalculateResult(calculateResult);
    }

    @Override
    protected void invokeUserOrderInfoSet(TransactionHandleContext context, UserOrderInfoBO orderInfo) {
        TransactionHandleContext.CalculateResult calculateResult = context.getCalculateResult();
        orderInfo.setPayAmount(calculateResult.getPaid());
    }
}

3.4.4、TransactionHandler类的设计

/**
 * 抽象退款处理器
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 10:26 AM
 */
@Slf4j
public abstract class AbstractRefundTransactionHandler extends AbstractTransactionHandler {

    public AbstractRefundTransactionHandler(ProductStrategyEnum productStrategy) {
        super(productStrategy, TransactionTypeEnum.REFUND);
    }

    @Override
    public void doExecute(TransactionHandleContext context) {
        PaymentRequestBO paymentRequestBO = context.getPaymentRequest();
        doPrepare(context);
        UserOrderInfoBO userOrderInfoBO = context.getUserOrderInfo();
        String trans = userOrderInfoBO.getPaymentNo();
        Long bizId = userOrderInfoBO.getBusinessId();
        if (userOrderInfoBO.getState() != PayStatusEnum.FREEZE.getValue()) {
            throw new BusinessException("订单不是冻结状态,无法取消支付!");
        }
        CheckedResult checked = doCheckOrder(context);
        if (!checked.isSuccess()) {
            throw new BusinessException(checked.getMessage());
        }
        doCalculate(context);
        ConfirmPaymentBO confirmPaymentBO = doBuildConfirmPayment(context);
        log.info("{}|{}|trans={}|bizId={}|Calculate={}|confirmPayment-cancel:{}", LOG_PREFIX, getName(), trans, bizId, JsonUtils.toJson(context.getCalculateResult()), JsonUtils.toJson(confirmPaymentBO));
        boolean result = paymentBusiness.confirmPayment(confirmPaymentBO);
        if (!result) {
            throw new BusinessException("订单取消支付失败!");
        }
        UserOrderInfoBO updateUserOrderInfoBO = doBuildUserOrderInfo(context);
        userOrderInfoDAO.update(updateUserOrderInfoBO);
        addOrderDetail(paymentRequestBO, userOrderInfoBO.getId());
    }

    protected ConfirmPaymentBO doBuildConfirmPayment(TransactionHandleContext context) {
        TransactionHandleContext.CalculateResult calculateResult = context.getCalculateResult();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        ConfirmPaymentBO confirmPaymentBO = new ConfirmPaymentBO();
        confirmPaymentBO.setUserOrderInfoBO(context.getUserOrderInfo());
        confirmPaymentBO.setConfirmType(ConfirmTypeEnum.valueOf(userOrderInfo.getConfirmType()));
        confirmPaymentBO.setRefunded(calculateResult.getConfirmed().longValue());
        return confirmPaymentBO;
    }

    protected UserOrderInfoBO doBuildUserOrderInfo(TransactionHandleContext context) {
        TransactionHandleContext.CalculateResult calculateResult = context.getCalculateResult();
        UserOrderInfoBO updateUserOrderInfo = new UserOrderInfoBO();
        updateUserOrderInfo.setId(context.getUserOrderInfo().getId());
        if (calculateResult.isMatchSettle()) {
            updateUserOrderInfo.setState(PayStatusEnum.FINISH.getValue());
        }
        invokeUserOrderInfoSet(context, updateUserOrderInfo);
        return updateUserOrderInfo;
    }

    /**
     * 设置需要更新订单的相关属性
     *
     * @param context   上下文信息
     * @param orderInfo 订单信息
     */
    protected abstract void invokeUserOrderInfoSet(TransactionHandleContext context, UserOrderInfoBO orderInfo);
}

3.4.4.1、StandardPriceRefundTransactionHandler类的设计

/**
 * (按商品价格)退款交易处理器
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 10:31 AM
 */
@Service
@Slf4j
public class StandardPriceRefundTransactionHandler extends AbstractRefundTransactionHandler {

    public StandardPriceRefundTransactionHandler() {
        super(ProductStrategyEnum.STANDARD_PRICE);
    }

    @Override
    public String getName() {
        return StandardPriceRefundTransactionHandler.class.getSimpleName();
    }

    @Override
    protected CheckedResult doCheckOrder(TransactionHandleContext context) {
        PaymentRequestBO paymentRequest = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        BigDecimal buyAmount = Optional.ofNullable(userOrderInfo.getBuyAmount()).orElse(BigDecimal.ZERO);
        BigDecimal payAmount = Optional.ofNullable(userOrderInfo.getPayAmount()).orElse(BigDecimal.ZERO);
        BigDecimal cancelAmount = Optional.ofNullable(userOrderInfo.getCancelAmount()).orElse(BigDecimal.ZERO);
        BigDecimal operateAmount = BigDecimal.valueOf(Optional.ofNullable(paymentRequest.getOperateCount()).orElse(0));
        BigDecimal rest = buyAmount.subtract(payAmount).subtract(cancelAmount);
        if (rest.compareTo(operateAmount) == -1) {
            return CheckedResult.builder().success(Boolean.FALSE).message("支付金额大于剩余金额,无法确认支付").build();
        }
        return CheckedResult.builder().success(Boolean.TRUE).build();
    }

    @Override
    protected void doCalculate(TransactionHandleContext context) {
        PaymentRequestBO paymentRequest = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        BigDecimal buyAmount = Optional.ofNullable(userOrderInfo.getBuyAmount()).orElse(BigDecimal.ZERO);
        BigDecimal payAmount = Optional.ofNullable(userOrderInfo.getPayAmount()).orElse(BigDecimal.ZERO);
        BigDecimal cancelAmount = Optional.ofNullable(userOrderInfo.getCancelAmount()).orElse(BigDecimal.ZERO);
        BigDecimal operateAmount = BigDecimal.valueOf(Optional.ofNullable(paymentRequest.getOperateCount()).orElse(0));
        BigDecimal total = payAmount.add(operateAmount).add(cancelAmount);
        boolean matchSettle = total.compareTo(buyAmount) == 0;
        TransactionHandleContext.CalculateResult calculateResult = TransactionHandleContext.CalculateResult.builder()
                .confirmed(cancelAmount.add(operateAmount))
                .refunded(cancelAmount.add(operateAmount))
                .matchSettle(matchSettle)
                .build();
        context.setCalculateResult(calculateResult);
    }

    @Override
    protected void invokeUserOrderInfoSet(TransactionHandleContext context, UserOrderInfoBO orderInfo) {
        TransactionHandleContext.CalculateResult calculateResult = context.getCalculateResult();
        orderInfo.setCancelAmount(calculateResult.getRefunded());
    }
}

3.4.4.2、StandardQuantityRefundTransactionHandler类的设计

/**
 * (按商品数量)退款交易处理器
 *
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 10:31 AM
 */
@Service
@Slf4j
public class StandardQuantityRefundTransactionHandler extends AbstractRefundTransactionHandler {

    public StandardQuantityRefundTransactionHandler() {
        super(ProductStrategyEnum.STANDARD_QUANTITY);
    }

    @Override
    public String getName() {
        return StandardQuantityRefundTransactionHandler.class.getSimpleName();
    }

    @Override
    protected CheckedResult doCheckOrder(TransactionHandleContext context) {
        PaymentRequestBO paymentRequestBO = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        int buyCount = Optional.ofNullable(userOrderInfo.getBuyCount()).orElse(0);
        int payCount = Optional.ofNullable(userOrderInfo.getPayCount()).orElse(0);
        int cancelCount = Optional.ofNullable(userOrderInfo.getCancelCount()).orElse(0);
        int operateCount = Optional.ofNullable(paymentRequestBO.getOperateCount()).orElse(0);
        if ((buyCount - payCount - cancelCount) < operateCount) {
            return CheckedResult.builder().success(Boolean.FALSE).message("支付数大于剩余数量,无法确认支付").build();
        }
        return CheckedResult.builder().success(Boolean.TRUE).build();
    }

    @Override
    protected void doCalculate(TransactionHandleContext context) {
        PaymentRequestBO paymentRequestBO = context.getPaymentRequest();
        UserOrderInfoBO userOrderInfo = context.getUserOrderInfo();
        int buyCount = Optional.ofNullable(userOrderInfo.getBuyCount()).orElse(0);
        int payCount = Optional.ofNullable(userOrderInfo.getPayCount()).orElse(0);
        int cancelCount = Optional.ofNullable(userOrderInfo.getCancelCount()).orElse(0);
        int operateCount = Optional.ofNullable(paymentRequestBO.getOperateCount()).orElse(0);
        int total = payCount + operateCount + cancelCount;
        boolean matchSettle = total == buyCount;
        TransactionHandleContext.CalculateResult calculateResult = TransactionHandleContext.CalculateResult.builder()
                .confirmed(BigDecimal.valueOf(cancelCount + operateCount))
                .refunded(BigDecimal.valueOf(cancelCount + operateCount))
                .matchSettle(matchSettle)
                .build();
        context.setCalculateResult(calculateResult);
    }

    @Override
    protected void invokeUserOrderInfoSet(TransactionHandleContext context, UserOrderInfoBO orderInfo) {
        TransactionHandleContext.CalculateResult calculateResult = context.getCalculateResult();
        orderInfo.setCancelCount(calculateResult.getRefunded().intValue());
    }
}

四、优化设计

1、计算模型优化

第一版的设计,有这么一个JobApplyLiveCalculator类,里边有若干个成员方法,但是缺点就是后续如果增加场景,就会增加再增加一个成员方法,同时,如果方法增加参数,意味着相关调用方都需要增加参数,意味着耦合性强,因此为了达到松耦合,符合单一原则,抽象了如下领域模型。

在这里插入图片描述

  • Context:上下文类封装各个场景所需要的相关参数,同时抽象出一个AbstractContext类,各个场景继承此类。
  • Calculator:抽象出AbstractCalculator,各个场景有对应的计算器完成计算。
  • Mediator:委托类,只能处理委托给相关场景的Calculator完成处理,返回把计算的结果再返回给调用方。

2、Dispatcher优化

该类具体就是完成业务场景处理器的路由与分发,场景不需要关注选择何种处理器,只需要委托该类处理,做到职责隔离,方便后期扩展。

在这里插入图片描述

2.1、第一版Dispatcher设计

/**
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 2:18 PM
 */
@Component
@Slf4j
public class TransactionHandleDispatcher {

    @Autowired
    private List<AbstractTransactionHandler> handlers;

    /**
     * 执行分发策略
     *
     * @param context 上下文对象
     */
    public void execute(TransactionHandleContext context) {
        handlers.stream().forEach(each -> {
        Optional<AbstractTransactionHandler> optional = handlers.stream().filter(each -> {
            TransactionHandleContext.Request request = context.getRequest();
            boolean find = request.getProductStrategy() == each.getProductStrategy()
                    && request.getTransactionType() == each.getTransactionType();
            if (find) {
                each.execute(context);
            }
        });
            return request.getProductStrategy() == each.getProductStrategy() && request.getTransactionType() == each.getTransactionType();
        }).findAny();
        if (optional.isPresent()) {
            optional.get().execute(context);
        } else {
            log.error("[TransactionHandle]未找到对应的处理器", JsonUtils.toJson(context));
        }
    }
}

由于各个模块都有对应的Dispatcher,但是该类仔细观察,还可以继续抽象。通过分析,发现这个类有以下几个特征:

  • 通过Spring @Autowired自动装配相关处理类的子类,子类具有有哪些,这个类不需要关注。
  • 对外提供的execute方法,返回类型void,入参是一个Context类。
  • execute方法内部,需要根据Context类的实例化参数来进行选择一个Handler来处理。

通过分析如上特征,我认为可以对Dispatcher再进行抽象,所有的上下文类提取一个Context接口,所有的处理器类实现一个Handler接口。

2.2、第二版Dispatcher设计

目标:基于面向接口编程的设计。

2.2.1、Context接口

该接口没有成员方法,它代表一种上下文能力特征,只要具备此特征就需要实现该接口。

/**
 * 上下文接口
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2023/2/17 7:14 AM
 */
public interface Context {
}

2.2.2、Handle接口

该接口两个成员方法,同时该类规约了上下文类需要实现Context接口。

/**
 * 处理接口
 * 适用于多场景、多策略的处理器
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2023/2/17 6:46 AM
 */
public interface Handle<C extends Context> {
    /**
     * 获取处理器名称
     *
     * @return 处理器名称
     */
    String getName();

    /**
     * 执行处理
     *
     * @param context 上下文对象
     */
    void execute(C context);
}

2.2.3、AbstractDispatcher抽象类

只要实现Handle接口的类,同时具备Spring Bean容器管理的相关处理器Handler都可以通@Autowired完成应用成员启动时自动装配。对于子类而言,主要实现find方法,完成从getHander()方法中获取处理器集合,选择一个场景匹配的处理器完成处理。

/**
 * 抽象处理分发器
 *
 * @author : 石冬冬-Sieg Heil
 * @since 2023/2/17 6:42 AM
 */
@Slf4j
public abstract class AbstractHandleDispatcher<H extends Handle, C extends Context> {
    /**
     * 获取分发器名称
     *
     * @return 分发器名称
     */
    protected abstract String getName();

    /**
     * 获取处理器集合
     *
     * @return 处理器集合
     */
    protected abstract List<H> getHandlers();

    /**
     * 查找处理器
     *
     * @param context 处理器上下文
     * @return 要分发的处理器
     */
    protected abstract Optional<H> find(C context);

    /**
     * 执行分发
     *
     * @param context 处理器上下文
     */
    public void execute(C context) {
        Optional<H> optional = find(context);
        if (optional.isPresent()) {
            optional.get().execute(context);
        } else {
            log.error("no find handler", JsonUtils.toJson(context));
        }
    }
}

具体一个Dispatcher类,只需要继承此类,实现抽象方法即可。

/**
 * @author 石冬冬(Chris Suk)
 * @since 2023/2/13 2:18 PM
 */
@Component
@Slf4j
public class TransactionHandleDispatcher extends AbstractHandleDispatcher<AbstractTransactionHandler, TransactionHandleContext> {

    @Autowired
    private List<AbstractTransactionHandler> handlers;

    @Override
    protected String getName() {
        return TransactionHandleDispatcher.class.getSimpleName();
    }

    @Override
    protected List<AbstractTransactionHandler> getHandlers() {
        return handlers;
    }

    @Override
    protected Optional<AbstractTransactionHandler> find(TransactionHandleContext context) {
        return getHandlers().stream().filter(each -> {
            TransactionHandleContext.Request request = context.getRequest();
            return request.getProductStrategy() == each.getProductStrategy() && request.getTransactionType() == each.getTransactionType();
        }).findAny();
    }
}

五、总结

谈言之,这个需求总体涉及变动点多,为了考虑尽可能代码变动影响面小,所以经过慎重选择,设计出一种利弊均衡的技术方案设计,这种设计同时达成了一个目标,业务场景与底层能力解耦,能力通过抽象下沉,委托给Dispatcher或者Mediator完成具体处理器Handler的分发和处理。

恰如一个需求来了,有个项目经理来完成跟产品经理对接,产品经理不需要关注后端、前端是哪位成员,他只需要跟项目经理对接即可,做到隔离。如果后端有成员变更,对于产品经理做到隔离,产品经理无需关注后端有成员变更,他只需要关注目标的达成,通过接口隔离即可实现。

同时,这个需求也让我从零熟悉了整个业务流程,它不仅是一种挑战,更是一种成长。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/356214.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

k8s篇之概念介绍

文章目录时光回溯什么是K8SK8S不是什么一、K8S构成组件控制平面组件&#xff08;Control Plane Components&#xff09;kube-apiserveretcdkube-schedulerkube-controller-managercloud-controller-managerNode 组件kubeletkube-proxy容器运行时&#xff08;Container Runtime&…

Spring Cloud Nacos实战(七)- Nacos之Linux版本安装

Nacos之Linux版本安装 Linux版NacosMySql生产环境配置 ​ 已经给大家讲解过了Nacos生产环境下需要搭建集群配置&#xff0c;那么这里我们预计需要&#xff1a;1个Nginx3个Nacos注册中心1个MySql 具体配置&#xff1a; 在官网上下载NacosLinux版本&#xff1a;https://github…

基于SSM框架的CMS内容管理系统的设计与实现

基于SSM框架的CMS内容管理系统的设计与实现 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目…

并查集(高级数据结构)-蓝桥杯

一、并查集并查集(Disioint Set)&#xff1a;一种非常精巧而实用的数据结构用于处理不相交集合的合并问题。用于处理不相交集合的合并问题。经典应用&#xff1a;连通子图。最小生成树Kruskal算法。最近公共祖先。二、应用场景有n个人&#xff0c;他们属于不同的帮派。 已知这些…

Kafka漏洞修复之CVE-2023-25194修复措施验证

Kafka漏洞修复之CVE-2023-25194修复措施验证前言风险分析解决方案AdoptOpenJDK Zookeeper Kafka多版本OpenJDK安装切换Zookeeper安装Kafka安装与使用其他Kafka消息发送流程Linux配置加载顺序参考链接前言 场景介绍 Kafka最近爆出高危漏洞CNNVD-202302-515&#xff0c;导致Apa…

LeetCode刷题复盘笔记—一文搞懂贪心算法之56. 合并区间(贪心算法系列第十四篇)

今日主要总结一下可以使用贪心算法解决的一道题目&#xff0c;56. 合并区间 题目&#xff1a;56. 合并区间 Leetcode题目地址 题目描述&#xff1a; 以数组 intervals 表示若干个区间的集合&#xff0c;其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间…

QXlsx(访问Excel)

再Qt中已经有了QAxObject来访问Excel&#xff0c;但访问的是微软的com&#xff0c;只能访问正版的Excl中的 .xls//xlsx ,而且使用起来及其不方便&#xff08;本人亲测&#xff09;。 在这里使用QXlsx,能更简单的访问Excel数据&#xff0c;但QXlsx这个类并没有在Qt Creator中&a…

《MySQL学习》 MySQL优化器选择如何选择索引

一.优化器的选择逻辑 建表语句 CREATE TABLE t (id int(11) NOT NULL AUTO_INCREMENT,a int(11) DEFAULT NULL,b int(11) DEFAULT NULL,PRIMARY KEY (id),KEY a (a),KEY b (b) ) ENGINEInnoDB;往表中插入10W条数据 delimiter ;; create procedure idata() begindeclare i in…

目标检测三大数据格式VOC,YOLO,COCO的详细介绍

注&#xff1a;本文仅供学习&#xff0c;未经同意请勿转载 说明&#xff1a;该博客来源于xiaobai_Ry:2020年3月笔记 对应的PDF下载链接在&#xff1a;待上传 目录 目标检测常见数据集总结 V0C数据集(Annotation的格式是xmI) A. 数据集包含种类: B. V0C2007和V0C2012的区别…

QT学习记录散件

fromLocal8Bit() qt中fromLocal8Bit()函数可以设置编码。 因为QT默认的编码是unicode&#xff0c;不能显示中文的 而windows默认使用&#xff08;GBK/GB2312/GB18030&#xff09; 所以使用fromLocal8Bit()函数&#xff0c;可以实现从本地字符集GB到Unicode的转换&#xff0c;从…

32-Golang中的map

Golang中的map基本介绍基本语法map声明的举例map使用的方式map的增删改查操作map的增加和更新map的删除map的查找map的遍历map切片基本介绍map排序map的使用细节基本介绍 map是key-value数据结构&#xff0c;又称为字段或者关联数组。类似其它编程语言的集合&#xff0c;在编程…

2023美赛ABCDEF思路汇总

注&#xff1a;以下每个题思路仅是个人所想所做&#xff0c;不代表他人。由于时间仓促完成这么多&#xff0c;难免有不足之处&#xff0c;还请谅解。 文章目录A题第一大问第二大问B题第一问第二问第三问C题第一问第二问第三问第四问D题第一问第二问第三问第四问第五问E题第一问…

#Paper Reading# Language Models are Unsupervised Multitask Learners

论文题目: Language Models are Unsupervised Multitask Learners 论文地址: https://life-extension.github.io/2020/05/27/GPT技术初探/language-models.pdf 论文发表于: OpenAI 2019 论文所属单位: OpenAI 论文大体内容&#xff1a; 本文主要提出了GPT-2&#xff08;Gener…

Visual Studio 2022: 增加对虚幻引擎的支持

自 Visual Studio 2022 发布以来&#xff0c;我们一直专注于为游戏和大型项目开发人员提供一系列生产力和性能改进。今天&#xff0c;我们很高兴与大家分享下一组专门用来提高虚幻引擎开发效率的功能。我们听到并看到了来自你&#xff08;我们的游戏开发人员&#xff09;的大量…

Spring MVC之 一次请求响应的过程

Spring MVC 会创建两个容器&#xff0c;其中创建Root WebApplicationContext 后&#xff0c;调用其refresh()方法会触发刷新事件&#xff0c;完成 Spring IOC 初始化相关工作&#xff0c;会初始化各种 Spring Bean 到当前容器中我们先来了解一个请求是如何被 Spring MVC 处理的…

2023最新文件快递柜系统网站源码 | 匿名口令分享 | 临时文件分享

内容目录一、详细介绍二、效果展示1.部分代码2.效果图展示三、学习资料下载一、详细介绍 2023最新文件快递柜系统网站源码 | 匿名口令分享 | 临时文件分享 很多时候&#xff0c;我们都想将一些文件或文本传送给别人&#xff0c;或者跨端传递一些信息&#xff0c;但是我们又不…

自抗扰控制ADRC之三种微分跟踪器TD仿真分析

目录 前言 1 全程快速微分器 1.1仿真分析 1.2仿真模型 1.3仿真结果 1.4结论 2 Levant微分器 2.1仿真分析 2.2仿真模型 2.3仿真结果 3.非线性跟踪微分器——韩教授 3.1仿真分析 3.2小结 4.总结 前言 工程上信号的微分是难以得到的&#xff0c;所以本文采用微分器…

重磅 | 小O软件新品【鲸鱼地图】发布

千呼万唤始出来.......&#xff0c;小O系列软件又添新品【鲸鱼地图】&#xff01;&#xff01;&#xff01; 2023年新年伊始&#xff0c;小O就投入到新品研发工作中&#xff0c;秉承“发现地理价值”理念&#xff0c;为用户提供更加好用、易用的地图软件产品&#xff0c;经过春…

【C语言】编程初学者入门训练(完结)

文章目录1. 实践出真知2. 我是大V3. 有容乃大4. 小飞机5. 缩短2进制6. 十六进制转十进制7. printf的返回值8. 成绩输入输出9. 学生基本信息输入输出10. 字符圣诞树11. ASCII码12. 出生日期输入输出13. 按照格式输入并交换输出14. 字符转ASCII码15. 计算表达式的值16. 计算带余除…

Java特性之设计模式【策略模式】

一、策略模式 概述 在策略模式&#xff08;Strategy Pattern&#xff09;中&#xff0c;一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式 在策略模式中&#xff0c;我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略…