系统设计-异步命令处理 + 分布式锁 + 策略路由:物流系统配送时间改约全链路

系统设计-异步命令处理 + 分布式锁 + 策略路由:物流系统配送时间改约全链路

一、业务全景

本需求涉及两条链路的协作:

复制代码
链路1(写入):物流系统回传改约节点 → 更新发货单配送时间
链路2(读取):发货详情接口 → 查询发货单配送时间并展示

核心数据流转:

复制代码
物流系统推送 
    → 接口接收保存日志
    → MQ异步消费
    → 分布式锁串行化
    → 解析opDesc中的改约时间
    → 更新配送时间
    → 发送改约通知

发货详情查询
    → MyBatis SQL 直接读取
    → 返回前端展示

二、涉及的设计模式

1. 命令模式(Command Pattern)------MQ 异步解耦

接口层接收物流系统推送后,不直接执行业务逻辑,而是将请求序列化为"命令"(SaveLogisticsMqDto)发送到消息队列,由消费者异步执行。

体现:

  • 生产者:SaveStoreLogisticsMqSender 将入参持久化后发送 MQ
  • 命令对象:StoreDeliveryLogisticsRecordxx(日志表记录,存储完整入参 JSON)
  • 消费者:SaveStoreLogisticsMqConsumer 反序列化后执行实际逻辑

优点:

  • 调用方(物流系统)无需等待业务处理完成
  • 失败可重试(日志表记录状态,status=F 可补偿)
  • 削峰填谷,应对批量推送

2. 策略模式(Strategy Pattern)------节点路由分发

specialOpCodeHandle() 根据 opCode 路由到不同处理方法,每个方法封装了该节点类型的完整业务策略:

java 复制代码
switch (OpCodeEnums.valueOfCode(paramsDto.getOpCode())) {
    case x1:   this.x1Handle(...);          break;  
    case x2/x3: 
               this.x23Handle(...);    break;  
    ...
}

扩展方式: 新增节点只需加 enum 值 + case 分支,开闭原则的简化实践。

3. 模板方法模式(Template Method)------物流节点处理流水线

正向物流节点的处理流程是固定模板:

复制代码
① 判断首次/重复回传
② 首次:新增物流主表+明细 / 重复:更新物流主表
③ 特殊节点处理(可变步骤)
④ 更新物流单号记录表
⑤ 事务后回调(通知下游系统)

其中第③步是"钩子方法",由 specialOpCodeHandle 根据节点类型决定具体行为。

4. 观察者模式(Observer)------事务同步回调

java 复制代码
AfterTransactionActionCollector collectors = new AfterTransactionActionCollector();
collectors.addCommitSyncAction(() -> {
    sendHpsOutboundLogisticsMqSender.sendHpsMessageMq(...);
});
TransactionSynchronizationManager.registerSynchronization(collectors);

利用 Spring 的 TransactionSynchronization 机制,在主事务提交成功后才执行下游通知。保证"数据已落库"才触发外部系统同步,避免数据不一致。

5. 单一职责分离------读写分离的数据展示

写入侧(LogisticsServiceImpl)负责解析时间并持久化,读取侧(StockDeliveryManagementSpiltNewServiceImpl)只负责查询并组装 DTO。两者通过数据库字段解耦,互不依赖。


三、涉及的技术知识点

1. 分布式消息队列(RabbitMQ)

  • 生产者在事务提交后发送消息(避免事务回滚后消息已发出)
  • 消费者用注解声明,支持并发度配置
  • 消息体为 DTO 对象,框架自动序列化/反序列化

2. 分布式锁(Redis)

java 复制代码
DistributedLock lock = distributedLockProvider.getLock(lockKey, TimeUnit.MINUTES, 5);
if (lock.tryLock(TimeUnit.MINUTES, 3)) { ... }
  • 锁粒度:按发货单号/物流单号,同一张单串行处理
  • 防止并发回传导致的数据覆盖(如两个改约节点同时到达)
  • tryLock 超时避免死锁

3. Spring 事务管理

  • @Transactional(propagation = REQUIRED) 保证原子性
  • 事务内完成:物流主表更新 + 物流明细插入 + 发货单更新
  • 事务后执行:MQ 发送、外部系统通知(通过 TransactionSynchronization)

4. 正则表达式------非结构化文本解析

java 复制代码
Pattern.compile("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}")
  • 从描述文本中提取第一个日期时间
  • 比字符串截取更鲁棒:不依赖分隔符位置,不受后缀内容影响

5. MyBatis 字段映射------读取链路

xml 复制代码
drm.delay_record_date as delayDate

SQL 直接映射到 DTO 字段,DTO 透传到前端。读取链路极简,无额外加工。

6. 枚举 + 反解析------类型安全的状态码管理

java 复制代码
public static OpCodeEnums valueOfCode(String value) {
    for (OpCodeEnums status : OpCodeEnums.values()) {
        if (Objects.equals(status.value, value)) return status;
    }
    return UNKNOWN;
}

将字符串状态码转为类型安全的枚举,避免魔法字符串散落,switch 编译期可检查覆盖度。

7. 幂等设计------改约节点的特殊处理

改约节点(x1x2)被排除在去重逻辑之外,允许多次回传。因为改约是"用最新值覆盖旧值"的操作,天然幂等------多次执行结果一致。


四、业务流程关键设计决策

