Merge branch 'dev' into 'test'

feat(databus): 完成阶段一+二-数据契约层与数据提供者

See merge request jygk/dsc!4
This commit is contained in:
wencai he
2025-12-04 06:13:11 +00:00
163 changed files with 11680 additions and 8 deletions

View File

@@ -17,7 +17,8 @@
<module>zt-spring-boot-starter-web</module>
<module>zt-spring-boot-starter-security</module>
<module>zt-spring-boot-starter-websocket</module>
<module>zt-spring-boot-starter-databus-server</module>
<module>zt-spring-boot-starter-databus-client</module>
<module>zt-spring-boot-starter-monitor</module>
<module>zt-spring-boot-starter-protection</module>
<!-- <module>zt-spring-boot-starter-config</module>-->

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,228 @@
package com.zt.plat.framework.databus.client.core.consumer;
import com.alibaba.fastjson2.JSON;
import com.zt.plat.framework.databus.client.core.registry.HandlerRegistry;
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 jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
/**
* DataBus 客户端统一消费者
* <p>
* 架构设计:
* 1. 监听客户端专属 Topic: databus-sync-{clientCode}
* 2. 消息体包含 eventType 字段,用于路由到具体 Handler
* 3. 根据 eventType 从 HandlerRegistry 获取对应 Handler
* 4. 区分增量消息和批量消息,调用不同的 Handler
* <p>
* Topic 简化前后对比:
* - 旧格式databus-sync-system-dept-create-branch-001每个事件一个 Topic
* - 新格式databus-sync-branch-001所有事件共用一个 Topic
*
* @author ZT
*/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@RocketMQMessageListener(
topic = "${zt.databus.sync.client.mq.topic:databus-sync}-${zt.databus.sync.client.client-code}",
consumerGroup = "${zt.databus.sync.client.mq.consumer-group:databus-client-consumer}-${zt.databus.sync.client.client-code}"
)
public class DatabusClientConsumer implements RocketMQListener<String> {
@Resource
private HandlerRegistry handlerRegistry;
@Override
public void onMessage(String body) {
log.debug("[DatabusClient] 收到消息, body={}", body);
try {
// 1. 解析消息获取 eventType
DatabusEventType eventType = parseEventType(body);
if (eventType == null) {
log.error("[DatabusClient] 无法解析 eventType, body={}", body);
return;
}
log.info("[DatabusClient] 收到消息, eventType={}", eventType);
// 2. 根据 eventType 判断消息类型并分发
if (isBatchMessage(eventType)) {
// 批量消息(全量同步)
handleBatchMessage(body, eventType);
} else {
// 增量消息
handleIncrementalMessage(body, eventType);
}
} catch (Exception e) {
log.error("[DatabusClient] 消息处理失败, body={}", body, e);
throw e; // 抛出异常触发重试
}
}
/**
* 处理批量消息(全量同步)
*/
@SuppressWarnings("unchecked")
private void handleBatchMessage(String body, DatabusEventType eventType) {
// 1. 获取 BatchHandler
BatchSyncEventHandler handler = handlerRegistry.getBatchHandler(eventType);
if (handler == null) {
log.warn("[DatabusClient] 未找到全量Handler, eventType={}", eventType);
return;
}
// 2. 获取数据类型
Class<?> dataType = handler.getDataType();
// 3. 解析批量消息(兼容服务端 BatchSyncMessage 格式)
var json = JSON.parseObject(body);
// 兼容处理:服务端使用 fullTaskId转换为 taskId
String taskId = json.getString("taskId");
if (taskId == null) {
taskId = String.valueOf(json.getLong("fullTaskId"));
}
DatabusBatchMessage<Object> message = new DatabusBatchMessage<>();
message.setMessageId(json.getString("messageId"));
message.setTaskId(taskId);
message.setEventType(eventType);
message.setBatchNo(json.getInteger("batchNo"));
message.setTotalBatch(json.getInteger("totalBatch"));
message.setCount(json.getInteger("count"));
message.setTotalCount(json.getInteger("totalCount") != null ? json.getInteger("totalCount") : 0);
message.setIsLastBatch(json.getBoolean("isLastBatch"));
message.setTenantId(json.getLong("tenantId"));
// 解析 dataList服务端是 SyncDataItem 列表,需要提取 data 字段并转换为具体类型)
var dataListJson = json.getJSONArray("dataList");
if (dataListJson != null) {
java.util.List<Object> dataList = new java.util.ArrayList<>();
for (int i = 0; i < dataListJson.size(); i++) {
var item = dataListJson.getJSONObject(i);
// 服务端 SyncDataItem 结构:{action, uid, data}
// data 字段是 JSON 字符串,需要解析成具体类型
String dataStr = item.getString("data");
if (dataStr != null) {
// 使用 handler.getDataType() 反序列化成具体类型
Object data = JSON.parseObject(dataStr, dataType);
dataList.add(data);
} else {
// 如果没有 data 字段,可能是直接的数据对象,也需要转换类型
Object data = JSON.parseObject(item.toJSONString(), dataType);
dataList.add(data);
}
}
message.setDataList(dataList);
}
// 4. 全量同步开始回调(第一批)
if (message.getBatchNo() == 1) {
handler.onFullSyncStart(message);
}
// 5. 处理批次数据
handler.handleBatch(message);
// 6. 全量同步完成回调(最后一批)
if (message.getBatchNo().equals(message.getTotalBatch())) {
handler.onFullSyncComplete(message);
}
log.info("[DatabusClient] 批量消息处理完成, eventType={}, taskId={}, batchNo={}/{}",
eventType, message.getTaskId(), message.getBatchNo(), message.getTotalBatch());
}
/**
* 处理增量消息
* <p>
* 兼容服务端 SyncMessage 格式:
* - syncId: 同步ID
* - eventRecordId: 事件记录ID
* - eventType: 事件类型
* - eventAction: 事件动作
* - dataSnapshot: 业务数据快照JSON字符串
* - dataVersion: 数据版本
* - timestamp: 时间戳
*/
@SuppressWarnings("unchecked")
private void handleIncrementalMessage(String body, DatabusEventType eventType) {
// 1. 获取 SyncEventHandler
SyncEventHandler handler = handlerRegistry.getIncrementalHandler(eventType);
if (handler == null) {
log.warn("[DatabusClient] 未找到增量Handler, eventType={}", eventType);
return;
}
// 2. 解析增量消息(兼容服务端 SyncMessage 格式)
var json = JSON.parseObject(body);
// 从 dataSnapshot 字段解析业务数据
String dataSnapshot = json.getString("dataSnapshot");
Object data = null;
Long dataId = null;
if (dataSnapshot != null && !dataSnapshot.isEmpty()) {
// dataSnapshot 是 JSON 字符串,需要解析成具体类型
var dataJson = JSON.parseObject(dataSnapshot);
// 获取数据类型并反序列化
Class<?> dataType = handler.getDataType();
data = JSON.parseObject(dataSnapshot, dataType);
// 提取 dataId
dataId = dataJson.getLong("id");
}
// 构建 DatabusMessage
DatabusMessage<Object> message = new DatabusMessage<>();
message.setMessageId(json.getString("syncId"));
message.setEventType(eventType);
message.setDataId(dataId);
message.setData(data);
message.setTenantId(json.getLong("tenantId"));
// 3. 处理消息
handler.handle(message);
log.info("[DatabusClient] 增量消息处理完成, eventType={}, syncId={}, dataId={}",
eventType, message.getMessageId(), dataId);
}
/**
* 解析 eventType
*/
private DatabusEventType parseEventType(String body) {
try {
// 先尝试从 JSON 中提取 eventType 字段
String eventTypeStr = JSON.parseObject(body).getString("eventType");
if (eventTypeStr != null) {
return DatabusEventType.valueOf(eventTypeStr);
}
} catch (Exception e) {
log.error("[DatabusClient] 解析 eventType 失败", e);
}
return null;
}
/**
* 判断是否为批量消息
*/
private boolean isBatchMessage(DatabusEventType eventType) {
try {
// 批量消息包含 batchNo, totalBatch 字段
// 服务端使用 fullTaskId客户端 API 使用 taskId兼容两种格式
return eventType.getAction().equals("full");
} catch (Exception e) {
return false;
}
}
}

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,136 @@
package com.zt.plat.framework.databus.client.core.registry;
import com.zt.plat.framework.databus.client.handler.BatchSyncEventHandler;
import com.zt.plat.framework.databus.client.handler.SyncEventHandler;
import com.zt.plat.module.databus.enums.DatabusEventType;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Handler 策略注册表
* <p>
* 负责管理所有 Handler 实例<EFBC8C><E6A0B9> eventType 路由到对应的 Handler
* <p>
* 架构设计:
* - 增量同步:使用 SyncEventHandler 处理单条消息
* - 全量同步:使用 BatchSyncEventHandler 处理批量消息
*
* @author ZT
*/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
public class HandlerRegistry {
/**
* 增量同步 Handler 映射表
* <p>
* Key: DatabusEventType事件类型
* Value: SyncEventHandler增量同步处理器
*/
private final Map<DatabusEventType, SyncEventHandler<?>> incrementalHandlers = new HashMap<>();
/**
* 全量同步 Handler 映射表
* <p>
* Key: DatabusEventType事件类型
* Value: BatchSyncEventHandler批量同步处理器
*/
private final Map<DatabusEventType, BatchSyncEventHandler<?>> batchHandlers = new HashMap<>();
/**
* 自动注入所有 SyncEventHandler Bean
*/
@Autowired(required = false)
private List<SyncEventHandler<?>> syncEventHandlers;
/**
* 自动注入所有 BatchSyncEventHandler Bean
*/
@Autowired(required = false)
private List<BatchSyncEventHandler<?>> batchSyncEventHandlers;
/**
* 初始化注册表
* <p>
* 在 Bean 创建后自动调用,注册所有 Handler
*/
@PostConstruct
public void init() {
log.info("[HandlerRegistry] 开始初始化 Handler 注册表...");
// 注册增量同步 Handler
if (syncEventHandlers != null) {
for (SyncEventHandler<?> handler : syncEventHandlers) {
DatabusEventType eventType = handler.getSupportedEventType();
incrementalHandlers.put(eventType, handler);
log.info("[HandlerRegistry] 注册增量Handler: {} -> {}",
eventType, handler.getClass().getSimpleName());
}
}
// 注册全量同步 Handler
if (batchSyncEventHandlers != null) {
for (BatchSyncEventHandler<?> handler : batchSyncEventHandlers) {
DatabusEventType eventType = handler.getSupportedEventType();
batchHandlers.put(eventType, handler);
log.info("[HandlerRegistry] 注册全量Handler: {} -> {}",
eventType, handler.getClass().getSimpleName());
}
}
log.info("[HandlerRegistry] 初始化完成, 增量Handler={}个, 全量Handler={}个",
incrementalHandlers.size(), batchHandlers.size());
}
/**
* 获取增量同步 Handler
*
* @param eventType 事件类型
* @param <T> 数据类型
* @return 对应的 Handler不存在则返回 null
*/
@SuppressWarnings("unchecked")
public <T> SyncEventHandler<T> getIncrementalHandler(DatabusEventType eventType) {
return (SyncEventHandler<T>) incrementalHandlers.get(eventType);
}
/**
* 获取全量同步 Handler
*
* @param eventType 事件类型
* @param <T> 数据类型
* @return 对应的 Handler不存在则返回 null
*/
@SuppressWarnings("unchecked")
public <T> BatchSyncEventHandler<T> getBatchHandler(DatabusEventType eventType) {
return (BatchSyncEventHandler<T>) batchHandlers.get(eventType);
}
/**
* 检查是否存在增量同步 Handler
*
* @param eventType 事件类型
* @return 是否存在
*/
public boolean hasIncrementalHandler(DatabusEventType eventType) {
return incrementalHandlers.containsKey(eventType);
}
/**
* 检查是否存在全量同步 Handler
*
* @param eventType 事件类型
* @return 是否存在
*/
public boolean hasBatchHandler(DatabusEventType eventType) {
return batchHandlers.containsKey(eventType);
}
}

