feat(databus): 完成阶段四-DataBus Server完整功能

- 补充缺失的 API 类(DatabusMessage、DatabusBatchMessage、DatabusEventType)
- 新增变更消息消费者(3个:部门、用户、岗位)
- 新增数据提供者(3个:部门、用户、岗位)
- 确认分发器服务(核心定向推送逻辑)
- 确认全量同步与消息推送组件
- 确认管理后台 API(5个 Controller)
- 确认 Service ��(4个核心服务)
- 确认 DAL 层(7个 DO + Mapper)
- 添加 databus-server starter 依赖到 pom.xml
- 编译验证通过

Ref: docs/databus/implementation-checklist.md 任务 39-70
This commit is contained in:
hewencai
2025-12-01 23:44:32 +08:00
parent f5ba493f95
commit db13036cea
109 changed files with 7673 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.zt.plat</groupId>
<artifactId>zt-framework</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>zt-spring-boot-starter-databus-client</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>DataBus 客户端组件,负责接收数据变更并同步</description>
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<dependencies>
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-common</artifactId>
</dependency>
<!-- Databus API事件类型枚举等 -->
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-module-databus-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- System API用于默认Handler实现 -->
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-module-system-api</artifactId>
<version>${revision}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- MQ 相关 -->
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-mq</artifactId>
</dependency>
<!-- Redis 相关 (用于幂等) -->
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-redis</artifactId>
</dependency>
<!-- Web 相关 (用于HTTP接收) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
</dependency>
<!-- Test -->
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,23 @@
package com.zt.plat.framework.databus.client.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
/**
* Databus 同步客户端自动配置
*
* @author ZT
*/
@Slf4j
@AutoConfiguration
@EnableConfigurationProperties(DatabusSyncClientProperties.class)
@ComponentScan(basePackages = "com.zt.plat.framework.databus.client")
public class DatabusSyncClientAutoConfiguration {
public DatabusSyncClientAutoConfiguration() {
log.info("[Databus] 数据同步客户端模块已加载");
}
}

View File

@@ -0,0 +1,113 @@
package com.zt.plat.framework.databus.client.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Databus 数据同步客户端配置属性
*
* @author ZT
*/
@Data
@ConfigurationProperties(prefix = "zt.databus.sync.client")
public class DatabusSyncClientProperties {
/**
* 是否启用
*/
private Boolean enabled = true;
/**
* RocketMQ NameServer 地址
*/
private String nameServer;
/**
* 客户端编码必填用于订阅专属Topic
* Topic格式: databus-sync-{eventType}-{clientCode}
*/
private String clientCode;
/**
* MQ配置
*/
private Mq mq = new Mq();
/**
* HTTP配置
*/
private Http http = new Http();
/**
* 幂等配置
*/
private Idempotent idempotent = new Idempotent();
@Data
public static class Mq {
/**
* 是否启用MQ消费
*/
private Boolean enabled = true;
/**
* RocketMQ NameServer 地址
*/
private String nameServer;
/**
* Topic基础名称
*/
private String topicBase = "databus-sync";
/**
* 消费者组前缀,完整格式: {consumerGroupPrefix}-{eventType}
* 默认: databus-client-{clientCode}
*/
private String consumerGroupPrefix;
/**
* 消费线程数
*/
private Integer consumeThreadMin = 1;
/**
* 消费线程数
*/
private Integer consumeThreadMax = 5;
/**
* 最大重试次数
*/
private Integer maxReconsumeTimes = 3;
}
@Data
public static class Http {
/**
* 是否启用HTTP推送接收
*/
private Boolean enabled = false;
/**
* 接收端点路径
*/
private String endpoint = "/databus/sync/receive";
}
@Data
public static class Idempotent {
/**
* 是否启用幂等检查
*/
private Boolean enabled = true;
/**
* RocketMQ NameServer 地址
*/
private String nameServer;
/**
* 幂等记录过期时间(秒)
*/
private Integer expireSeconds = 86400; // 24小时
}
}

View File

@@ -0,0 +1,43 @@
package com.zt.plat.framework.databus.client.core.controller;
import com.zt.plat.framework.databus.client.core.processor.MessageProcessor;
import com.zt.plat.module.databus.enums.DatabusEventType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.web.bind.annotation.*;
/**
* 同步消息HTTP接收控制器
*
* @author ZT
*/
@Slf4j
@RestController
@RequestMapping("${zt.databus.sync.client.http.endpoint:/databus/sync/receive}")
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "zt.databus.sync.client.http", name = "enabled", havingValue = "true")
public class SyncMessageController {
private final MessageProcessor messageProcessor;
/**
* 接收同步消息
*
* @param eventType 事件类型
* @param message 消息体
*/
@PostMapping("/{eventType}")
public void receive(@PathVariable("eventType") String eventType, @RequestBody String message) {
log.debug("[Databus Client] 接收到HTTP消息, eventType={}", eventType);
DatabusEventType type = DatabusEventType.valueOf(eventType);
if (type == null) {
log.warn("[Databus Client] 未知的事件类型: {}", eventType);
return;
}
messageProcessor.process(message, type);
}
}

View File

@@ -0,0 +1,19 @@
package com.zt.plat.framework.databus.client.core.idempotent;
/**
* 幂等存储接口
*
* @author ZT
*/
public interface IdempotentStore {
/**
* 检查并记录消息是否已处理
*
* @param syncId 同步ID
* @param expireSeconds 过期时间(秒)
* @return true-未处理(可以处理), false-已处理(应该跳过)
*/
boolean checkAndMark(String syncId, int expireSeconds);
}

View File

