笔记2025/11/23

拼团支付系统设计与思考

拼团对接支付系统之后,想梳理一下

这里是一个区分逻辑,判断是普通订单还是拼团订单。

通过检查 MarketType(营销类型)字段,

  • 普通订单:else 分支,直接调用 repository.changeOrderPaySuccess,这会立即变更状态并发送 EventBus 消息触发发货。

  • 拼团订单:if 分支,进入“等待拼团”流程。

@Override
    public void changeOrderPaySuccess(String orderId, Date payTime) {
        OrderEntity orderEntity = repository.queryOrderByOrderId(orderId);
        if (null == orderEntity) return;

        if (MarketTypeVO.GROUP_BUY_MARKET.getCode().equals(orderEntity.getMarketType())) {
            repository.changeMarketOrderPaySuccess(orderId);
            // 发起营销结算。这个过程可以是http/rpc直接调用,也可以发一个商城交易支付完成的消息,之后拼团系统自己接收做结算。
            port.settlementMarketPayOrder(orderEntity.getUserId(), orderId, payTime);
            // 注意;在公司中,发起结算的http/rpc调用可能会失败,这个时候还会有增加job任务补偿。条件为,检查一笔走了拼团的订单,超过n分钟后,仍然没有做拼团结算状态变更。
            // 我们这里失败了,会抛异常,借助支付宝回调/job来重试。你可以单独实现一个独立的job来处理。
        } else {
            repository.changeOrderPaySuccess(orderId, payTime);
        }

    }

如果确认为拼团订单,系统不会立即发货,而是执行了以下两步关键操作:

1)repository.changeMarketOrderPaySuccess(orderId)将订单状态改为 PAY_SUCCESS(支付成功),但没有触发发货的 EventBus 消息。

2)调用 port.settlementMarketPayOrder(...)穿透到了 Infrastructure 层,ProductPort 使用 Retrofit 构建了一个 HTTP POST 请求,将 SourceChannelUserIdOrderId 发送给外部的 GroupBuyMarket(拼团系统)。

@Override
    public void settlementMarketPayOrder(String userId, String orderId, Date orderTime) {
        SettlementMarketPayOrderRequestDTO requestDTO = new SettlementMarketPayOrderRequestDTO();
        requestDTO.setSource(source);
        requestDTO.setChannel(chanel);
        requestDTO.setUserId(userId);
        requestDTO.setOutTradeNo(orderId);
        requestDTO.setOutTradeTime(orderTime);

        try {
            Call<Response<SettlementMarketPayOrderResponseDTO>> call = groupBuyMarketService.settlementMarketPayOrder(requestDTO);

            // 获取结果
            Response<SettlementMarketPayOrderResponseDTO> response = call.execute().body();
            log.info("营销结算{} requestDTO:{} responseDTO:{}", userId, JSON.toJSONString(requestDTO), JSON.toJSONString(response));
            if (null == response) return;

            // 异常判断
            if (!"0000".equals(response.getCode())) {
                throw new AppException(response.getCode(), response.getInfo());
            }

        } catch (Exception e) {
            log.error("营销结算失败{}", userId, e);
        }
    }

此时流程暂停,等待拼团系统的“回信”。

几个问题,可能会拷打:

1. changeMarketOrderPaySuccess,然后才调用远程接口 port.settlementMarketPayOrder。如果本地库更新成功了,但远程 HTTP 请求超时或失败了(比如断网),这笔订单会发生什么?

需要补偿机制,job定时任务扫描数据库一直在PAY_SUCCESS超过N分钟(未变为DEAL_DONE)重新触发settlementMarketPayOrder

2.关于“异步回调”的设计

提问: “为什么拼团系统结算完成后,不直接在 settlementMarketPayOrder 的 HTTP 响应里返回‘拼团成功’,而是要单独搞一个 groupBuyNotify 接口?”

  • 解耦与耗时: 支付成功(结算)≠拼团成功。用户A支付后,可能还要等用户B、用户C支付才能成团。这个过程可能持续几小时。HTTP 请求不可能挂起几小时等待

    流程闭环: 拼团系统确认成团后,主动回调商城的 groupBuyNotify -> orderService.changeOrderMarketSettlement -> EventBus 发送消息 -> 触发发货。