View File

@@ -0,0 +1,79 @@
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;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
/**
* 批量同步事件处理器接口
* <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) {
// 默认空实现,子类可覆盖
}
/**
* 获取数据类型
* <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(BatchSyncEventHandler.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,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,44 @@
package com.zt.plat.framework.databus.client.handler.dept;
import com.zt.plat.module.databus.api.data.DatabusDeptData;
/**
* 部门同步服务接口
* <p>
* 分公司需要实现此接口,完成数据的本地持久化
* 或通过默认实现 {@link DeptSyncServiceImpl} 使用 Feign 调用远程 API
*
* @author ZT
*/
public interface DeptSyncService {
/**
* 创建部门(增量同步)
*
* @param data 部门数据
*/
void create(DatabusDeptData data);
/**
* 更<><E69BB4>部门增量同步
*
* @param data 部门数据
*/
void update(DatabusDeptData data);
/**
* 删除部门(增量同步)
*
* @param id 部门ID
*/
void delete(Long id);
/**
* 全量同步单条数据
* <p>
* 逻辑:存在则更新,不存在则插入
*
* @param data 部门数据
*/
void fullSync(DatabusDeptData data);
}

View File

@@ -0,0 +1,105 @@
package com.zt.plat.framework.databus.client.handler.dept;
import com.zt.plat.module.databus.api.data.DatabusDeptData;
import com.zt.plat.module.system.api.dept.DeptApi;
import com.zt.plat.module.system.api.dept.dto.DeptSaveReqDTO;
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;
/**
* 部门同步服务实现(通过 Feign API 调用远程服务)
* <p>
* 使用条件:
* 1. zt.databus.sync.client.enabled=true
* 2. 系统中存在 DeptApi 接口Feign 客户端)
* <p>
* 如果分公司需要自定义实现,可以创建自己的 DeptSyncService Bean
* 此默认实现会自动失效(@ConditionalOnMissingBean
*
* @author ZT
*/
@Slf4j
@Service
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnClass(name = "com.zt.plat.module.system.api.dept.DeptApi")
public class DeptSyncServiceImpl implements DeptSyncService {
@Autowired(required = false)
private DeptApi deptApi; // Feign 远程调用接口
@Override
public void create(DatabusDeptData data) {
if (deptApi == null) {
log.warn("[DeptSync] DeptApi未注入跳过创建部门操作, deptId={}", data.getId());
return;
}
DeptSaveReqDTO dto = buildDeptDTO(data);
deptApi.createDept(dto).checkError();
log.info("[DeptSync] 部门创建成功, deptId={}, deptName={}", dto.getId(), dto.getName());
}
@Override
public void update(DatabusDeptData data) {
if (deptApi == null) {
log.warn("[DeptSync] DeptApi未注入跳过更新部门操作, deptId={}", data.getId());
return;
}
DeptSaveReqDTO dto = buildDeptDTO(data);
deptApi.updateDept(dto).checkError();
log.info("[DeptSync] 部门更新成功, deptId={}, deptName={}", dto.getId(), dto.getName());
}
@Override
public void delete(Long id) {
if (deptApi == null) {
log.warn("[DeptSync] DeptApi未注入跳过删除部门操作, deptId={}", id);
return;
}
deptApi.deleteDept(id).checkError();
log.info("[DeptSync] 部门删除成功, deptId={}", id);
}
@Override
public void fullSync(DatabusDeptData data) {
if (deptApi == null) {
log.warn("[DeptSync] DeptApi未注入跳过全量同步部门操作, deptId={}", data.getId());
return;
}
DeptSaveReqDTO dto = buildDeptDTO(data);
try {
// 尝试获取,存在则更新,不存在则创建
var existing = deptApi.getDept(dto.getId());
if (existing.isSuccess() && existing.getData() != null) {
deptApi.updateDept(dto).checkError();
log.info("[DeptSync] 部门全量同步-更新成功, deptId={}", dto.getId());
} else {
deptApi.createDept(dto).checkError();
log.info("[DeptSync] 部门全量同步-创建成功, deptId={}", dto.getId());
}
} catch (Exception e) {
// 获取失败,尝试创建
log.warn("[DeptSync] 部门获取失败,尝试创建, deptId={}", dto.getId());
deptApi.createDept(dto).checkError();
log.info("[DeptSync] 部门全量同步-创建成功, deptId={}", dto.getId());
}
}
/**
* 构建部门 DTO用于 Feign 调用)
*/
private DeptSaveReqDTO buildDeptDTO(DatabusDeptData data) {
DeptSaveReqDTO dto = new DeptSaveReqDTO();
dto.setId(data.getId());
dto.setName(data.getName());
dto.setParentId(data.getParentId());
dto.setSort(data.getSort());
dto.setLeaderUserId(data.getLeaderUserId());
dto.setPhone(data.getPhone());
dto.setEmail(data.getEmail());
dto.setStatus(data.getStatus());
return dto;
}
}

