三年前我写第一个代购系统的时候,所有的邮件、短信、站内信都直接写在业务逻辑里。后来运营要增加钉钉机器人通知,我改代码改到吐。再后来要加 Webhook 推送给第三方仓库系统,更是痛苦。直到我研究了 taocarts 的事件总线设计,用观察者模式彻底重构了通知模块,才终于解脱。
一、代购系统有哪些通知需求?
一套完整的反向海淘平台需要通知的场景非常多:
用户下单后,通知采购员(钉钉/飞书)
淘宝卖家发货后,通知用户(邮件 + 站内信)
包裹到达代购转运仓,提醒用户合箱(短信)
国际集运出库后,推送轨迹给用户的微信
财务需要订阅所有订单状态变更(Webhook 到内部对账系统)
如果用 if (status == 3) sendEmail() 这种方式,三个月后代码就没人敢动了。
二、taocarts的事件驱动设计
taocarts 实现了一个轻量级的 EventDispatcher,支持多个监听器订阅同一个事件。我把它翻译成了 Java 版本(我们主栈是 Spring):
首先定义一个事件抽象类:
```public abstract class OrderEvent extends ApplicationEvent {
private final Long orderId;
private final OrderState newState;
public OrderEvent(Object source, Long orderId, OrderState newState) {
super(source);
this.orderId = orderId;
this.newState = newState;
}
// getters
}
具体事件:OrderShippedEvent, ParcelArrivedEvent, ConsolidationCompletedEvent 等。
然后定义监听器接口(注解式):
```@Component
public class EmailNotifier {
@EventListener
public void handleOrderShipped(OrderShippedEvent event) {
Order order = orderService.getById(event.getOrderId());
String subject = "您的订单已从淘宝发货";
String body = buildEmailContent(order);
emailClient.send(order.getUserEmail(), subject, body);
}
}
钉钉通知:
public class DingTalkNotifier {
@EventListener
public void handleParcelArrived(ParcelArrivedEvent event) {
String message = String.format("包裹 %s 已到达集运仓,请及时处理", event.getTrackingNo());
dingTalkClient.sendToGroup(message);
}
}
Webhook 给第三方系统(比如代购集运的WMS):
public class WebhookNotifier {
@EventListener
public void onOrderStatusChanged(OrderStatusChangedEvent event) {
// 遍历所有已注册的webhook url
List<String> urls = webhookRepo.findByEventType(event.getNewState().name());
for (String url : urls) {
restTemplate.postForEntity(url, event, Void.class);
}
}
}
三、异步化防止影响主流程
默认情况下 Spring 的 @EventListener 是同步的,如果钉钉接口超时,用户下单也会慢。我们改用 @Async:
@EnableAsync
public class AsyncConfig {
@Bean(name = "notificationExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("notify-");
executor.initialize();
return executor;
}
}
@Component
public class DingTalkNotifier {
@Async("notificationExecutor")
@EventListener
public void handleParcelArrived(ParcelArrivedEvent event) {
// 异步发送,不阻塞主流程
}
}
四、与taocarts的区别
taocarts 的事件系统没有提供异步选项,所有的监听器顺序执行。如果某个监听器抛出异常,后续监听器就不会执行。我在生产环境遇到过因为邮件服务器超时导致整个订单保存失败的问题。所以后来我们做了两个改进:
所有通知类监听器都用 @Async
关键业务监听器(比如库存扣减)用同步,并且放在事务提交后执行(@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT))
五、扩展:让用户自己配置 Webhook
跨境独立站的商家经常需要把自己的系统和我们代购系统对接。我们开发了一个 Webhook 管理界面,允许商家填写 URL 和订阅的事件类型(比如 order.paid, order.shipped)。
存储结构:``CREATE TABLEwebhook_subscriptions(idint,merchant_idint,event_namevarchar(64),target_urlvarchar(255),secret` varchar(64) -- 用于签名验证
);
触发时用 HMAC-SHA256 签名:
```String payload = objectMapper.writeValueAsString(event);
String signature = HmacUtils.hmacSha256Hex(secret, payload);
httpHeaders.set("X-Signature", signature);
这套机制上线后,有三家海外物流公司通过 Webhook 接入了我们的国际集运轨迹推送,完全不需要我们改代码。
六、总结
观察者模式 + 事件总线 是代购源码必备的扩展能力。taocarts 给了我们一个很好的起点,但生产环境还需要考虑异步、重试、死信队列。我个人建议,如果你的系统监听器超过 5 个,就赶紧重构,否则后面全是坑。