@Override
    public void changeOrderMarketSettlement(List<String> outTradeNoList) {
        // 更新拼团结算状态
        orderDao.changeOrderMarketSettlement(outTradeNoList);

        // 循环成功发送消息 - 一般在公司的场景里,还会有job任务扫描超时没有结算的订单,查询订单状态。查询对方服务端的接口,会被限制一次查询多少,频次多少。
        outTradeNoList.forEach(outTradeNo -> {
            BaseEvent.EventMessage<PaySuccessMessageEvent.PaySuccessMessage> paySuccessMessageEventMessage = paySuccessMessageEvent.buildEventMessage(
                    PaySuccessMessageEvent.PaySuccessMessage.builder()
                            .tradeNo(outTradeNo)
                            .build());
            PaySuccessMessageEvent.PaySuccessMessage paySuccessMessage = paySuccessMessageEventMessage.getData();

            eventBus.post(JSON.toJSONString(paySuccessMessage));
        });
    }

大概流程:

  • 普通单: PAY_WAIT -> PAY_SUCCESS -> (EventBus) -> SELL_DONE

  • 拼团单: PAY_WAIT -> PAY_SUCCESS (钱到了) -> RPC调用 -> (等待回调) -> DEAL_DONE (团成了) -> (EventBus) -> SELL_DONE

而提到:通过 ProductPort 连接拼团系统。在 DDD架构中,为什么要设计这个 Port?直接在 Service 层写 HTTP 请求不可以吗?
跟一些原则有关,依赖倒置原则···。而port通常是适配器(Adapter),它实现了领域层的接口,底层确实是 HTTP,使得商城业务逻辑不依赖于具体的拼团API细节。

3.关于这个结算:为什么支付成功后,要先调用拼团系统的‘结算’接口?为什么不能等拼团满员了再统一结算?如果这单是拼团的第一单(开团),和第 N 单(参团)的处理逻辑在接口对接上有什么区别吗?”

这里的“结算”是作为“确认参团”**

4.支付宝的回调可能会发送多次(重试机制),拼团完成的通知也可能因为网络抖动发送多次。接口如何保证幂等性?如果同一个订单被处理了两次,会不会导致发了两次货?

public void changeOrderPaySuccess(String orderId, Date payTime) {
    // 1. 先查数据库当前状态
    OrderEntity order = repository.queryOrderByOrderId(orderId);
    
    // 2. 幂等判断:如果状态已经是“支付成功”或“交易完成”,直接返回!
    // 这里的关键是:只能从 PAY_WAIT 流转到 PAY_SUCCESS
    if (!OrderStatusVO.PAY_WAIT.getCode().equals(order.getStatus())) {
        log.info("订单[{}]状态已经是[{}],忽略重复的支付回调", orderId, order.getStatus());
        return; // 直接结束,不做任何操作
    }

    // 3. 只有状态是 PAY_WAIT,才执行后续的 Update 和 EventBus
    // ... 执行原有逻辑
}