View File

@@ -0,0 +1,50 @@
package com.zt.plat.framework.databus.client.handler.dept;
import com.zt.plat.framework.databus.client.handler.SyncEventHandler;
import com.zt.plat.module.databus.api.data.DatabusDeptData;
import com.zt.plat.module.databus.api.message.DatabusMessage;
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;
/**
* 部门创建事件处理器
* <p>
* 使用条件:
* 1. zt.databus.sync.client.enabled=true
* 2. 存在 DeptSyncService Bean
*
* @author ZT
*/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnBean(DeptSyncService.class)
public class SystemDeptCreateHandler implements SyncEventHandler<DatabusDeptData> {
@Resource
private DeptSyncService deptSyncService;
@Override
public DatabusEventType getSupportedEventType() {
return DatabusEventType.SYSTEM_DEPT_CREATE;
}
@Override
public void handle(DatabusMessage<DatabusDeptData> message) {
DatabusDeptData data = message.getData();
log.info("[DeptSync] 收到部门创建事件, id={}, name={}, parentId={}",
data.getId(), data.getName(), data.getParentId());
try {
deptSyncService.create(data);
log.info("[DeptSync] 部门创建成功, id={}", data.getId());
} catch (Exception e) {
log.error("[DeptSync] 部门创建失败, id={}", data.getId(), e);
throw e; // 抛出异常触发重试
}
}
}

View File

@@ -0,0 +1,49 @@
package com.zt.plat.framework.databus.client.handler.dept;
import com.zt.plat.framework.databus.client.handler.SyncEventHandler;
import com.zt.plat.module.databus.api.data.DatabusDeptData;
import com.zt.plat.module.databus.api.message.DatabusMessage;
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;
/**
* 部门删除事件处理器
* <p>
* 使用条件:
* 1. zt.databus.sync.client.enabled=true
* 2. 存在 DeptSyncService Bean
*
* @author ZT
*/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnBean(DeptSyncService.class)
public class SystemDeptDeleteHandler implements SyncEventHandler<DatabusDeptData> {
@Resource
private DeptSyncService deptSyncService;
@Override
public DatabusEventType getSupportedEventType() {
return DatabusEventType.SYSTEM_DEPT_DELETE;
}
@Override
public void handle(DatabusMessage<DatabusDeptData> message) {
DatabusDeptData data = message.getData();
log.info("[DeptSync] 收到部门删除事件, id={}", data.getId());
try {
deptSyncService.delete(data.getId());
log.info("[DeptSync] 部门删除成功, id={}", data.getId());
} catch (Exception e) {
log.error("[DeptSync] 部门删除失败, id={}", data.getId(), e);
throw e; // 抛出异常触发重试
}
}
}

View File

@@ -0,0 +1,68 @@
package com.zt.plat.framework.databus.client.handler.dept;
import com.zt.plat.framework.databus.client.handler.BatchSyncEventHandler;
import com.zt.plat.module.databus.api.data.DatabusDeptData;
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;
/**
* 部门全量同步事件处理器(批量处理)
* <p>
* 使用条件:
* 1. zt.databus.sync.client.enabled=true
* 2. 存在 DeptSyncService Bean
*
* @author ZT
*/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnBean(DeptSyncService.class)
public class SystemDeptFullHandler implements BatchSyncEventHandler<DatabusDeptData> {
@Resource
private DeptSyncService deptSyncService;
@Override
public DatabusEventType getSupportedEventType() {
return DatabusEventType.SYSTEM_DEPT_FULL;
}
@Override
public void onFullSyncStart(DatabusBatchMessage<DatabusDeptData> message) {
log.info("[DeptSync] 开始部门全量同步, taskId={}, totalBatch={}",
message.getTaskId(), message.getTotalBatch());
}
@Override
public void handleBatch(DatabusBatchMessage<DatabusDeptData> message) {
log.info("[DeptSync] 处理部门批次数据, taskId={}, batchNo={}/{}, size={}",
message.getTaskId(), message.getBatchNo(), message.getTotalBatch(),
message.getDataList().size());
// 逐条处理全量同步数据
for (DatabusDeptData data : message.getDataList()) {
try {
deptSyncService.fullSync(data);
log.debug("[DeptSync] 部门全量同步成功, id={}, name={}", data.getId(), data.getName());
} catch (Exception e) {
log.error("[DeptSync] 部门全量同步失败, id={}, name={}", data.getId(), data.getName(), e);
// 单条失败不影响其他数据,继续处理
}
}
log.info("[DeptSync] 部门批次处理完成, taskId={}, batchNo={}/{}",
message.getTaskId(), message.getBatchNo(), message.getTotalBatch());
}
@Override
public void onFullSyncComplete(DatabusBatchMessage<DatabusDeptData> message) {
log.info("[DeptSync] 部门全量同步完成, taskId={}, totalBatch={}",
message.getTaskId(), message.getTotalBatch());
}
}

View File

@@ -0,0 +1,49 @@
package com.zt.plat.framework.databus.client.handler.dept;
import com.zt.plat.framework.databus.client.handler.SyncEventHandler;
import com.zt.plat.module.databus.api.data.DatabusDeptData;
import com.zt.plat.module.databus.api.message.DatabusMessage;
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;
/**
* 部门更新事件处理器
* <p>
* 使用条件:
* 1. zt.databus.sync.client.enabled=true
* 2. 存在 DeptSyncService Bean
*
* @author ZT
*/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnBean(DeptSyncService.class)
public class SystemDeptUpdateHandler implements SyncEventHandler<DatabusDeptData> {
@Resource
private DeptSyncService deptSyncService;
@Override
public DatabusEventType getSupportedEventType() {
return DatabusEventType.SYSTEM_DEPT_UPDATE;
}
@Override
public void handle(DatabusMessage<DatabusDeptData> message) {
DatabusDeptData data = message.getData();
log.info("[DeptSync] 收到部门更新事件, id={}, name={}", data.getId(), data.getName());
try {
deptSyncService.update(data);
log.info("[DeptSync] 部门更新成功, id={}", data.getId());
} catch (Exception e) {
log.error("[DeptSync] 部门更新失败, id={}", data.getId(), e);
throw e; // 抛出异常触发重试
}
}
}