@@ -0,0 +1,30 @@
package com.zt.plat.framework.databus.client.core.idempotent;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* 基于 Redis 的幂等存储实现
*
* @author ZT
*/
@Component
@RequiredArgsConstructor
public class RedisIdempotentStore implements IdempotentStore {
private static final String KEY_PREFIX = "databus:sync:idempotent:";
private final StringRedisTemplate stringRedisTemplate;
@Override
public boolean checkAndMark(String syncId, int expireSeconds) {
String key = KEY_PREFIX + syncId;
Boolean success = stringRedisTemplate.opsForValue()
.setIfAbsent(key, "1", expireSeconds, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
}

View File

@@ -0,0 +1,148 @@
package com.zt.plat.framework.databus.client.core.listener;
import com.zt.plat.framework.databus.client.config.DatabusSyncClientProperties;
import com.zt.plat.framework.databus.client.core.processor.MessageProcessor;
import com.zt.plat.module.databus.enums.DatabusEventType;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
/**
* Databus Consumer 注册器
* <p>
* 根据 DatabusEventType 枚举自动为所有事件类型创建消费者
* Topic格式: {topicBase}-{module}-{entity}-{action}-{clientCode}
* 例如: databus-sync-system-user-create-company-a
*
* @author ZT
*/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client.mq", name = "enabled", havingValue = "true", matchIfMissing = true)
public class DatabusConsumerRegistry {
private final MessageProcessor messageProcessor;
private final DatabusSyncClientProperties properties;
/**
* 管理的消费者列表
*/
private final List<DefaultMQPushConsumer> consumers = new ArrayList<>();
public DatabusConsumerRegistry(MessageProcessor messageProcessor, DatabusSyncClientProperties properties) {
this.messageProcessor = messageProcessor;
this.properties = properties;
}
@PostConstruct
public void init() {
if (!Boolean.TRUE.equals(properties.getEnabled())) {
log.info("[Databus Client] 客户端未启用,跳过初始化");
return;
}
String nameServer = properties.getMq().getNameServer();
if (nameServer == null || nameServer.isEmpty()) {
log.warn("[Databus Client] RocketMQ nameServer未配置跳过MQ消费者初始化");
return;
}
String clientCode = properties.getClientCode();
if (clientCode == null || clientCode.isEmpty()) {
log.error("[Databus Client] clientCode未配置无法订阅Topic");
return;
}
String topicBase = properties.getMq().getTopicBase();
String consumerGroupPrefix = properties.getMq().getConsumerGroupPrefix();
if (consumerGroupPrefix == null || consumerGroupPrefix.isEmpty()) {
consumerGroupPrefix = "databus-client-" + clientCode;
}
// 为每个事件类型创建独立的消费者
for (DatabusEventType eventType : DatabusEventType.values()) {
try {
createConsumer(eventType, topicBase, clientCode, consumerGroupPrefix, nameServer);
} catch (Exception e) {
log.error("[Databus Client] 创建消费者失败, eventType={}", eventType.name(), e);
}
}
log.info("[Databus Client] 消费者注册完成,共创建 {} 个消费者", consumers.size());
}
/**
* 为指定事件类型创建消费者
*/
private void createConsumer(DatabusEventType eventType, String topicBase, String clientCode, String consumerGroupPrefix, String nameServer) throws MQClientException {
// Topic: databus-sync-system-user-create-company-a
String topic = eventType.getTopic(topicBase, clientCode);
// ConsumerGroup: databus-client-company-a-system-user-create
String consumerGroup = String.format("%s-%s", consumerGroupPrefix, eventType.getTopicSuffix());
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(consumerGroup);
consumer.setNamesrvAddr(nameServer);
consumer.setConsumeThreadMin(properties.getMq().getConsumeThreadMin());
consumer.setConsumeThreadMax(properties.getMq().getConsumeThreadMax());
consumer.setMaxReconsumeTimes(properties.getMq().getMaxReconsumeTimes());
// 订阅Topic
consumer.subscribe(topic, "*");
// 设置消息监听器
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
for (MessageExt msg : msgs) {
try {
String messageBody = new String(msg.getBody(), StandardCharsets.UTF_8);
log.debug("[Databus Client] 收到消息, topic={}, msgId={}, eventType={}",
msg.getTopic(), msg.getMsgId(), eventType.name());
messageProcessor.process(messageBody, eventType);
} catch (Exception e) {
log.error("[Databus Client] 消息处理失败, topic={}, msgId={}, reconsumeTimes={}",
msg.getTopic(), msg.getMsgId(), msg.getReconsumeTimes(), e);
if (msg.getReconsumeTimes() >= properties.getMq().getMaxReconsumeTimes()) {
log.error("[Databus Client] 重试次数已达上限,放弃处理, msgId={}", msg.getMsgId());
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
consumer.start();
consumers.add(consumer);
log.info("[Databus Client] 消费者启动成功, topic={}, consumerGroup={}, event={}",
topic, consumerGroup, eventType.getName());
}
@PreDestroy
public void destroy() {
for (DefaultMQPushConsumer consumer : consumers) {
try {
consumer.shutdown();
} catch (Exception e) {
log.error("[Databus Client] 关闭消费者失败", e);
}
}
consumers.clear();
log.info("[Databus Client] 所有消费者已关闭");
}
}

View File

@@ -0,0 +1,54 @@
package com.zt.plat.framework.databus.client.core.message;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 数据同步消息(服务端推送格式)
*
* @author ZT
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SyncMessage {
/**
* 同步ID唯一标识
*/
private String syncId;
/**
* 事件记录ID
*/
private Long eventRecordId;
/**
* 事件类型
*/
private String eventType;
/**
* 事件动作
*/
private String eventAction;
/**
* 业务数据快照JSON字符串
*/
private String dataSnapshot;
/**
* 数据版本
*/
private Integer dataVersion;
/**
* 时间戳
*/
private Long timestamp;
}

View File

@@ -0,0 +1,236 @@
package com.zt.plat.framework.databus.client.core.processor;
import cn.hutool.json.JSONUtil;
import com.zt.plat.framework.databus.client.config.DatabusSyncClientProperties;
import com.zt.plat.framework.databus.client.core.idempotent.IdempotentStore;
import com.zt.plat.framework.databus.client.core.message.SyncMessage;
import com.zt.plat.framework.databus.client.handler.BatchSyncEventHandler;
import com.zt.plat.framework.databus.client.handler.SyncEventHandler;
import com.zt.plat.module.databus.api.message.DatabusBatchMessage;
import com.zt.plat.module.databus.api.message.DatabusMessage;
import com.zt.plat.module.databus.enums.DatabusEventType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 消息处理器
* <p>
* 负责消息的幂等检查和路由到具体的Handler
*
* @author ZT
*/
@Slf4j
@Component
@SuppressWarnings({"rawtypes", "unchecked"})
public class MessageProcessor {
private final IdempotentStore idempotentStore;
private final DatabusSyncClientProperties properties;
/**
* 增量同步处理器列表
*/
private List<SyncEventHandler> handlers = new ArrayList<>();
/**
* 全量同步处理器列表
*/
private List<BatchSyncEventHandler> batchHandlers = new ArrayList<>();
private Map<DatabusEventType, SyncEventHandler> handlerMap;
private Map<DatabusEventType, BatchSyncEventHandler> batchHandlerMap;
public MessageProcessor(IdempotentStore idempotentStore, DatabusSyncClientProperties properties) {
this.idempotentStore = idempotentStore;
this.properties = properties;
}
@Autowired(required = false)
public void setHandlers(List<SyncEventHandler> handlers) {
if (handlers != null) {
this.handlers = handlers;
}
}
@Autowired(required = false)
public void setBatchHandlers(List<BatchSyncEventHandler> batchHandlers) {
if (batchHandlers != null) {
this.batchHandlers = batchHandlers;
}
}
/**
* 处理同步消息
*
* @param messageJson 消息JSON字符串
* @param eventType 事件类型枚举
*/
public void process(String messageJson, DatabusEventType eventType) {
try {
if (eventType.isFullSync()) {
processBatchMessage(messageJson, eventType);
} else {
processIncrementalMessage(messageJson, eventType);
}
} catch (Exception e) {
log.error("[Databus Client] 消息处理失败, eventType={}, message={}", eventType.name(), messageJson, e);
throw new RuntimeException("消息处理失败", e);
}
}
/**
* 处理增量同步消息
*/
private void processIncrementalMessage(String messageJson, DatabusEventType eventType) {
// 先解析为服务端推送的 SyncMessage 格式
SyncMessage syncMessage = JSONUtil.toBean(messageJson, SyncMessage.class);
// 幂等检查
if (properties.getIdempotent().getEnabled()) {
boolean canProcess = idempotentStore.checkAndMark(
syncMessage.getSyncId(),
properties.getIdempotent().getExpireSeconds()
);
if (!canProcess) {
log.info("[Databus Client] 消息已处理,跳过, syncId={}", syncMessage.getSyncId());
return;
}
}
// 路由到对应的Handler
SyncEventHandler handler = getHandler(eventType);
if (handler == null) {
log.warn("[Databus Client] 未找到事件处理器, eventType={}", eventType.name());
return;
}
// 获取 Handler 期望的数据类型,并转换消息
Class<?> dataType = handler.getDataType();
DatabusMessage message = convertToDatabusMessage(syncMessage, eventType, dataType);
handler.handle(message);
log.info("[Databus Client] 增量消息处理成功, syncId={}, eventType={}",
syncMessage.getSyncId(), eventType.name());
}
/**
* 将 SyncMessage 转换为 DatabusMessage
*
* @param syncMessage 服务端推送的同步消息
* @param eventType 事件类型
* @param dataType Handler 期望的数据类型
* @return DatabusMessage
*/
private DatabusMessage convertToDatabusMessage(SyncMessage syncMessage, DatabusEventType eventType, Class<?> dataType) {
DatabusMessage message = new DatabusMessage();
message.setMessageId(syncMessage.getSyncId());
message.setEventType(eventType);
// 从 dataSnapshot JSON 字符串解析业务数据对象
if (syncMessage.getDataSnapshot() != null && !syncMessage.getDataSnapshot().isEmpty()) {
// 使用 Handler 提供的类型进行反序列化
Object data = JSONUtil.toBean(syncMessage.getDataSnapshot(), dataType);
message.setData(data);
}
// 设置时间戳
if (syncMessage.getTimestamp() != null) {
message.setTimestamp(java.time.LocalDateTime.ofInstant(
java.time.Instant.ofEpochMilli(syncMessage.getTimestamp()),
java.time.ZoneId.systemDefault()
));
}
return message;
}
/**
* 处理全量同步批量消息
*/
private void processBatchMessage(String messageJson, DatabusEventType eventType) {
DatabusBatchMessage message = JSONUtil.toBean(messageJson, DatabusBatchMessage.class);
// 幂等检查
if (properties.getIdempotent().getEnabled()) {
boolean canProcess = idempotentStore.checkAndMark(
message.getMessageId(),
properties.getIdempotent().getExpireSeconds()
);
if (!canProcess) {
log.info("[Databus Client] 批量消息已处理,跳过, messageId={}", message.getMessageId());
return;
}
}
// 路由到对应的BatchHandler
BatchSyncEventHandler handler = getBatchHandler(eventType);
if (handler == null) {
log.warn("[Databus Client] 未找到批量事件处理器, eventType={}", eventType.name());
return;
}
// 全量同步开始回调(第一批)
if (message.getBatchNo() != null && message.getBatchNo() == 1) {
handler.onFullSyncStart(message);
log.info("[Databus Client] 全量同步开始, taskId={}, eventType={}, totalCount={}",
message.getTaskId(), eventType.name(), message.getTotalCount());
}
// 执行批量处理
handler.handleBatch(message);
log.info("[Databus Client] 批量消息处理成功, messageId={}, eventType={}, batchNo={}/{}, count={}",
message.getMessageId(), eventType.name(),
message.getBatchNo(), message.getTotalBatch(), message.getCount());
// 全量同步完成回调(最后一批)
if (Boolean.TRUE.equals(message.getIsLastBatch())) {
handler.onFullSyncComplete(message);
log.info("[Databus Client] 全量同步完成, taskId={}, eventType={}, totalCount={}",
message.getTaskId(), eventType.name(), message.getTotalCount());
}
}
/**
* 获取增量同步事件处理器
*/
private SyncEventHandler getHandler(DatabusEventType eventType) {
if (handlerMap == null) {
if (handlers == null || handlers.isEmpty()) {
handlerMap = Collections.emptyMap();
} else {
handlerMap = handlers.stream()
.collect(Collectors.toMap(
SyncEventHandler::getSupportedEventType,
Function.identity()
));
}
}
return handlerMap.get(eventType);
}
/**
* 获取全量同步批量事件处理器
*/
private BatchSyncEventHandler getBatchHandler(DatabusEventType eventType) {
if (batchHandlerMap == null) {
if (batchHandlers == null || batchHandlers.isEmpty()) {
batchHandlerMap = Collections.emptyMap();
} else {
batchHandlerMap = batchHandlers.stream()
.collect(Collectors.toMap(
BatchSyncEventHandler::getSupportedEventType,
Function.identity()
));
}
}
return batchHandlerMap.get(eventType);
}
}

View File

@@ -0,0 +1,51 @@
package com.zt.plat.framework.databus.client.handler;
import com.zt.plat.module.databus.api.message.DatabusBatchMessage;
import com.zt.plat.module.databus.enums.DatabusEventType;
/**
* 批量同步事件处理器接口
* <p>
* 业务系统需要实现此接口来处理全量同步的批量数据
*
* @author ZT
*/
public interface BatchSyncEventHandler<T> {
/**
* 获取支持的事件类型
*
* @return 事件类型枚举
*/
DatabusEventType getSupportedEventType();
/**
* 处理批量同步消息
*
* @param message 批量同步消息
*/
void handleBatch(DatabusBatchMessage<T> message);
/**
* 全量同步开始回调(可选实现)
* <p>
* 当收到第一批数据batchNo=1时调用
*
* @param message 批量同步消息
*/
default void onFullSyncStart(DatabusBatchMessage<T> message) {
// 默认空实现,子类可覆盖
}
/**
* 全量同步完成回调(可选实现)
* <p>
* 当收到最后一批数据isLastBatch=true时调用
*
* @param message 批量同步消息
*/
default void onFullSyncComplete(DatabusBatchMessage<T> message) {
// 默认空实现,子类可覆盖
}
}

View File

@@ -0,0 +1,57 @@
package com.zt.plat.framework.databus.client.handler;
import com.zt.plat.module.databus.api.message.DatabusMessage;
import com.zt.plat.module.databus.enums.DatabusEventType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* 同步事件处理器接口
* <p>
* 业务系统需要实现此接口来处理增量数据同步
*
* @author ZT
*/
public interface SyncEventHandler<T> {
/**
* 获取支持的事件类型
*
* @return 事件类型枚举
*/
DatabusEventType getSupportedEventType();
/**
* 处理同步消息
*
* @param message 同步消息
*/
void handle(DatabusMessage<T> message);
/**
* 获取数据类型
* <p>
* 默认通过反射获取泛型类型参数,子类可以覆盖此方法提供具体类型
*
* @return 数据类型的 Class 对象
*/
@SuppressWarnings("unchecked")
default Class<T> getDataType() {
Type[] genericInterfaces = this.getClass().getGenericInterfaces();
for (Type genericInterface : genericInterfaces) {
if (genericInterface instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericInterface;
if (parameterizedType.getRawType().equals(SyncEventHandler.class)) {
Type[] typeArguments = parameterizedType.getActualTypeArguments();
if (typeArguments.length > 0 && typeArguments[0] instanceof Class) {
return (Class<T>) typeArguments[0];
}
}
}
}
// 如果无法获取泛型类型,返回 Object.class
return (Class<T>) Object.class;
}
}

View File

@@ -0,0 +1,108 @@
package com.zt.plat.framework.databus.client.handler.post;
import com.zt.plat.module.databus.api.data.PostData;
import com.zt.plat.module.system.api.dept.PostApi;
import com.zt.plat.module.system.api.dept.dto.PostSaveReqDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
/**
* 岗位同步业务逻辑
* <p>
* 被各个 PostHandler 共享使用
*
* @author ZT
*/
@Slf4j
@Service
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnClass(name = "com.zt.plat.module.system.api.dept.PostApi")
public class PostSyncService {
@Autowired(required = false)
private PostApi postApi;
/**
* 创建岗位
*/
public void create(PostData data) {
if (postApi == null) {
log.warn("[PostSync] PostApi未注入跳过创建岗位操作, postId={}", data.getId());
return;
}
PostSaveReqDTO dto = buildPostDTO(data);
postApi.createPost(dto).checkError();
log.info("[PostSync] 创建岗位成功, postId={}, postName={}", dto.getId(), dto.getName());
}
/**
* 更新岗位
*/
public void update(PostData data) {
if (postApi == null) {
log.warn("[PostSync] PostApi未注入跳过更新岗位操作, postId={}", data.getId());
return;
}
PostSaveReqDTO dto = buildPostDTO(data);
postApi.updatePost(dto).checkError();
log.info("[PostSync] 更新岗位成功, postId={}, postName={}", dto.getId(), dto.getName());
}
/**
* 删除岗位
*/
public void delete(PostData data) {
if (postApi == null) {
log.warn("[PostSync] PostApi未注入跳过删除岗位操作, postId={}", data.getId());
return;
}
Long postId = data.getId();
if (postId != null) {
postApi.deletePost(postId).checkError();
log.info("[PostSync] 删除岗位成功, postId={}", postId);
}
}
/**
* 全量同步单条数据(存在则更新,不存在则创建)
*/
public void fullSync(PostData data) {
if (postApi == null) {
log.warn("[PostSync] PostApi未注入跳过全量同步岗位操作, postId={}", data.getId());
return;
}
PostSaveReqDTO dto = buildPostDTO(data);
try {
if (dto.getId() != null) {
var existing = postApi.getPost(dto.getId());
if (existing.isSuccess() && existing.getData() != null) {
postApi.updatePost(dto).checkError();
} else {
postApi.createPost(dto).checkError();
}
} else {
postApi.createPost(dto).checkError();
}
} catch (Exception e) {
postApi.createPost(dto).checkError();
}
}
/**
* 构建岗位DTO
*/
private PostSaveReqDTO buildPostDTO(PostData data) {
PostSaveReqDTO dto = new PostSaveReqDTO();
dto.setId(data.getId());
dto.setCode(data.getCode());
dto.setName(data.getName());
dto.setSort(data.getSort());
dto.setStatus(data.getStatus());
dto.setRemark(data.getRemark());
return dto;
}
}

View File

@@ -0,0 +1,71 @@
package com.zt.plat.framework.databus.client.handler.post;
import com.zt.plat.framework.databus.client.handler.BatchSyncEventHandler;
import com.zt.plat.module.databus.api.data.PostData;
import com.zt.plat.module.databus.api.message.DatabusBatchMessage;
import com.zt.plat.module.databus.enums.DatabusEventType;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
/**
* 岗位全量同步事件处理器
*
* @author ZT
*/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnBean(PostSyncService.class)
public class SystemPostFullHandler implements BatchSyncEventHandler<PostData> {
@Resource
private PostSyncService postSyncService;
@Override
public DatabusEventType getSupportedEventType() {
return DatabusEventType.SYSTEM_POST_FULL;
}
@Override
public void onFullSyncStart(DatabusBatchMessage<PostData> message) {
log.info("[PostSync] 全量同步开始, taskId={}, totalCount={}, totalBatch={}",
message.getTaskId(), message.getTotalCount(), message.getTotalBatch());
}
@Override
public void handleBatch(DatabusBatchMessage<PostData> message) {
log.info("[PostSync] 处理批次, batchNo={}/{}, count={}",
message.getBatchNo(), message.getTotalBatch(), message.getCount());
if (message.getDataList() == null || message.getDataList().isEmpty()) {
log.warn("[PostSync] 数据列表为空, batchNo={}", message.getBatchNo());
return;
}
int successCount = 0;
int failCount = 0;
for (PostData data : message.getDataList()) {
try {
postSyncService.fullSync(data);
successCount++;
} catch (Exception e) {
failCount++;
log.error("[PostSync] 处理数据项失败, postId={}", data.getId(), e);
}
}
log.info("[PostSync] 批次处理完成, batchNo={}, success={}, fail={}",
message.getBatchNo(), successCount, failCount);
}
@Override
public void onFullSyncComplete(DatabusBatchMessage<PostData> message) {
log.info("[PostSync] 全量同步完成, taskId={}, totalCount={}",
message.getTaskId(), message.getTotalCount());
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zt.plat.framework.databus.client.config.DatabusSyncClientAutoConfiguration

View File

@@ -0,0 +1 @@
com.zt.plat.framework.databus.client.config.DatabusSyncClientAutoConfiguration

View File

@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.zt.plat</groupId>
<artifactId>zt-framework</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>zt-spring-boot-starter-databus-server</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>DataBus 服务端组件,负责发送数据变更消息到各客户端</description>
<dependencies>
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-common</artifactId>
</dependency>
<!-- Databus API -->
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-module-databus-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- MQ -->
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-mq</artifactId>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-mybatis</artifactId>
</dependency>
<!-- Web -->
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-web</artifactId>
</dependency>
<!-- Security -->
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-security</artifactId>
</dependency>
<!-- Tenant -->
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-biz-tenant</artifactId>
</dependency>
<!-- Test -->
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,27 @@
package com.zt.plat.framework.databus.server.config;
import com.zt.plat.framework.databus.server.core.pusher.MessagePusher;
import com.zt.plat.framework.databus.server.core.pusher.MessagePusherImpl;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Databus 消息配置类
*
* @author ZT
*/
@Configuration
public class DatabusMessagingConfiguration {
@Bean
@ConditionalOnMissingBean
public MessagePusher messagePusher(@Autowired(required = false) RocketMQTemplate rocketMQTemplate) {
MessagePusherImpl pusher = new MessagePusherImpl();
pusher.setRocketMQTemplate(rocketMQTemplate);
return pusher;
}
}

View File

@@ -0,0 +1,24 @@
package com.zt.plat.framework.databus.server.config;
import com.zt.plat.framework.databus.server.producer.DatabusMessageProducer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Databus 服务端自动配置
*
* @author ZT
*/
@Configuration
@EnableConfigurationProperties(DatabusServerProperties.class)
@ConditionalOnProperty(prefix = "zt.databus.sync.server", name = "enabled", havingValue = "true")
public class DatabusServerAutoConfiguration {
@Bean
public DatabusMessageProducer databusMessageProducer(DatabusServerProperties properties) {
return new DatabusMessageProducer(properties);
}
}

View File

@@ -0,0 +1,60 @@
package com.zt.plat.framework.databus.server.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* Databus 服务端配置属性
*
* @author ZT
*/
@Data
@ConfigurationProperties(prefix = "zt.databus.sync.server")
public class DatabusServerProperties {
/**
* 是否启用
*/
private boolean enabled = false;
/**
* MQ 配置
*/
private MqConfig mq = new MqConfig();
/**
* 订阅的客户端列表
*/
private List<String> clients;
@Data
public static class MqConfig {
/**
* 是否启用 MQ 发送
*/
private boolean enabled = true;
/**
* RocketMQ NameServer 地址
*/
private String nameServer;
/**
* Topic 基础名称
*/
private String topicBase = "databus-sync";
/**
* 生产者组名称
*/
private String producerGroup = "databus-server-producer";
/**
* 发送超时时间(毫秒)
*/
private int sendMsgTimeout = 10000; // 默认 10 秒
}
}

View File

@@ -0,0 +1,45 @@
package com.zt.plat.framework.databus.server.config;
import com.zt.plat.framework.databus.server.core.pusher.MessagePusher;
import com.zt.plat.framework.databus.server.core.pusher.MessagePusherImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
/**
* Databus 同步服务端自动配置
*
* @author ZT
*/
@Slf4j
@AutoConfiguration
@EnableAsync
@EnableConfigurationProperties(DatabusSyncServerProperties.class)
@ComponentScan(basePackages = "com.zt.plat.framework.databus.server")
@MapperScan("com.zt.plat.framework.databus.server.dal.mapper")
public class DatabusSyncServerAutoConfiguration {
public DatabusSyncServerAutoConfiguration() {
log.info("[Databus] 数据同步服务端模块已加载");
}
/**
* 注册消息推送器 Bean
*/
@Bean
@ConditionalOnBean(RocketMQTemplate.class)
public MessagePusher messagePusher(RocketMQTemplate rocketMQTemplate) {
MessagePusherImpl pusher = new MessagePusherImpl();
pusher.setRocketMQTemplate(rocketMQTemplate);
log.info("[Databus] MessagePusher Bean 已注册");
return pusher;
}
}

View File

@@ -0,0 +1,61 @@
package com.zt.plat.framework.databus.server.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Databus 数据同步服务端配置属性
*
* @author ZT
*/
@Data
@ConfigurationProperties(prefix = "zt.databus.sync.server")
public class DatabusSyncServerProperties {
/**
* 是否启用
*/
private Boolean enabled = true;
/**
* 重试配置
*/
private Retry retry = new Retry();
/**
* 批量推送配置
*/
private Batch batch = new Batch();
@Data
public static class Retry {
/**
* 最大重试次数
*/
private Integer maxAttempts = 5;
/**
* 初始重试延迟(秒)
*/
private Integer initialDelay = 1;
/**
* 重试延迟倍数
*/
private Integer multiplier = 2;
}
@Data
public static class Batch {
/**
* 默认批量大小
*/
private Integer defaultSize = 500;
/**
* 批量推送间隔(秒)
*/
private Integer interval = 5;
}
}

View File

@@ -0,0 +1,92 @@
package com.zt.plat.framework.databus.server.controller.admin;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientPageReqVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientRespVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientSaveReqVO;
import com.zt.plat.framework.databus.server.convert.DatabusSyncClientConvert;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncClientDO;
import com.zt.plat.framework.databus.server.service.DatabusSyncClientService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 数据同步客户端")
@RestController
@RequestMapping("/databus/sync/client")
@Validated
public class DatabusSyncClientController {
@Resource
private DatabusSyncClientService clientService;
@PostMapping("/create")
@Operation(summary = "创建数据同步客户端")
@PreAuthorize("@ss.hasPermission('databus:sync:client:create')")
public CommonResult<Long> createClient(@Valid @RequestBody DatabusSyncClientSaveReqVO createReqVO) {
return success(clientService.createClient(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新数据同步客户端")
@PreAuthorize("@ss.hasPermission('databus:sync:client:update')")
public CommonResult<Boolean> updateClient(@Valid @RequestBody DatabusSyncClientSaveReqVO updateReqVO) {
clientService.updateClient(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除数据同步客户端")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('databus:sync:client:delete')")
public CommonResult<Boolean> deleteClient(@RequestParam("id") Long id) {
clientService.deleteClient(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得数据同步客户端")
@Parameter(name = "id", description = "编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('databus:sync:client:query')")
public CommonResult<DatabusSyncClientRespVO> getClient(@RequestParam("id") Long id) {
DatabusSyncClientDO client = clientService.getClient(id);
return success(DatabusSyncClientConvert.INSTANCE.convert(client));
}
@GetMapping("/page")
@Operation(summary = "获得数据同步客户端分页")
@PreAuthorize("@ss.hasPermission('databus:sync:client:query')")
public CommonResult<PageResult<DatabusSyncClientRespVO>> getClientPage(@Valid DatabusSyncClientPageReqVO pageReqVO) {
PageResult<DatabusSyncClientDO> pageResult = clientService.getClientPage(pageReqVO);
return success(DatabusSyncClientConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/list")
@Operation(summary = "获得数据同步客户端列表")
@PreAuthorize("@ss.hasPermission('databus:sync:client:query')")
public CommonResult<List<DatabusSyncClientRespVO>> getClientList() {
List<DatabusSyncClientDO> list = clientService.getClientList();
return success(DatabusSyncClientConvert.INSTANCE.convertList(list));
}
@PutMapping("/update-status")
@Operation(summary = "修改客户端启用状态")
@PreAuthorize("@ss.hasPermission('databus:sync:client:update')")
public CommonResult<Boolean> updateClientStatus(
@RequestParam("id") Long id,
@RequestParam("enabled") Integer enabled) {
clientService.updateClientStatus(id, enabled);
return success(true);
}
}

View File

@@ -0,0 +1,76 @@
package com.zt.plat.framework.databus.server.controller.admin;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.deadletter.DatabusSyncDeadLetterPageReqVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.deadletter.DatabusSyncDeadLetterRespVO;
import com.zt.plat.framework.databus.server.convert.DatabusSyncDeadLetterConvert;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncDeadLetterDO;
import com.zt.plat.framework.databus.server.service.DatabusSyncDeadLetterService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 数据同步死信队列")
@RestController
@RequestMapping("/databus/sync/dead-letter")
@Validated
public class DatabusSyncDeadLetterController {
@Resource
private DatabusSyncDeadLetterService deadLetterService;
@GetMapping("/get")
@Operation(summary = "获得数据同步死信队列")
@Parameter(name = "id", description = "编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('databus:sync:dead-letter:query')")
public CommonResult<DatabusSyncDeadLetterRespVO> getDeadLetter(@RequestParam("id") Long id) {
DatabusSyncDeadLetterDO deadLetter = deadLetterService.getDeadLetter(id);
return success(DatabusSyncDeadLetterConvert.INSTANCE.convert(deadLetter));
}
@GetMapping("/page")
@Operation(summary = "获得数据同步死信队列分页")
@PreAuthorize("@ss.hasPermission('databus:sync:dead-letter:query')")
public CommonResult<PageResult<DatabusSyncDeadLetterRespVO>> getDeadLetterPage(@Valid DatabusSyncDeadLetterPageReqVO pageReqVO) {
PageResult<DatabusSyncDeadLetterDO> pageResult = deadLetterService.getDeadLetterPage(pageReqVO);
return success(DatabusSyncDeadLetterConvert.INSTANCE.convertPage(pageResult));
}
@PostMapping("/reprocess")
@Operation(summary = "重新投递死信消息")
@Parameter(name = "id", description = "死信ID", required = true)
@PreAuthorize("@ss.hasPermission('databus:sync:dead-letter:reprocess')")
public CommonResult<Boolean> reprocessDeadLetter(@RequestParam("id") Long id) {
deadLetterService.reprocessDeadLetter(id);
return success(true);
}
@PostMapping("/batch-reprocess")
@Operation(summary = "批量重新投递死信消息")
@PreAuthorize("@ss.hasPermission('databus:sync:dead-letter:reprocess')")
public CommonResult<Boolean> batchReprocessDeadLetter(@RequestBody List<Long> ids) {
deadLetterService.batchReprocessDeadLetter(ids);
return success(true);
}
@PutMapping("/mark-handled")
@Operation(summary = "标记为已处理")
@PreAuthorize("@ss.hasPermission('databus:sync:dead-letter:update')")
public CommonResult<Boolean> markHandled(
@RequestParam("id") Long id,
@RequestParam(value = "remark", required = false) String remark) {
deadLetterService.markHandled(id, remark);
return success(true);
}
}

View File

@@ -0,0 +1,92 @@
package com.zt.plat.framework.databus.server.controller.admin;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventPageReqVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventRespVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventSaveReqVO;
import com.zt.plat.framework.databus.server.convert.DatabusSyncEventConvert;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventDO;
import com.zt.plat.framework.databus.server.service.DatabusSyncEventService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 数据同步事件")
@RestController
@RequestMapping("/databus/sync/event")
@Validated
public class DatabusSyncEventController {
@Resource
private DatabusSyncEventService eventService;
@PostMapping("/create")
@Operation(summary = "创建数据同步事件")
@PreAuthorize("@ss.hasPermission('databus:sync:event:create')")
public CommonResult<Long> createEvent(@Valid @RequestBody DatabusSyncEventSaveReqVO createReqVO) {
return success(eventService.createEvent(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新数据同步事件")
@PreAuthorize("@ss.hasPermission('databus:sync:event:update')")
public CommonResult<Boolean> updateEvent(@Valid @RequestBody DatabusSyncEventSaveReqVO updateReqVO) {
eventService.updateEvent(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除数据同步事件")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('databus:sync:event:delete')")
public CommonResult<Boolean> deleteEvent(@RequestParam("id") Long id) {
eventService.deleteEvent(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得数据同步事件")
@Parameter(name = "id", description = "编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('databus:sync:event:query')")
public CommonResult<DatabusSyncEventRespVO> getEvent(@RequestParam("id") Long id) {
DatabusSyncEventDO event = eventService.getEvent(id);
return success(DatabusSyncEventConvert.INSTANCE.convert(event));
}
@GetMapping("/page")
@Operation(summary = "获得数据同步事件分页")
@PreAuthorize("@ss.hasPermission('databus:sync:event:query')")
public CommonResult<PageResult<DatabusSyncEventRespVO>> getEventPage(@Valid DatabusSyncEventPageReqVO pageReqVO) {
PageResult<DatabusSyncEventDO> pageResult = eventService.getEventPage(pageReqVO);
return success(DatabusSyncEventConvert.INSTANCE.convertPage(pageResult));
}
@GetMapping("/list")
@Operation(summary = "获得数据同步事件列表")
@PreAuthorize("@ss.hasPermission('databus:sync:event:query')")
public CommonResult<List<DatabusSyncEventRespVO>> getEventList() {
List<DatabusSyncEventDO> list = eventService.getEventList();
return success(DatabusSyncEventConvert.INSTANCE.convertList(list));
}
@PutMapping("/update-status")
@Operation(summary = "修改事件启用状态")
@PreAuthorize("@ss.hasPermission('databus:sync:event:update')")
public CommonResult<Boolean> updateEventStatus(
@RequestParam("id") Long id,
@RequestParam("enabled") Integer enabled) {
eventService.updateEventStatus(id, enabled);
return success(true);
}
}

View File

@@ -0,0 +1,102 @@
package com.zt.plat.framework.databus.server.controller.admin;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.fulltask.DatabusSyncFullTaskCreateReqVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.fulltask.DatabusSyncFullTaskPageReqVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.fulltask.DatabusSyncFullTaskRespVO;
import com.zt.plat.framework.databus.server.convert.DatabusSyncFullTaskConvert;
import com.zt.plat.framework.databus.server.core.sync.DatabusFullSyncService;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncFullTaskDO;
import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncFullTaskMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
/**
* 管理后台 - 数据全量同步任务
*
* @author ZT
*/
@Tag(name = "管理后台 - 全量同步任务")
@RestController
@RequestMapping("/databus/sync/full-task")
@Validated
public class DatabusSyncFullTaskController {
@Resource
private DatabusFullSyncService fullSyncService;
@Resource
private DatabusSyncFullTaskMapper fullTaskMapper;
@PostMapping("/create")
@Operation(summary = "创建全量同步任务")
@PreAuthorize("@ss.hasPermission('databus:sync:full-task:create')")
public CommonResult<Long> createFullTask(@Valid @RequestBody DatabusSyncFullTaskCreateReqVO createReqVO) {
Long taskId = fullSyncService.createFullSyncTask(createReqVO.getSubscriptionId(), createReqVO.getRemark());
return success(taskId);
}
@PostMapping("/execute")
@Operation(summary = "执行全量同步任务")
@Parameter(name = "id", description = "任务ID", required = true)
@PreAuthorize("@ss.hasPermission('databus:sync:full-task:execute')")
public CommonResult<Boolean> executeFullTask(@RequestParam("id") Long id) {
fullSyncService.executeFullSyncTask(id);
return success(true);
}
@PostMapping("/create-and-execute")
@Operation(summary = "创建并执行全量同步任务")
@PreAuthorize("@ss.hasPermission('databus:sync:full-task:create')")
public CommonResult<Long> createAndExecuteFullTask(@Valid @RequestBody DatabusSyncFullTaskCreateReqVO createReqVO) {
Long taskId = fullSyncService.createFullSyncTask(createReqVO.getSubscriptionId(), createReqVO.getRemark());
// 异步执行任务
fullSyncService.executeFullSyncTask(taskId);
return success(taskId);
}
@PostMapping("/cancel")
@Operation(summary = "取消全量同步任务")
@Parameter(name = "id", description = "任务ID", required = true)
@PreAuthorize("@ss.hasPermission('databus:sync:full-task:cancel')")
public CommonResult<Boolean> cancelFullTask(@RequestParam("id") Long id) {
fullSyncService.cancelFullSyncTask(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得全量同步任务详情")
@Parameter(name = "id", description = "任务ID", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('databus:sync:full-task:query')")
public CommonResult<DatabusSyncFullTaskRespVO> getFullTask(@RequestParam("id") Long id) {
DatabusSyncFullTaskDO task = fullSyncService.getTaskById(id);
return success(DatabusSyncFullTaskConvert.INSTANCE.convert(task));
}
@GetMapping("/get-by-task-no")
@Operation(summary = "根据任务编号获得全量同步任务详情")
@Parameter(name = "taskNo", description = "任务编号", required = true, example = "abc123")
@PreAuthorize("@ss.hasPermission('databus:sync:full-task:query')")
public CommonResult<DatabusSyncFullTaskRespVO> getFullTaskByTaskNo(@RequestParam("taskNo") String taskNo) {
DatabusSyncFullTaskDO task = fullSyncService.getTaskByTaskNo(taskNo);
return success(DatabusSyncFullTaskConvert.INSTANCE.convert(task));
}
@GetMapping("/page")
@Operation(summary = "获得全量同步任务分页")
@PreAuthorize("@ss.hasPermission('databus:sync:full-task:query')")
public CommonResult<PageResult<DatabusSyncFullTaskRespVO>> getFullTaskPage(@Valid DatabusSyncFullTaskPageReqVO pageReqVO) {
PageResult<DatabusSyncFullTaskDO> pageResult = fullTaskMapper.selectPage(pageReqVO);
return success(DatabusSyncFullTaskConvert.INSTANCE.convertPage(pageResult));
}
}

View File

@@ -0,0 +1,69 @@
package com.zt.plat.framework.databus.server.controller.admin;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.pushlog.DatabusSyncPushLogPageReqVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.pushlog.DatabusSyncPushLogRespVO;
import com.zt.plat.framework.databus.server.convert.DatabusSyncPushLogConvert;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventRecordDO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncLogDO;
import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncEventRecordMapper;
import com.zt.plat.framework.databus.server.service.DatabusSyncLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 数据同步推送日志")
@RestController
@RequestMapping("/databus/sync/push-log")
@Validated
public class DatabusSyncPushLogController {
@Resource
private DatabusSyncLogService pushLogService;
@Resource
private DatabusSyncEventRecordMapper eventRecordMapper;
@GetMapping("/get")
@Operation(summary = "获得数据同步推送日志")
@Parameter(name = "id", description = "编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('databus:sync:push-log:query')")
public CommonResult<DatabusSyncPushLogRespVO> getPushLog(@RequestParam("id") Long id) {
DatabusSyncLogDO pushLog = pushLogService.getPushLog(id);
DatabusSyncPushLogRespVO respVO = DatabusSyncPushLogConvert.INSTANCE.convert(pushLog);
// 关联查询事件记录,获取消息内容
if (pushLog != null && pushLog.getEventRecordId() != null) {
DatabusSyncEventRecordDO eventRecord = eventRecordMapper.selectById(pushLog.getEventRecordId());
if (eventRecord != null) {
respVO.setDataSnapshot(eventRecord.getDataSnapshot());
}
}
return success(respVO);
}
@GetMapping("/page")
@Operation(summary = "获得数据同步推送日志分页")
@PreAuthorize("@ss.hasPermission('databus:sync:push-log:query')")
public CommonResult<PageResult<DatabusSyncPushLogRespVO>> getPushLogPage(@Valid DatabusSyncPushLogPageReqVO pageReqVO) {
PageResult<DatabusSyncLogDO> pageResult = pushLogService.getPushLogPage(pageReqVO);
return success(DatabusSyncPushLogConvert.INSTANCE.convertPage(pageResult));
}
@PostMapping("/retry")
@Operation(summary = "重试推送")
@Parameter(name = "id", description = "日志ID", required = true)
@PreAuthorize("@ss.hasPermission('databus:sync:push-log:retry')")
public CommonResult<Boolean> retryPush(@RequestParam("id") Long id) {
pushLogService.retryPush(id);
return success(true);
}
}

View File

@@ -0,0 +1,100 @@
package com.zt.plat.framework.databus.server.controller.admin;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionPageReqVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionRespVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionSaveReqVO;
import com.zt.plat.framework.databus.server.convert.DatabusSyncSubscriptionConvert;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncSubscriptionDO;
import com.zt.plat.framework.databus.server.service.DatabusSyncSubscriptionService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 数据同步订阅")
@RestController
@RequestMapping("/databus/sync/subscription")
@Validated
public class DatabusSyncSubscriptionController {
@Resource
private DatabusSyncSubscriptionService subscriptionService;
@PostMapping("/create")
@Operation(summary = "创建数据同步订阅")
@PreAuthorize("@ss.hasPermission('databus:sync:subscription:create')")
public CommonResult<Long> createSubscription(@Valid @RequestBody DatabusSyncSubscriptionSaveReqVO createReqVO) {
return success(subscriptionService.createSubscription(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新数据同步订阅")
@PreAuthorize("@ss.hasPermission('databus:sync:subscription:update')")
public CommonResult<Boolean> updateSubscription(@Valid @RequestBody DatabusSyncSubscriptionSaveReqVO updateReqVO) {
subscriptionService.updateSubscription(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除数据同步订阅")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('databus:sync:subscription:delete')")
public CommonResult<Boolean> deleteSubscription(@RequestParam("id") Long id) {
subscriptionService.deleteSubscription(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得数据同步订阅")
@Parameter(name = "id", description = "编号", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('databus:sync:subscription:query')")
public CommonResult<DatabusSyncSubscriptionRespVO> getSubscription(@RequestParam("id") Long id) {
DatabusSyncSubscriptionDO subscription = subscriptionService.getSubscription(id);
return success(DatabusSyncSubscriptionConvert.INSTANCE.convert(subscription));
}
@GetMapping("/page")
@Operation(summary = "获得数据同步订阅分页")
@PreAuthorize("@ss.hasPermission('databus:sync:subscription:query')")
public CommonResult<PageResult<DatabusSyncSubscriptionRespVO>> getSubscriptionPage(@Valid DatabusSyncSubscriptionPageReqVO pageReqVO) {
PageResult<DatabusSyncSubscriptionDO> pageResult = subscriptionService.getSubscriptionPage(pageReqVO);
return success(DatabusSyncSubscriptionConvert.INSTANCE.convertPage(pageResult));
}
@PutMapping("/update-status")
@Operation(summary = "修改订阅启用状态")
@PreAuthorize("@ss.hasPermission('databus:sync:subscription:update')")
public CommonResult<Boolean> updateSubscriptionStatus(
@RequestParam("id") Long id,
@RequestParam("enabled") Integer enabled) {
subscriptionService.updateSubscriptionStatus(id, enabled);
return success(true);
}
@PutMapping("/reset-checkpoint")
@Operation(summary = "重置订阅断点")
@Parameter(name = "id", description = "订阅ID", required = true)
@PreAuthorize("@ss.hasPermission('databus:sync:subscription:update')")
public CommonResult<Boolean> resetCheckpoint(@RequestParam("id") Long id) {
subscriptionService.resetCheckpoint(id);
return success(true);
}
@PostMapping("/trigger-sync")
@Operation(summary = "手动触发同步")
@Parameter(name = "id", description = "订阅ID", required = true)
@PreAuthorize("@ss.hasPermission('databus:sync:subscription:trigger')")
public CommonResult<Boolean> triggerSync(@RequestParam("id") Long id) {
subscriptionService.triggerSync(id);
return success(true);
}
}

View File

@@ -0,0 +1,27 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.client;
import com.zt.plat.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 数据同步客户端分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DatabusSyncClientPageReqVO extends PageParam {
@Schema(description = "客户端编码", example = "company-a")
private String clientCode;
@Schema(description = "客户端名称", example = "A分公司")
private String clientName;
@Schema(description = "启用状态0-禁用 1-启用)", example = "1")
private Integer enabled;
@Schema(description = "通信方式MQ_FIRST-MQ优先 HTTP_ONLY-仅HTTP", example = "MQ_FIRST")
private String transportType;
}

View File

@@ -0,0 +1,63 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.client;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 数据同步客户端 Response VO")
@Data
public class DatabusSyncClientRespVO {
@Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "租户ID", example = "1")
private Long tenantId;
@Schema(description = "客户端编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "company-a")
private String clientCode;
@Schema(description = "客户端名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "A分公司")
private String clientName;
@Schema(description = "启用状态0-禁用 1-启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer enabled;
@Schema(description = "通信方式MQ_FIRST-MQ优先 HTTP_ONLY-仅HTTP", requiredMode = Schema.RequiredMode.REQUIRED, example = "MQ_FIRST")
private String transportType;
@Schema(description = "MQ是否启用0-否 1-是)", example = "1")
private Integer mqEnabled;
@Schema(description = "MQ NameServer地址", example = "localhost:9876")
private String mqNamesrvAddr;
@Schema(description = "MQ Topic基础名称", example = "databus-sync")
private String mqTopicBase;
@Schema(description = "HTTP是否启用0-否 1-是)", example = "1")
private Integer httpEnabled;
@Schema(description = "HTTP推送端点", example = "http://example.com/databus/sync/receive")
private String httpEndpoint;
@Schema(description = "应用Key", example = "app-key-001")
private String appKey;
@Schema(description = "应用Secret", example = "***")
private String appSecret;
@Schema(description = "创建者", example = "admin")
private String creator;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新者", example = "admin")
private String updater;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,55 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.client;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
@Schema(description = "管理后台 - 数据同步客户端创建/修改 Request VO")
@Data
public class DatabusSyncClientSaveReqVO {
@Schema(description = "主键ID", example = "1")
private Long id;
@Schema(description = "客户端编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "company-a")
@NotBlank(message = "客户端编码不能为空")
@Pattern(regexp = "^[a-z0-9-]+$", message = "客户端编码只能包含小写字母、数字和短横线")
private String clientCode;
@Schema(description = "客户端名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "A分公司")
@NotBlank(message = "客户端名称不能为空")
private String clientName;
@Schema(description = "启用状态0-禁用 1-启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "启用状态不能为空")
private Integer enabled;
@Schema(description = "通信方式MQ_FIRST-MQ优先 HTTP_ONLY-仅HTTP", requiredMode = Schema.RequiredMode.REQUIRED, example = "MQ_FIRST")
@NotBlank(message = "通信方式不能为空")
private String transportType;
@Schema(description = "MQ是否启用0-否 1-是)", example = "1")
private Integer mqEnabled;
@Schema(description = "MQ NameServer地址", example = "localhost:9876")
private String mqNamesrvAddr;
@Schema(description = "MQ Topic基础名称", example = "databus-sync")
private String mqTopicBase;
@Schema(description = "HTTP是否启用0-否 1-是)", example = "1")
private Integer httpEnabled;
@Schema(description = "HTTP推送端点", example = "http://example.com/databus/sync/receive")
private String httpEndpoint;
@Schema(description = "应用Key", example = "app-key-001")
private String appKey;
@Schema(description = "应用Secret加密存储", example = "secret")
private String appSecret;
}

View File

@@ -0,0 +1,43 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.deadletter;
import com.zt.plat.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 数据同步死信队列分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DatabusSyncDeadLetterPageReqVO extends PageParam {
@Schema(description = "同步ID", example = "sync-20231124-001")
private String syncId;
@Schema(description = "客户端编码", example = "company-a")
private String clientCode;
@Schema(description = "事件类型", example = "user-changed")
private String eventType;
@Schema(description = "状态PENDING-待处理 REDELIVERED-已重新投递 IGNORED-已忽略)", example = "PENDING")
private String status;
@Schema(description = "是否已处理0-否 1-是)", example = "0")
private Integer handled;
@Schema(description = "创建时间开始", example = "2023-11-24 00:00:00")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime createTimeStart;
@Schema(description = "创建时间结束", example = "2023-11-24 23:59:59")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime createTimeEnd;
}

View File

@@ -0,0 +1,63 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.deadletter;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 数据同步死信队列 Response VO")
@Data
public class DatabusSyncDeadLetterRespVO {
@Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "同步ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "sync-20231124-001")
private String syncId;
@Schema(description = "同步日志ID", example = "10001")
private Long syncLogId;
@Schema(description = "客户端编码", example = "company-a")
private String clientCode;
@Schema(description = "事件类型", example = "user-changed")
private String eventType;
@Schema(description = "消息体JSON", example = "{\"userId\":1001,\"userName\":\"张三\"}")
private String messageBody;
@Schema(description = "状态PENDING-待处理 REDELIVERED-已重新投递 IGNORED-已忽略)", example = "PENDING")
private String status;
@Schema(description = "失败原因", example = "连接超时")
private String lastErrorMessage;
@Schema(description = "失败时间")
private LocalDateTime lastErrorTime;
@Schema(description = "重试次数", example = "5")
private Integer retryCount;
@Schema(description = "是否已处理0-否 1-是)", example = "0")
private Integer handled;
@Schema(description = "处理时间")
private LocalDateTime handleTime;
@Schema(description = "处理人", example = "admin")
private String handler;
@Schema(description = "处理备注", example = "已通知开发人员修复")
private String handleRemark;
@Schema(description = "租户ID", example = "1")
private Long tenantId;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,24 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.event;
import com.zt.plat.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 数据同步事件分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DatabusSyncEventPageReqVO extends PageParam {
@Schema(description = "事件类型编码", example = "user-changed")
private String eventType;
@Schema(description = "事件名称", example = "用户信息变更")
private String eventName;
@Schema(description = "启用状态0-禁用 1-启用)", example = "1")
private Integer enabled;
}

View File

@@ -0,0 +1,54 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.event;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 数据同步事件 Response VO")
@Data
public class DatabusSyncEventRespVO {
@Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "租户ID", example = "1")
private Long tenantId;
@Schema(description = "事件类型编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "system-user-full")
private String eventType;
@Schema(description = "事件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "用户信息变更")
private String eventName;
@Schema(description = "事件描述", example = "用户基本信息变更事件")
private String eventDesc;
@Schema(description = "启用状态0-禁用 1-启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer enabled;
@Schema(description = "是否支持全量同步0-否 1-是)", example = "1")
private Integer supportFullSync;
@Schema(description = "是否支持增量同步0-否 1-是)", example = "1")
private Integer supportIncrementalSync;
@Schema(description = "数据结构版本", example = "1")
private Integer dataVersion;
@Schema(description = "数据提供者类型(全量同步使用)", example = "ORG")
private String dataProviderMethod;
@Schema(description = "创建者", example = "admin")
private String creator;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新者", example = "admin")
private String updater;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,45 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.event;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
@Schema(description = "管理后台 - 数据同步事件创建/修改 Request VO")
@Data
public class DatabusSyncEventSaveReqVO {
@Schema(description = "主键ID", example = "1")
private Long id;
@Schema(description = "事件类型编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "system-user-full")
@NotBlank(message = "事件类型编码不能为空")
@Pattern(regexp = "^[a-z0-9-]+$", message = "事件类型编码只能包含小写字母、数字和短横线")
private String eventType;
@Schema(description = "事件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "用户信息变更")
@NotBlank(message = "事件名称不能为空")
private String eventName;
@Schema(description = "事件描述", example = "用户基本信息变更事件")
private String eventDesc;
@Schema(description = "启用状态0-禁用 1-启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "启用状态不能为空")
private Integer enabled;
@Schema(description = "数据提供者类型(全量同步使用)", example = "ORG")
private String dataProviderMethod;
@Schema(description = "是否支持全量同步0-否 1-是)", example = "1")
private Integer supportFullSync;
@Schema(description = "是否支持增量同步0-否 1-是)", example = "1")
private Integer supportIncrementalSync;
@Schema(description = "数据结构版本", example = "1")
private Integer dataVersion;
}

View File

@@ -0,0 +1,18 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.fulltask;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - 全量同步任务创建 Request VO")
@Data
public class DatabusSyncFullTaskCreateReqVO {
@Schema(description = "订阅关系ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "订阅关系ID不能为空")
private Long subscriptionId;
@Schema(description = "备注", example = "手动触发全量同步")
private String remark;
}

View File

@@ -0,0 +1,30 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.fulltask;
import com.zt.plat.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 全量同步任务分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DatabusSyncFullTaskPageReqVO extends PageParam {
@Schema(description = "任务编号", example = "abc123")
private String taskNo;
@Schema(description = "订阅关系ID", example = "1")
private Long subscriptionId;
@Schema(description = "客户端编码", example = "client-01")
private String clientCode;
@Schema(description = "事件类型", example = "ORG_SYNC")
private String eventType;
@Schema(description = "任务状态0-待执行 1-执行中 2-已完成 3-失败 4-已取消)", example = "2")
private Integer status;
}

View File

@@ -0,0 +1,88 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.fulltask;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 全量同步任务 Response VO")
@Data
public class DatabusSyncFullTaskRespVO {
@Schema(description = "主键ID", example = "1")
private Long id;
@Schema(description = "任务编号", example = "abc123")
private String taskNo;
@Schema(description = "订阅关系ID", example = "1")
private Long subscriptionId;
@Schema(description = "客户端编码", example = "client-01")
private String clientCode;
@Schema(description = "事件类型", example = "ORG_SYNC")
private String eventType;
@Schema(description = "任务状态0-待执行 1-执行中 2-已完成 3-失败 4-已取消)", example = "2")
private Integer status;
@Schema(description = "总数据量", example = "1000")
private Long totalCount;
@Schema(description = "已处理数据量", example = "500")
private Long processedCount;
@Schema(description = "成功数据量", example = "480")
private Long successCount;
@Schema(description = "失败数据量", example = "20")
private Long failCount;
@Schema(description = "总批次数", example = "10")
private Integer totalBatch;
@Schema(description = "当前批次号", example = "5")
private Integer currentBatch;
@Schema(description = "批量大小", example = "100")
private Integer batchSize;
@Schema(description = "任务开始时间")
private LocalDateTime startTime;
@Schema(description = "任务结束时间")
private LocalDateTime endTime;
@Schema(description = "最后一次错误信息", example = "网络超时")
private String lastErrorMessage;
@Schema(description = "租户ID", example = "1")
private Long tenantId;
@Schema(description = "备注", example = "手动触发全量同步")
private String remark;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
@Schema(description = "进度百分比", example = "50.0")
private Double progressPercent;
/**
* 计算进度百分比
*/
public Double getProgressPercent() {
if (totalCount == null || totalCount == 0) {
return 0.0;
}
if (processedCount == null) {
return 0.0;
}
return Math.round(processedCount * 10000.0 / totalCount) / 100.0;
}
}

View File

@@ -0,0 +1,46 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.pushlog;
import com.zt.plat.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.zt.plat.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 数据同步推送日志分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DatabusSyncPushLogPageReqVO extends PageParam {
@Schema(description = "同步ID", example = "sync-20231124-001")
private String syncId;
@Schema(description = "订阅关系ID", example = "1")
private Long subscriptionId;
@Schema(description = "客户端编码", example = "company-a")
private String clientCode;
@Schema(description = "事件类型", example = "user-changed")
private String eventType;
@Schema(description = "状态PENDING-待处理 SUCCESS-成功 FAILED-失败 RETRYING-重试中)", example = "SUCCESS")
private String status;
@Schema(description = "传输方式MQ/HTTP", example = "MQ")
private String transportType;
@Schema(description = "创建时间开始", example = "2023-11-24 00:00:00")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime createTimeStart;
@Schema(description = "创建时间结束", example = "2023-11-24 23:59:59")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime createTimeEnd;
}

View File

@@ -0,0 +1,72 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.pushlog;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 数据同步推送日志 Response VO")
@Data
public class DatabusSyncPushLogRespVO {
@Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "同步ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "sync-20231124-001")
private String syncId;
@Schema(description = "事件记录ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "10001")
private Long eventRecordId;
@Schema(description = "订阅关系ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long subscriptionId;
@Schema(description = "客户端编码", example = "company-a")
private String clientCode;
@Schema(description = "事件类型", example = "user-changed")
private String eventType;
@Schema(description = "同步模式FULL-全量 INCREMENTAL-增量)", example = "INCREMENTAL")
private String syncMode;
@Schema(description = "传输方式MQ/HTTP", requiredMode = Schema.RequiredMode.REQUIRED, example = "MQ")
private String transportType;
@Schema(description = "MQ Topic", example = "databus-sync-user-changed-company-a")
private String mqTopic;
@Schema(description = "MQ消息ID", example = "C0A8012300002A9F0000000000000001")
private String mqMsgId;
@Schema(description = "状态PENDING-待处理 SUCCESS-成功 FAILED-失败 RETRYING-重试中)", requiredMode = Schema.RequiredMode.REQUIRED, example = "SUCCESS")
private String status;
@Schema(description = "重试次数", example = "0")
private Integer retryCount;
@Schema(description = "错误信息", example = "连接超时")
private String errorMessage;
@Schema(description = "开始时间")
private LocalDateTime startTime;
@Schema(description = "结束时间")
private LocalDateTime endTime;
@Schema(description = "耗时(毫秒)", example = "150")
private Integer duration;
@Schema(description = "租户ID", example = "1")
private Long tenantId;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
@Schema(description = "推送消息内容JSON", example = "{\"id\":1,\"name\":\"张三\"}")
private String dataSnapshot;
}

View File

@@ -0,0 +1,24 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.subscription;
import com.zt.plat.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - 数据同步订阅分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DatabusSyncSubscriptionPageReqVO extends PageParam {
@Schema(description = "客户端ID", example = "1")
private Long clientId;
@Schema(description = "事件ID", example = "1")
private Long eventId;
@Schema(description = "启用状态0-禁用 1-启用)", example = "1")
private Integer enabled;
}

View File

@@ -0,0 +1,78 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.subscription;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 数据同步订阅 Response VO")
@Data
public class DatabusSyncSubscriptionRespVO {
@Schema(description = "主键ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "租户ID", example = "1")
private Long tenantId;
@Schema(description = "客户端ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long clientId;
@Schema(description = "客户端编码(关联查询)", example = "company-a")
private String clientCode;
@Schema(description = "客户端名称(关联查询)", example = "A分公司")
private String clientName;
@Schema(description = "事件ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long eventId;
@Schema(description = "事件类型(关联查询)", example = "user-changed")
private String eventType;
@Schema(description = "事件名称(关联查询)", example = "用户信息变更")
private String eventName;
@Schema(description = "启用状态0-禁用 1-启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer enabled;
@Schema(description = "同步模式FULL-全量 INCREMENTAL-增量)", example = "INCREMENTAL")
private String syncMode;
@Schema(description = "时效性REALTIME-实时 NEAR_REALTIME-准实时 BATCH-批量)", example = "NEAR_REALTIME")
private String timeliness;
@Schema(description = "时效值(秒)", example = "60")
private Integer timelinessValue;
@Schema(description = "批量大小", example = "500")
private Integer batchSize;
@Schema(description = "最后同步的事件记录ID", example = "10001")
private Long lastSyncEventId;
@Schema(description = "最后同步时间")
private LocalDateTime lastSyncTime;
@Schema(description = "总同步次数", example = "100")
private Long totalSyncCount;
@Schema(description = "成功次数", example = "95")
private Long totalSuccessCount;
@Schema(description = "失败次数", example = "5")
private Long totalFailCount;
@Schema(description = "创建者", example = "admin")
private String creator;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新者", example = "admin")
private String updater;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,39 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.subscription;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
@Schema(description = "管理后台 - 数据同步订阅创建/修改 Request VO")
@Data
public class DatabusSyncSubscriptionSaveReqVO {
@Schema(description = "主键ID", example = "1")
private Long id;
@Schema(description = "客户端ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "客户端ID不能为空")
private Long clientId;
@Schema(description = "事件ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "事件ID不能为空")
private Long eventId;
@Schema(description = "启用状态0-禁用 1-启用)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "启用状态不能为空")
private Integer enabled;
@Schema(description = "同步模式FULL-全量 INCREMENTAL-增量)", example = "INCREMENTAL")
private String syncMode;
@Schema(description = "时效性REALTIME-实时 NEAR_REALTIME-准实时 BATCH-批量)", example = "NEAR_REALTIME")
private String timeliness;
@Schema(description = "时效值(秒)", example = "60")
private Integer timelinessValue;
@Schema(description = "批量大小", example = "500")
private Integer batchSize;
}

View File

@@ -0,0 +1,25 @@
package com.zt.plat.framework.databus.server.convert;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientRespVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientSaveReqVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncClientDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface DatabusSyncClientConvert {
DatabusSyncClientConvert INSTANCE = Mappers.getMapper(DatabusSyncClientConvert.class);
DatabusSyncClientDO convert(DatabusSyncClientSaveReqVO bean);
DatabusSyncClientRespVO convert(DatabusSyncClientDO bean);
List<DatabusSyncClientRespVO> convertList(List<DatabusSyncClientDO> list);
PageResult<DatabusSyncClientRespVO> convertPage(PageResult<DatabusSyncClientDO> page);
}

View File

@@ -0,0 +1,22 @@
package com.zt.plat.framework.databus.server.convert;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.deadletter.DatabusSyncDeadLetterRespVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncDeadLetterDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface DatabusSyncDeadLetterConvert {
DatabusSyncDeadLetterConvert INSTANCE = Mappers.getMapper(DatabusSyncDeadLetterConvert.class);
DatabusSyncDeadLetterRespVO convert(DatabusSyncDeadLetterDO bean);
List<DatabusSyncDeadLetterRespVO> convertList(List<DatabusSyncDeadLetterDO> list);
PageResult<DatabusSyncDeadLetterRespVO> convertPage(PageResult<DatabusSyncDeadLetterDO> page);
}

View File

@@ -0,0 +1,25 @@
package com.zt.plat.framework.databus.server.convert;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventRespVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventSaveReqVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface DatabusSyncEventConvert {
DatabusSyncEventConvert INSTANCE = Mappers.getMapper(DatabusSyncEventConvert.class);
DatabusSyncEventDO convert(DatabusSyncEventSaveReqVO bean);
DatabusSyncEventRespVO convert(DatabusSyncEventDO bean);
List<DatabusSyncEventRespVO> convertList(List<DatabusSyncEventDO> list);
PageResult<DatabusSyncEventRespVO> convertPage(PageResult<DatabusSyncEventDO> page);
}

View File

@@ -0,0 +1,22 @@
package com.zt.plat.framework.databus.server.convert;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.fulltask.DatabusSyncFullTaskRespVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncFullTaskDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface DatabusSyncFullTaskConvert {
DatabusSyncFullTaskConvert INSTANCE = Mappers.getMapper(DatabusSyncFullTaskConvert.class);
DatabusSyncFullTaskRespVO convert(DatabusSyncFullTaskDO bean);
List<DatabusSyncFullTaskRespVO> convertList(List<DatabusSyncFullTaskDO> list);
PageResult<DatabusSyncFullTaskRespVO> convertPage(PageResult<DatabusSyncFullTaskDO> page);
}

View File

@@ -0,0 +1,22 @@
package com.zt.plat.framework.databus.server.convert;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.pushlog.DatabusSyncPushLogRespVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncLogDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface DatabusSyncPushLogConvert {
DatabusSyncPushLogConvert INSTANCE = Mappers.getMapper(DatabusSyncPushLogConvert.class);
DatabusSyncPushLogRespVO convert(DatabusSyncLogDO bean);
List<DatabusSyncPushLogRespVO> convertList(List<DatabusSyncLogDO> list);
PageResult<DatabusSyncPushLogRespVO> convertPage(PageResult<DatabusSyncLogDO> page);
}

View File

@@ -0,0 +1,25 @@
package com.zt.plat.framework.databus.server.convert;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionRespVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionSaveReqVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncSubscriptionDO;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface DatabusSyncSubscriptionConvert {
DatabusSyncSubscriptionConvert INSTANCE = Mappers.getMapper(DatabusSyncSubscriptionConvert.class);
DatabusSyncSubscriptionDO convert(DatabusSyncSubscriptionSaveReqVO bean);
DatabusSyncSubscriptionRespVO convert(DatabusSyncSubscriptionDO bean);
List<DatabusSyncSubscriptionRespVO> convertList(List<DatabusSyncSubscriptionDO> list);
PageResult<DatabusSyncSubscriptionRespVO> convertPage(PageResult<DatabusSyncSubscriptionDO> page);
}

View File

@@ -0,0 +1,71 @@
package com.zt.plat.framework.databus.server.core.event;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* Databus 事件对象
*
* @author ZT
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DatabusEvent {
/**
* 事件类型编码
*/
private String eventType;
/**
* 事件动作create-创建 update-更新 delete-删除)
*/
private String eventAction;
/**
* 完整业务数据快照JSON格式
*/
private String dataSnapshot;
/**
* 数据版本号
*/
private Integer dataVersion;
/**
* 来源服务名
*/
private String sourceService;
/**
* 来源MQ Topic
*/
private String sourceTopic;
/**
* 来源MQ消息ID
*/
private String sourceMsgId;
/**
* 租户ID
*/
private Long tenantId;
/**
* 操作人
*/
private String operator;
/**
* 事件发生时间
*/
private LocalDateTime eventTime;
}

View File

@@ -0,0 +1,121 @@
package com.zt.plat.framework.databus.server.core.message;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 批量数据同步消息
* <p>
* 用于全量同步和批量增量同步,一条消息包含多条数据
*
* @author ZT
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BatchSyncMessage {
/**
* 消息ID唯一标识
*/
private String messageId;
/**
* 请求ID用于追踪
*/
private String requestId;
/**
* 事件类型
*/
private String eventType;
/**
* 同步模式1-全量 2-增量)
*/
private Integer syncMode;
/**
* 数据结构版本
*/
private Integer dataVersion;
/**
* 消息时间戳
*/
private Long timestamp;
/**
* 租户ID
*/
private Long tenantId;
/**
* 数据列表
*/
private List<SyncDataItem> dataList;
/**
* 本批次数据条数
*/
private Integer count;
// ========== 全量同步相关字段 ==========
/**
* 全量任务ID
*/
private Long fullTaskId;
/**
* 当前批次号
*/
private Integer batchNo;
/**
* 总批次数
*/
private Integer totalBatch;
/**
* 是否最后一批
*/
private Boolean isLastBatch;
/**
* 总数据量
*/
private Long totalCount;
/**
* 同步数据项
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class SyncDataItem {
/**
* 操作类型CREATE/UPDATE/DELETE
*/
private String action;
/**
* 业务数据ID
*/
private Long uid;
/**
* 业务数据快照JSON
*/
private String data;
}
}

View File

@@ -0,0 +1,54 @@
package com.zt.plat.framework.databus.server.core.message;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 数据同步消息
*
* @author ZT
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SyncMessage {
/**
* 同步ID唯一标识
*/
private String syncId;
/**
* 事件记录ID
*/
private Long eventRecordId;
/**
* 事件类型
*/
private String eventType;
/**
* 事件动作
*/
private String eventAction;
/**
* 业务数据快照JSON
*/
private String dataSnapshot;
/**
* 数据版本
*/
private Integer dataVersion;
/**
* 时间戳
*/
private Long timestamp;
}

View File

@@ -0,0 +1,120 @@
package com.zt.plat.framework.databus.server.core.provider;
import java.time.LocalDateTime;
import java.util.List;
/**
* 数据提供者接口
* <p>
* 用于全量同步时获取数据,由业务模块实现具体的数据获取逻辑
*
* @author ZT
*/
public interface DataProvider<T> {
/**
* 获取数据提供者类型标识
*
* @return 类型标识,如 "ORG"、"USER"
*/
String getProviderType();
/**
* 游标分页获取数据
*
* @param cursorTime 游标时间(首次查询传 null
* @param cursorId 游标ID首次查询传 null
* @param batchSize 批量大小
* @param tenantId 租户ID
* @return 分页结果
*/
CursorPageData<T> getPageByCursor(LocalDateTime cursorTime, Long cursorId, int batchSize, Long tenantId);
/**
* 统计总数
*
* @param tenantId 租户ID
* @return 总数量
*/
long count(Long tenantId);
/**
* 从数据对象中提取 UID
*
* @param data 数据对象
* @return UID
*/
Long extractUid(T data);
/**
* 游标分页数据结果
*/
class CursorPageData<T> {
private List<T> list;
private LocalDateTime nextCursorTime;
private Long nextCursorId;
private int count;
private boolean hasMore;
private long total;
public List<T> getList() {
return list;
}
public void setList(List<T> list) {
this.list = list;
}
public LocalDateTime getNextCursorTime() {
return nextCursorTime;
}
public void setNextCursorTime(LocalDateTime nextCursorTime) {
this.nextCursorTime = nextCursorTime;
}
public Long getNextCursorId() {
return nextCursorId;
}
public void setNextCursorId(Long nextCursorId) {
this.nextCursorId = nextCursorId;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public boolean isHasMore() {
return hasMore;
}
public void setHasMore(boolean hasMore) {
this.hasMore = hasMore;
}
public long getTotal() {
return total;
}
public void setTotal(long total) {
this.total = total;
}
public static <T> CursorPageData<T> of(List<T> list, LocalDateTime nextCursorTime, Long nextCursorId,
int count, boolean hasMore, long total) {
CursorPageData<T> data = new CursorPageData<>();
data.setList(list);
data.setNextCursorTime(nextCursorTime);
data.setNextCursorId(nextCursorId);
data.setCount(count);
data.setHasMore(hasMore);
data.setTotal(total);
return data;
}
}
}

View File

@@ -0,0 +1,62 @@
package com.zt.plat.framework.databus.server.core.provider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 数据提供者注册中心
* <p>
* 管理所有注册的数据提供者,根据类型获取对应的提供者
*
* @author ZT
*/
@Slf4j
@Component
public class DataProviderRegistry {
private final Map<String, DataProvider<?>> providers = new ConcurrentHashMap<>();
/**
* 注册数据提供者
*
* @param provider 数据提供者
*/
public void register(DataProvider<?> provider) {
String type = provider.getProviderType();
providers.put(type, provider);
log.info("[Databus] 数据提供者已注册: type={}", type);
}
/**
* 获取数据提供者
*
* @param type 类型标识
* @return 数据提供者,不存在则返回 null
*/
@SuppressWarnings("unchecked")
public <T> DataProvider<T> getProvider(String type) {
return (DataProvider<T>) providers.get(type);
}
/**
* 检查是否存在指定类型的提供者
*
* @param type 类型标识
* @return 是否存在
*/
public boolean hasProvider(String type) {
return providers.containsKey(type);
}
/**
* 获取所有已注册的提供者类型
*
* @return 类型列表
*/
public java.util.Set<String> getRegisteredTypes() {
return providers.keySet();
}
}

View File

@@ -0,0 +1,28 @@
package com.zt.plat.framework.databus.server.core.publisher;
import com.zt.plat.framework.databus.server.core.event.DatabusEvent;
/**
* Databus 事件发布器接口
* 用于发布业务数据变更事件到同步流水表
*
* @author ZT
*/
public interface DatabusEventPublisher {
/**
* 发布事件(异步)
*
* @param event 事件对象
*/
void publish(DatabusEvent event);
/**
* 发布事件(同步,等待入库完成)
*
* @param event 事件对象
* @return 事件记录ID
*/
Long publishSync(DatabusEvent event);
}

View File

@@ -0,0 +1,60 @@
package com.zt.plat.framework.databus.server.core.publisher;
import cn.hutool.core.bean.BeanUtil;
import com.zt.plat.framework.databus.server.core.event.DatabusEvent;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventRecordDO;
import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncEventRecordMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* Databus 事件发布器实现
*
* @author ZT
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class DatabusEventPublisherImpl implements DatabusEventPublisher {
private final DatabusSyncEventRecordMapper eventRecordMapper;
@Async
@Override
public void publish(DatabusEvent event) {
try {
saveEventRecord(event);
} catch (Exception e) {
log.error("[Databus] 发布事件失败, eventType={}, eventAction={}",
event.getEventType(), event.getEventAction(), e);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long publishSync(DatabusEvent event) {
return saveEventRecord(event);
}
/**
* 保存事件记录到流水表
*/
private Long saveEventRecord(DatabusEvent event) {
// 查询事件定义ID这里简化处理实际应该注入EventMapper查询
// TODO: 根据 eventType 查询 event_id
DatabusSyncEventRecordDO record = new DatabusSyncEventRecordDO();
BeanUtil.copyProperties(event, record);
eventRecordMapper.insert(record);
log.info("[Databus] 事件记录已保存, id={}, eventType={}, eventAction={}",
record.getId(), event.getEventType(), event.getEventAction());
return record.getId();
}
}

View File

@@ -0,0 +1,50 @@
package com.zt.plat.framework.databus.server.core.pusher;
import com.zt.plat.framework.databus.server.core.message.BatchSyncMessage;
import com.zt.plat.framework.databus.server.core.message.SyncMessage;
/**
* 消息推送器接口
* 支持 MQ 和 HTTP 两种推送方式
*
* @author ZT
*/
public interface MessagePusher {
/**
* 通过 MQ 推送消息
*
* @param topic Topic
* @param message 消息内容
* @return 消息ID
*/
String pushByMQ(String topic, SyncMessage message);
/**
* 通过 HTTP 推送消息
*
* @param endpoint HTTP端点
* @param message 消息内容
* @return 是否成功
*/
boolean pushByHttp(String endpoint, SyncMessage message);
/**
* 通过 MQ 推送批量消息
*
* @param topic Topic
* @param message 批量消息内容
* @return 消息ID
*/
String pushBatchByMQ(String topic, BatchSyncMessage message);
/**
* 通过 HTTP 推送批量消息
*
* @param endpoint HTTP端点
* @param message 批量消息内容
* @return 是否成功
*/
boolean pushBatchByHttp(String endpoint, BatchSyncMessage message);
}

View File

@@ -0,0 +1,103 @@
package com.zt.plat.framework.databus.server.core.pusher;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.zt.plat.framework.databus.server.core.message.BatchSyncMessage;
import com.zt.plat.framework.databus.server.core.message.SyncMessage;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
/**
* 消息推送器实现
*
* @author ZT
*/
@Slf4j
public class MessagePusherImpl implements MessagePusher {
private RocketMQTemplate rocketMQTemplate;
public void setRocketMQTemplate(RocketMQTemplate rocketMQTemplate) {
this.rocketMQTemplate = rocketMQTemplate;
}
@Override
public String pushByMQ(String topic, SyncMessage message) {
if (rocketMQTemplate == null) {
log.warn("[Databus] RocketMQTemplate未配置无法推送MQ消息");
throw new RuntimeException("RocketMQTemplate未配置");
}
try {
// 使用 RocketMQ 发送消息
SendResult sendResult = rocketMQTemplate.syncSend(topic, message);
if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
log.error("[Databus] MQ推送失败, topic={}, syncId={}, status={}, msgId={}",
topic, message.getSyncId(), sendResult.getSendStatus(), sendResult.getMsgId());
throw new RuntimeException("MQ推送失败: " + sendResult.getSendStatus());
}
log.info("[Databus] MQ推送成功, topic={}, syncId={}, msgId={}",
topic, message.getSyncId(), sendResult.getMsgId());
return message.getSyncId();
} catch (Exception e) {
log.error("[Databus] MQ推送失败, topic={}, syncId={}", topic, message.getSyncId(), e);
throw new RuntimeException("MQ推送失败", e);
}
}
@Override
public boolean pushByHttp(String endpoint, SyncMessage message) {
try {
String jsonBody = JSONUtil.toJsonStr(message);
String response = HttpUtil.post(endpoint, jsonBody);
log.info("[Databus] HTTP推送成功, endpoint={}, syncId={}", endpoint, message.getSyncId());
return true;
} catch (Exception e) {
log.error("[Databus] HTTP推送失败, endpoint={}, syncId={}", endpoint, message.getSyncId(), e);
return false;
}
}
@Override
public String pushBatchByMQ(String topic, BatchSyncMessage message) {
if (rocketMQTemplate == null) {
log.warn("[Databus] RocketMQTemplate未配置无法推送MQ消息");
throw new RuntimeException("RocketMQTemplate未配置");
}
try {
// 使用 RocketMQ 发送批量消息
SendResult sendResult = rocketMQTemplate.syncSend(topic, message);
if (sendResult.getSendStatus() != SendStatus.SEND_OK) {
log.error("[Databus] 批量MQ推送失败, topic={}, messageId={}, status={}, mqMsgId={}",
topic, message.getMessageId(), sendResult.getSendStatus(), sendResult.getMsgId());
throw new RuntimeException("批量MQ推送失败: " + sendResult.getSendStatus());
}
log.info("[Databus] 批量MQ推送成功, topic={}, messageId={}, count={}, mqMsgId={}",
topic, message.getMessageId(), message.getCount(), sendResult.getMsgId());
return message.getMessageId();
} catch (Exception e) {
log.error("[Databus] 批量MQ推送失败, topic={}, messageId={}",
topic, message.getMessageId(), e);
throw new RuntimeException("批量MQ推送失败", e);
}
}
@Override
public boolean pushBatchByHttp(String endpoint, BatchSyncMessage message) {
try {
String jsonBody = JSONUtil.toJsonStr(message);
String response = HttpUtil.post(endpoint, jsonBody);
log.info("[Databus] 批量HTTP推送成功, endpoint={}, messageId={}, count={}",
endpoint, message.getMessageId(), message.getCount());
return true;
} catch (Exception e) {
log.error("[Databus] 批量HTTP推送失败, endpoint={}, messageId={}",
endpoint, message.getMessageId(), e);
return false;
}
}
}

View File

@@ -0,0 +1,51 @@
package com.zt.plat.framework.databus.server.core.sync;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncFullTaskDO;
/**
* Databus 全量同步服务接口
*
* @author ZT
*/
public interface DatabusFullSyncService {
/**
* 创建全量同步任务
*
* @param subscriptionId 订阅关系ID
* @param remark 备注
* @return 任务ID
*/
Long createFullSyncTask(Long subscriptionId, String remark);
/**
* 执行全量同步任务
*
* @param taskId 任务ID
*/
void executeFullSyncTask(Long taskId);
/**
* 取消全量同步任务
*
* @param taskId 任务ID
*/
void cancelFullSyncTask(Long taskId);
/**
* 获取任务详情
*
* @param taskId 任务ID
* @return 任务详情
*/
DatabusSyncFullTaskDO getTaskById(Long taskId);
/**
* 根据任务编号获取任务详情
*
* @param taskNo 任务编号
* @return 任务详情
*/
DatabusSyncFullTaskDO getTaskByTaskNo(String taskNo);
}

View File

@@ -0,0 +1,27 @@
package com.zt.plat.framework.databus.server.core.sync;
import com.zt.plat.framework.databus.server.core.event.DatabusEvent;
/**
* Databus 增量同步服务接口
* 用于实时处理业务MQ消息进行三态判断、记录流水、推送到客户端Topic
*
* @author ZT
*/
public interface DatabusIncrementalSyncService {
/**
* 处理增量同步事件
* <p>
* 核心流程:
* 1. 根据 eventType 查询事件定义,判断是否启用
* 2. 查询所有启用的订阅关系
* 3. 对每个订阅进行三态判断(事件/客户端/订阅是否启用)
* 4. 记录到 databus_sync_event_record 流水表
* 5. 推送到客户端专属 Topicdatabus-sync-{eventType}-{clientCode}
*
* @param event 业务变更事件
*/
void processEvent(DatabusEvent event);
}

View File

@@ -0,0 +1,246 @@
package com.zt.plat.framework.databus.server.core.sync;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.IdUtil;
import com.zt.plat.framework.databus.server.core.event.DatabusEvent;
import com.zt.plat.framework.databus.server.core.message.SyncMessage;
import com.zt.plat.framework.databus.server.core.pusher.MessagePusher;
import com.zt.plat.framework.databus.server.dal.dataobject.*;
import com.zt.plat.framework.databus.server.dal.mapper.*;
import com.zt.plat.framework.databus.server.enums.SyncStatusEnum;
import com.zt.plat.framework.databus.server.enums.TransportTypeEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
/**
* Databus 增量同步服务实现
* <p>
* 核心流程:
* 1. 根据 eventType 查询事件定义,判断是否启用
* 2. 记录到 event_record 流水表
* 3. 查询所有启用的订阅关系
* 4. 对每个订阅进行三态判断(事件/客户端/订阅是否启用)
* 5. 推送到客户端专属 Topicdatabus-sync-{eventType}-{clientCode}
*
* @author ZT
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DatabusIncrementalSyncServiceImpl implements DatabusIncrementalSyncService {
private final DatabusSyncEventMapper eventMapper;
private final DatabusSyncEventRecordMapper eventRecordMapper;
private final DatabusSyncSubscriptionMapper subscriptionMapper;
private final DatabusSyncClientMapper clientMapper;
private final DatabusSyncLogMapper syncLogMapper;
private final MessagePusher messagePusher;
@Override
@Transactional(rollbackFor = Exception.class)
public void processEvent(DatabusEvent event) {
log.info("[Databus增量同步] 开始处理事件, eventType={}, eventAction={}",
event.getEventType(), event.getEventAction());
// 1. 查询事件定义
DatabusSyncEventDO eventDef = eventMapper.selectByEventType(event.getEventType());
if (eventDef == null) {
log.warn("[Databus增量同步] 事件类型未定义, eventType={}", event.getEventType());
return;
}
// 2. 判断事件是否启用(三态判断之一)
if (eventDef.getEnabled() != 1) {
log.debug("[Databus增量同步] 事件未启用, eventType={}", event.getEventType());
return;
}
// 3. 判断事件是否支持增量同步
if (eventDef.getSupportIncrementalSync() != 1) {
log.debug("[Databus增量同步] 事件不支持增量同步, eventType={}", event.getEventType());
return;
}
// 4. 记录到 event_record 流水表
DatabusSyncEventRecordDO eventRecord = saveEventRecord(event, eventDef);
// 5. 查询所有启用的订阅关系
List<DatabusSyncSubscriptionDO> subscriptions = subscriptionMapper.selectEnabledByEventId(eventDef.getId());
if (subscriptions.isEmpty()) {
log.debug("[Databus增量同步] 无启用的订阅, eventType={}", event.getEventType());
return;
}
log.info("[Databus增量同步] 找到{}个订阅需要推送, eventType={}",
subscriptions.size(), event.getEventType());
// 6. 对每个订阅进行推送
for (DatabusSyncSubscriptionDO subscription : subscriptions) {
try {
pushToSubscriber(subscription, eventDef, eventRecord);
} catch (Exception e) {
log.error("[Databus增量同步] 推送到订阅者失败, subscriptionId={}, eventType={}",
subscription.getId(), event.getEventType(), e);
}
}
log.info("[Databus增量同步] 事件处理完成, eventType={}, eventAction={}, recordId={}",
event.getEventType(), event.getEventAction(), eventRecord.getId());
}
/**
* 保存事件记录到流水表
*/
private DatabusSyncEventRecordDO saveEventRecord(DatabusEvent event, DatabusSyncEventDO eventDef) {
DatabusSyncEventRecordDO record = new DatabusSyncEventRecordDO();
BeanUtil.copyProperties(event, record);
record.setEventId(eventDef.getId());
eventRecordMapper.insert(record);
log.info("[Databus增量同步] 事件记录已保存, recordId={}, eventType={}, eventAction={}",
record.getId(), event.getEventType(), event.getEventAction());
return record;
}
/**
* 推送到单个订阅者
*/
private void pushToSubscriber(DatabusSyncSubscriptionDO subscription,
DatabusSyncEventDO eventDef,
DatabusSyncEventRecordDO eventRecord) {
// 查询客户端信息
DatabusSyncClientDO client = clientMapper.selectById(subscription.getClientId());
if (client == null) {
log.warn("[Databus增量同步] 客户端不存在, clientId={}", subscription.getClientId());
return;
}
// 三态判断之二:客户端是否启用
if (client.getEnabled() != 1) {
log.debug("[Databus增量同步] 客户端未启用, clientCode={}", client.getClientCode());
return;
}
// 三态判断之三:订阅是否启用(已在查询时过滤,这里再确认一次)
if (subscription.getEnabled() != 1) {
log.debug("[Databus增量同步] 订阅未启用, subscriptionId={}", subscription.getId());
return;
}
String syncId = IdUtil.fastSimpleUUID();
LocalDateTime startTime = LocalDateTime.now();
// 构建同步消息
SyncMessage message = SyncMessage.builder()
.syncId(syncId)
.eventRecordId(eventRecord.getId())
.eventType(eventRecord.getEventType())
.eventAction(eventRecord.getEventAction())
.dataSnapshot(eventRecord.getDataSnapshot())
.dataVersion(eventRecord.getDataVersion())
.timestamp(System.currentTimeMillis())
.build();
// 构建同步日志
DatabusSyncLogDO syncLog = DatabusSyncLogDO.builder()
.syncId(syncId)
.eventRecordId(eventRecord.getId())
.subscriptionId(subscription.getId())
.clientCode(client.getClientCode())
.eventType(eventRecord.getEventType())
.syncMode(2) // 增量同步
.startTime(startTime)
.status(SyncStatusEnum.PENDING.getStatus())
.retryCount(0)
.dataCount(1)
.tenantId(client.getTenantId())
.build();
try {
// 根据客户端配置选择推送方式
if (TransportTypeEnum.isMqFirst(client.getTransportType()) && client.getMqEnabled() == 1) {
// MQ 推送Topic格式 = databus-sync-{eventType}-{clientCode}
String topic = buildClientTopic(client.getMqTopicBase(), eventDef.getEventType(), client.getClientCode());
String mqMsgId = messagePusher.pushByMQ(topic, message);
syncLog.setTransportType(TransportTypeEnum.MQ_FIRST.getType());
syncLog.setMqTopic(topic);
syncLog.setMqMsgId(mqMsgId);
log.info("[Databus增量同步] MQ推送成功, topic={}, syncId={}", topic, syncId);
} else if (client.getHttpEnabled() == 1) {
// HTTP 推送
boolean success = messagePusher.pushByHttp(client.getHttpEndpoint(), message);
if (!success) {
throw new RuntimeException("HTTP推送失败");
}
syncLog.setTransportType(TransportTypeEnum.HTTP_ONLY.getType());
log.info("[Databus增量同步] HTTP推送成功, endpoint={}, syncId={}",
client.getHttpEndpoint(), syncId);
} else {
throw new RuntimeException("无可用的推送方式");
}
// 更新日志状态为成功
syncLog.setStatus(SyncStatusEnum.SUCCESS.getStatus());
syncLog.setEndTime(LocalDateTime.now());
syncLog.setDuration((int) (System.currentTimeMillis() -
startTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()));
// 更新订阅断点
updateSubscriptionCheckpoint(subscription, eventRecord.getId());
} catch (Exception e) {
log.error("[Databus增量同步] 推送失败, syncId={}, clientCode={}", syncId, client.getClientCode(), e);
syncLog.setStatus(SyncStatusEnum.FAILED.getStatus());
syncLog.setErrorMessage(e.getMessage());
syncLog.setEndTime(LocalDateTime.now());
}
// 保存同步日志
syncLogMapper.insert(syncLog);
}
/**
* 构建客户端专属Topic
* 格式: {topicBase}-{module}-{entity}-{action}-{clientCode}
* 示例: databus-sync-system-org-create-company-a
*
* @param topicBase 基础Topic名称如 databus-sync
* @param eventType 事件类型(格式: system-org-create
* @param clientCode 客户端编码
*/
private String buildClientTopic(String topicBase, String eventType, String clientCode) {
// 默认topicBase为 databus-sync
if (topicBase == null || topicBase.isEmpty()) {
topicBase = "databus-sync";
}
// eventType 格式已经是 system-org-create直接拼接
return String.format("%s-%s-%s", topicBase, eventType.toLowerCase(), clientCode);
}
/**
* 更新订阅断点
*/
private void updateSubscriptionCheckpoint(DatabusSyncSubscriptionDO subscription, Long eventRecordId) {
subscription.setLastSyncEventId(eventRecordId);
subscription.setLastSyncTime(LocalDateTime.now());
subscription.setTotalSyncCount((subscription.getTotalSyncCount() == null ? 0 : subscription.getTotalSyncCount()) + 1);
subscription.setTotalSuccessCount((subscription.getTotalSuccessCount() == null ? 0 : subscription.getTotalSuccessCount()) + 1);
subscriptionMapper.updateById(subscription);
}
}

View File

@@ -0,0 +1,24 @@
package com.zt.plat.framework.databus.server.core.sync;
/**
* Databus 同步服务接口
* 负责扫描事件记录并推送到客户端
*
* @author ZT
*/
public interface DatabusSyncService {
/**
* 执行同步任务
* 扫描所有启用的订阅关系,推送新事件到客户端
*/
void executeSyncTask();
/**
* 手动触发指定订阅的同步
*
* @param subscriptionId 订阅关系ID
*/
void triggerSync(Long subscriptionId);
}

View File

@@ -0,0 +1,237 @@
package com.zt.plat.framework.databus.server.core.sync;
import cn.hutool.core.util.IdUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zt.plat.framework.databus.server.config.DatabusSyncServerProperties;
import com.zt.plat.framework.databus.server.core.message.SyncMessage;
import com.zt.plat.framework.databus.server.core.pusher.MessagePusher;
import com.zt.plat.framework.databus.server.dal.dataobject.*;
import com.zt.plat.framework.databus.server.dal.mapper.*;
import com.zt.plat.framework.databus.server.enums.DeadLetterStatusEnum;
import com.zt.plat.framework.databus.server.enums.SyncStatusEnum;
import com.zt.plat.framework.databus.server.enums.TransportTypeEnum;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
/**
* Databus 同步服务实现
*
* @author ZT
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DatabusSyncServiceImpl implements DatabusSyncService {
private final DatabusSyncSubscriptionMapper subscriptionMapper;
private final DatabusSyncEventRecordMapper eventRecordMapper;
private final DatabusSyncClientMapper clientMapper;
private final DatabusSyncEventMapper eventMapper;
private final DatabusSyncLogMapper syncLogMapper;
private final DatabusSyncDeadLetterMapper deadLetterMapper;
private final MessagePusher messagePusher;
private final DatabusSyncServerProperties properties;
@Override
public void executeSyncTask() {
log.info("[Databus] 开始执行同步任务");
// 查询所有启用的订阅关系
List<DatabusSyncSubscriptionDO> subscriptions = subscriptionMapper.selectList(
new LambdaQueryWrapper<DatabusSyncSubscriptionDO>()
.eq(DatabusSyncSubscriptionDO::getEnabled, 1)
);
for (DatabusSyncSubscriptionDO subscription : subscriptions) {
try {
processSubscription(subscription);
} catch (Exception e) {
log.error("[Databus] 处理订阅失败, subscriptionId={}", subscription.getId(), e);
}
}
log.info("[Databus] 同步任务执行完成, 处理订阅数={}", subscriptions.size());
}
@Override
public void triggerSync(Long subscriptionId) {
DatabusSyncSubscriptionDO subscription = subscriptionMapper.selectById(subscriptionId);
if (subscription == null) {
log.warn("[Databus] 订阅不存在, subscriptionId={}", subscriptionId);
return;
}
processSubscription(subscription);
}
/**
* 处理单个订阅
*/
@Transactional(rollbackFor = Exception.class)
protected void processSubscription(DatabusSyncSubscriptionDO subscription) {
// 三状态启用检查
DatabusSyncClientDO client = clientMapper.selectById(subscription.getClientId());
if (client == null || client.getEnabled() != 1) {
log.debug("[Databus] 客户端未启用, clientId={}", subscription.getClientId());
return;
}
DatabusSyncEventDO event = eventMapper.selectById(subscription.getEventId());
if (event == null || event.getEnabled() != 1) {
log.debug("[Databus] 事件未启用, eventId={}", subscription.getEventId());
return;
}
// 查询新事件记录(断点续传)
LambdaQueryWrapper<DatabusSyncEventRecordDO> queryWrapper = new LambdaQueryWrapper<DatabusSyncEventRecordDO>()
.eq(DatabusSyncEventRecordDO::getEventId, subscription.getEventId())
.orderByAsc(DatabusSyncEventRecordDO::getId);
if (subscription.getLastSyncEventId() != null) {
queryWrapper.gt(DatabusSyncEventRecordDO::getId, subscription.getLastSyncEventId());
}
// 批量查询 - 使用 Page 分页,兼容达梦等数据库
Page<DatabusSyncEventRecordDO> page = new Page<>(1, subscription.getBatchSize(), false);
List<DatabusSyncEventRecordDO> records = eventRecordMapper.selectPage(page, queryWrapper).getRecords();
if (records.isEmpty()) {
log.debug("[Databus] 无新事件, subscriptionId={}", subscription.getId());
return;
}
log.info("[Databus] 开始同步, subscriptionId={}, eventCount={}", subscription.getId(), records.size());
// 推送事件
for (DatabusSyncEventRecordDO record : records) {
boolean success = pushEvent(subscription, client, record);
if (success) {
// 更新断点
subscription.setLastSyncEventId(record.getId());
subscription.setLastSyncTime(LocalDateTime.now());
subscription.setTotalSyncCount(subscription.getTotalSyncCount() + 1);
subscription.setTotalSuccessCount(subscription.getTotalSuccessCount() + 1);
} else {
subscription.setTotalFailCount(subscription.getTotalFailCount() + 1);
break; // 失败则停止当前批次
}
}
// 更新订阅统计
subscriptionMapper.updateById(subscription);
}
/**
* 推送单个事件
*/
private boolean pushEvent(DatabusSyncSubscriptionDO subscription,
DatabusSyncClientDO client,
DatabusSyncEventRecordDO record) {
String syncId = IdUtil.fastSimpleUUID();
LocalDateTime startTime = LocalDateTime.now();
// 构建消息
SyncMessage message = SyncMessage.builder()
.syncId(syncId)
.eventRecordId(record.getId())
.eventType(record.getEventType())
.eventAction(record.getEventAction())
.dataSnapshot(record.getDataSnapshot())
.dataVersion(record.getDataVersion())
.timestamp(System.currentTimeMillis())
.build();
// 记录日志
DatabusSyncLogDO syncLog = DatabusSyncLogDO.builder()
.syncId(syncId)
.eventRecordId(record.getId())
.subscriptionId(subscription.getId())
.clientCode(client.getClientCode())
.eventType(record.getEventType())
.syncMode(subscription.getSyncMode())
.startTime(startTime)
.status(SyncStatusEnum.PENDING.getStatus())
.retryCount(0)
.tenantId(client.getTenantId())
.build();
try {
// 推送消息
if (TransportTypeEnum.isMqFirst(client.getTransportType()) && client.getMqEnabled() == 1) {
// MQ 推送
String topic = String.format("%s-%s-%s",
client.getMqTopicBase(), record.getEventType(), client.getClientCode());
String mqMsgId = messagePusher.pushByMQ(topic, message);
syncLog.setTransportType(TransportTypeEnum.MQ_FIRST.getType());
syncLog.setMqTopic(topic);
syncLog.setMqMsgId(mqMsgId);
} else if (client.getHttpEnabled() == 1) {
// HTTP 推送
boolean success = messagePusher.pushByHttp(client.getHttpEndpoint(), message);
if (!success) {
throw new RuntimeException("HTTP推送失败");
}
syncLog.setTransportType(TransportTypeEnum.HTTP_ONLY.getType());
} else {
throw new RuntimeException("无可用的推送方式");
}
// 更新日志状态
syncLog.setStatus(SyncStatusEnum.SUCCESS.getStatus());
syncLog.setEndTime(LocalDateTime.now());
syncLog.setDuration((int) (System.currentTimeMillis() - startTime.atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli()));
syncLogMapper.insert(syncLog);
return true;
} catch (Exception e) {
log.error("[Databus] 推送失败, syncId={}, eventRecordId={}", syncId, record.getId(), e);
// 更新日志状态
syncLog.setStatus(SyncStatusEnum.FAILED.getStatus());
syncLog.setErrorMessage(e.getMessage());
syncLog.setEndTime(LocalDateTime.now());
syncLogMapper.insert(syncLog);
// 检查重试次数,超过限制则进入死信队列
if (syncLog.getRetryCount() >= properties.getRetry().getMaxAttempts()) {
saveToDeadLetter(syncLog, message);
}
return false;
}
}
/**
* 保存到死信队列
*/
private void saveToDeadLetter(DatabusSyncLogDO syncLog, SyncMessage message) {
DatabusSyncDeadLetterDO deadLetter = DatabusSyncDeadLetterDO.builder()
.syncLogId(syncLog.getId())
.syncId(syncLog.getSyncId())
.clientCode(syncLog.getClientCode())
.eventType(syncLog.getEventType())
.eventRecordId(syncLog.getEventRecordId())
.retryCount(syncLog.getRetryCount())
.lastErrorMessage(syncLog.getErrorMessage())
.lastErrorTime(LocalDateTime.now())
.messageBody(cn.hutool.json.JSONUtil.toJsonStr(message))
.status(DeadLetterStatusEnum.PENDING.getStatus())
.handled(0)
.tenantId(syncLog.getTenantId())
.build();
deadLetterMapper.insert(deadLetter);
log.warn("[Databus] 消息已进入死信队列, syncId={}", syncLog.getSyncId());
}
}

View File

@@ -0,0 +1,84 @@
package com.zt.plat.framework.databus.server.dal.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
import lombok.*;
/**
* 数据同步客户端配置 DO
*
* @author ZT
*/
@TableName("databus_sync_client")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DatabusSyncClientDO extends TenantBaseDO {
/**
* 主键ID
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 客户端编码
*/
private String clientCode;
/**
* 客户端名称
*/
private String clientName;
/**
* 启用状态0-禁用 1-启用)
*/
private Integer enabled;
/**
* 通信方式1-MQ优先 2-仅HTTP
*/
private Integer transportType;
/**
* MQ是否启用0-否 1-是)
*/
private Integer mqEnabled;
/**
* MQ NameServer地址
*/
private String mqNamesrvAddr;
/**
* MQ Topic基础名称
*/
private String mqTopicBase;
/**
* HTTP是否启用0-否 1-是)
*/
private Integer httpEnabled;
/**
* HTTP推送端点
*/
private String httpEndpoint;
/**
* 应用Key
*/
private String appKey;
/**
* 应用Secret加密存储
*/
private String appSecret;
}

View File

@@ -0,0 +1,106 @@
package com.zt.plat.framework.databus.server.dal.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zt.plat.framework.mybatis.core.dataobject.BaseDO;
import lombok.*;
import java.time.LocalDateTime;
/**
* 数据同步死信队列 DO
*
* @author ZT
*/
@TableName("databus_sync_dead_letter")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DatabusSyncDeadLetterDO extends BaseDO {
/**
* 主键ID
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 同步日志ID
*/
private Long syncLogId;
/**
* 同步ID
*/
private String syncId;
/**
* 客户端编码
*/
private String clientCode;
/**
* 事件类型
*/
private String eventType;
/**
* 事件记录ID
*/
private Long eventRecordId;
/**
* 已重试次数
*/
private Integer retryCount;
/**
* 最后错误信息
*/
private String lastErrorMessage;
/**
* 最后错误时间
*/
private LocalDateTime lastErrorTime;
/**
* 消息内容JSON格式
*/
private String messageBody;
/**
* 状态0-待处理 1-已重新投递 2-已忽略)
*/
private Integer status;
/**
* 是否已处理0-否 1-是)
*/
private Integer handled;
/**
* 处理时间
*/
private LocalDateTime handleTime;
/**
* 处理人
*/
private String handler;
/**
* 处理备注
*/
private String handleRemark;
/**
* 租户ID
*/
private Long tenantId;
}

View File

@@ -0,0 +1,76 @@
package com.zt.plat.framework.databus.server.dal.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
import lombok.*;
/**
* 数据同步事件定义 DO
*
* @author ZT
*/
@TableName("databus_sync_event")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DatabusSyncEventDO extends TenantBaseDO {
/**
* 主键ID
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 事件类型编码
*/
private String eventType;
/**
* 事件名称
*/
private String eventName;
/**
* 事件描述
*/
private String eventDesc;
/**
* 启用状态0-禁用 1-启用)
*/
private Integer enabled;
/**
* 是否支持全量同步0-否 1-是)
*/
private Integer supportFullSync;
/**
* 是否支持增量同步0-否 1-是)
*/
private Integer supportIncrementalSync;
/**
* 数据结构版本
*/
private Integer dataVersion;
/**
* 数据提供者服务名Feign服务名
* 用于全量同步时调用对应服务获取数据
*/
private String dataProviderService;
/**
* 数据提供者方法标识
* 如: ORG组织、USER用户
*/
private String dataProviderMethod;
}

View File

@@ -0,0 +1,87 @@
package com.zt.plat.framework.databus.server.dal.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zt.plat.framework.mybatis.core.dataobject.BaseDO;
import lombok.*;
import java.time.LocalDateTime;
/**
* 数据同步事件流水 DO
* 用于断点续传
*
* @author ZT
*/
@TableName("databus_sync_event_record")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DatabusSyncEventRecordDO extends BaseDO {
/**
* 主键ID雪花算法全局唯一递增
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 事件定义ID
*/
private Long eventId;
/**
* 事件类型编码
*/
private String eventType;
/**
* 事件动作create-创建 update-更新 delete-删除)
*/
private String eventAction;
/**
* 完整业务数据快照JSON格式
*/
private String dataSnapshot;
/**
* 数据版本号
*/
private Integer dataVersion;
/**
* 来源服务名
*/
private String sourceService;
/**
* 来源MQ Topic
*/
private String sourceTopic;
/**
* 来源MQ消息ID
*/
private String sourceMsgId;
/**
* 租户ID
*/
private Long tenantId;
/**
* 操作人
*/
private String operator;
/**
* 事件发生时间
*/
private LocalDateTime eventTime;
}

View File

@@ -0,0 +1,126 @@
package com.zt.plat.framework.databus.server.dal.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zt.plat.framework.mybatis.core.dataobject.BaseDO;
import lombok.*;
import java.time.LocalDateTime;
/**
* 数据全量同步任务 DO
*
* @author ZT
*/
@TableName("databus_sync_full_task")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DatabusSyncFullTaskDO extends BaseDO {
/**
* 主键ID
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 任务编号(唯一标识)
*/
private String taskNo;
/**
* 订阅关系ID
*/
private Long subscriptionId;
/**
* 客户端编码
*/
private String clientCode;
/**
* 事件类型
*/
private String eventType;
/**
* 任务状态0-待执行 1-执行中 2-已完成 3-失败 4-已取消)
*/
private Integer status;
/**
* 总数据量
*/
private Long totalCount;
/**
* 已处理数据量
*/
private Long processedCount;
/**
* 成功数据量
*/
private Long successCount;
/**
* 失败数据量
*/
private Long failCount;
/**
* 总批次数
*/
private Integer totalBatch;
/**
* 当前批次号
*/
private Integer currentBatch;
/**
* 批量大小
*/
private Integer batchSize;
/**
* 游标时间(断点续传)
*/
private LocalDateTime cursorTime;
/**
* 游标ID断点续传
*/
private Long cursorId;
/**
* 任务开始时间
*/
private LocalDateTime startTime;
/**
* 任务结束时间
*/
private LocalDateTime endTime;
/**
* 最后一次错误信息
*/
private String lastErrorMessage;
/**
* 租户ID
*/
private Long tenantId;
/**
* 备注
*/
private String remark;
}

View File

@@ -0,0 +1,126 @@
package com.zt.plat.framework.databus.server.dal.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zt.plat.framework.mybatis.core.dataobject.BaseDO;
import lombok.*;
import java.time.LocalDateTime;
/**
* 数据同步日志 DO
*
* @author ZT
*/
@TableName("databus_sync_log")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DatabusSyncLogDO extends BaseDO {
/**
* 主键ID
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 同步ID唯一标识
*/
private String syncId;
/**
* 事件记录ID
*/
private Long eventRecordId;
/**
* 订阅关系ID
*/
private Long subscriptionId;
/**
* 客户端编码
*/
private String clientCode;
/**
* 事件类型
*/
private String eventType;
/**
* 同步模式1-全量 2-增量)
*/
private Integer syncMode;
/**
* 传输方式1-MQ优先 2-仅HTTP
*/
private Integer transportType;
/**
* MQ Topic
*/
private String mqTopic;
/**
* MQ消息ID
*/
private String mqMsgId;
/**
* 状态0-待处理 1-成功 2-失败 3-重试中)
*/
private Integer status;
/**
* 重试次数
*/
private Integer retryCount;
/**
* 错误信息
*/
private String errorMessage;
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 结束时间
*/
private LocalDateTime endTime;
/**
* 耗时(毫秒)
*/
private Integer duration;
/**
* 租户ID
*/
private Long tenantId;
/**
* 本批次数据条数
*/
private Integer dataCount;
/**
* 批次号(全量同步时使用)
*/
private Integer batchNo;
/**
* 全量任务ID全量同步时关联
*/
private Long fullTaskId;
}

View File

@@ -0,0 +1,91 @@
package com.zt.plat.framework.databus.server.dal.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.zt.plat.framework.tenant.core.db.TenantBaseDO;
import lombok.*;
import java.time.LocalDateTime;
/**
* 数据同步订阅关系 DO
*
* @author ZT
*/
@TableName("databus_sync_subscription")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DatabusSyncSubscriptionDO extends TenantBaseDO {
/**
* 主键ID
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 客户端ID
*/
private Long clientId;
/**
* 事件ID
*/
private Long eventId;
/**
* 订阅启用状态0-禁用 1-启用)
*/
private Integer enabled;
/**
* 同步模式1-全量 2-增量)
*/
private Integer syncMode;
/**
* 时效性1-实时 2-准实时 3-批量)
*/
private Integer timeliness;
/**
* 时效值(秒)
*/
private Integer timelinessValue;
/**
* 批量大小
*/
private Integer batchSize;
/**
* 最后同步的事件记录ID断点续传
*/
private Long lastSyncEventId;
/**
* 最后同步时间
*/
private LocalDateTime lastSyncTime;
/**
* 总同步次数
*/
private Long totalSyncCount;
/**
* 成功次数
*/
private Long totalSuccessCount;
/**
* 失败次数
*/
private Long totalFailCount;
}

View File

@@ -0,0 +1,38 @@
package com.zt.plat.framework.databus.server.dal.mapper;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientPageReqVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncClientDO;
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 数据同步客户端 Mapper
*
* @author ZT
*/
@Mapper
public interface DatabusSyncClientMapper extends BaseMapperX<DatabusSyncClientDO> {
default DatabusSyncClientDO selectByClientCode(String clientCode) {
return selectOne(DatabusSyncClientDO::getClientCode, clientCode);
}
default PageResult<DatabusSyncClientDO> selectPage(DatabusSyncClientPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<DatabusSyncClientDO>()
.likeIfPresent(DatabusSyncClientDO::getClientCode, reqVO.getClientCode())
.likeIfPresent(DatabusSyncClientDO::getClientName, reqVO.getClientName())
.eqIfPresent(DatabusSyncClientDO::getEnabled, reqVO.getEnabled())
.eqIfPresent(DatabusSyncClientDO::getTransportType, reqVO.getTransportType())
.orderByDesc(DatabusSyncClientDO::getId));
}
default List<DatabusSyncClientDO> selectList() {
return selectList(new LambdaQueryWrapperX<DatabusSyncClientDO>()
.orderByDesc(DatabusSyncClientDO::getId));
}
}

View File

@@ -0,0 +1,29 @@
package com.zt.plat.framework.databus.server.dal.mapper;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.deadletter.DatabusSyncDeadLetterPageReqVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncDeadLetterDO;
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
import org.apache.ibatis.annotations.Mapper;
/**
* 数据同步死信队列 Mapper
*
* @author ZT
*/
@Mapper
public interface DatabusSyncDeadLetterMapper extends BaseMapperX<DatabusSyncDeadLetterDO> {
default PageResult<DatabusSyncDeadLetterDO> selectPage(DatabusSyncDeadLetterPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<DatabusSyncDeadLetterDO>()
.eqIfPresent(DatabusSyncDeadLetterDO::getSyncId, reqVO.getSyncId())
.likeIfPresent(DatabusSyncDeadLetterDO::getClientCode, reqVO.getClientCode())
.likeIfPresent(DatabusSyncDeadLetterDO::getEventType, reqVO.getEventType())
.eqIfPresent(DatabusSyncDeadLetterDO::getStatus, reqVO.getStatus())
.eqIfPresent(DatabusSyncDeadLetterDO::getHandled, reqVO.getHandled())
.betweenIfPresent(DatabusSyncDeadLetterDO::getCreateTime, reqVO.getCreateTimeStart(), reqVO.getCreateTimeEnd())
.orderByDesc(DatabusSyncDeadLetterDO::getId));
}
}

View File

@@ -0,0 +1,37 @@
package com.zt.plat.framework.databus.server.dal.mapper;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventPageReqVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventDO;
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 数据同步事件定义 Mapper
*
* @author ZT
*/
@Mapper
public interface DatabusSyncEventMapper extends BaseMapperX<DatabusSyncEventDO> {
default DatabusSyncEventDO selectByEventType(String eventType) {
return selectOne(DatabusSyncEventDO::getEventType, eventType);
}
default PageResult<DatabusSyncEventDO> selectPage(DatabusSyncEventPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<DatabusSyncEventDO>()
.likeIfPresent(DatabusSyncEventDO::getEventType, reqVO.getEventType())
.likeIfPresent(DatabusSyncEventDO::getEventName, reqVO.getEventName())
.eqIfPresent(DatabusSyncEventDO::getEnabled, reqVO.getEnabled())
.orderByDesc(DatabusSyncEventDO::getId));
}
default List<DatabusSyncEventDO> selectList() {
return selectList(new LambdaQueryWrapperX<DatabusSyncEventDO>()
.orderByDesc(DatabusSyncEventDO::getId));
}
}

View File

@@ -0,0 +1,15 @@
package com.zt.plat.framework.databus.server.dal.mapper;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventRecordDO;
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
import org.apache.ibatis.annotations.Mapper;
/**
* 数据同步事件流水 Mapper
*
* @author ZT
*/
@Mapper
public interface DatabusSyncEventRecordMapper extends BaseMapperX<DatabusSyncEventRecordDO> {
}

View File

@@ -0,0 +1,62 @@
package com.zt.plat.framework.databus.server.dal.mapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.fulltask.DatabusSyncFullTaskPageReqVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncFullTaskDO;
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 数据全量同步任务 Mapper
*
* @author ZT
*/
@Mapper
public interface DatabusSyncFullTaskMapper extends BaseMapperX<DatabusSyncFullTaskDO> {
/**
* 根据任务编号查询
*/
default DatabusSyncFullTaskDO selectByTaskNo(String taskNo) {
return selectOne(DatabusSyncFullTaskDO::getTaskNo, taskNo);
}
/**
* 查询指定订阅的进行中任务
*/
default DatabusSyncFullTaskDO selectRunningBySubscriptionId(Long subscriptionId) {
return selectOne(new LambdaQueryWrapperX<DatabusSyncFullTaskDO>()
.eq(DatabusSyncFullTaskDO::getSubscriptionId, subscriptionId)
.in(DatabusSyncFullTaskDO::getStatus, 0, 1)); // 待执行或执行中
}
/**
* 查询待执行的任务列表
* 使用 MyBatis-Plus 分页机制,兼容达梦等数据库
*/
default List<DatabusSyncFullTaskDO> selectPendingTasks(int limit) {
// 使用 Page 分页而不是 LIMIT 语法,以兼容达梦数据库
Page<DatabusSyncFullTaskDO> page = new Page<>(1, limit, false);
return selectPage(page, new LambdaQueryWrapperX<DatabusSyncFullTaskDO>()
.eq(DatabusSyncFullTaskDO::getStatus, 0)
.orderByAsc(DatabusSyncFullTaskDO::getCreateTime)).getRecords();
}
/**
* 分页查询
*/
default PageResult<DatabusSyncFullTaskDO> selectPage(DatabusSyncFullTaskPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<DatabusSyncFullTaskDO>()
.likeIfPresent(DatabusSyncFullTaskDO::getTaskNo, reqVO.getTaskNo())
.eqIfPresent(DatabusSyncFullTaskDO::getSubscriptionId, reqVO.getSubscriptionId())
.eqIfPresent(DatabusSyncFullTaskDO::getClientCode, reqVO.getClientCode())
.eqIfPresent(DatabusSyncFullTaskDO::getEventType, reqVO.getEventType())
.eqIfPresent(DatabusSyncFullTaskDO::getStatus, reqVO.getStatus())
.orderByDesc(DatabusSyncFullTaskDO::getId));
}
}

View File

@@ -0,0 +1,30 @@
package com.zt.plat.framework.databus.server.dal.mapper;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.pushlog.DatabusSyncPushLogPageReqVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncLogDO;
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
import org.apache.ibatis.annotations.Mapper;
/**
* 数据同步日志 Mapper
*
* @author ZT
*/
@Mapper
public interface DatabusSyncLogMapper extends BaseMapperX<DatabusSyncLogDO> {
default PageResult<DatabusSyncLogDO> selectPage(DatabusSyncPushLogPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<DatabusSyncLogDO>()
.eqIfPresent(DatabusSyncLogDO::getSyncId, reqVO.getSyncId())
.eqIfPresent(DatabusSyncLogDO::getSubscriptionId, reqVO.getSubscriptionId())
.likeIfPresent(DatabusSyncLogDO::getClientCode, reqVO.getClientCode())
.likeIfPresent(DatabusSyncLogDO::getEventType, reqVO.getEventType())
.eqIfPresent(DatabusSyncLogDO::getStatus, reqVO.getStatus())
.eqIfPresent(DatabusSyncLogDO::getTransportType, reqVO.getTransportType())
.betweenIfPresent(DatabusSyncLogDO::getCreateTime, reqVO.getCreateTimeStart(), reqVO.getCreateTimeEnd())
.orderByDesc(DatabusSyncLogDO::getId));
}
}

View File

@@ -0,0 +1,41 @@
package com.zt.plat.framework.databus.server.dal.mapper;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionPageReqVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncSubscriptionDO;
import com.zt.plat.framework.mybatis.core.mapper.BaseMapperX;
import com.zt.plat.framework.mybatis.core.query.LambdaQueryWrapperX;
import org.apache.ibatis.annotations.Mapper;
/**
* 数据同步订阅关系 Mapper
*
* @author ZT
*/
@Mapper
public interface DatabusSyncSubscriptionMapper extends BaseMapperX<DatabusSyncSubscriptionDO> {
default DatabusSyncSubscriptionDO selectByClientIdAndEventId(Long clientId, Long eventId) {
return selectOne(new LambdaQueryWrapperX<DatabusSyncSubscriptionDO>()
.eq(DatabusSyncSubscriptionDO::getClientId, clientId)
.eq(DatabusSyncSubscriptionDO::getEventId, eventId));
}
default PageResult<DatabusSyncSubscriptionDO> selectPage(DatabusSyncSubscriptionPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<DatabusSyncSubscriptionDO>()
.eqIfPresent(DatabusSyncSubscriptionDO::getClientId, reqVO.getClientId())
.eqIfPresent(DatabusSyncSubscriptionDO::getEventId, reqVO.getEventId())
.eqIfPresent(DatabusSyncSubscriptionDO::getEnabled, reqVO.getEnabled())
.orderByDesc(DatabusSyncSubscriptionDO::getId));
}
/**
* 根据事件ID查询所有启用的订阅
*/
default java.util.List<DatabusSyncSubscriptionDO> selectEnabledByEventId(Long eventId) {
return selectList(new LambdaQueryWrapperX<DatabusSyncSubscriptionDO>()
.eq(DatabusSyncSubscriptionDO::getEventId, eventId)
.eq(DatabusSyncSubscriptionDO::getEnabled, 1));
}
}

View File

@@ -0,0 +1,36 @@
package com.zt.plat.framework.databus.server.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 数据提供者类型枚举
*
* @author ZT
*/
@Getter
@AllArgsConstructor
public enum DataProviderTypeEnum {
ORG("ORG", "组织机构"),
USER("USER", "用户");
/**
* 类型编码
*/
private final String code;
/**
* 类型名称
*/
private final String name;
public static DataProviderTypeEnum getByCode(String code) {
for (DataProviderTypeEnum value : values()) {
if (value.getCode().equals(code)) {
return value;
}
}
return null;
}
}

View File

@@ -0,0 +1,51 @@
package com.zt.plat.framework.databus.server.enums;
import cn.hutool.core.util.ObjUtil;
import com.zt.plat.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 死信队列状态枚举
*
* @author ZT
*/
@Getter
@AllArgsConstructor
public enum DeadLetterStatusEnum implements ArrayValuable<Integer> {
PENDING(0, "待处理"),
REDELIVERED(1, "已重新投递"),
IGNORED(2, "已忽略");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(DeadLetterStatusEnum::getStatus).toArray(Integer[]::new);
/**
* 状态值
*/
private final Integer status;
/**
* 状态名
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
public static boolean isPending(Integer status) {
return ObjUtil.equal(PENDING.status, status);
}
public static boolean isRedelivered(Integer status) {
return ObjUtil.equal(REDELIVERED.status, status);
}
public static boolean isIgnored(Integer status) {
return ObjUtil.equal(IGNORED.status, status);
}
}

View File

@@ -0,0 +1,30 @@
package com.zt.plat.framework.databus.server.enums;
import com.zt.plat.framework.common.exception.ErrorCode;
/**
* Databus 同步服务错误码枚举类
* <p>
* databus 系统,使用 1-010-000-000 段
*/
public interface ErrorCodeConstants {
// ========== 数据同步事件 1-010-001-000 ==========
ErrorCode EVENT_NOT_EXISTS = new ErrorCode(1_010_001_000, "数据同步事件不存在");
ErrorCode EVENT_TYPE_DUPLICATE = new ErrorCode(1_010_001_001, "事件类型编码已存在");
// ========== 数据同步客户端 1-010-002-000 ==========
ErrorCode CLIENT_NOT_EXISTS = new ErrorCode(1_010_002_000, "数据同步客户端不存在");
ErrorCode CLIENT_CODE_DUPLICATE = new ErrorCode(1_010_002_001, "客户端编码已存在");
// ========== 数据同步订阅 1-010-003-000 ==========
ErrorCode SUBSCRIPTION_NOT_EXISTS = new ErrorCode(1_010_003_000, "数据同步订阅不存在");
ErrorCode SUBSCRIPTION_DUPLICATE = new ErrorCode(1_010_003_001, "该客户端已订阅该事件,不能重复订阅");
// ========== 数据同步推送日志 1-010-004-000 ==========
ErrorCode PUSH_LOG_NOT_EXISTS = new ErrorCode(1_010_004_000, "数据同步推送日志不存在");
// ========== 数据同步死信队列 1-010-005-000 ==========
ErrorCode DEAD_LETTER_NOT_EXISTS = new ErrorCode(1_010_005_000, "数据同步死信队列记录不存在");
}

View File

@@ -0,0 +1,75 @@
package com.zt.plat.framework.databus.server.enums;
import cn.hutool.core.util.ObjUtil;
import com.zt.plat.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 全量任务状态枚举
*
* @author ZT
*/
@Getter
@AllArgsConstructor
public enum FullTaskStatusEnum implements ArrayValuable<Integer> {
PENDING(0, "待执行"),
RUNNING(1, "执行中"),
COMPLETED(2, "已完成"),
FAILED(3, "失败"),
CANCELLED(4, "已取消");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(FullTaskStatusEnum::getStatus).toArray(Integer[]::new);
/**
* 状态值
*/
private final Integer status;
/**
* 状态名
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
public static boolean isPending(Integer status) {
return ObjUtil.equal(PENDING.status, status);
}
public static boolean isRunning(Integer status) {
return ObjUtil.equal(RUNNING.status, status);
}
public static boolean isCompleted(Integer status) {
return ObjUtil.equal(COMPLETED.status, status);
}
public static boolean isFailed(Integer status) {
return ObjUtil.equal(FAILED.status, status);
}
public static boolean isCancelled(Integer status) {
return ObjUtil.equal(CANCELLED.status, status);
}
/**
* 是否可以取消
*/
public static boolean canCancel(Integer status) {
return isPending(status) || isRunning(status);
}
/**
* 是否已终止(完成/失败/取消)
*/
public static boolean isTerminated(Integer status) {
return isCompleted(status) || isFailed(status) || isCancelled(status);
}
}

View File

@@ -0,0 +1,46 @@
package com.zt.plat.framework.databus.server.enums;
import cn.hutool.core.util.ObjUtil;
import com.zt.plat.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 同步模式枚举
*
* @author ZT
*/
@Getter
@AllArgsConstructor
public enum SyncModeEnum implements ArrayValuable<Integer> {
FULL(1, "全量同步"),
INCREMENTAL(2, "增量同步");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(SyncModeEnum::getMode).toArray(Integer[]::new);
/**
* 模式值
*/
private final Integer mode;
/**
* 模式名
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
public static boolean isFull(Integer mode) {
return ObjUtil.equal(FULL.mode, mode);
}
public static boolean isIncremental(Integer mode) {
return ObjUtil.equal(INCREMENTAL.mode, mode);
}
}

View File

@@ -0,0 +1,56 @@
package com.zt.plat.framework.databus.server.enums;
import cn.hutool.core.util.ObjUtil;
import com.zt.plat.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 同步状态枚举
*
* @author ZT
*/
@Getter
@AllArgsConstructor
public enum SyncStatusEnum implements ArrayValuable<Integer> {
PENDING(0, "待处理"),
SUCCESS(1, "成功"),
FAILED(2, "失败"),
RETRYING(3, "重试中");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(SyncStatusEnum::getStatus).toArray(Integer[]::new);
/**
* 状态值
*/
private final Integer status;
/**
* 状态名
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
public static boolean isPending(Integer status) {
return ObjUtil.equal(PENDING.status, status);
}
public static boolean isSuccess(Integer status) {
return ObjUtil.equal(SUCCESS.status, status);
}
public static boolean isFailed(Integer status) {
return ObjUtil.equal(FAILED.status, status);
}
public static boolean isRetrying(Integer status) {
return ObjUtil.equal(RETRYING.status, status);
}
}

View File

@@ -0,0 +1,51 @@
package com.zt.plat.framework.databus.server.enums;
import cn.hutool.core.util.ObjUtil;
import com.zt.plat.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 时效性枚举
*
* @author ZT
*/
@Getter
@AllArgsConstructor
public enum TimelinessEnum implements ArrayValuable<Integer> {
REALTIME(1, "实时"),
NEAR_REALTIME(2, "准实时"),
BATCH(3, "批量");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(TimelinessEnum::getType).toArray(Integer[]::new);
/**
* 类型值
*/
private final Integer type;
/**
* 类型名
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
public static boolean isRealtime(Integer type) {
return ObjUtil.equal(REALTIME.type, type);
}
public static boolean isNearRealtime(Integer type) {
return ObjUtil.equal(NEAR_REALTIME.type, type);
}
public static boolean isBatch(Integer type) {
return ObjUtil.equal(BATCH.type, type);
}
}

View File

@@ -0,0 +1,46 @@
package com.zt.plat.framework.databus.server.enums;
import cn.hutool.core.util.ObjUtil;
import com.zt.plat.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
/**
* 传输方式枚举
*
* @author ZT
*/
@Getter
@AllArgsConstructor
public enum TransportTypeEnum implements ArrayValuable<Integer> {
MQ_FIRST(1, "MQ优先"),
HTTP_ONLY(2, "仅HTTP");
public static final Integer[] ARRAYS = Arrays.stream(values()).map(TransportTypeEnum::getType).toArray(Integer[]::new);
/**
* 类型值
*/
private final Integer type;
/**
* 类型名
*/
private final String name;
@Override
public Integer[] array() {
return ARRAYS;
}
public static boolean isMqFirst(Integer type) {
return ObjUtil.equal(MQ_FIRST.type, type);
}
public static boolean isHttpOnly(Integer type) {
return ObjUtil.equal(HTTP_ONLY.type, type);
}
}

View File

@@ -0,0 +1,195 @@
package com.zt.plat.framework.databus.server.producer;
import cn.hutool.json.JSONUtil;
import com.zt.plat.framework.databus.server.config.DatabusServerProperties;
import com.zt.plat.module.databus.api.message.DatabusBatchMessage;
import com.zt.plat.module.databus.api.message.DatabusMessage;
import com.zt.plat.module.databus.enums.DatabusEventType;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import java.nio.charset.StandardCharsets;
import java.util.List;
/**
* Databus 消息生产者
* <p>
* 负责将数据变更消息发送到各个客户端的 Topic
*
* @author ZT
*/
@Slf4j
@RequiredArgsConstructor
public class DatabusMessageProducer {
private final DatabusServerProperties properties;
private DefaultMQProducer producer;
@PostConstruct
public void init() throws MQClientException {
if (!properties.isEnabled() || !properties.getMq().isEnabled()) {
log.info("[Databus Server] MQ 发送未启用");
return;
}
producer = new DefaultMQProducer(properties.getMq().getProducerGroup());
producer.setNamesrvAddr(properties.getMq().getNameServer());
producer.setSendMsgTimeout(properties.getMq().getSendMsgTimeout());
producer.start();
log.info("[Databus Server] 消息生产者启动成功, nameServer={}, producerGroup={}",
properties.getMq().getNameServer(), properties.getMq().getProducerGroup());
}
@PreDestroy
public void destroy() {
if (producer != null) {
producer.shutdown();
log.info("[Databus Server] 消息生产者已关闭");
}
}
/**
* 发送增量消息到所有客户端
*
* @param message 消息
* @param <T> 数据类型
*/
public <T> void send(DatabusMessage<T> message) {
if (producer == null) {
log.warn("[Databus Server] Producer 未初始化,无法发送消息");
return;
}
List<String> clients = properties.getClients();
if (clients == null || clients.isEmpty()) {
log.warn("[Databus Server] 未配置客户端列表,无法发送消息");
return;
}
String messageJson = JSONUtil.toJsonStr(message);
DatabusEventType eventType = message.getEventType();
for (String clientCode : clients) {
try {
String topic = eventType.getTopic(properties.getMq().getTopicBase(), clientCode);
Message mqMessage = new Message(topic, messageJson.getBytes(StandardCharsets.UTF_8));
mqMessage.setKeys(message.getMessageId());
SendResult result = producer.send(mqMessage);
log.debug("[Databus Server] 消息发送成功, topic={}, messageId={}, result={}",
topic, message.getMessageId(), result.getMsgId());
} catch (Exception e) {
log.error("[Databus Server] 消息发送失败, clientCode={}, eventType={}, messageId={}",
clientCode, eventType.name(), message.getMessageId(), e);
}
}
}
/**
* 发送增量消息到指定客户端
*
* @param message 消息
* @param clientCode 客户端编码
* @param <T> 数据类型
*/
public <T> void sendTo(DatabusMessage<T> message, String clientCode) {
if (producer == null) {
log.warn("[Databus Server] Producer 未初始化,无法发送消息");
return;
}
String messageJson = JSONUtil.toJsonStr(message);
DatabusEventType eventType = message.getEventType();
String topic = eventType.getTopic(properties.getMq().getTopicBase(), clientCode);
try {
Message mqMessage = new Message(topic, messageJson.getBytes(StandardCharsets.UTF_8));
mqMessage.setKeys(message.getMessageId());
SendResult result = producer.send(mqMessage);
log.info("[Databus Server] 消息发送成功, topic={}, messageId={}, result={}",
topic, message.getMessageId(), result.getMsgId());
} catch (Exception e) {
log.error("[Databus Server] 消息发送失败, clientCode={}, eventType={}, messageId={}",
clientCode, eventType.name(), message.getMessageId(), e);
throw new RuntimeException("消息发送失败", e);
}
}
/**
* 发送批量消息到所有客户端
*
* @param message 批量消息
* @param <T> 数据类型
*/
public <T> void sendBatch(DatabusBatchMessage<T> message) {
if (producer == null) {
log.warn("[Databus Server] Producer 未初始化,无法发送消息");
return;
}
List<String> clients = properties.getClients();
if (clients == null || clients.isEmpty()) {
log.warn("[Databus Server] 未配置客户端列表,无法发送消息");
return;
}
String messageJson = JSONUtil.toJsonStr(message);
DatabusEventType eventType = message.getEventType();
for (String clientCode : clients) {
try {
String topic = eventType.getTopic(properties.getMq().getTopicBase(), clientCode);
Message mqMessage = new Message(topic, messageJson.getBytes(StandardCharsets.UTF_8));
mqMessage.setKeys(message.getMessageId());
SendResult result = producer.send(mqMessage);
log.debug("[Databus Server] 批量消息发送成功, topic={}, batchNo={}/{}, result={}",
topic, message.getBatchNo(), message.getTotalBatch(), result.getMsgId());
} catch (Exception e) {
log.error("[Databus Server] 批量消息发送失败, clientCode={}, eventType={}, batchNo={}",
clientCode, eventType.name(), message.getBatchNo(), e);
}
}
}
/**
* 发送批量消息到指定客户端
*
* @param message 批量消息
* @param clientCode 客户端编码
* @param <T> 数据类型
*/
public <T> void sendBatchTo(DatabusBatchMessage<T> message, String clientCode) {
if (producer == null) {
log.warn("[Databus Server] Producer 未初始化,无法发送消息");
return;
}
String messageJson = JSONUtil.toJsonStr(message);
DatabusEventType eventType = message.getEventType();
String topic = eventType.getTopic(properties.getMq().getTopicBase(), clientCode);
try {
Message mqMessage = new Message(topic, messageJson.getBytes(StandardCharsets.UTF_8));
mqMessage.setKeys(message.getMessageId());
SendResult result = producer.send(mqMessage);
log.info("[Databus Server] 批量消息发送成功, topic={}, batchNo={}/{}, result={}",
topic, message.getBatchNo(), message.getTotalBatch(), result.getMsgId());
} catch (Exception e) {
log.error("[Databus Server] 批量消息发送失败, clientCode={}, eventType={}, batchNo={}",
clientCode, eventType.name(), message.getBatchNo(), e);
throw new RuntimeException("批量消息发送失败", e);
}
}
}

View File

@@ -0,0 +1,70 @@
package com.zt.plat.framework.databus.server.service;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientPageReqVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientSaveReqVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncClientDO;
import java.util.List;
/**
* 数据同步客户端 Service 接口
*
* @author ZT
*/
public interface DatabusSyncClientService {
/**
* 创建数据同步客户端
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createClient(DatabusSyncClientSaveReqVO createReqVO);
/**
* 更新数据同步客户端
*
* @param updateReqVO 更新信息
*/
void updateClient(DatabusSyncClientSaveReqVO updateReqVO);
/**
* 删除数据同步客户端
*
* @param id 编号
*/
void deleteClient(Long id);
/**
* 获得数据同步客户端
*
* @param id 编号
* @return 数据同步客户端
*/
DatabusSyncClientDO getClient(Long id);
/**
* 获得数据同步客户端分页
*
* @param pageReqVO 分页查询
* @return 数据同步客户端分页
*/
PageResult<DatabusSyncClientDO> getClientPage(DatabusSyncClientPageReqVO pageReqVO);
/**
* 获得数据同步客户端列表
*
* @return 数据同步客户端列表
*/
List<DatabusSyncClientDO> getClientList();
/**
* 更新客户端启用状态
*
* @param id 编号
* @param enabled 启用状态
*/
void updateClientStatus(Long id, Integer enabled);
}

View File

@@ -0,0 +1,54 @@
package com.zt.plat.framework.databus.server.service;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.deadletter.DatabusSyncDeadLetterPageReqVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncDeadLetterDO;
import java.util.List;
/**
* 数据同步死信队列 Service 接口
*
* @author ZT
*/
public interface DatabusSyncDeadLetterService {
/**
* 获得数据同步死信队列
*
* @param id 编号
* @return 数据同步死信队列
*/
DatabusSyncDeadLetterDO getDeadLetter(Long id);
/**
* 获得数据同步死信队列分页
*
* @param pageReqVO 分页查询
* @return 数据同步死信队列分页
*/
PageResult<DatabusSyncDeadLetterDO> getDeadLetterPage(DatabusSyncDeadLetterPageReqVO pageReqVO);
/**
* 重新投递死信消息
*
* @param id 死信ID
*/
void reprocessDeadLetter(Long id);
/**
* 批量重新投递死信消息
*
* @param ids 死信ID列表
*/
void batchReprocessDeadLetter(List<Long> ids);
/**
* 标记为已处理
*
* @param id 死信ID
* @param remark 处理备注
*/
void markHandled(Long id, String remark);
}

View File

@@ -0,0 +1,70 @@
package com.zt.plat.framework.databus.server.service;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventPageReqVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventSaveReqVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventDO;
import java.util.List;
/**
* 数据同步事件 Service 接口
*
* @author ZT
*/
public interface DatabusSyncEventService {
/**
* 创建数据同步事件
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createEvent(DatabusSyncEventSaveReqVO createReqVO);
/**
* 更新数据同步事件
*
* @param updateReqVO 更新信息
*/
void updateEvent(DatabusSyncEventSaveReqVO updateReqVO);
/**
* 删除数据同步事件
*
* @param id 编号
*/
void deleteEvent(Long id);
/**
* 获得数据同步事件
*
* @param id 编号
* @return 数据同步事件
*/
DatabusSyncEventDO getEvent(Long id);
/**
* 获得数据同步事件分页
*
* @param pageReqVO 分页查询
* @return 数据同步事件分页
*/
PageResult<DatabusSyncEventDO> getEventPage(DatabusSyncEventPageReqVO pageReqVO);
/**
* 获得数据同步事件列表
*
* @return 数据同步事件列表
*/
List<DatabusSyncEventDO> getEventList();
/**
* 更新事件启用状态
*
* @param id 编号
* @param enabled 启用状态
*/
void updateEventStatus(Long id, Integer enabled);
}

View File

@@ -0,0 +1,37 @@
package com.zt.plat.framework.databus.server.service;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.pushlog.DatabusSyncPushLogPageReqVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncLogDO;
/**
* 数据同步推送日志 Service 接口
*
* @author ZT
*/
public interface DatabusSyncLogService {
/**
* 获得数据同步推送日志
*
* @param id 编号
* @return 数据同步推送日志
*/
DatabusSyncLogDO getPushLog(Long id);
/**
* 获得数据同步推送日志分页
*
* @param pageReqVO 分页查询
* @return 数据同步推送日志分页
*/
PageResult<DatabusSyncLogDO> getPushLogPage(DatabusSyncPushLogPageReqVO pageReqVO);
/**
* 重试推送
*
* @param id 日志ID
*/
void retryPush(Long id);
}

View File

@@ -0,0 +1,75 @@
package com.zt.plat.framework.databus.server.service;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionPageReqVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionSaveReqVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncSubscriptionDO;
/**
* 数据同步订阅 Service 接口
*
* @author ZT
*/
public interface DatabusSyncSubscriptionService {
/**
* 创建数据同步订阅
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createSubscription(DatabusSyncSubscriptionSaveReqVO createReqVO);
/**
* 更新数据同步订阅
*
* @param updateReqVO 更新信息
*/
void updateSubscription(DatabusSyncSubscriptionSaveReqVO updateReqVO);
/**
* 删除数据同步订阅
*
* @param id 编号
*/
void deleteSubscription(Long id);
/**
* 获得数据同步订阅
*
* @param id 编号
* @return 数据同步订阅
*/
DatabusSyncSubscriptionDO getSubscription(Long id);
/**
* 获得数据同步订阅分页
*
* @param pageReqVO 分页查询
* @return 数据同步订阅分页
*/
PageResult<DatabusSyncSubscriptionDO> getSubscriptionPage(DatabusSyncSubscriptionPageReqVO pageReqVO);
/**
* 更新订阅启用状态
*
* @param id 编号
* @param enabled 启用状态
*/
void updateSubscriptionStatus(Long id, Integer enabled);
/**
* 重置订阅断点
*
* @param id 编号
*/
void resetCheckpoint(Long id);
/**
* 手动触发同步
*
* @param id 编号
*/
void triggerSync(Long id);
}

View File

@@ -0,0 +1,328 @@
package com.zt.plat.framework.databus.server.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.zt.plat.framework.databus.server.config.DatabusSyncServerProperties;
import com.zt.plat.framework.databus.server.core.message.BatchSyncMessage;
import com.zt.plat.framework.databus.server.core.provider.DataProvider;
import com.zt.plat.framework.databus.server.core.provider.DataProviderRegistry;
import com.zt.plat.framework.databus.server.core.pusher.MessagePusher;
import com.zt.plat.framework.databus.server.core.sync.DatabusFullSyncService;
import com.zt.plat.framework.databus.server.dal.dataobject.*;
import com.zt.plat.framework.databus.server.dal.mapper.*;
import com.zt.plat.framework.databus.server.enums.*;
import com.zt.plat.module.databus.enums.DatabusEventType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
/**
* Databus Full Sync Service Implementation
*
* @author ZT
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class DatabusFullSyncServiceImpl implements DatabusFullSyncService {
private final DatabusSyncFullTaskMapper fullTaskMapper;
private final DatabusSyncSubscriptionMapper subscriptionMapper;
private final DatabusSyncClientMapper clientMapper;
private final DatabusSyncEventMapper eventMapper;
private final DatabusSyncLogMapper syncLogMapper;
private final MessagePusher messagePusher;
private final DatabusSyncServerProperties properties;
private final DataProviderRegistry dataProviderRegistry;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createFullSyncTask(Long subscriptionId, String remark) {
DatabusSyncSubscriptionDO subscription = subscriptionMapper.selectById(subscriptionId);
if (subscription == null) {
throw new RuntimeException("Subscription not found");
}
DatabusSyncFullTaskDO runningTask = fullTaskMapper.selectRunningBySubscriptionId(subscriptionId);
if (runningTask != null) {
throw new RuntimeException("Full sync task already running: " + runningTask.getTaskNo());
}
DatabusSyncClientDO client = clientMapper.selectById(subscription.getClientId());
DatabusSyncEventDO event = eventMapper.selectById(subscription.getEventId());
if (client == null || event == null) {
throw new RuntimeException("Client or event config not found");
}
if (event.getSupportFullSync() != 1) {
throw new RuntimeException("Event does not support full sync");
}
DatabusSyncFullTaskDO task = DatabusSyncFullTaskDO.builder()
.taskNo(IdUtil.fastSimpleUUID())
.subscriptionId(subscriptionId)
.clientCode(client.getClientCode())
.eventType(event.getEventType())
.status(FullTaskStatusEnum.PENDING.getStatus())
.totalCount(0L)
.processedCount(0L)
.successCount(0L)
.failCount(0L)
.totalBatch(0)
.currentBatch(0)
.batchSize(subscription.getBatchSize())
.tenantId(client.getTenantId())
.remark(remark)
.build();
fullTaskMapper.insert(task);
log.info("[Databus] Full sync task created, taskId={}, taskNo={}", task.getId(), task.getTaskNo());
return task.getId();
}
@Override
@Async
public void executeFullSyncTask(Long taskId) {
DatabusSyncFullTaskDO task = fullTaskMapper.selectById(taskId);
if (task == null) {
log.error("[Databus] Task not found, taskId={}", taskId);
return;
}
// 允许 PENDING、RUNNING、FAILED 状态执行(支持重试)
if (FullTaskStatusEnum.isCompleted(task.getStatus()) ||
FullTaskStatusEnum.isCancelled(task.getStatus())) {
log.warn("[Databus] Task status not allowed, taskId={}, status={}", taskId, task.getStatus());
return;
}
task.setStatus(FullTaskStatusEnum.RUNNING.getStatus());
task.setStartTime(LocalDateTime.now());
fullTaskMapper.updateById(task);
try {
DatabusSyncSubscriptionDO subscription = subscriptionMapper.selectById(task.getSubscriptionId());
DatabusSyncClientDO client = clientMapper.selectById(subscription.getClientId());
DatabusSyncEventDO event = eventMapper.selectById(subscription.getEventId());
String providerType = event.getDataProviderMethod();
if (providerType == null) {
throw new RuntimeException("Event data provider type not configured");
}
DataProvider<?> dataProvider = dataProviderRegistry.getProvider(providerType);
if (dataProvider == null) {
throw new RuntimeException("Data provider not found: " + providerType);
}
executeGenericFullSync(task, subscription, client, event, dataProvider);
task.setStatus(FullTaskStatusEnum.COMPLETED.getStatus());
task.setEndTime(LocalDateTime.now());
fullTaskMapper.updateById(task);
log.info("[Databus] Full sync task completed, taskId={}, totalCount={}, successCount={}",
taskId, task.getTotalCount(), task.getSuccessCount());
} catch (Exception e) {
log.error("[Databus] Full sync task failed, taskId={}", taskId, e);
task.setStatus(FullTaskStatusEnum.FAILED.getStatus());
task.setEndTime(LocalDateTime.now());
task.setLastErrorMessage(e.getMessage());
fullTaskMapper.updateById(task);
}
}
private <T> void executeGenericFullSync(DatabusSyncFullTaskDO task,
DatabusSyncSubscriptionDO subscription,
DatabusSyncClientDO client,
DatabusSyncEventDO event,
DataProvider<T> dataProvider) {
long totalCount = dataProvider.count(task.getTenantId());
int totalBatch = (int) Math.ceil((double) totalCount / task.getBatchSize());
task.setTotalCount(totalCount);
task.setTotalBatch(totalBatch);
fullTaskMapper.updateById(task);
LocalDateTime cursorTime = task.getCursorTime();
Long cursorId = task.getCursorId();
int batchNo = task.getCurrentBatch();
while (true) {
DatabusSyncFullTaskDO currentTask = fullTaskMapper.selectById(task.getId());
if (FullTaskStatusEnum.isCancelled(currentTask.getStatus())) {
log.info("[Databus] Task cancelled, taskId={}", task.getId());
return;
}
batchNo++;
DataProvider.CursorPageData<T> page = dataProvider.getPageByCursor(
cursorTime, cursorId, task.getBatchSize(), task.getTenantId());
if (CollUtil.isEmpty(page.getList())) {
break;
}
boolean isLastBatch = !page.isHasMore();
BatchSyncMessage message = buildBatchMessage(
task, event, page.getList(), dataProvider, batchNo, totalBatch, isLastBatch, totalCount);
boolean success = pushBatchMessage(client, event.getEventType(), message);
recordSyncLog(task, subscription, client, event, message, success, batchNo);
task.setCurrentBatch(batchNo);
task.setCursorTime(page.getNextCursorTime());
task.setCursorId(page.getNextCursorId());
task.setProcessedCount(task.getProcessedCount() + page.getCount());
if (success) {
task.setSuccessCount(task.getSuccessCount() + page.getCount());
} else {
task.setFailCount(task.getFailCount() + page.getCount());
}
fullTaskMapper.updateById(task);
if (isLastBatch) {
break;
}
cursorTime = page.getNextCursorTime();
cursorId = page.getNextCursorId();
}
}
private <T> BatchSyncMessage buildBatchMessage(DatabusSyncFullTaskDO task,
DatabusSyncEventDO event,
List<T> dataList,
DataProvider<T> dataProvider,
int batchNo,
int totalBatch,
boolean isLastBatch,
long totalCount) {
List<BatchSyncMessage.SyncDataItem> items = new ArrayList<>();
for (T data : dataList) {
Long uid = dataProvider.extractUid(data);
items.add(BatchSyncMessage.SyncDataItem.builder()
.action("FULL")
.uid(uid)
.data(JSONUtil.toJsonStr(data))
.build());
}
return BatchSyncMessage.builder()
.messageId(IdUtil.fastSimpleUUID())
.requestId(task.getTaskNo())
.eventType(event.getEventType())
.syncMode(SyncModeEnum.FULL.getMode())
.dataVersion(event.getDataVersion())
.timestamp(System.currentTimeMillis())
.tenantId(task.getTenantId())
.dataList(items)
.count(items.size())
.fullTaskId(task.getId())
.batchNo(batchNo)
.totalBatch(totalBatch)
.isLastBatch(isLastBatch)
.totalCount(totalCount)
.build();
}
private boolean pushBatchMessage(DatabusSyncClientDO client, String eventType,
BatchSyncMessage message) {
try {
if (TransportTypeEnum.isMqFirst(client.getTransportType()) && client.getMqEnabled() == 1) {
// 使用 getByTopicSuffix 支持数据库存储的 topic 格式(如 system-post-full
DatabusEventType databusEventType = DatabusEventType.getByTopicSuffix(eventType);
if (databusEventType == null) {
log.error("[Databus] Unknown event type: {}", eventType);
return false;
}
String topicBase = client.getMqTopicBase();
if (topicBase == null || topicBase.isEmpty()) {
topicBase = "databus-sync";
}
String topic = databusEventType.getTopic(topicBase, client.getClientCode());
log.info("[Databus] Pushing batch message, topic={}, eventType={}, clientCode={}, messageId={}",
topic, eventType, client.getClientCode(), message.getMessageId());
messagePusher.pushBatchByMQ(topic, message);
} else if (client.getHttpEnabled() == 1) {
return messagePusher.pushBatchByHttp(client.getHttpEndpoint(), message);
} else {
throw new RuntimeException("No available push method");
}
return true;
} catch (Exception e) {
log.error("[Databus] Push batch message failed, messageId={}", message.getMessageId(), e);
return false;
}
}
private void recordSyncLog(DatabusSyncFullTaskDO task,
DatabusSyncSubscriptionDO subscription,
DatabusSyncClientDO client,
DatabusSyncEventDO event,
BatchSyncMessage message,
boolean success,
int batchNo) {
DatabusSyncLogDO syncLog = DatabusSyncLogDO.builder()
.syncId(message.getMessageId())
.eventRecordId(0L)
.subscriptionId(subscription.getId())
.clientCode(client.getClientCode())
.eventType(event.getEventType())
.syncMode(SyncModeEnum.FULL.getMode())
.transportType(TransportTypeEnum.isMqFirst(client.getTransportType()) && client.getMqEnabled() == 1
? TransportTypeEnum.MQ_FIRST.getType()
: TransportTypeEnum.HTTP_ONLY.getType())
.status(success ? SyncStatusEnum.SUCCESS.getStatus() : SyncStatusEnum.FAILED.getStatus())
.retryCount(0)
.startTime(LocalDateTime.now())
.endTime(LocalDateTime.now())
.tenantId(task.getTenantId())
.dataCount(message.getCount())
.batchNo(batchNo)
.fullTaskId(task.getId())
.build();
syncLogMapper.insert(syncLog);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void cancelFullSyncTask(Long taskId) {
DatabusSyncFullTaskDO task = fullTaskMapper.selectById(taskId);
if (task == null) {
throw new RuntimeException("Task not found");
}
if (!FullTaskStatusEnum.canCancel(task.getStatus())) {
throw new RuntimeException("Task status not allowed to cancel");
}
task.setStatus(FullTaskStatusEnum.CANCELLED.getStatus());
task.setEndTime(LocalDateTime.now());
fullTaskMapper.updateById(task);
log.info("[Databus] Full sync task cancelled, taskId={}", taskId);
}
@Override
public DatabusSyncFullTaskDO getTaskById(Long taskId) {
return fullTaskMapper.selectById(taskId);
}
@Override
public DatabusSyncFullTaskDO getTaskByTaskNo(String taskNo) {
return fullTaskMapper.selectByTaskNo(taskNo);
}
}

View File

@@ -0,0 +1,115 @@
package com.zt.plat.framework.databus.server.service.impl;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientPageReqVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.client.DatabusSyncClientSaveReqVO;
import com.zt.plat.framework.databus.server.convert.DatabusSyncClientConvert;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncClientDO;
import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncClientMapper;
import com.zt.plat.framework.databus.server.service.DatabusSyncClientService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.zt.plat.framework.databus.server.enums.ErrorCodeConstants.*;
/**
* 数据同步客户端 Service 实现类
*
* @author ZT
*/
@Slf4j
@Service
@Validated
public class DatabusSyncClientServiceImpl implements DatabusSyncClientService {
@Resource
private DatabusSyncClientMapper clientMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createClient(DatabusSyncClientSaveReqVO createReqVO) {
// 校验客户端编码唯一性
validateClientCodeUnique(null, createReqVO.getClientCode());
// 插入
DatabusSyncClientDO client = DatabusSyncClientConvert.INSTANCE.convert(createReqVO);
clientMapper.insert(client);
return client.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateClient(DatabusSyncClientSaveReqVO updateReqVO) {
// 校验存在
validateClientExists(updateReqVO.getId());
// 校验客户端编码唯一性
validateClientCodeUnique(updateReqVO.getId(), updateReqVO.getClientCode());
// 更新
DatabusSyncClientDO updateObj = DatabusSyncClientConvert.INSTANCE.convert(updateReqVO);
clientMapper.updateById(updateObj);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteClient(Long id) {
// 校验存在
validateClientExists(id);
// 删除
clientMapper.deleteById(id);
}
private void validateClientExists(Long id) {
if (clientMapper.selectById(id) == null) {
throw exception(CLIENT_NOT_EXISTS);
}
}
private void validateClientCodeUnique(Long id, String clientCode) {
DatabusSyncClientDO client = clientMapper.selectByClientCode(clientCode);
if (client == null) {
return;
}
// 如果 id 为空,说明不用比较是否为相同 id 的客户端
if (id == null) {
throw exception(CLIENT_CODE_DUPLICATE);
}
if (!client.getId().equals(id)) {
throw exception(CLIENT_CODE_DUPLICATE);
}
}
@Override
public DatabusSyncClientDO getClient(Long id) {
return clientMapper.selectById(id);
}
@Override
public PageResult<DatabusSyncClientDO> getClientPage(DatabusSyncClientPageReqVO pageReqVO) {
return clientMapper.selectPage(pageReqVO);
}
@Override
public List<DatabusSyncClientDO> getClientList() {
return clientMapper.selectList();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateClientStatus(Long id, Integer enabled) {
// 校验存在
validateClientExists(id);
// 更新状态
DatabusSyncClientDO updateObj = new DatabusSyncClientDO();
updateObj.setId(id);
updateObj.setEnabled(enabled);
clientMapper.updateById(updateObj);
}
}

View File

@@ -0,0 +1,99 @@
package com.zt.plat.framework.databus.server.service.impl;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.deadletter.DatabusSyncDeadLetterPageReqVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncDeadLetterDO;
import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncDeadLetterMapper;
import com.zt.plat.framework.databus.server.enums.DeadLetterStatusEnum;
import com.zt.plat.framework.databus.server.service.DatabusSyncDeadLetterService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.List;
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.zt.plat.framework.databus.server.enums.ErrorCodeConstants.DEAD_LETTER_NOT_EXISTS;
/**
* 数据同步死信队列 Service 实现类
*
* @author ZT
*/
@Slf4j
@Service
@Validated
public class DatabusSyncDeadLetterServiceImpl implements DatabusSyncDeadLetterService {
@Resource
private DatabusSyncDeadLetterMapper deadLetterMapper;
@Override
public DatabusSyncDeadLetterDO getDeadLetter(Long id) {
return deadLetterMapper.selectById(id);
}
@Override
public PageResult<DatabusSyncDeadLetterDO> getDeadLetterPage(DatabusSyncDeadLetterPageReqVO pageReqVO) {
return deadLetterMapper.selectPage(pageReqVO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void reprocessDeadLetter(Long id) {
// 校验存在
DatabusSyncDeadLetterDO deadLetter = deadLetterMapper.selectById(id);
if (deadLetter == null) {
throw exception(DEAD_LETTER_NOT_EXISTS);
}
// TODO: 实现重新投递逻辑,将消息重新发送到 MQ 或 HTTP
log.info("[reprocessDeadLetter] 重新投递死信消息ID: {}, 同步ID: {}", id, deadLetter.getSyncId());
// 更新状态为已重新投递
DatabusSyncDeadLetterDO updateObj = new DatabusSyncDeadLetterDO();
updateObj.setId(id);
updateObj.setStatus(DeadLetterStatusEnum.REDELIVERED.getStatus());
updateObj.setHandled(1);
updateObj.setHandleTime(LocalDateTime.now());
deadLetterMapper.updateById(updateObj);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void batchReprocessDeadLetter(List<Long> ids) {
if (ids == null || ids.isEmpty()) {
return;
}
for (Long id : ids) {
try {
reprocessDeadLetter(id);
} catch (Exception e) {
log.error("[batchReprocessDeadLetter] 重新投递失败ID: {}", id, e);
}
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void markHandled(Long id, String remark) {
// 校验存在
DatabusSyncDeadLetterDO deadLetter = deadLetterMapper.selectById(id);
if (deadLetter == null) {
throw exception(DEAD_LETTER_NOT_EXISTS);
}
// 标记为已处理(忽略)
DatabusSyncDeadLetterDO updateObj = new DatabusSyncDeadLetterDO();
updateObj.setId(id);
updateObj.setStatus(DeadLetterStatusEnum.IGNORED.getStatus());
updateObj.setHandled(1);
updateObj.setHandleTime(LocalDateTime.now());
updateObj.setHandleRemark(remark);
deadLetterMapper.updateById(updateObj);
}
}

View File

@@ -0,0 +1,115 @@
package com.zt.plat.framework.databus.server.service.impl;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventPageReqVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.event.DatabusSyncEventSaveReqVO;
import com.zt.plat.framework.databus.server.convert.DatabusSyncEventConvert;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventDO;
import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncEventMapper;
import com.zt.plat.framework.databus.server.service.DatabusSyncEventService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.util.List;
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.zt.plat.framework.databus.server.enums.ErrorCodeConstants.*;
/**
* 数据同步事件 Service 实现类
*
* @author ZT
*/
@Slf4j
@Service
@Validated
public class DatabusSyncEventServiceImpl implements DatabusSyncEventService {
@Resource
private DatabusSyncEventMapper eventMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createEvent(DatabusSyncEventSaveReqVO createReqVO) {
// 校验事件类型唯一性
validateEventTypeUnique(null, createReqVO.getEventType());
// 插入
DatabusSyncEventDO event = DatabusSyncEventConvert.INSTANCE.convert(createReqVO);
eventMapper.insert(event);
return event.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateEvent(DatabusSyncEventSaveReqVO updateReqVO) {
// 校验存在
validateEventExists(updateReqVO.getId());
// 校验事件类型唯一性
validateEventTypeUnique(updateReqVO.getId(), updateReqVO.getEventType());
// 更新
DatabusSyncEventDO updateObj = DatabusSyncEventConvert.INSTANCE.convert(updateReqVO);
eventMapper.updateById(updateObj);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteEvent(Long id) {
// 校验存在
validateEventExists(id);
// 删除
eventMapper.deleteById(id);
}
private void validateEventExists(Long id) {
if (eventMapper.selectById(id) == null) {
throw exception(EVENT_NOT_EXISTS);
}
}
private void validateEventTypeUnique(Long id, String eventType) {
DatabusSyncEventDO event = eventMapper.selectByEventType(eventType);
if (event == null) {
return;
}
// 如果 id 为空,说明不用比较是否为相同 id 的事件
if (id == null) {
throw exception(EVENT_TYPE_DUPLICATE);
}
if (!event.getId().equals(id)) {
throw exception(EVENT_TYPE_DUPLICATE);
}
}
@Override
public DatabusSyncEventDO getEvent(Long id) {
return eventMapper.selectById(id);
}
@Override
public PageResult<DatabusSyncEventDO> getEventPage(DatabusSyncEventPageReqVO pageReqVO) {
return eventMapper.selectPage(pageReqVO);
}
@Override
public List<DatabusSyncEventDO> getEventList() {
return eventMapper.selectList();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateEventStatus(Long id, Integer enabled) {
// 校验存在
validateEventExists(id);
// 更新状态
DatabusSyncEventDO updateObj = new DatabusSyncEventDO();
updateObj.setId(id);
updateObj.setEnabled(enabled);
eventMapper.updateById(updateObj);
}
}

View File

@@ -0,0 +1,50 @@
package com.zt.plat.framework.databus.server.service.impl;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.pushlog.DatabusSyncPushLogPageReqVO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncLogDO;
import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncLogMapper;
import com.zt.plat.framework.databus.server.service.DatabusSyncLogService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.zt.plat.framework.databus.server.enums.ErrorCodeConstants.PUSH_LOG_NOT_EXISTS;
/**
* 数据同步推送日志 Service 实现类
*
* @author ZT
*/
@Slf4j
@Service
@Validated
public class DatabusSyncLogServiceImpl implements DatabusSyncLogService {
@Resource
private DatabusSyncLogMapper pushLogMapper;
@Override
public DatabusSyncLogDO getPushLog(Long id) {
return pushLogMapper.selectById(id);
}
@Override
public PageResult<DatabusSyncLogDO> getPushLogPage(DatabusSyncPushLogPageReqVO pageReqVO) {
return pushLogMapper.selectPage(pageReqVO);
}
@Override
public void retryPush(Long id) {
// 校验存在
DatabusSyncLogDO pushLog = pushLogMapper.selectById(id);
if (pushLog == null) {
throw exception(PUSH_LOG_NOT_EXISTS);
}
// TODO: 实现重试逻辑,将日志重新入队或触发推送
log.info("[retryPush] 重试推送日志ID: {}, 同步ID: {}", id, pushLog.getSyncId());
}
}

View File

@@ -0,0 +1,129 @@
package com.zt.plat.framework.databus.server.service.impl;
import com.zt.plat.framework.common.pojo.PageResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionPageReqVO;
import com.zt.plat.framework.databus.server.controller.admin.vo.subscription.DatabusSyncSubscriptionSaveReqVO;
import com.zt.plat.framework.databus.server.convert.DatabusSyncSubscriptionConvert;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncSubscriptionDO;
import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncSubscriptionMapper;
import com.zt.plat.framework.databus.server.service.DatabusSyncSubscriptionService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.zt.plat.framework.databus.server.enums.ErrorCodeConstants.*;
/**
* 数据同步订阅 Service 实现类
*
* @author ZT
*/
@Slf4j
@Service
@Validated
public class DatabusSyncSubscriptionServiceImpl implements DatabusSyncSubscriptionService {
@Resource
private DatabusSyncSubscriptionMapper subscriptionMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createSubscription(DatabusSyncSubscriptionSaveReqVO createReqVO) {
// 校验订阅唯一性(同一个客户端不能重复订阅同一个事件)
validateSubscriptionUnique(null, createReqVO.getClientId(), createReqVO.getEventId());
// 插入
DatabusSyncSubscriptionDO subscription = DatabusSyncSubscriptionConvert.INSTANCE.convert(createReqVO);
subscriptionMapper.insert(subscription);
return subscription.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateSubscription(DatabusSyncSubscriptionSaveReqVO updateReqVO) {
// 校验存在
validateSubscriptionExists(updateReqVO.getId());
// 校验订阅唯一性
validateSubscriptionUnique(updateReqVO.getId(), updateReqVO.getClientId(), updateReqVO.getEventId());
// 更新
DatabusSyncSubscriptionDO updateObj = DatabusSyncSubscriptionConvert.INSTANCE.convert(updateReqVO);
subscriptionMapper.updateById(updateObj);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteSubscription(Long id) {
// 校验存在
validateSubscriptionExists(id);
// 删除
subscriptionMapper.deleteById(id);
}
private void validateSubscriptionExists(Long id) {
if (subscriptionMapper.selectById(id) == null) {
throw exception(SUBSCRIPTION_NOT_EXISTS);
}
}
private void validateSubscriptionUnique(Long id, Long clientId, Long eventId) {
DatabusSyncSubscriptionDO subscription = subscriptionMapper.selectByClientIdAndEventId(clientId, eventId);
if (subscription == null) {
return;
}
// 如果 id 为空,说明不用比较是否为相同 id 的订阅
if (id == null) {
throw exception(SUBSCRIPTION_DUPLICATE);
}
if (!subscription.getId().equals(id)) {
throw exception(SUBSCRIPTION_DUPLICATE);
}
}
@Override
public DatabusSyncSubscriptionDO getSubscription(Long id) {
return subscriptionMapper.selectById(id);
}
@Override
public PageResult<DatabusSyncSubscriptionDO> getSubscriptionPage(DatabusSyncSubscriptionPageReqVO pageReqVO) {
return subscriptionMapper.selectPage(pageReqVO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateSubscriptionStatus(Long id, Integer enabled) {
// 校验存在
validateSubscriptionExists(id);
// 更新状态
DatabusSyncSubscriptionDO updateObj = new DatabusSyncSubscriptionDO();
updateObj.setId(id);
updateObj.setEnabled(enabled);
subscriptionMapper.updateById(updateObj);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void resetCheckpoint(Long id) {
// 校验存在
validateSubscriptionExists(id);
// 重置断点
DatabusSyncSubscriptionDO updateObj = new DatabusSyncSubscriptionDO();
updateObj.setId(id);
updateObj.setLastSyncEventId(null);
updateObj.setLastSyncTime(null);
subscriptionMapper.updateById(updateObj);
}
@Override
public void triggerSync(Long id) {
// 校验存在
validateSubscriptionExists(id);
// TODO: 发送消息到 MQ 或调用异步任务触发同步
log.info("[triggerSync] 手动触发同步订阅ID: {}", id);
}
}

View File

@@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zt.plat.framework.databus.server.config.DatabusSyncServerAutoConfiguration

View File

@@ -0,0 +1,2 @@
com.zt.plat.framework.databus.server.config.DatabusServerAutoConfiguration
com.zt.plat.framework.databus.server.config.DatabusSyncServerAutoConfiguration