决策点 选择 原因
接口同步 vs 异步 异步(MQ) 物流系统大批量推送场景,需要削峰+解耦
改约时间存储位置 xx.delay_record_date 直接覆盖,查询无需关联其他表
去重策略 改约节点不去重 业务允许多次改约,最后一次为准
时间解析方式 带【】→截取;不带→正则 兼容多种描述格式,正则对未来格式变化适应性好

五、代码示例

以下是一个通用的"异步命令处理 + 分布式锁 + 策略路由"的简化示例,模拟一个通知中心接收多种类型消息并异步分发的场景:

java 复制代码
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 通知中心------接收消息并异步分发处理.
 * 演示:命令模式 + 策略路由 + 正则解析
 */
public class NotificationCenter {

    /** 日期时间正则 */
    private static final Pattern DATE_TIME_PATTERN =
        Pattern.compile("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}");

    /** 策略注册表:消息类型 → 处理器 */
    private final Map<String, MessageHandler> handlerRegistry = new ConcurrentHashMap<>();

    public NotificationCenter() {
        // 注册策略
        handlerRegistry.put("SMS", new SmsHandler());
        handlerRegistry.put("EMAIL", new EmailHandler());
        handlerRegistry.put("PUSH", new PushHandler());
    }

    /**
     * 接收消息入口(模拟接口层).
     * 保存日志后异步处理,立即返回。
     */
    public String receive(NotificationMessage message) {
        // 1. 持久化消息日志(模拟)
        Integer logId = saveLog(message);

        // 2. 异步投递(模拟MQ发送)
        asyncProcess(logId, message);

        return "accepted";
    }

    /**
     * 异步处理(模拟MQ消费者).
     */
    private void asyncProcess(Integer logId, NotificationMessage message) {
        // 模拟分布式锁:按接收人维度串行化
        String lockKey = "notification-lock:" + message.getReceiverId();
        if (!tryLock(lockKey, 5, TimeUnit.SECONDS)) {
            throw new RuntimeException("获取锁失败: " + lockKey);
        }
        try {
            // 策略路由:根据消息类型分发
            MessageHandler handler = handlerRegistry.get(message.getType());
            if (handler == null) {
                throw new IllegalArgumentException("未知消息类型: " + message.getType());
            }

            // 解析内容中的时间(如果有)
            String scheduledTime = extractDateTime(message.getContent());

            // 执行处理
            handler.handle(message, scheduledTime);

            // 更新日志状态为成功
            updateLogStatus(logId, "SUCCESS");
        } catch (Exception e) {
            updateLogStatus(logId, "FAILED");
            throw e;
        } finally {
            unlock(lockKey);
        }
    }

    /**
     * 从文本中提取第一个日期时间.
     */
    private String extractDateTime(String content) {
        if (content == null) {
            return null;
        }
        // 优先处理带括号的格式
        if (content.contains("【")) {
            int start = content.indexOf("【") + 1;
            int end = content.indexOf("】");
            if (end > start) {
                return content.substring(start, end);
            }
        }
        // 正则匹配第一个日期时间
        Matcher matcher = DATE_TIME_PATTERN.matcher(content);
        if (matcher.find()) {
            return matcher.group();
        }
        return null;
    }

    // ========== 策略接口与实现 ==========

    interface MessageHandler {
        void handle(NotificationMessage message, String scheduledTime);
    }

    static class SmsHandler implements MessageHandler {
        @Override
        public void handle(NotificationMessage message, String scheduledTime) {
            System.out.println("发送短信给 " + message.getReceiverId()
                + ",定时:" + scheduledTime);
        }
    }

    static class EmailHandler implements MessageHandler {
        @Override
        public void handle(NotificationMessage message, String scheduledTime) {
            System.out.println("发送邮件给 " + message.getReceiverId()
                + ",定时:" + scheduledTime);
        }
    }

    static class PushHandler implements MessageHandler {
        @Override
        public void handle(NotificationMessage message, String scheduledTime) {
            System.out.println("推送消息给 " + message.getReceiverId()
                + ",定时:" + scheduledTime);
        }
    }

    // ========== 消息模型 ==========

    static class NotificationMessage {
        private String type;       // SMS / EMAIL / PUSH
        private String receiverId;
        private String content;

        public NotificationMessage(String type, String receiverId, String content) {
            this.type = type;
            this.receiverId = receiverId;
            this.content = content;
        }

        public String getType() { return type; }
        public String getReceiverId() { return receiverId; }
        public String getContent() { return content; }
    }

    // ========== 辅助方法(模拟) ==========

    private Integer saveLog(NotificationMessage msg) { return 1; }
    private void updateLogStatus(Integer id, String status) { }
    private boolean tryLock(String key, long time, TimeUnit unit) { return true; }
    private void unlock(String key) { }

    // ========== 使用示例 ==========

    public static void main(String[] args) {
        NotificationCenter center = new NotificationCenter();

        // 带【】格式
        center.receive(new NotificationMessage(
            "SMS", "user001",
            "您的订单已改约,新配送时间:【2026-07-01 14:00:00】"));

        // 不带括号,时间后有备注
        center.receive(new NotificationMessage(
            "PUSH", "user002",
            "配送预约成功,预计到达时间:2026-07-02 09:00:00,请保持电话畅通"));

        // 无时间
        center.receive(new NotificationMessage(
            "EMAIL", "user003",
            "您的订单已发货,请注意查收"));
    }
}

示例输出:

复制代码
发送短信给 user001,定时:2026-07-01 14:00:00
推送消息给 user002,定时:2026-07-02 09:00:00
发送邮件给 user003,定时:null