View File

@@ -0,0 +1,43 @@
package com.zt.plat.framework.databus.client.handler.post;
import com.zt.plat.module.databus.api.data.DatabusPostData;
/**
* 岗位同步服务接口
* <p>
* 客户端可以自定义实现此接口,覆盖默认的 Feign 调用实现
* <p>
* 默认实现:{@link PostSyncServiceImpl}(通过 Feign 远程调用)
*
* @author ZT
*/
public interface PostSyncService {
/**
* 创建岗位
*
* @param data 岗位数据
*/
void create(DatabusPostData data);
/**
* 更新岗位
*
* @param data 岗位数据
*/
void update(DatabusPostData data);
/**
* 删除岗位
*
* @param id 岗位 ID
*/
void delete(Long id);
/**
* 全量同步岗位(创建或更新)
*
* @param data 岗位数据
*/
void fullSync(DatabusPostData data);
}

View File

@@ -0,0 +1,103 @@
package com.zt.plat.framework.databus.client.handler.post;
import com.zt.plat.module.databus.api.data.DatabusPostData;
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;
/**
* 岗位同步服务默认实现(通过 Feign 调用)
* <p>
* 使用条件:
* 1. zt.databus.sync.client.enabled=true
* 2. PostApi 类存在于 classpath
* <p>
* 客户端可以自定义实现 PostSyncService 接口覆盖此默认实现
*
* @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 PostSyncServiceImpl implements PostSyncService {
@Autowired(required = false)
private PostApi postApi;
@Override
public void create(DatabusPostData 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());
}
@Override
public void update(DatabusPostData 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());
}
@Override
public void delete(Long id) {
if (postApi == null) {
log.warn("[PostSync] PostApi未注入跳过删除岗位操作, postId={}", id);
return;
}
postApi.deletePost(id).checkError();
log.info("[PostSync] 岗位删除成功, postId={}", id);
}
@Override
public void fullSync(DatabusPostData data) {
if (postApi == null) {
log.warn("[PostSync] PostApi未注入跳过全量同步岗位操作, postId={}", data.getId());
return;
}
PostSaveReqDTO dto = buildPostDTO(data);
try {
// 尝试获取,存在则更新,不存在则创建
var existing = postApi.getPost(dto.getId());
if (existing.isSuccess() && existing.getData() != null) {
postApi.updatePost(dto).checkError();
log.info("[PostSync] 岗位全量同步-更新成功, postId={}, postName={}", dto.getId(), dto.getName());
} else {
postApi.createPost(dto).checkError();
log.info("[PostSync] 岗位全量同步-创建成功, postId={}, postName={}", dto.getId(), dto.getName());
}
} catch (Exception e) {
// 获取失败,尝试创建
try {
postApi.createPost(dto).checkError();
log.info("[PostSync] 岗位全量同步-创建成功, postId={}, postName={}", dto.getId(), dto.getName());
} catch (Exception createEx) {
log.error("[PostSync] 岗位全量同步失败, postId={}, postName={}", dto.getId(), dto.getName(), createEx);
throw createEx;
}
}
}
private PostSaveReqDTO buildPostDTO(DatabusPostData data) {
PostSaveReqDTO dto = new PostSaveReqDTO();
dto.setId(data.getId());
dto.setName(data.getName());
dto.setCode(data.getCode());
dto.setSort(data.getSort());
dto.setStatus(data.getStatus());
dto.setRemark(data.getRemark());
return dto;
}
}

View File

@@ -0,0 +1,50 @@
package com.zt.plat.framework.databus.client.handler.post;
import com.zt.plat.framework.databus.client.handler.SyncEventHandler;
import com.zt.plat.module.databus.api.data.DatabusPostData;
import com.zt.plat.module.databus.api.message.DatabusMessage;
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;
/**
* 岗位创建事件处理器
* <p>
* 使用条件:
* 1. zt.databus.sync.client.enabled=true
* 2. 存在 PostSyncService Bean
*
* @author ZT
*/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnBean(PostSyncService.class)
public class SystemPostCreateHandler implements SyncEventHandler<DatabusPostData> {
@Resource
private PostSyncService postSyncService;
@Override
public DatabusEventType getSupportedEventType() {
return DatabusEventType.SYSTEM_POST_CREATE;
}
@Override
public void handle(DatabusMessage<DatabusPostData> message) {
DatabusPostData data = message.getData();
log.info("[PostSync] 收到岗位创建事件, id={}, name={}, code={}",
data.getId(), data.getName(), data.getCode());
try {
postSyncService.create(data);
log.info("[PostSync] 岗位创建成功, id={}", data.getId());
} catch (Exception e) {
log.error("[PostSync] 岗位创建失败, id={}", data.getId(), e);
throw e; // 抛出异常触发重试
}
}
}

View File

@@ -0,0 +1,49 @@
package com.zt.plat.framework.databus.client.handler.post;
import com.zt.plat.framework.databus.client.handler.SyncEventHandler;
import com.zt.plat.module.databus.api.data.DatabusPostData;
import com.zt.plat.module.databus.api.message.DatabusMessage;
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;
/**
* 岗位删除事件处理器
* <p>
* 使用条件:
* 1. zt.databus.sync.client.enabled=true
* 2. 存在 PostSyncService Bean
*
* @author ZT
*/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnBean(PostSyncService.class)
public class SystemPostDeleteHandler implements SyncEventHandler<DatabusPostData> {
@Resource
private PostSyncService postSyncService;
@Override
public DatabusEventType getSupportedEventType() {
return DatabusEventType.SYSTEM_POST_DELETE;
}
@Override
public void handle(DatabusMessage<DatabusPostData> message) {
DatabusPostData data = message.getData();
log.info("[PostSync] 收到岗位删除事件, id={}", data.getId());
try {
postSyncService.delete(data.getId());
log.info("[PostSync] 岗位删除成功, id={}", data.getId());
} catch (Exception e) {
log.error("[PostSync] 岗位删除失败, id={}", data.getId(), e);
throw e; // 抛出异常触发重试
}
}
}

View File

