系统设计-异步命令处理 + 分布式锁 + 策略路由:物流系统配送时间改约全链路
一、业务全景
本需求涉及两条链路的协作:
链路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