构建可扩展的企业微信消息推送服务:事件驱动架构在Java中的应用
企业微信消息推送场景复杂,包括审批通知、打卡提醒、机器人消息、客户群发等。若采用同步调用模式,业务逻辑与推送强耦合,难以应对高并发、多通道、失败重试等需求。事件驱动架构(Event-Driven Architecture)通过解耦生产者与消费者,实现异步、可扩展、容错的消息推送系统。本文基于Spring Boot与Spring ApplicationEvent,构建一个高内聚低耦合的企业微信推送服务。
1. 定义推送事件模型
首先抽象通用推送事件,包含目标、内容、通道策略等:
java
package wlkankan.cn.event;
import org.springframework.context.ApplicationEvent;
public class WecomPushEvent extends ApplicationEvent {
private final String appId;
private final String receiverId;
private final PushContentType contentType;
private final Object payload;
private final int retryCount;
public WecomPushEvent(Object source, String appId, String receiverId,
PushContentType contentType, Object payload) {
this(source, appId, receiverId, contentType, payload, 0);
}
public WecomPushEvent(Object source, String appId, String receiverId,
PushContentType contentType, Object payload, int retryCount) {
super(source);
this.appId = appId;
this.receiverId = receiverId;
this.contentType = contentType;
this.payload = payload;
this.retryCount = retryCount;
}
// getters
}
内容类型枚举:
java
package wlkankan.cn.event;
public enum PushContentType {
TEXT, MARKDOWN, CARD, TEMPLATE
}

2. 事件发布器封装
提供简洁的推送入口:
java
package wlkankan.cn.service;
import wlkankan.cn.event.WecomPushEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
@Service
public class WecomPushService {
private final ApplicationEventPublisher eventPublisher;
public WecomPushService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void sendText(String appId, String userId, String content) {
eventPublisher.publishEvent(new WecomPushEvent(
this, appId, userId, PushContentType.TEXT, content
));
}
public void sendMarkdown(String appId, String userId, String markdown) {
eventPublisher.publishEvent(new WecomPushEvent(
this, appId, userId, PushContentType.MARKDOWN, markdown
));
}
}
3. 异步事件监听器
使用@Async实现异步处理,避免阻塞主业务流程:
java
package wlkankan.cn.listener;
import wlkankan.cn.event.WecomPushEvent;
import wlkankan.cn.wecom.WecomApiClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
public class WecomPushEventListener {
private static final Logger log = LoggerFactory.getLogger(WecomPushEventListener.class);
private static final int MAX_RETRY = 3;
@Autowired
private WecomApiClient wecomClient;
@EventListener
@Async
public void handlePush(WecomPushEvent event) {
try {
boolean success = wecomClient.sendMessage(
event.getAppId(),
event.getReceiverId(),
event.getContentType(),
event.getPayload()
);
if (!success) {
handleFailure(event);
}
} catch (Exception e) {
log.error("Failed to push wecom message [appId={}, to={}]",
event.getAppId(), event.getReceiverId(), e);
handleFailure(event);
}
}
private void handleFailure(WecomPushEvent event) {
if (event.getRetryCount() < MAX_RETRY) {
// 重新发布带重试计数的事件
WecomPushEvent retryEvent = new WecomPushEvent(
this,
event.getAppId(),
event.getReceiverId(),
event.getContentType(),
event.getPayload(),
event.getRetryCount() + 1
);
// 延迟重试(可集成RabbitMQ TTL或Redis ZSet)
delayRetry(retryEvent, (long) Math.pow(2, event.getRetryCount()) * 1000);
} else {
log.warn("Wecom push failed after {} retries, giving up", MAX_RETRY);
// 上报监控或存入死信表
}
}
private void delayRetry(WecomPushEvent event, long delayMs) {
// 简化:使用线程睡眠(生产环境应使用消息队列延迟投递)
new Thread(() -> {
try {
Thread.sleep(delayMs);
org.springframework.context.ApplicationContext context =
org.springframework.context.event.SimpleApplicationEventMulticaster
.class.getEnclosingClass().getClassLoader()
.loadClass("org.springframework.boot.SpringApplication")
.getMethod("run")
.getDeclaringClass()
.getClassLoader()
.loadClass("wlkankan.cn.WecomApplication")
.getMethod("getApplicationContext")
.invoke(null);
// 实际项目中应通过注入ApplicationContext发布
// 此处仅为示意,真实代码需依赖注入
} catch (Exception ignored) {}
}).start();
}
}
注:实际延迟重试应使用RabbitMQ死信队列、RocketMQ延迟消息或Redis ZSet实现,此处简化处理。
4. 多通道扩展支持
通过策略模式支持不同内容类型:
java
package wlkankan.cn.wecom;
import wlkankan.cn.event.PushContentType;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class WecomMessageBuilder {
private static final Map<PushContentType, MessageConverter> CONVERTERS = new ConcurrentHashMap<>();
static {
CONVERTERS.put(PushContentType.TEXT, new TextMessageConverter());
CONVERTERS.put(PushContentType.MARKDOWN, new MarkdownMessageConverter());
}
public static String buildMessage(PushContentType type, Object payload) {
MessageConverter converter = CONVERTERS.get(type);
if (converter == null) throw new IllegalArgumentException("Unsupported content type: " + type);
return converter.convert(payload);
}
interface MessageConverter {
String convert(Object payload);
}
static class TextMessageConverter implements MessageConverter {
@Override
public String convert(Object payload) {
return "{\"msgtype\":\"text\",\"text\":{\"content\":\"" + payload + "\"}}";
}
}
static class MarkdownMessageConverter implements MessageConverter {
@Override
public String convert(Object payload) {
return "{\"msgtype\":\"markdown\",\"markdown\":{\"content\":\"" + payload + "\"}}";
}
}
}
5. 启用异步支持
在启动类添加@EnableAsync:
java
package wlkankan.cn;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync
public class WecomApplication {
public static void main(String[] args) {
SpringApplication.run(WecomApplication.class, args);
}
}
通过事件驱动架构,企业微信消息推送服务实现业务逻辑与通信细节完全解耦,支持动态扩展消息类型、灵活重试策略及异步非阻塞处理,为高可用、高并发的企业级推送系统奠定坚实基础。