@@ -0,0 +1,68 @@
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.DatabusPostData;
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;
/**
* 岗位全量同步事件处理器(批量处理)
* <p>
* 使用条件:
* 1. zt.databus.sync.client.enabled=true
* 2. 存在 PostSyncService Bean
*
* @author ZT
*/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnBean(PostSyncService.class)
public class SystemPostFullHandler implements BatchSyncEventHandler<DatabusPostData> {
@Resource
private PostSyncService postSyncService;
@Override
public DatabusEventType getSupportedEventType() {
return DatabusEventType.SYSTEM_POST_FULL;
}
@Override
public void onFullSyncStart(DatabusBatchMessage<DatabusPostData> message) {
log.info("[PostSync] 开始岗位全量同步, taskId={}, totalBatch={}",
message.getTaskId(), message.getTotalBatch());
}
@Override
public void handleBatch(DatabusBatchMessage<DatabusPostData> message) {
log.info("[PostSync] 处理岗位批次数据, taskId={}, batchNo={}/{}, size={}",
message.getTaskId(), message.getBatchNo(), message.getTotalBatch(),
message.getDataList().size());
// 逐条处理全量同步数据
for (DatabusPostData data : message.getDataList()) {
try {
postSyncService.fullSync(data);
log.debug("[PostSync] 岗位全量同步成功, id={}, name={}", data.getId(), data.getName());
} catch (Exception e) {
log.error("[PostSync] 岗位全量同步失败, id={}, name={}", data.getId(), data.getName(), e);
// 单条失败不影响其他数据,继续处理
}
}
log.info("[PostSync] 岗位批次处理完成, taskId={}, batchNo={}/{}",
message.getTaskId(), message.getBatchNo(), message.getTotalBatch());
}
@Override
public void onFullSyncComplete(DatabusBatchMessage<DatabusPostData> message) {
log.info("[PostSync] 岗位全量同步完成, taskId={}, totalBatch={}",
message.getTaskId(), message.getTotalBatch());
}
}

View File

@@ -0,0 +1,50 @@
package com.zt.plat.framework.databus.client.handler.post;
import com.zt.plat.framework.databus.client.handler.SyncEventHandler;
import com.zt.plat.module.databus.api.data.DatabusPostData;
import com.zt.plat.module.databus.api.message.DatabusMessage;
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;
/**
* 岗位更新事件处理器
* <p>
* 使用条件:
* 1. zt.databus.sync.client.enabled=true
* 2. 存在 PostSyncService Bean
*
* @author ZT
*/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnBean(PostSyncService.class)
public class SystemPostUpdateHandler implements SyncEventHandler<DatabusPostData> {
@Resource
private PostSyncService postSyncService;
@Override
public DatabusEventType getSupportedEventType() {
return DatabusEventType.SYSTEM_POST_UPDATE;
}
@Override
public void handle(DatabusMessage<DatabusPostData> message) {
DatabusPostData data = message.getData();
log.info("[PostSync] 收到岗位更新事件, id={}, name={}, code={}",
data.getId(), data.getName(), data.getCode());
try {
postSyncService.update(data);
log.info("[PostSync] 岗位更新成功, id={}", data.getId());
} catch (Exception e) {
log.error("[PostSync] 岗位更新失败, id={}", data.getId(), e);
throw e; // 抛出异常触发重试
}
}
}

View File

@@ -0,0 +1,44 @@
package com.zt.plat.framework.databus.client.handler.user;
import com.zt.plat.module.databus.api.data.DatabusAdminUserData;
/**
* 用户同步服务接口
* <p>
* 分公司需要实现此接口,完成数据的本地持久化
* 或通过默认实现 {@link AdminUserSyncServiceImpl} 使用 Feign 调用远程 API
*
* @author ZT
*/
public interface AdminUserSyncService {
/**
* 创建用户(增量同步)
*
* @param data 用户数据
*/
void create(DatabusAdminUserData data);
/**
* 更新用户(增量同步)
*
* @param data 用户数据
*/
void update(DatabusAdminUserData data);
/**
* 删除用户(增量同步)
*
* @param id 用户ID
*/
void delete(Long id);
/**
* 全量同步单条数据
* <p>
* 逻辑:存在则更新,不存在则插入
*
* @param data 用户数据
*/
void fullSync(DatabusAdminUserData data);
}

View File

@@ -0,0 +1,112 @@
package com.zt.plat.framework.databus.client.handler.user;
import com.zt.plat.module.databus.api.data.DatabusAdminUserData;
import com.zt.plat.module.system.api.user.AdminUserApi;
import com.zt.plat.module.system.api.user.dto.AdminUserSaveReqDTO;
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;
import java.util.HashSet;
import java.util.Set;
/**
* 用户同步服务实现(通过 Feign API 调用远程服务)
* <p>
* 使用条件:
* 1. zt.databus.sync.client.enabled=true
* 2. 系统中存在 AdminUserApi 接口Feign 客户端)
* <p>
* 如果分公司需要自定义实现,可以创建自己的 AdminUserSyncService Bean
* 此默认实现会自动失效(@ConditionalOnMissingBean
*
* @author ZT
*/
@Slf4j
@Service
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnClass(name = "com.zt.plat.module.system.api.user.AdminUserApi")
public class AdminUserSyncServiceImpl implements AdminUserSyncService {
@Autowired(required = false)
private AdminUserApi adminUserApi; // Feign 远程调用接口
@Override
public void create(DatabusAdminUserData data) {
if (adminUserApi == null) {
log.warn("[UserSync] AdminUserApi未注入跳过创建用户操作, userId={}", data.getId());
return;
}
AdminUserSaveReqDTO dto = buildUserDTO(data);
adminUserApi.createUser(dto).checkError();
log.info("[UserSync] 用户创建成功, userId={}, username={}", dto.getId(), dto.getUsername());
}
@Override
public void update(DatabusAdminUserData data) {
if (adminUserApi == null) {
log.warn("[UserSync] AdminUserApi未注入跳过更新用户操作, userId={}", data.getId());
return;
}
AdminUserSaveReqDTO dto = buildUserDTO(data);
adminUserApi.updateUser(dto).checkError();
log.info("[UserSync] 用户更新成功, userId={}, username={}", dto.getId(), dto.getUsername());
}
@Override
public void delete(Long id) {
if (adminUserApi == null) {
log.warn("[UserSync] AdminUserApi未注入跳过删除用户操作, userId={}", id);
return;
}
adminUserApi.deleteUser(id).checkError();
log.info("[UserSync] 用户删除成功, userId={}", id);
}
@Override
public void fullSync(DatabusAdminUserData data) {
if (adminUserApi == null) {
log.warn("[UserSync] AdminUserApi未注入跳过全量同步用户操作, userId={}", data.getId());
return;
}
AdminUserSaveReqDTO dto = buildUserDTO(data);
try {
// 尝试获取,存在则更新,不存在则创建
var existing = adminUserApi.getUser(dto.getId());
if (existing.isSuccess() && existing.getData() != null) {
adminUserApi.updateUser(dto).checkError();
log.info("[UserSync] 用户全量同步-更新成功, userId={}", dto.getId());
} else {
adminUserApi.createUser(dto).checkError();
log.info("[UserSync] 用户全量同步-创建成功, userId={}", dto.getId());
}
} catch (Exception e) {
// 获取失败,尝试创建
log.warn("[UserSync] 用户获取失败,尝试创建, userId={}", dto.getId());
adminUserApi.createUser(dto).checkError();
log.info("[UserSync] 用户全量同步-创建成功, userId={}", dto.getId());
}
}
/**
* 构建用户 DTO用于 Feign 调用)
*/
private AdminUserSaveReqDTO buildUserDTO(DatabusAdminUserData data) {
AdminUserSaveReqDTO dto = new AdminUserSaveReqDTO();
dto.setId(data.getId());
dto.setUsername(data.getUsername());
dto.setNickname(data.getNickname());
dto.setRemark(data.getRemark());
// 将 List<Long> 转换为 Set<Long>
dto.setDeptIds(data.getDeptIds() != null ? new HashSet<>(data.getDeptIds()) : null);
dto.setPostIds(data.getPostIds() != null ? new HashSet<>(data.getPostIds()) : null);
dto.setEmail(data.getEmail());
dto.setMobile(data.getMobile());
dto.setSex(data.getSex());
dto.setAvatar(data.getAvatar());
dto.setStatus(data.getStatus());
return dto;
}
}