(1)根据所提供的日期,提取月份 (2)请对2010年各地区各产品的销售情况进行分析(图表展现) (3)请对各个业务员的业绩进行分析(图表展现) (4)请对各季度的业绩进行分析(图表展现),同时给出商家一些可以提高销售业绩的建议 日期 地区 业务员 品名 销量(个) 单价(元) 销售额(元) 月份 2010/1/6 A 张三 订书机 95 25 2375 2010/1/23 B 王五 钢笔 50 35 1750 2010/2/9 B 周六 钢笔 36 35 1260 2010/2/26 B 周六 笔记本 360 15 5400 2010/3/15 C 田七 订书机 600 25 15000 2010/4/1 A 李四 铅笔 930 0.5 465 2010/4/18 B 周六 订书机 740 25 18500 2010/5/5 B 周六 钢笔 960 35 33600 2010/5/22 C 田七 钢笔 530 35 18550 2010/6/8 A 李四 笔记本 410 15 6150 2010/6/25 B 王五 订书机 940 25 23500 2010/7/12 A 张三 铅笔 280 0.5 140 2010/7/29 A 张三 订书机 81 25 2025 2010/8/15 A 张三 钢笔 35 35 1225 2010/9/1 B 周六 钢笔 65 35 2275 2010/9/18 A 李四 笔记本 93 15 1395 2010/10/5 B 周六 订书机 28 25 700 2010/10/22 A 李四 铅笔 640 0.5 320 2010/11/8 A 李四 订书机 870 25 21750 2010/11/25 B 周六 钢笔 620 35 21700 2010/12/12 B 周六 钢笔 550 35 19250 2010/12/29 A 李四 笔记本 74 15 1110 2010/1/15 B 王五 订书机 46 25 1150 2010/2/1 B 王五 铅笔 870 0.5 435 2010/2/18 A 张三 订书机 32 25 800 2010/3/7 C 赵大 钢笔 70 35 2450 2010/3/24 B 王五 钢笔 50 35 1750 2010/4/10 B 王五 钢笔 66 35 2310 2010/4/27 A 李四 笔记本 96 15 1440 2010/5/14 B 周六 笔记本 53 15 795 2010/5/31 B 周六 订书机 80 25 2000 2010/6/17 B 周六 订书机 27 25 675 2010/7/4 A 李四 铅笔 620 0.5 310 2010/7/21 B 周六 铅笔 550 0.5 275 2010/8/7 B 周六 订书机 42 25 1050 2010/8/24 C 田七 订书机 76 25 1900 2010/9/10 B 王五 钢笔 69 35 2415 2010/9/27 C 赵大 钢笔 76 35 2660 2010/10/14 C 赵大 钢笔 57 35 1995 2010/10/31 B 王五 钢笔 47 35 1645 2010/11/17 B 王五 笔记110 15 1650 2010/12/4 B 王五 笔记本 94 15 1410 2010/12/21 B 周六 笔记本 28 15 420 excel
07-17
首先,根据提供的数据,我们可以进行以下操作: 1. 根据日期提取月份: 在H2单元格中输入以下公式,并将其拖动到H列的下方单元格中: =MONTH(A2) 这将提取出日期列中的月份,并在H列中显示。 2. 对2010年各地区各产品的销售情况进行分析: 可以使用数据透视表来分析销售情况。按照以下步骤操作: - 将数据复制到一个新的工作表,并确保列标题正确。 - 在Excel菜单栏中选择“插入”,然后点击“数据透视表”。 - 在“创建数据透视表”对话框中,选择“选择一个范围”并输入数据范围。例如,假设数据位于Sheet1的A1:H41,输入A1:H41。 - 选择“新工作表”,然后点击“确定”。 - 在新的工作表中,将“地区”拖放到“行”区域,将“品名”拖放到“列”区域,将“销售额(元)”拖放到“值”区域。 - 在数据透视表字段列表中,将“月份”拖放到“筛选器”区域,并选择要分析的特定月份。 - 根据需要进行调整和格式化,以展现销售情况。 3. 对各个业务员的业绩进行分析: 同样,可以使用数据透视表来分析业绩情况。按照以下步骤操作: - 在新的工作表中,将“业务员”拖放到“行”区域,将“销售额(元)”拖放到“值”区域。 - 根据需要进行调整和格式化,以展现业绩情况。 4. 对各个季度的业绩进行分析: 在I2单元格中输入以下公式,并将其拖动到I列的下方单元格中: =IF(MONTH(A2)<=3, "第一季度", IF(MONTH(A2)<=6, "第二季度", IF(MONTH(A2)<=9, "第三季度", "第四季度"))) 这将根据提取的月份确定对应的季度,并在I列中显示。 然后,根据季度和销售额,使用数据透视表或其他图表工具进行分析和展示。 至于提高销售业绩的建议,可以根据分析结果得出一些结论,例如: - 哪些产品在哪些地区销售额较高,可以进一步加大推广力度。 - 哪些业务员的业绩较好,可以给予相应的奖励和激励措施。 - 哪个季度的销售额较低,可以重点关注和加强销售策略。 根据具体情况,您可以制定更具体的建议来提高销售业绩。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值