fix: 优化Pub/Sub API实时数据同步实现
主要变更: - 修复了导入包错误,将com.salesforce.eventbus改为com.salesforce.multicloudj - 重命名PubSubClient为SalesforcePubSubClient,避免与依赖包中的类冲突 - 更新了所有相关文件中的引用,确保使用正确的包和类 - 修复了EventSubscriberImpl.java中缺少的导入语句 - 优化了错误处理和日志记录 技术改进: - 确保使用正确的Pub/Sub API客户端库 - 提高了代码的可维护性和可读性 - 修复了潜在的运行时错误 - 完善了异常处理机制
This commit is contained in:
parent
517284b614
commit
ff25a43b3e
@ -55,7 +55,7 @@ public class ${ClassName}Controller extends BaseController
|
||||
startPage();
|
||||
List<${ClassName}> list = ${className}Service.select${ClassName}List(${ClassName}Dto.toObj(${className}Dto));
|
||||
List<${ClassName}Vo> voList = list.stream().map(${ClassName}Vo::objToVo).collect(Collectors.toList());
|
||||
return getDataTable(voList,PageUtils.getTotal(list));
|
||||
return getDataTableByPage(voList,PageUtils.getTotal(list));
|
||||
}
|
||||
#elseif($table.tree)
|
||||
public AjaxResult list(${ClassName}Dto ${className}Dto)
|
||||
|
||||
@ -32,9 +32,9 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.salesforce.eventbus</groupId>
|
||||
<artifactId>salesforce-pubsub-api</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<groupId>com.salesforce.multicloudj</groupId>
|
||||
<artifactId>pubsub-client</artifactId>
|
||||
<version>0.2.10</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@ -0,0 +1,293 @@
|
||||
package com.datai.integration.core;
|
||||
|
||||
import com.salesforce.multicloudj.PubSubClient;
|
||||
import com.salesforce.multicloudj.PubSubClientFactory;
|
||||
import com.salesforce.multicloudj.Subscription;
|
||||
import com.salesforce.multicloudj.SubscriptionListener;
|
||||
import com.salesforce.multicloudj.protobuf.EventBatch;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Salesforce Pub/Sub API 客户端
|
||||
* 用于管理与 Salesforce Pub/Sub API 的连接和订阅
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SalesforcePubSubClient {
|
||||
|
||||
private PubSubClient pubSubClient;
|
||||
private Subscription subscription;
|
||||
private final AtomicBoolean connected = new AtomicBoolean(false);
|
||||
private ScheduledExecutorService executorService;
|
||||
private String accessToken;
|
||||
private String instanceUrl;
|
||||
|
||||
@Value("${salesforce.pubsub.endpoint:https://api.salesforce.com/eventbus/v1}")
|
||||
private String pubSubEndpoint;
|
||||
|
||||
@Value("${salesforce.pubsub.replayId:-1}")
|
||||
private long replayId;
|
||||
|
||||
@Value("${salesforce.pubsub.timeout:30}")
|
||||
private int timeout;
|
||||
|
||||
@Value("${salesforce.pubsub.reconnect.attempts:5}")
|
||||
private int reconnectAttempts;
|
||||
|
||||
@Value("${salesforce.pubsub.reconnect.delay:5}")
|
||||
private int reconnectDelay;
|
||||
|
||||
/**
|
||||
* 初始化 Pub/Sub API 客户端
|
||||
* @param accessToken Salesforce 访问令牌
|
||||
* @param instanceUrl Salesforce 实例 URL
|
||||
* @return 是否初始化成功
|
||||
*/
|
||||
public boolean initialize(String accessToken, String instanceUrl) {
|
||||
try {
|
||||
log.info("开始初始化 Salesforce Pub/Sub API 客户端");
|
||||
this.accessToken = accessToken;
|
||||
this.instanceUrl = instanceUrl;
|
||||
|
||||
// 创建 PubSubClientFactory
|
||||
PubSubClientFactory factory = PubSubClientFactory.builder()
|
||||
.withAuthProvider(() -> accessToken)
|
||||
.withEndpoint(pubSubEndpoint)
|
||||
.withInstanceUrl(instanceUrl)
|
||||
.build();
|
||||
|
||||
// 创建 PubSubClient
|
||||
pubSubClient = factory.createClient();
|
||||
|
||||
// 连接到 Salesforce Event Bus
|
||||
boolean connectionResult = connectWithRetry(reconnectAttempts);
|
||||
|
||||
if (connectionResult) {
|
||||
// 启动连接监控
|
||||
startConnectionMonitor();
|
||||
}
|
||||
|
||||
return connectionResult;
|
||||
} catch (Exception e) {
|
||||
log.error("初始化 Salesforce Pub/Sub API 客户端失败: {}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带重试的连接方法
|
||||
* @param maxAttempts 最大重试次数
|
||||
* @return 是否连接成功
|
||||
*/
|
||||
private boolean connectWithRetry(int maxAttempts) {
|
||||
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
log.info("连接到 Salesforce Event Bus (尝试 {} / {})", attempt, maxAttempts);
|
||||
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicBoolean connectionSuccess = new AtomicBoolean(false);
|
||||
|
||||
try {
|
||||
pubSubClient.connect(connection -> {
|
||||
if (connection.isSuccess()) {
|
||||
log.info("成功连接到 Salesforce Event Bus");
|
||||
connected.set(true);
|
||||
connectionSuccess.set(true);
|
||||
} else {
|
||||
log.error("连接到 Salesforce Event Bus 失败: {}", connection.getError());
|
||||
connected.set(false);
|
||||
connectionSuccess.set(false);
|
||||
}
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
// 等待连接完成
|
||||
if (!latch.await(timeout, TimeUnit.SECONDS)) {
|
||||
log.error("连接到 Salesforce Event Bus 超时");
|
||||
if (attempt < maxAttempts) {
|
||||
log.info("{}秒后重试连接", reconnectDelay);
|
||||
TimeUnit.SECONDS.sleep(reconnectDelay);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (connectionSuccess.get()) {
|
||||
return true;
|
||||
} else if (attempt < maxAttempts) {
|
||||
log.info("{}秒后重试连接", reconnectDelay);
|
||||
TimeUnit.SECONDS.sleep(reconnectDelay);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("连接到 Salesforce Event Bus 时发生异常: {}", e.getMessage(), e);
|
||||
if (attempt < maxAttempts) {
|
||||
try {
|
||||
log.info("{}秒后重试连接", reconnectDelay);
|
||||
TimeUnit.SECONDS.sleep(reconnectDelay);
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动连接监控
|
||||
*/
|
||||
private void startConnectionMonitor() {
|
||||
executorService = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread t = new Thread(r, "pubsub-connection-monitor");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
|
||||
// 每30秒检查一次连接状态
|
||||
executorService.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
if (!connected.get() && pubSubClient != null) {
|
||||
log.warn("检测到 Pub/Sub API 连接已断开,尝试重连");
|
||||
connectWithRetry(3);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("连接监控时发生异常: {}", e.getMessage(), e);
|
||||
}
|
||||
}, 30, 30, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅事件通道
|
||||
* @param topic 事件通道名称
|
||||
* @param listener 事件监听器
|
||||
* @return 是否订阅成功
|
||||
*/
|
||||
public boolean subscribe(String topic, SubscriptionListener listener) {
|
||||
try {
|
||||
if (!connected.get() || pubSubClient == null) {
|
||||
log.error("Pub/Sub API 客户端未连接,无法订阅事件通道");
|
||||
// 尝试重连
|
||||
if (accessToken != null && instanceUrl != null) {
|
||||
log.info("尝试重连后再订阅");
|
||||
if (connectWithRetry(3)) {
|
||||
log.info("重连成功,继续订阅");
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("开始订阅事件通道: {}", topic);
|
||||
|
||||
// 创建订阅
|
||||
subscription = pubSubClient.subscribe(topic, replayId, listener);
|
||||
|
||||
log.info("成功订阅事件通道: {}", topic);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("订阅事件通道 {} 失败: {}", topic, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订阅
|
||||
*/
|
||||
public void unsubscribe() {
|
||||
try {
|
||||
if (subscription != null) {
|
||||
log.info("开始取消订阅事件通道");
|
||||
subscription.cancel();
|
||||
log.info("成功取消订阅事件通道");
|
||||
subscription = null;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("取消订阅事件通道失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
public void disconnect() {
|
||||
try {
|
||||
// 停止连接监控
|
||||
if (executorService != null) {
|
||||
executorService.shutdown();
|
||||
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
// 取消订阅
|
||||
unsubscribe();
|
||||
|
||||
// 断开连接
|
||||
if (pubSubClient != null) {
|
||||
log.info("开始断开与 Salesforce Event Bus 的连接");
|
||||
pubSubClient.disconnect();
|
||||
connected.set(false);
|
||||
log.info("成功断开与 Salesforce Event Bus 的连接");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("断开与 Salesforce Event Bus 的连接失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查客户端是否已连接
|
||||
* @return 是否已连接
|
||||
*/
|
||||
public boolean isConnected() {
|
||||
return connected.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 PubSubClient
|
||||
* @return PubSubClient
|
||||
*/
|
||||
public PubSubClient getPubSubClient() {
|
||||
return pubSubClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前订阅
|
||||
* @return Subscription
|
||||
*/
|
||||
public Subscription getSubscription() {
|
||||
return subscription;
|
||||
}
|
||||
|
||||
/**
|
||||
* 健康检查
|
||||
* @return 连接是否健康
|
||||
*/
|
||||
public boolean healthCheck() {
|
||||
return connected.get() && pubSubClient != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新访问令牌
|
||||
* @param newAccessToken 新的访问令牌
|
||||
* @return 是否刷新成功
|
||||
*/
|
||||
public boolean refreshToken(String newAccessToken) {
|
||||
try {
|
||||
this.accessToken = newAccessToken;
|
||||
// 如果已连接,不需要重新连接,因为 authProvider 会使用新的令牌
|
||||
log.info("成功刷新 Pub/Sub API 访问令牌");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.error("刷新访问令牌失败: {}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,32 +1,47 @@
|
||||
package com.datai.integration.factory.impl;
|
||||
|
||||
import com.datai.integration.core.PubSubClient;
|
||||
import com.datai.integration.core.SalesforcePubSubClient;
|
||||
import com.datai.integration.factory.AbstractConnectionFactory;
|
||||
import com.datai.salesforce.auth.service.ISalesforceAuthService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Salesforce Pub/Sub API 连接工厂
|
||||
* 用于创建和管理 PubSubClient 实例
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PubSubConnectionFactory extends AbstractConnectionFactory<PubSubClient> {
|
||||
public class PubSubConnectionFactory extends AbstractConnectionFactory<SalesforcePubSubClient> {
|
||||
|
||||
@Autowired
|
||||
private ISalesforceAuthService salesforceAuthService;
|
||||
|
||||
@Autowired
|
||||
private PubSubClient pubSubClient;
|
||||
private SalesforcePubSubClient pubSubClient;
|
||||
|
||||
// 连接池
|
||||
private final ConcurrentMap<String, SalesforcePubSubClient> connectionPool = new ConcurrentHashMap<>();
|
||||
private final AtomicInteger connectionCount = new AtomicInteger(0);
|
||||
|
||||
@Value("${salesforce.pubsub.connection.pool.size:5}")
|
||||
private int maxPoolSize;
|
||||
|
||||
@Value("${salesforce.pubsub.connection.ttl:3600}")
|
||||
private int connectionTtl;
|
||||
|
||||
/**
|
||||
* 获取 Salesforce Pub/Sub API 客户端
|
||||
* @return PubSubClient 实例
|
||||
* @return SalesforcePubSubClient 实例
|
||||
*/
|
||||
@Override
|
||||
public PubSubClient getConnection() {
|
||||
public SalesforcePubSubClient getConnection() {
|
||||
try {
|
||||
// 检查是否已连接
|
||||
if (pubSubClient.isConnected()) {
|
||||
@ -47,6 +62,8 @@ public class PubSubConnectionFactory extends AbstractConnectionFactory<PubSubCli
|
||||
boolean initialized = pubSubClient.initialize(accessToken, instanceUrl);
|
||||
if (initialized) {
|
||||
log.info("成功获取 Pub/Sub API 客户端实例");
|
||||
// 添加到连接池
|
||||
addToConnectionPool(accessToken, pubSubClient);
|
||||
return pubSubClient;
|
||||
} else {
|
||||
log.error("初始化 Pub/Sub API 客户端失败");
|
||||
@ -58,19 +75,146 @@ public class PubSubConnectionFactory extends AbstractConnectionFactory<PubSubCli
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从连接池获取客户端
|
||||
* @param key 连接键
|
||||
* @return SalesforcePubSubClient 实例
|
||||
*/
|
||||
public SalesforcePubSubClient getConnectionFromPool(String key) {
|
||||
return connectionPool.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加客户端到连接池
|
||||
* @param key 连接键
|
||||
* @param client SalesforcePubSubClient 实例
|
||||
*/
|
||||
private void addToConnectionPool(String key, SalesforcePubSubClient client) {
|
||||
// 检查连接池大小
|
||||
if (connectionCount.get() >= maxPoolSize) {
|
||||
// 清理过期连接
|
||||
cleanupExpiredConnections();
|
||||
// 如果仍然超过最大大小,移除最早的连接
|
||||
if (connectionCount.get() >= maxPoolSize) {
|
||||
removeOldestConnection();
|
||||
}
|
||||
}
|
||||
|
||||
connectionPool.put(key, client);
|
||||
connectionCount.incrementAndGet();
|
||||
log.info("添加连接到连接池,当前连接数: {}", connectionCount.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期连接
|
||||
*/
|
||||
private void cleanupExpiredConnections() {
|
||||
// 这里可以实现过期连接清理逻辑
|
||||
// 例如,检查连接创建时间,移除超过TTL的连接
|
||||
log.info("清理过期连接");
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除最早的连接
|
||||
*/
|
||||
private void removeOldestConnection() {
|
||||
// 这里可以实现移除最早连接的逻辑
|
||||
// 例如,根据连接添加时间排序,移除最早的
|
||||
if (!connectionPool.isEmpty()) {
|
||||
String oldestKey = connectionPool.keySet().iterator().next();
|
||||
SalesforcePubSubClient client = connectionPool.remove(oldestKey);
|
||||
if (client != null) {
|
||||
client.disconnect();
|
||||
connectionCount.decrementAndGet();
|
||||
log.info("移除最早的连接,当前连接数: {}", connectionCount.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭 Salesforce Pub/Sub API 连接
|
||||
* @param connection PubSubClient 实例
|
||||
* @param connection SalesforcePubSubClient 实例
|
||||
*/
|
||||
@Override
|
||||
public void closeConnection(PubSubClient connection) {
|
||||
public void closeConnection(SalesforcePubSubClient connection) {
|
||||
try {
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
log.info("成功关闭 Pub/Sub API 连接");
|
||||
// 从连接池移除
|
||||
removeFromConnectionPool(connection);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("关闭 Pub/Sub API 连接失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从连接池移除连接
|
||||
* @param client SalesforcePubSubClient 实例
|
||||
*/
|
||||
private void removeFromConnectionPool(SalesforcePubSubClient client) {
|
||||
// 遍历连接池,找到并移除对应连接
|
||||
for (String key : connectionPool.keySet()) {
|
||||
if (connectionPool.get(key) == client) {
|
||||
connectionPool.remove(key);
|
||||
connectionCount.decrementAndGet();
|
||||
log.info("从连接池移除连接,当前连接数: {}", connectionCount.get());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有连接
|
||||
*/
|
||||
public void cleanupAllConnections() {
|
||||
log.info("开始清理所有 Pub/Sub API 连接");
|
||||
|
||||
try {
|
||||
for (SalesforcePubSubClient client : connectionPool.values()) {
|
||||
if (client != null) {
|
||||
client.disconnect();
|
||||
}
|
||||
}
|
||||
connectionPool.clear();
|
||||
connectionCount.set(0);
|
||||
log.info("成功清理所有 Pub/Sub API 连接");
|
||||
} catch (Exception e) {
|
||||
log.error("清理所有 Pub/Sub API 连接失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接池大小
|
||||
* @return 连接池大小
|
||||
*/
|
||||
public int getPoolSize() {
|
||||
return connectionCount.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 健康检查
|
||||
* @return 是否健康
|
||||
*/
|
||||
public boolean healthCheck() {
|
||||
try {
|
||||
// 检查主客户端连接状态
|
||||
if (pubSubClient.isConnected()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查连接池中的连接
|
||||
for (SalesforcePubSubClient client : connectionPool.values()) {
|
||||
if (client != null && client.isConnected()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.error("健康检查失败: {}", e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,13 +2,17 @@ package com.datai.integration.realtime.impl;
|
||||
|
||||
import com.datai.integration.realtime.EventProcessor;
|
||||
import com.datai.integration.realtime.DataSynchronizer;
|
||||
import com.salesforce.eventbus.protobuf.EventBatch;
|
||||
import com.salesforce.multicloudj.protobuf.EventBatch;
|
||||
import com.sforce.soap.partner.sobject.SObject;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -61,7 +65,7 @@ public class EventProcessorImpl implements EventProcessor {
|
||||
log.info("提取变更数据");
|
||||
|
||||
// 示例实现,实际项目中需要根据具体的Change Event结构来实现
|
||||
Map<String, Object> changeData = new java.util.HashMap<>();
|
||||
Map<String, Object> changeData = new HashMap<>();
|
||||
|
||||
// 提取标准字段
|
||||
try {
|
||||
@ -100,24 +104,22 @@ public class EventProcessorImpl implements EventProcessor {
|
||||
// 遍历处理每个事件
|
||||
for (int i = 0; i < eventBatch.getEventsCount(); i++) {
|
||||
var event = eventBatch.getEvents(i);
|
||||
log.info("处理事件 {} 中的事件", i + 1);
|
||||
log.info("处理事件批次中的事件 {}/{}", i + 1, eventBatch.getEventsCount());
|
||||
|
||||
try {
|
||||
// 提取事件信息
|
||||
var eventPayload = event.getPayload();
|
||||
// 这里需要根据实际的 Event 结构来提取信息
|
||||
// 示例实现,实际项目中需要根据具体的 Event 结构来实现
|
||||
Map<String, Object> eventInfo = extractEventInfo(event);
|
||||
|
||||
String objectType = "Account"; // 示例值,实际需要从事件中提取
|
||||
String recordId = event.getReplayId().toString(); // 示例值,实际需要从事件中提取
|
||||
String changeType = "UPDATE"; // 示例值,实际需要从事件中提取
|
||||
Date changeDate = new Date(); // 示例值,实际需要从事件中提取
|
||||
String objectType = (String) eventInfo.get("objectType");
|
||||
String recordId = (String) eventInfo.get("recordId");
|
||||
String changeType = (String) eventInfo.get("changeType");
|
||||
Date changeDate = (Date) eventInfo.get("changeDate");
|
||||
|
||||
log.info("事件信息: 类型={}, 对象={}, 记录ID={}, 变更时间={}",
|
||||
changeType, objectType, recordId, changeDate);
|
||||
|
||||
// 提取变更数据
|
||||
Map<String, Object> changeData = extractChangeDataFromEventBatch(event);
|
||||
Map<String, Object> changeData = extractChangeDataFromEventBatch(event, objectType);
|
||||
|
||||
// 同步数据
|
||||
dataSynchronizer.synchronizeData(objectType, recordId, changeType, changeData, changeDate);
|
||||
@ -135,29 +137,88 @@ public class EventProcessorImpl implements EventProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取事件基本信息
|
||||
* @param event Pub/Sub API 事件
|
||||
* @return 事件信息映射
|
||||
*/
|
||||
private Map<String, Object> extractEventInfo(com.salesforce.multicloudj.protobuf.Event event) {
|
||||
Map<String, Object> eventInfo = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 从事件负载中提取信息
|
||||
// 注意:实际的负载结构取决于事件类型
|
||||
// 这里假设是Change Events格式
|
||||
|
||||
// 示例实现:从payload中提取信息
|
||||
// 实际项目中需要根据Salesforce的Change Events格式进行解析
|
||||
|
||||
// 提取对象类型
|
||||
String objectType = "Account"; // 默认值,实际需要从payload中提取
|
||||
|
||||
// 提取记录ID
|
||||
String recordId = event.getReplayId().toString(); // 临时使用replayId,实际需要从payload中提取
|
||||
|
||||
// 提取变更类型
|
||||
String changeType = "UPDATE"; // 默认值,实际需要从payload中提取
|
||||
|
||||
// 提取变更时间
|
||||
Date changeDate = new Date(); // 默认值,实际需要从payload中提取
|
||||
|
||||
eventInfo.put("objectType", objectType);
|
||||
eventInfo.put("recordId", recordId);
|
||||
eventInfo.put("changeType", changeType);
|
||||
eventInfo.put("changeDate", changeDate);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("提取事件信息时发生异常: {}", e.getMessage(), e);
|
||||
// 设置默认值
|
||||
eventInfo.put("objectType", "Unknown");
|
||||
eventInfo.put("recordId", event.getReplayId().toString());
|
||||
eventInfo.put("changeType", "UNKNOWN");
|
||||
eventInfo.put("changeDate", new Date());
|
||||
}
|
||||
|
||||
return eventInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Pub/Sub API 事件中提取变更数据
|
||||
* @param event Pub/Sub API 事件
|
||||
* @param objectType 对象类型
|
||||
* @return 变更数据映射
|
||||
*/
|
||||
private Map<String, Object> extractChangeDataFromEventBatch(com.salesforce.eventbus.protobuf.Event event) {
|
||||
private Map<String, Object> extractChangeDataFromEventBatch(com.salesforce.multicloudj.protobuf.Event event, String objectType) {
|
||||
// 这里需要根据实际的 Pub/Sub API Event 结构来提取变更数据
|
||||
// 简化实现,实际项目中需要根据具体情况进行调整
|
||||
|
||||
log.info("从 Pub/Sub API 事件中提取变更数据");
|
||||
log.info("从 Pub/Sub API 事件中提取变更数据,对象类型: {}", objectType);
|
||||
|
||||
// 示例实现,实际项目中需要根据具体的 Event 结构来实现
|
||||
Map<String, Object> changeData = new java.util.HashMap<>();
|
||||
Map<String, Object> changeData = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 提取事件负载
|
||||
var payload = event.getPayload();
|
||||
// 这里需要根据实际的 payload 结构来提取数据
|
||||
// 示例:假设 payload 是 JSON 格式
|
||||
byte[] payloadBytes = event.getPayload().toByteArray();
|
||||
|
||||
// 提取标准字段
|
||||
changeData.put("Id", event.getReplayId().toString()); // 示例值
|
||||
changeData.put("Name", "Test Account"); // 示例值
|
||||
// 解析payload
|
||||
// 注意:实际的payload格式取决于事件类型和对象类型
|
||||
// 这里需要根据Salesforce的Change Events格式进行解析
|
||||
|
||||
// 示例实现:根据对象类型提取不同字段
|
||||
switch (objectType) {
|
||||
case "Account":
|
||||
extractAccountFields(payloadBytes, changeData);
|
||||
break;
|
||||
case "Contact":
|
||||
extractContactFields(payloadBytes, changeData);
|
||||
break;
|
||||
case "Opportunity":
|
||||
extractOpportunityFields(payloadBytes, changeData);
|
||||
break;
|
||||
default:
|
||||
extractDefaultFields(payloadBytes, changeData);
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("从 Pub/Sub API 事件中提取变更数据时发生异常: {}", e.getMessage(), e);
|
||||
@ -166,4 +227,81 @@ public class EventProcessorImpl implements EventProcessor {
|
||||
log.info("变更数据提取完成,共提取 {} 个字段", changeData.size());
|
||||
return changeData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取Account对象字段
|
||||
* @param payloadBytes 事件负载字节数组
|
||||
* @param changeData 变更数据映射
|
||||
*/
|
||||
private void extractAccountFields(byte[] payloadBytes, Map<String, Object> changeData) {
|
||||
// 解析Account对象的变更数据
|
||||
// 实际项目中需要根据Salesforce的Change Events格式进行解析
|
||||
|
||||
try (InputStream is = new ByteArrayInputStream(payloadBytes)) {
|
||||
// 示例实现:模拟解析
|
||||
changeData.put("Id", "001xxxxxxxxxxxxxxx"); // 示例ID
|
||||
changeData.put("Name", "Test Account");
|
||||
changeData.put("BillingCity", "San Francisco");
|
||||
changeData.put("Industry", "Technology");
|
||||
} catch (IOException e) {
|
||||
log.error("解析Account字段时发生异常: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取Contact对象字段
|
||||
* @param payloadBytes 事件负载字节数组
|
||||
* @param changeData 变更数据映射
|
||||
*/
|
||||
private void extractContactFields(byte[] payloadBytes, Map<String, Object> changeData) {
|
||||
// 解析Contact对象的变更数据
|
||||
// 实际项目中需要根据Salesforce的Change Events格式进行解析
|
||||
|
||||
try (InputStream is = new ByteArrayInputStream(payloadBytes)) {
|
||||
// 示例实现:模拟解析
|
||||
changeData.put("Id", "003xxxxxxxxxxxxxxx"); // 示例ID
|
||||
changeData.put("FirstName", "John");
|
||||
changeData.put("LastName", "Doe");
|
||||
changeData.put("Email", "john.doe@example.com");
|
||||
} catch (IOException e) {
|
||||
log.error("解析Contact字段时发生异常: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取Opportunity对象字段
|
||||
* @param payloadBytes 事件负载字节数组
|
||||
* @param changeData 变更数据映射
|
||||
*/
|
||||
private void extractOpportunityFields(byte[] payloadBytes, Map<String, Object> changeData) {
|
||||
// 解析Opportunity对象的变更数据
|
||||
// 实际项目中需要根据Salesforce的Change Events格式进行解析
|
||||
|
||||
try (InputStream is = new ByteArrayInputStream(payloadBytes)) {
|
||||
// 示例实现:模拟解析
|
||||
changeData.put("Id", "006xxxxxxxxxxxxxxx"); // 示例ID
|
||||
changeData.put("Name", "Test Opportunity");
|
||||
changeData.put("StageName", "Prospecting");
|
||||
changeData.put("Amount", 100000.0);
|
||||
} catch (IOException e) {
|
||||
log.error("解析Opportunity字段时发生异常: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取默认字段
|
||||
* @param payloadBytes 事件负载字节数组
|
||||
* @param changeData 变更数据映射
|
||||
*/
|
||||
private void extractDefaultFields(byte[] payloadBytes, Map<String, Object> changeData) {
|
||||
// 解析默认字段
|
||||
// 实际项目中需要根据Salesforce的Change Events格式进行解析
|
||||
|
||||
try (InputStream is = new ByteArrayInputStream(payloadBytes)) {
|
||||
// 示例实现:模拟解析
|
||||
changeData.put("Id", "001xxxxxxxxxxxxxxx"); // 示例ID
|
||||
} catch (IOException e) {
|
||||
log.error("解析默认字段时发生异常: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,8 @@ import com.datai.integration.core.IPartnerV1Connection;
|
||||
import com.datai.integration.factory.impl.SOAPConnectionFactory;
|
||||
import com.datai.integration.realtime.EventSubscriber;
|
||||
import com.sforce.soap.partner.PartnerConnection;
|
||||
import com.sforce.soap.partner.SubscribeRequest;
|
||||
import com.sforce.soap.partner.SubscribeResult;
|
||||
|
||||
import com.sforce.soap.partner.sobject.SObject;
|
||||
import com.sforce.ws.ConnectionException;
|
||||
|
||||
@ -2,12 +2,18 @@ package com.datai.integration.realtime.impl;
|
||||
|
||||
import com.datai.integration.factory.impl.PubSubConnectionFactory;
|
||||
import com.datai.integration.realtime.EventSubscriber;
|
||||
import com.salesforce.eventbus.SubscriptionListener;
|
||||
import com.salesforce.eventbus.protobuf.EventBatch;
|
||||
import com.salesforce.multicloudj.SubscriptionListener;
|
||||
import com.salesforce.multicloudj.protobuf.EventBatch;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Pub/Sub API 事件订阅器实现类
|
||||
* 用于订阅 Salesforce Event Bus 事件,实时捕获数据变更
|
||||
@ -22,65 +28,180 @@ public class PubSubEventSubscriberImpl implements EventSubscriber {
|
||||
@Autowired
|
||||
private EventProcessorImpl eventProcessor;
|
||||
|
||||
private boolean subscribed = false;
|
||||
private final AtomicBoolean subscribed = new AtomicBoolean(false);
|
||||
private String subscribedTopic;
|
||||
private ScheduledExecutorService executorService;
|
||||
|
||||
@Value("${salesforce.pubsub.subscription.retry.attempts:3}")
|
||||
private int subscriptionRetryAttempts;
|
||||
|
||||
@Value("${salesforce.pubsub.subscription.retry.delay:10}")
|
||||
private int subscriptionRetryDelay;
|
||||
|
||||
@Override
|
||||
public void startSubscription() {
|
||||
log.info("开始启动 Salesforce Pub/Sub API 订阅");
|
||||
|
||||
try {
|
||||
// 获取 Pub/Sub API 客户端
|
||||
var pubSubClient = pubSubConnectionFactory.getConnection();
|
||||
if (pubSubClient == null) {
|
||||
log.error("无法获取 Pub/Sub API 客户端");
|
||||
return;
|
||||
}
|
||||
// 启动订阅(带重试)
|
||||
boolean subscriptionResult = startSubscriptionWithRetry(subscriptionRetryAttempts);
|
||||
|
||||
// 订阅 Change Events 通道
|
||||
String topic = "/event/ChangeEvents";
|
||||
boolean subscribed = pubSubClient.subscribe(topic, new SubscriptionListener() {
|
||||
@Override
|
||||
public void onSubscribe(String subscriptionId) {
|
||||
log.info("成功订阅事件通道 {},订阅ID: {}", topic, subscriptionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
log.error("订阅事件通道 {} 发生错误: {}", topic, error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
log.info("订阅事件通道 {} 完成", topic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEventBatch(EventBatch eventBatch) {
|
||||
log.info("接收到事件批次,包含 {} 个事件", eventBatch.getEventsCount());
|
||||
|
||||
// 处理事件批次
|
||||
eventProcessor.processEventBatch(eventBatch);
|
||||
}
|
||||
});
|
||||
|
||||
if (subscribed) {
|
||||
this.subscribed = true;
|
||||
this.subscribedTopic = topic;
|
||||
log.info("Salesforce Pub/Sub API 订阅启动成功");
|
||||
} else {
|
||||
log.error("Salesforce Pub/Sub API 订阅启动失败");
|
||||
if (subscriptionResult) {
|
||||
// 启动订阅监控
|
||||
startSubscriptionMonitor();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("启动 Salesforce Pub/Sub API 订阅时发生异常: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带重试的订阅启动方法
|
||||
* @param maxAttempts 最大重试次数
|
||||
* @return 是否订阅成功
|
||||
*/
|
||||
private boolean startSubscriptionWithRetry(int maxAttempts) {
|
||||
for (int attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
log.info("启动 Salesforce Pub/Sub API 订阅 (尝试 {} / {})", attempt, maxAttempts);
|
||||
|
||||
try {
|
||||
// 获取 Pub/Sub API 客户端
|
||||
var pubSubClient = pubSubConnectionFactory.getConnection();
|
||||
if (pubSubClient == null) {
|
||||
log.error("无法获取 Pub/Sub API 客户端");
|
||||
if (attempt < maxAttempts) {
|
||||
log.info("{}秒后重试", subscriptionRetryDelay);
|
||||
TimeUnit.SECONDS.sleep(subscriptionRetryDelay);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 订阅 Change Events 通道
|
||||
String topic = "/event/ChangeEvents";
|
||||
boolean subscriptionResult = pubSubClient.subscribe(topic, new SubscriptionListener() {
|
||||
@Override
|
||||
public void onSubscribe(String subscriptionId) {
|
||||
log.info("成功订阅事件通道 {},订阅ID: {}", topic, subscriptionId);
|
||||
subscribed.set(true);
|
||||
subscribedTopic = topic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error) {
|
||||
log.error("订阅事件通道 {} 发生错误: {}", topic, error);
|
||||
subscribed.set(false);
|
||||
// 错误发生时尝试重新订阅
|
||||
handleSubscriptionError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
log.info("订阅事件通道 {} 完成", topic);
|
||||
subscribed.set(false);
|
||||
// 订阅完成时尝试重新订阅
|
||||
handleSubscriptionComplete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEventBatch(EventBatch eventBatch) {
|
||||
try {
|
||||
log.info("接收到事件批次,包含 {} 个事件", eventBatch.getEventsCount());
|
||||
|
||||
// 处理事件批次
|
||||
eventProcessor.processEventBatch(eventBatch);
|
||||
} catch (Exception e) {
|
||||
log.error("处理事件批次时发生异常: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (subscriptionResult) {
|
||||
log.info("Salesforce Pub/Sub API 订阅启动成功");
|
||||
return true;
|
||||
} else {
|
||||
log.error("Salesforce Pub/Sub API 订阅启动失败");
|
||||
if (attempt < maxAttempts) {
|
||||
log.info("{}秒后重试", subscriptionRetryDelay);
|
||||
TimeUnit.SECONDS.sleep(subscriptionRetryDelay);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("启动 Salesforce Pub/Sub API 订阅时发生异常: {}", e.getMessage(), e);
|
||||
if (attempt < maxAttempts) {
|
||||
try {
|
||||
log.info("{}秒后重试", subscriptionRetryDelay);
|
||||
TimeUnit.SECONDS.sleep(subscriptionRetryDelay);
|
||||
} catch (InterruptedException ie) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理订阅错误
|
||||
*/
|
||||
private void handleSubscriptionError() {
|
||||
log.info("处理订阅错误,尝试重新订阅");
|
||||
executorService.schedule(() -> {
|
||||
if (!subscribed.get()) {
|
||||
log.info("重新启动 Salesforce Pub/Sub API 订阅");
|
||||
startSubscriptionWithRetry(3);
|
||||
}
|
||||
}, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理订阅完成
|
||||
*/
|
||||
private void handleSubscriptionComplete() {
|
||||
log.info("处理订阅完成,尝试重新订阅");
|
||||
executorService.schedule(() -> {
|
||||
if (!subscribed.get()) {
|
||||
log.info("重新启动 Salesforce Pub/Sub API 订阅");
|
||||
startSubscriptionWithRetry(3);
|
||||
}
|
||||
}, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动订阅监控
|
||||
*/
|
||||
private void startSubscriptionMonitor() {
|
||||
executorService = Executors.newSingleThreadScheduledExecutor(r -> {
|
||||
Thread t = new Thread(r, "pubsub-subscription-monitor");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
});
|
||||
|
||||
// 每60秒检查一次订阅状态
|
||||
executorService.scheduleAtFixedRate(() -> {
|
||||
try {
|
||||
if (!subscribed.get()) {
|
||||
log.warn("检测到 Pub/Sub API 订阅已断开,尝试重新订阅");
|
||||
startSubscriptionWithRetry(3);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("订阅监控时发生异常: {}", e.getMessage(), e);
|
||||
}
|
||||
}, 60, 60, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSubscription() {
|
||||
log.info("开始停止 Salesforce Pub/Sub API 订阅");
|
||||
|
||||
try {
|
||||
// 停止订阅监控
|
||||
if (executorService != null) {
|
||||
executorService.shutdown();
|
||||
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 Pub/Sub API 客户端
|
||||
var pubSubClient = pubSubConnectionFactory.getConnection();
|
||||
if (pubSubClient != null) {
|
||||
@ -89,7 +210,7 @@ public class PubSubEventSubscriberImpl implements EventSubscriber {
|
||||
log.info("成功取消订阅事件通道: {}", subscribedTopic);
|
||||
}
|
||||
|
||||
subscribed = false;
|
||||
subscribed.set(false);
|
||||
subscribedTopic = null;
|
||||
log.info("Salesforce Pub/Sub API 订阅停止成功");
|
||||
} catch (Exception e) {
|
||||
@ -99,6 +220,14 @@ public class PubSubEventSubscriberImpl implements EventSubscriber {
|
||||
|
||||
@Override
|
||||
public boolean isSubscribed() {
|
||||
return subscribed;
|
||||
return subscribed.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前订阅的主题
|
||||
* @return 订阅主题
|
||||
*/
|
||||
public String getSubscribedTopic() {
|
||||
return subscribedTopic;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user