View File

@@ -0,0 +1,49 @@
package com.zt.plat.framework.databus.client.handler.user;
import com.zt.plat.framework.databus.client.handler.SyncEventHandler;
import com.zt.plat.module.databus.api.data.DatabusAdminUserData;
import com.zt.plat.module.databus.api.message.DatabusMessage;
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;
/**
* 用户创建事件处理器
* <p>
* 使用条件:
* 1. zt.databus.sync.client.enabled=true
* 2. 存在 AdminUserSyncService Bean
*
* @author ZT
*/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnBean(AdminUserSyncService.class)
public class SystemUserCreateHandler implements SyncEventHandler<DatabusAdminUserData> {
@Resource
private AdminUserSyncService adminUserSyncService;
@Override
public DatabusEventType getSupportedEventType() {
return DatabusEventType.SYSTEM_USER_CREATE;
}
@Override
public void handle(DatabusMessage<DatabusAdminUserData> message) {
DatabusAdminUserData data = message.getData();
log.info("[UserSync] 收到用户创建事件, id={}, username={}", data.getId(), data.getUsername());
try {
adminUserSyncService.create(data);
log.info("[UserSync] 用户创建成功, id={}", data.getId());
} catch (Exception e) {
log.error("[UserSync] 用户创建失败, id={}", data.getId(), e);
throw e; // 抛出异常触发重试
}
}
}

View File

@@ -0,0 +1,49 @@
package com.zt.plat.framework.databus.client.handler.user;
import com.zt.plat.framework.databus.client.handler.SyncEventHandler;
import com.zt.plat.module.databus.api.data.DatabusAdminUserData;
import com.zt.plat.module.databus.api.message.DatabusMessage;
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;
/**
* 用户删除事件处理器
* <p>
* 使用条件:
* 1. zt.databus.sync.client.enabled=true
* 2. 存在 AdminUserSyncService Bean
*
* @author ZT
*/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnBean(AdminUserSyncService.class)
public class SystemUserDeleteHandler implements SyncEventHandler<DatabusAdminUserData> {
@Resource
private AdminUserSyncService adminUserSyncService;
@Override
public DatabusEventType getSupportedEventType() {
return DatabusEventType.SYSTEM_USER_DELETE;
}
@Override
public void handle(DatabusMessage<DatabusAdminUserData> message) {
DatabusAdminUserData data = message.getData();
log.info("[UserSync] 收到用户删除事件, id={}", data.getId());
try {
adminUserSyncService.delete(data.getId());
log.info("[UserSync] 用户删除成功, id={}", data.getId());
} catch (Exception e) {
log.error("[UserSync] 用户删除失败, id={}", data.getId(), e);
throw e; // 抛出异常触发重试
}
}
}

View File

@@ -0,0 +1,68 @@
package com.zt.plat.framework.databus.client.handler.user;
import com.zt.plat.framework.databus.client.handler.BatchSyncEventHandler;
import com.zt.plat.module.databus.api.data.DatabusAdminUserData;
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;
/**
* 用户全量同步事件处理器(批量处理)
* <p>
* 使用条件:
* 1. zt.databus.sync.client.enabled=true
* 2. 存在 AdminUserSyncService Bean
*
* @author ZT
*/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnBean(AdminUserSyncService.class)
public class SystemUserFullHandler implements BatchSyncEventHandler<DatabusAdminUserData> {
@Resource
private AdminUserSyncService adminUserSyncService;
@Override
public DatabusEventType getSupportedEventType() {
return DatabusEventType.SYSTEM_USER_FULL;
}
@Override
public void onFullSyncStart(DatabusBatchMessage<DatabusAdminUserData> message) {
log.info("[UserSync] 开始用户全量同步, taskId={}, totalBatch={}",
message.getTaskId(), message.getTotalBatch());
}
@Override
public void handleBatch(DatabusBatchMessage<DatabusAdminUserData> message) {
log.info("[UserSync] 处理用户批次数据, taskId={}, batchNo={}/{}, size={}",
message.getTaskId(), message.getBatchNo(), message.getTotalBatch(),
message.getDataList().size());
// 逐条处理全量同步数据
for (DatabusAdminUserData data : message.getDataList()) {
try {
adminUserSyncService.fullSync(data);
log.debug("[UserSync] 用户全量同步成功, id={}, username={}", data.getId(), data.getUsername());
} catch (Exception e) {
log.error("[UserSync] 用户全量同步失败, id={}, username={}", data.getId(), data.getUsername(), e);
// 单条失败不影响其他数据,继续处理
}
}
log.info("[UserSync] 用户批次处理完成, taskId={}, batchNo={}/{}",
message.getTaskId(), message.getBatchNo(), message.getTotalBatch());
}
@Override
public void onFullSyncComplete(DatabusBatchMessage<DatabusAdminUserData> message) {
log.info("[UserSync] 用户全量同步完成, taskId={}, totalBatch={}",
message.getTaskId(), message.getTotalBatch());
}
}

View File

@@ -0,0 +1,49 @@
package com.zt.plat.framework.databus.client.handler.user;
import com.zt.plat.framework.databus.client.handler.SyncEventHandler;
import com.zt.plat.module.databus.api.data.DatabusAdminUserData;
import com.zt.plat.module.databus.api.message.DatabusMessage;
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;
/**
* 用户更新事件处理器
* <p>
* 使用条件:
* 1. zt.databus.sync.client.enabled=true
* 2. 存在 AdminUserSyncService Bean
*
* @author ZT
*/
@Slf4j
@Component
@ConditionalOnProperty(prefix = "zt.databus.sync.client", name = "enabled", havingValue = "true")
@ConditionalOnBean(AdminUserSyncService.class)
public class SystemUserUpdateHandler implements SyncEventHandler<DatabusAdminUserData> {
@Resource
private AdminUserSyncService adminUserSyncService;
@Override
public DatabusEventType getSupportedEventType() {
return DatabusEventType.SYSTEM_USER_UPDATE;
}
@Override
public void handle(DatabusMessage<DatabusAdminUserData> message) {
DatabusAdminUserData data = message.getData();
log.info("[UserSync] 收到用户更新事件, id={}, username={}", data.getId(), data.getUsername());
try {
adminUserSyncService.update(data);
log.info("[UserSync] 用户更新成功, id={}", data.getId());
} catch (Exception e) {
log.error("[UserSync] 用户更新失败, id={}", data.getId(), e);
throw e; // 抛出异常触发重试
}
}
}

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,80 @@
<?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>
<!-- System API (for consuming change messages) -->
<dependency>
<groupId>com.zt.plat</groupId>
<artifactId>zt-module-system-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,90 @@
package com.zt.plat.framework.databus.server.consumer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zt.plat.framework.databus.server.core.event.DatabusEvent;
import com.zt.plat.framework.databus.server.core.sync.DatabusIncrementalSyncService;
import com.zt.plat.module.system.api.mq.DatabusDeptChangeMessage;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* Databus 部门变更消息消费者
* <p>
* 消费来自 system-server 的部门变更消息,通过增量同步服务进行:
* 1. 三态判断(事件/客户端/订阅是否启用)
* 2. 记录到 event_record 流水表
* 3. 推送到客户端专属 Topicdatabus-sync-{clientCode}
*
* @author ZT
*/
@Slf4j
@Component
@RocketMQMessageListener(
topic = DatabusDeptChangeMessage.TOPIC,
consumerGroup = DatabusDeptChangeMessage.TOPIC + "_CONSUMER"
)
public class DatabusDeptChangeConsumer implements RocketMQListener<DatabusDeptChangeMessage> {
private static final String SOURCE_SERVICE = "system-server";
@Resource
private DatabusIncrementalSyncService databusIncrementalSyncService;
@Resource
private ObjectMapper objectMapper;
@Override
public void onMessage(DatabusDeptChangeMessage message) {
log.info("[Databus] 收到部门变更消息, action={}, deptId={}", message.getAction(), message.getDeptId());
try {
// 构建完整的业务数据快照
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("id", message.getDeptId());
dataMap.put("code", message.getDeptCode());
dataMap.put("name", message.getDeptName());
dataMap.put("shortName", message.getShortName());
dataMap.put("parentId", message.getParentId());
dataMap.put("sort", message.getSort());
dataMap.put("leaderUserId", message.getLeaderUserId());
dataMap.put("phone", message.getPhone());
dataMap.put("email", message.getStatus());
dataMap.put("isCompany", message.getIsCompany());
dataMap.put("isGroup", message.getIsGroup());
dataMap.put("deptSource", message.getDeptSource());
dataMap.put("tenantId", message.getTenantId());
dataMap.put("eventTime", message.getEventTime());
// 构建完整的事件类型: SYSTEM_DEPT_{ACTION}(大写下划线格式,与数据库存储一致)
String eventType = String.format("SYSTEM_DEPT_%s", message.getAction().toUpperCase());
// 构建 Databus 事件
DatabusEvent databusEvent = DatabusEvent.builder()
.eventType(eventType)
.eventAction(message.getAction())
.dataSnapshot(objectMapper.writeValueAsString(dataMap))
.dataVersion(1)
.sourceService(SOURCE_SERVICE)
.sourceTopic(DatabusDeptChangeMessage.TOPIC)
.tenantId(message.getTenantId())
.eventTime(message.getEventTime())
.build();
// 调用增量同步服务处理(三态判断 + 记录流水 + 推送客户端Topic
databusIncrementalSyncService.processEvent(databusEvent);
log.info("[Databus] 部门变更事件处理完成, eventType={}, deptId={}",
eventType, message.getDeptId());
} catch (Exception e) {
log.error("[Databus] 处理部门变更消息失败, action={}, deptId={}",
message.getAction(), message.getDeptId(), e);
throw new RuntimeException("处理部门变更消息失败", e);
}
}
}

View File

@@ -0,0 +1,74 @@
package com.zt.plat.framework.databus.server.consumer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zt.plat.framework.databus.server.core.event.DatabusEvent;
import com.zt.plat.framework.databus.server.core.sync.DatabusIncrementalSyncService;
import com.zt.plat.module.system.api.mq.DatabusPostChangeMessage;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* Databus 岗位变更消息消费者
*/
@Slf4j
@Component
@RocketMQMessageListener(
topic = DatabusPostChangeMessage.TOPIC,
consumerGroup = DatabusPostChangeMessage.TOPIC + "_CONSUMER"
)
public class DatabusPostChangeConsumer implements RocketMQListener<DatabusPostChangeMessage> {
private static final String SOURCE_SERVICE = "system-server";
@Resource
private DatabusIncrementalSyncService databusIncrementalSyncService;
@Resource
private ObjectMapper objectMapper;
@Override
public void onMessage(DatabusPostChangeMessage message) {
log.info("[Databus] 收到岗位变更消息, action={}, postId={}", message.getAction(), message.getPostId());
try {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("id", message.getPostId());
dataMap.put("code", message.getPostCode());
dataMap.put("name", message.getPostName());
dataMap.put("sort", message.getSort());
dataMap.put("status", message.getStatus());
dataMap.put("remark", message.getRemark());
dataMap.put("tenantId", message.getTenantId());
dataMap.put("eventTime", message.getEventTime());
// 构建完整的事件类型: SYSTEM_POST_{ACTION}(大写下划线格式,与数据库存储一致)
String eventType = String.format("SYSTEM_POST_%s", message.getAction().toUpperCase());
DatabusEvent databusEvent = DatabusEvent.builder()
.eventType(eventType)
.eventAction(message.getAction())
.dataSnapshot(objectMapper.writeValueAsString(dataMap))
.dataVersion(1)
.sourceService(SOURCE_SERVICE)
.sourceTopic(DatabusPostChangeMessage.TOPIC)
.tenantId(message.getTenantId())
.eventTime(message.getEventTime())
.build();
databusIncrementalSyncService.processEvent(databusEvent);
log.info("[Databus] 岗位变更事件处理完成, eventType={}, postId={}",
eventType, message.getPostId());
} catch (Exception e) {
log.error("[Databus] 处理岗位变更消息失败, action={}, postId={}",
message.getAction(), message.getPostId(), e);
throw new RuntimeException("处理岗位变更消息失败", e);
}
}
}

View File

@@ -0,0 +1,80 @@
package com.zt.plat.framework.databus.server.consumer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zt.plat.framework.databus.server.core.event.DatabusEvent;
import com.zt.plat.framework.databus.server.core.sync.DatabusIncrementalSyncService;
import com.zt.plat.module.system.api.mq.DatabusUserChangeMessage;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* Databus 用户变更消息消费者
*/
@Slf4j
@Component
@RocketMQMessageListener(
topic = DatabusUserChangeMessage.TOPIC,
consumerGroup = DatabusUserChangeMessage.TOPIC + "_CONSUMER"
)
public class DatabusUserChangeConsumer implements RocketMQListener<DatabusUserChangeMessage> {
private static final String SOURCE_SERVICE = "system-server";
@Resource
private DatabusIncrementalSyncService databusIncrementalSyncService;
@Resource
private ObjectMapper objectMapper;
@Override
public void onMessage(DatabusUserChangeMessage message) {
log.info("[Databus] 收到用户变更消息, action={}, userId={}", message.getAction(), message.getUserId());
try {
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("id", message.getUserId());
dataMap.put("username", message.getUsername());
dataMap.put("nickname", message.getNickname());
dataMap.put("remark", message.getRemark());
dataMap.put("deptIds", message.getDeptIds());
dataMap.put("postIds", message.getPostIds());
dataMap.put("email", message.getEmail());
dataMap.put("mobile", message.getMobile());
dataMap.put("sex", message.getSex());
dataMap.put("avatar", message.getAvatar());
dataMap.put("status", message.getStatus());
dataMap.put("userSource", message.getUserSource());
dataMap.put("tenantId", message.getTenantId());
dataMap.put("eventTime", message.getEventTime());
// 构建完整的事件类型: SYSTEM_USER_{ACTION}(大写下划线格式,与数据库存储一致)
String eventType = String.format("SYSTEM_USER_%s", message.getAction().toUpperCase());
DatabusEvent databusEvent = DatabusEvent.builder()
.eventType(eventType)
.eventAction(message.getAction())
.dataSnapshot(objectMapper.writeValueAsString(dataMap))
.dataVersion(1)
.sourceService(SOURCE_SERVICE)
.sourceTopic(DatabusUserChangeMessage.TOPIC)
.tenantId(message.getTenantId())
.eventTime(message.getEventTime())
.build();
databusIncrementalSyncService.processEvent(databusEvent);
log.info("[Databus] 用户变更事件处理完成, eventType={}, userId={}",
eventType, message.getUserId());
} catch (Exception e) {
log.error("[Databus] 处理用户变更消息失败, action={}, userId={}",
message.getAction(), message.getUserId(), e);
throw new RuntimeException("处理用户变更消息失败", e);
}
}
}

View File

@@ -0,0 +1,101 @@
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.DatabusSyncClientBatchStatusReqVO;
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);
}
@PutMapping("/batch-status")
@Operation(summary = "批量修改客户端启用状态")
@PreAuthorize("@ss.hasPermission('databus:sync:client:update')")
public CommonResult<Boolean> batchUpdateClientStatus(@Valid @RequestBody DatabusSyncClientBatchStatusReqVO reqVO) {
clientService.batchUpdateClientStatus(reqVO.getIds(), reqVO.getEnabled());
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,101 @@
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.DatabusSyncEventBatchStatusReqVO;
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);
}
@PutMapping("/batch-status")
@Operation(summary = "批量修改事件启用状态")
@PreAuthorize("@ss.hasPermission('databus:sync:event:update')")
public CommonResult<Boolean> batchUpdateEventStatus(@Valid @RequestBody DatabusSyncEventBatchStatusReqVO reqVO) {
eventService.batchUpdateEventStatus(reqVO.getIds(), reqVO.getEnabled());
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,38 @@
package com.zt.plat.framework.databus.server.controller.admin;
import com.zt.plat.framework.common.pojo.CommonResult;
import com.zt.plat.framework.databus.server.controller.admin.vo.statistics.DatabusSyncStatisticsRespVO;
import com.zt.plat.framework.databus.server.service.DatabusSyncStatisticsService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import static com.zt.plat.framework.common.pojo.CommonResult.success;
/**
* DataBus 同步统计 Controller
*
* @author ZT
*/
@Tag(name = "管理后台 - DataBus 同步统计")
@RestController
@RequestMapping("/databus/sync")
@Validated
public class DatabusSyncStatisticsController {
@Resource
private DatabusSyncStatisticsService statisticsService;
@GetMapping("/statistics")
@Operation(summary = "获取同步统计数据")
@PreAuthorize("@ss.hasPermission('databus:sync:query')")
public CommonResult<DatabusSyncStatisticsRespVO> getStatistics() {
DatabusSyncStatisticsRespVO statistics = statisticsService.getStatistics();
return success(statistics);
}
}

View File

@@ -0,0 +1,118 @@
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.DatabusSyncSubscriptionBatchStatusReqVO;
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);
}
@GetMapping("/list-by-client")
@Operation(summary = "根据客户端ID获取订阅列表")
@Parameter(name = "clientId", description = "客户端ID", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('databus:sync:subscription:query')")
public CommonResult<java.util.List<DatabusSyncSubscriptionRespVO>> getSubscriptionListByClient(@RequestParam("clientId") Long clientId) {
java.util.List<DatabusSyncSubscriptionDO> list = subscriptionService.getSubscriptionListByClient(clientId);
return success(DatabusSyncSubscriptionConvert.INSTANCE.convertList(list));
}
@PutMapping("/batch-status")
@Operation(summary = "批量修改订阅启用状态")
@PreAuthorize("@ss.hasPermission('databus:sync:subscription:update')")
public CommonResult<Boolean> batchUpdateSubscriptionStatus(@Valid @RequestBody DatabusSyncSubscriptionBatchStatusReqVO reqVO) {
subscriptionService.batchUpdateSubscriptionStatus(reqVO.getIds(), reqVO.getEnabled());
return success(true);
}
}

View File

@@ -0,0 +1,26 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.client;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
/**
* 客户端批量状态更新请求 VO
*
* @author ZT
*/
@Schema(description = "管理后台 - 客户端批量状态更新请求 VO")
@Data
public class DatabusSyncClientBatchStatusReqVO {
@Schema(description = "客户端ID列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]")
@NotEmpty(message = "客户端ID列表不能为空")
private List<Long> ids;
@Schema(description = "启用状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "启用状态不能为空")
private Integer enabled;
}

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,26 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.event;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
/**
* 事件批量状态更新请求 VO
*
* @author ZT
*/
@Schema(description = "管理后台 - 事件批量状态更新请求 VO")
@Data
public class DatabusSyncEventBatchStatusReqVO {
@Schema(description = "事件ID列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]")
@NotEmpty(message = "事件ID列表不能为空")
private List<Long> ids;
@Schema(description = "启用状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "启用状态不能为空")
private Integer enabled;
}

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,38 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.statistics;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* DataBus 同步统计响应 VO
*
* @author ZT
*/
@Schema(description = "管理后台 - DataBus 同步统计响应 VO")
@Data
public class DatabusSyncStatisticsRespVO {
@Schema(description = "事件总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "12")
private Integer totalEvents;
@Schema(description = "客户端总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
private Integer totalClients;
@Schema(description = "订阅总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "60")
private Integer totalSubscriptions;
@Schema(description = "活跃订阅数", requiredMode = Schema.RequiredMode.REQUIRED, example = "58")
private Integer activeSubscriptions;
@Schema(description = "今日推送总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1234")
private Integer todayPushCount;
@Schema(description = "今日成功数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1200")
private Integer todaySuccessCount;
@Schema(description = "今日失败数", requiredMode = Schema.RequiredMode.REQUIRED, example = "34")
private Integer todayFailureCount;
@Schema(description = "死信队列数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer deadLetterCount;
}

View File

@@ -0,0 +1,26 @@
package com.zt.plat.framework.databus.server.controller.admin.vo.subscription;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
/**
* 订阅批量状态更新请求 VO
*
* @author ZT
*/
@Schema(description = "管理后台 - 订阅批量状态更新请求 VO")
@Data
public class DatabusSyncSubscriptionBatchStatusReqVO {
@Schema(description = "订阅ID列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]")
@NotEmpty(message = "订阅ID列表不能为空")
private List<Long> ids;
@Schema(description = "启用状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "启用状态不能为空")
private Integer enabled;
}

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,71 @@
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.DatabusSyncEventDO;
import com.zt.plat.framework.databus.server.dal.dataobject.DatabusSyncEventRecordDO;
import com.zt.plat.framework.databus.server.dal.mapper.DatabusSyncEventMapper;
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;
private final DatabusSyncEventMapper eventMapper;
@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) {
// 根据 eventType 查询事件定义 ID
DatabusSyncEventDO eventDO = eventMapper.selectByEventType(event.getEventType());
Long eventId = (eventDO != null) ? eventDO.getId() : null;
if (eventId == null) {
log.warn("[Databus] 事件定义不存在, eventType={}, 仍然保存事件记录", event.getEventType());
}
// 构建事件记录对象
DatabusSyncEventRecordDO record = new DatabusSyncEventRecordDO();
BeanUtil.copyProperties(event, record);
record.setEventId(eventId); // 设置事件定义 ID
// 保存到流水表
eventRecordMapper.insert(record);
log.info("[Databus] 事件记录已保存, id={}, eventId={}, eventType={}, eventAction={}",
record.getId(), eventId, 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,256 @@
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 com.zt.plat.framework.tenant.core.util.TenantUtils;
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());
// 使用 TenantUtils.executeIgnore 忽略租户隔离,因为事件定义、客户端、订阅关系是全局共享的
TenantUtils.executeIgnore(() -> processEventInternal(event));
}
/**
* 内部处理事件逻辑(忽略租户隔离后执行)
*/
private void processEventInternal(DatabusEvent event) {
// 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}-{clientCode}(简化版,所有事件共用一个 Topic
* 示例: databus-sync-branch-001
*
* @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";
}
// 简化 Topic 格式databus-sync-{clientCode}
// 不再为每个事件创建独立 Topic而是通过消息体中的 eventType 字段路由
return String.format("%s-%s", topicBase, 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));
}
}

Some files were not shown because too many files have changed in this diff Show More