通过canal实现自定义监听数据库变化并触发自定义请求
binlog是mysql的二进制日志,对于操作数据库的语句,都以此形式保存。Canal是阿里MySQL数据库Binlog的增量订阅&消费组件 。基于数据库Binlog可以监控数据库数据的变化进而用于数据同步等业务。
这边为了节约时间直接引入别人的博客:链接: Linux(Ubuntu)安装canal.
省略SpringBoot工程创建过程…
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-configuration-processorartifactId>
dependency>
<dependency>
<groupId>com.alibaba.ottergroupId>
<artifactId>canal.protocolartifactId>
<version>1.1.5version>
dependency>
<dependency>
<groupId>com.alibaba.ottergroupId>
<artifactId>canal.clientartifactId>
<version>1.1.5version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
server:
port: 8080
canal:
client:
instances:
example:
host: 开通canal的ip地址
port: 端口
batchSize: 1000
userName: 你的账号/可不填
password: 你的密码/可不填
spring:
main:
allow-bean-definition-overriding: true
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface CanalEventListener {
@AliasFor(annotation = Component.class)
String value() default "";
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ListenPoint(eventType = CanalEntry.EventType.DELETE)
public @interface DeleteListenPoint {
/**
* canal destination
* default for all
* @return canal destination
*/
@AliasFor(annotation = ListenPoint.class)
String destination() default "";
/**
* database schema which you are concentrate on
* default for all
* @return canal destination
*/
@AliasFor(annotation = ListenPoint.class)
String[] schema() default {};
/**
* tables which you are concentrate on
* default for all
* @return canal destination
*/
@AliasFor(annotation = ListenPoint.class)
String[] table() default {};
}
import com.yyq.common.canal.config.CanalClientConfiguration;
import com.yyq.common.canal.config.CanalConfig;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import({CanalConfig.class, CanalClientConfiguration.class})
public @interface EnableCanalClient {
}
import com.alibaba.otter.canal.protocol.CanalEntry;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ListenPoint(eventType = CanalEntry.EventType.INSERT)
public @interface InsertListenPoint {
/**
* canal destination
* default for all
* @return canal destination
*/
@AliasFor(annotation = ListenPoint.class)
String destination() default "";
/**
* database schema which you are concentrate on
* default for all
* @return canal destination
*/
@AliasFor(annotation = ListenPoint.class)
String[] schema() default {};
/**
* tables which you are concentrate on
* default for all
* @return canal destination
*/
@AliasFor(annotation = ListenPoint.class)
String[] table() default {};
}
import com.alibaba.otter.canal.protocol.CanalEntry;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ListenPoint(eventType = CanalEntry.EventType.UPDATE)
public @interface UpdateListenPoint {
/**
* canal destination
* default for all
* @return canal destination
*/
@AliasFor(annotation = ListenPoint.class)
String destination() default "";
/**
* database schema which you are concentrate on
* default for all
* @return canal destination
*/
@AliasFor(annotation = ListenPoint.class)
String[] schema() default {};
/**
* tables which you are concentrate on
* default for all
* @return canal destination
*/
@AliasFor(annotation = ListenPoint.class)
String[] table() default {};
}
import com.alibaba.otter.canal.protocol.CanalEntry;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ListenPoint {
/**
* canal destination
* default for all
* @return canal destination
*/
String destination() default "";
/**
* database schema which you are concentrate on
* default for all
* @return canal destination
*/
String[] schema() default {};
/**
* tables which you are concentrate on
* default for all
* @return canal destination
*/
String[] table() default {};
/**
* canal event type
* default for all
* @return canal event type
*/
CanalEntry.EventType[] eventType() default {};
}
public class CanalClientException extends RuntimeException {
public CanalClientException() {
}
public CanalClientException(String message) {
super(message);
}
public CanalClientException(String message, Throwable cause) {
super(message, cause);
}
public CanalClientException(Throwable cause) {
super(cause);
}
public CanalClientException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.alibaba.otter.canal.protocol.Message;
import com.yyq.common.canal.annotation.ListenPoint;
import com.yyq.common.canal.client.ListenerPoint;
import com.yyq.common.canal.client.exception.CanalClientException;
import com.yyq.common.canal.config.CanalConfig;
import com.yyq.common.canal.event.CanalEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
public abstract class AbstractBasicMessageTransponder extends AbstractMessageTransponder {
private final static Logger logger = LoggerFactory.getLogger(AbstractBasicMessageTransponder.class);
public AbstractBasicMessageTransponder(CanalConnector connector, Map.Entry<String, CanalConfig.Instance> config, List<CanalEventListener> listeners, List<ListenerPoint> annoListeners) {
super(connector, config, listeners, annoListeners);
}
@Override
protected void distributeEvent(Message message) {
List<CanalEntry.Entry> entries = message.getEntries();
for (CanalEntry.Entry entry : entries) {
//ignore the transaction operations
List<CanalEntry.EntryType> ignoreEntryTypes = getIgnoreEntryTypes();
if (ignoreEntryTypes != null
&& ignoreEntryTypes.stream().anyMatch(t -> entry.getEntryType() == t)) {
continue;
}
CanalEntry.RowChange rowChange;
try {
rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
} catch (Exception e) {
throw new CanalClientException("ERROR ## parser of event has an error , data:" + entry.toString(),
e);
}
//ignore the ddl operation
if (rowChange.hasIsDdl() && rowChange.getIsDdl()) {
processDdl(rowChange);
continue;
}
for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
//distribute to listener interfaces
distributeByImpl(rowChange.getEventType(), entry);
//distribute to annotation listener interfaces
distributeByAnnotation(destination,
entry.getHeader().getSchemaName(),
entry.getHeader().getTableName(),
rowChange.getEventType(),
entry);
}
}
}
/**
* process the ddl event
* @param rowChange rowChange
*/
protected void processDdl(CanalEntry.RowChange rowChange) {}
/**
* distribute to listener interfaces
*
* @param eventType eventType
* @param rowData rowData
*/
protected void distributeByImpl(CanalEntry.EventType eventType, CanalEntry.Entry rowData) {
if (listeners != null) {
for (CanalEventListener listener : listeners) {
listener.onEvent(eventType, rowData);
}
}
}
/**
* distribute to annotation listener interfaces
*
* @param destination destination
* @param schemaName schema
* @param tableName table name
* @param eventType event type
* @param rowData row data
*/
protected void distributeByAnnotation(String destination,
String schemaName,
String tableName,
CanalEntry.EventType eventType,
CanalEntry.Entry rowData) {
//invoke the listeners
annoListeners.forEach(point -> point
.getInvokeMap()
.entrySet()
.stream()
.filter(getAnnotationFilter(destination, schemaName, tableName, eventType))
.forEach(entry -> {
Method method = entry.getKey();
method.setAccessible(true);
// try {
Object[] args = getInvokeArgs(method, eventType, rowData);
try {
method.invoke(point.getTarget(), args);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
// } catch (Exception e) {
// logger.error("{}: Error occurred when invoke the listener's interface! class:{}, method:{}",
// Thread.currentThread().getName(),
// point.getTarget().getClass().getName(), method.getName());
// }
}));
}
/**
* get the filters predicate
*
* @param destination destination
* @param schemaName schema
* @param tableName table name
* @param eventType event type
* @return predicate
*/
protected abstract Predicate<Map.Entry<Method, ListenPoint>> getAnnotationFilter(String destination,
String schemaName,
String tableName,
CanalEntry.EventType eventType);
/**
* get the args
*
* @param method method
* @param eventType event type
* @param rowData row data
* @return args which will be used by invoking the annotation methods
*/
protected abstract Object[] getInvokeArgs(Method method, CanalEntry.EventType eventType,
CanalEntry.Entry rowData);
/**
* get the ignore eventType list
*
* @return eventType list
*/
protected List<CanalEntry.EntryType> getIgnoreEntryTypes() {
return Collections.emptyList();
}
}
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.protocol.Message;
import com.alibaba.otter.canal.protocol.exception.CanalClientException;
import com.yyq.common.canal.client.ListenerPoint;
import com.yyq.common.canal.config.CanalConfig;
import com.yyq.common.canal.event.CanalEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public abstract class AbstractMessageTransponder implements MessageTransponder {
/**
* canal connector
*/
private final CanalConnector connector;
/**
* custom config
*/
protected final CanalConfig.Instance config;
/**
* destination of canal server
*/
protected final String destination;
/**
* listeners which are used by implementing the Interface
*/
protected final List<CanalEventListener> listeners = new ArrayList<>();
/**
* listeners which are used by annotation
*/
protected final List<ListenerPoint> annoListeners = new ArrayList<>();
/**
* running flag
*/
private volatile boolean running = true;
private static final Logger logger = LoggerFactory.getLogger(AbstractMessageTransponder.class);
public AbstractMessageTransponder(CanalConnector connector,
Map.Entry<String, CanalConfig.Instance> config,
List<CanalEventListener> listeners,
List<ListenerPoint> annoListeners) {
Objects.requireNonNull(connector, "connector can not be null!");
Objects.requireNonNull(config, "config can not be null!");
this.connector = connector;
this.destination = config.getKey();
this.config = config.getValue();
if (listeners != null)
this.listeners.addAll(listeners);
if (annoListeners != null)
this.annoListeners.addAll(annoListeners);
}
@Override
public void run() {
int errorCount = config.getRetryCount();
final long interval = config.getAcquireInterval();
final String threadName = Thread.currentThread().getName();
while (running && !Thread.currentThread().isInterrupted()) {
try {
Message message = connector.getWithoutAck(config.getBatchSize());
long batchId = message.getId();
int size = message.getEntries().size();
if (logger.isDebugEnabled()) {
logger.debug("{}: Get message from canal server >>>>> size:{}", threadName, size);
}
//empty message
if (batchId == -1 || size == 0) {
if (logger.isDebugEnabled()) {
logger.debug("{}: Empty message... sleep for {} millis", threadName, interval);
}
Thread.sleep(interval);
} else {
distributeEvent(message);
}
// commit ack
connector.ack(batchId);
if (logger.isDebugEnabled()) {
logger.debug("{}: Ack message. batchId:{}", threadName, batchId);
}
} catch (CanalClientException e) {
errorCount--;
logger.error(threadName + ": Error occurred!! ", e);
try {
Thread.sleep(interval);
} catch (InterruptedException e1) {
errorCount = 0;
}
} catch (InterruptedException e) {
errorCount = 0;
connector.rollback();
} finally {
if (errorCount <= 0) {
stop();
logger.info("{}: Topping the client.. ", Thread.currentThread().getName());
}
}
}
stop();
logger.info("{}: client stopped. ", Thread.currentThread().getName());
}
/**
* to distribute the message to special event and let the event listeners to handle it
*
* @param message canal message
*/
protected abstract void distributeEvent(Message message);
/**
* stop running
*/
void stop() {
running = false;
}
}
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.yyq.common.canal.annotation.ListenPoint;
import com.yyq.common.canal.client.ListenerPoint;
import com.yyq.common.canal.config.CanalConfig;
import com.yyq.common.canal.event.CanalEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
public class DefaultMessageTransponder extends AbstractBasicMessageTransponder {
private final static Logger logger = LoggerFactory.getLogger(DefaultMessageTransponder.class);
public DefaultMessageTransponder(CanalConnector connector,
Map.Entry<String, CanalConfig.Instance> config,
List<CanalEventListener> listeners,
List<ListenerPoint> annoListeners) {
super(connector, config, listeners, annoListeners);
}
/**
* get the filters predicate
*
* @param destination destination
* @param schemaName schema
* @param tableName table name
* @param eventType event type
* @return predicate
*/
@Override
protected Predicate<Map.Entry<Method, ListenPoint>> getAnnotationFilter(String destination,
String schemaName,
String tableName,
CanalEntry.EventType eventType) {
Predicate<Map.Entry<Method, ListenPoint>> df = e -> StringUtils.isEmpty(e.getValue().destination())
|| e.getValue().destination().equals(destination);
Predicate<Map.Entry<Method, ListenPoint>> sf = e -> e.getValue().schema().length == 0
|| Arrays.stream(e.getValue().schema()).anyMatch(s -> s.equals(schemaName));
Predicate<Map.Entry<Method, ListenPoint>> tf = e -> e.getValue().table().length == 0
|| Arrays.stream(e.getValue().table()).anyMatch(t -> t.equals(tableName));
Predicate<Map.Entry<Method, ListenPoint>> ef = e -> e.getValue().eventType().length == 0
|| Arrays.stream(e.getValue().eventType()).anyMatch(ev -> ev == eventType);
return df.and(sf).and(tf).and(ef);
}
@Override
protected Object[] getInvokeArgs(Method method, CanalEntry.EventType eventType, CanalEntry.Entry rowData) {
return Arrays.stream(method.getParameterTypes())
.map(p -> p == CanalEntry.EventType.class
? eventType
: p == CanalEntry.Entry.class
? rowData : null)
.toArray();
}
@Override
protected List<CanalEntry.EntryType> getIgnoreEntryTypes() {
return Arrays.asList(CanalEntry.EntryType.TRANSACTIONBEGIN, CanalEntry.EntryType.TRANSACTIONEND, CanalEntry.EntryType.HEARTBEAT);
}
}
import com.alibaba.otter.canal.client.CanalConnector;
import com.yyq.common.canal.client.ListenerPoint;
import com.yyq.common.canal.config.CanalConfig;
import com.yyq.common.canal.event.CanalEventListener;
import java.util.List;
import java.util.Map;
public class DefaultTransponderFactory implements TransponderFactory {
@Override
public MessageTransponder newTransponder(CanalConnector connector, Map.Entry<String, CanalConfig.Instance> config, List<CanalEventListener> listeners,
List<ListenerPoint> annoListeners) {
return new DefaultMessageTransponder(connector, config, listeners, annoListeners);
}
}
public interface MessageTransponder extends Runnable {
}
public class MessageTransponders {
public static TransponderFactory defaultMessageTransponder() {
return new DefaultTransponderFactory();
}
}
import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.client.CanalConnectors;
import com.yyq.common.canal.client.exception.CanalClientException;
import com.yyq.common.canal.client.transfer.TransponderFactory;
import com.yyq.common.canal.config.CanalConfig;
import org.springframework.util.StringUtils;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public abstract class AbstractCanalClient implements CanalClient {
/**
* running flag
*/
private volatile boolean running;
/**
* customer config
*/
private CanalConfig canalConfig;
/**
* TransponderFactory
*/
protected final TransponderFactory factory;
AbstractCanalClient(CanalConfig canalConfig, TransponderFactory factory) {
Objects.requireNonNull(canalConfig, "canalConfig can not be null!");
Objects.requireNonNull(canalConfig, "transponderFactory can not be null!");
this.canalConfig = canalConfig;
this.factory = factory;
}
@Override
public void start() {
Map<String, CanalConfig.Instance> instanceMap = getConfig();
for (Map.Entry<String, CanalConfig.Instance> instanceEntry : instanceMap.entrySet()) {
process(processInstanceEntry(instanceEntry), instanceEntry);
}
}
/**
* To initialize the canal connector
* @param connector CanalConnector
* @param config config
*/
protected abstract void process(CanalConnector connector, Map.Entry<String, CanalConfig.Instance> config);
private CanalConnector processInstanceEntry(Map.Entry<String, CanalConfig.Instance> instanceEntry) {
CanalConfig.Instance instance = instanceEntry.getValue();
CanalConnector connector;
if (instance.isClusterEnabled()) {
List<SocketAddress> addresses = new ArrayList<>();
for (String s : instance.getZookeeperAddress()) {
String[] entry = s.split(":");
if (entry.length != 2)
throw new CanalClientException("error parsing zookeeper address:" + s);
addresses.add(new InetSocketAddress(entry[0], Integer.parseInt(entry[1])));
}
connector = CanalConnectors.newClusterConnector(addresses, instanceEntry.getKey(),
instance.getUserName(),
instance.getPassword());
} else {
connector = CanalConnectors.newSingleConnector(new InetSocketAddress(instance.getHost(), instance.getPort()),
instanceEntry.getKey(),
instance.getUserName(),
instance.getPassword());
}
connector.connect();
if (!StringUtils.isEmpty(instance.getFilter())) {
connector.subscribe(instance.getFilter());
} else {
connector.subscribe();
}
connector.rollback();
return connector;
}
/**
* get the config
*
* @return config
*/
protected Map<String, CanalConfig.Instance> getConfig() {
CanalConfig config = canalConfig;
Map<String, CanalConfig.Instance> instanceMap;
if (config != null && (instanceMap = config.getInstances()) != null && !instanceMap.isEmpty()) {
return config.getInstances();
} else {
throw new CanalClientException("can not get the configuration of canal client!");
}
}
@Override
public void stop() {
setRunning(false);
}
@Override
public boolean isRunning() {
return running;
}
private void setRunning(boolean running) {
this.running = running;
}
}
public interface CanalClient {
/**
* open the canal client
* to get the config and connect to the canal server (1 : 1 or 1 : n)
* and then transfer the event to the special listener
* */
void start();
/**
* stop the client
*/
void stop();
/**
* is running
* @return yes or no
*/
boolean isRunning();
}
import com.yyq.common.canal.annotation.ListenPoint;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class ListenerPoint {
private Object target;
private Map<Method, ListenPoint> invokeMap = new HashMap<>();
ListenerPoint(Object target, Method method, ListenPoint anno) {
this.target = target;
this.invokeMap.put(method, anno);
}
public Object getTarget() {
return target;
}
public Map<Method, ListenPoint> getInvokeMap() {
return invokeMap;
}
}
import com.alibaba.otter.canal.client.CanalConnector;
import com.yyq.common.canal.annotation.ListenPoint;
import com.yyq.common.canal.client.transfer.TransponderFactory;
import com.yyq.common.canal.config.CanalConfig;
import com.yyq.common.canal.event.CanalEventListener;
import com.yyq.common.canal.util.BeanUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class SimpleCanalClient extends AbstractCanalClient {
/**
* executor
*/
private ThreadPoolExecutor executor;
/**
* listeners which are used by implementing the Interface
*/
private final List<CanalEventListener> listeners = new ArrayList<>();
/**
* listeners which are used by annotation
*/
private final List<ListenerPoint> annoListeners = new ArrayList<>();
private final static Logger logger = LoggerFactory.getLogger(SimpleCanalClient.class);
public SimpleCanalClient(CanalConfig canalConfig, TransponderFactory factory) {
super(canalConfig, factory);
executor = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<>(), Executors.defaultThreadFactory());
initListeners();
}
@Override
protected void process(CanalConnector connector, Map.Entry<String, CanalConfig.Instance> config) {
executor.submit(factory.newTransponder(connector, config, listeners, annoListeners));
}
@Override
public void stop() {
super.stop();
executor.shutdown();
}
/**
* init listeners
*/
private void initListeners() {
logger.info("{}: initializing the listeners....", Thread.currentThread().getName());
List<CanalEventListener> list = BeanUtil.getBeansOfType(CanalEventListener.class);
if (list != null) {
listeners.addAll(list);
}
Map<String, Object> listenerMap = BeanUtil.getBeansWithAnnotation(com.yyq.common.canal.annotation.CanalEventListener.class);
if (listenerMap != null) {
for (Object target : listenerMap.values()) {
Method[] methods = target.getClass().getDeclaredMethods();
if (methods != null && methods.length > 0) {
for (Method method : methods) {
//只能解析到ListenPointa
// ListenPoint l = AnnotationUtils.findAnnotation(method, ListenPoint.class);
//子注解属性传递给父注解
ListenPoint l = AnnotatedElementUtils.getMergedAnnotation(method, ListenPoint.class);
if (l != null) {
annoListeners.add(new ListenerPoint(target, method, l));
}
}
}
}
}
logger.info("{}: initializing the listeners end.", Thread.currentThread().getName());
if (logger.isWarnEnabled() && listeners.isEmpty() && annoListeners.isEmpty()) {
logger.warn("{}: No listener found in context! ", Thread.currentThread().getName());
}
}
}
public class CanalClientConfiguration {
private final static Logger logger = LoggerFactory.getLogger(CanalClientConfiguration.class);
@Autowired
private CanalConfig canalConfig;
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public BeanUtil beanUtil() {
return new BeanUtil();
}
@Bean
private CanalClient canalClient() {
ConditionVo conditionVo = new ConditionVo();
// TODO 目前为SpringBoot 启动需要使用@EnableCanalClient标识启动类
// TODO 后续可修改为触发执行
CanalClient canalClient = new SimpleCanalClient(canalConfig, MessageTransponders.defaultMessageTransponder());
canalClient.start();
logger.info("Starting canal client....");
return canalClient;
}
}
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@Order(Ordered.HIGHEST_PRECEDENCE)
@ConfigurationProperties(prefix = "canal.client")
@Component
@Data
public class CanalConfig {
/**
* instance config
*/
private Map<String, Instance> instances = new LinkedHashMap<>();
public Map<String, Instance> getInstances() {
return instances;
}
public void setInstances(Map<String, Instance> instances) {
this.instances = instances;
}
/**
* instance config class
*/
public static class Instance {
/**
* is cluster-mod
*/
private boolean clusterEnabled;
/**
* zookeeper address
*/
private Set<String> zookeeperAddress = new LinkedHashSet<>();
/**
* canal server host
*/
private String host = "127.0.0.1";
/**
* canal server port
*/
private int port = 11111;
/**
* canal user name
*/
private String userName = "root";
/**
* canal password
*/
private String password = "YYQKitty123";
/**
* size when get messages from the canal server
*/
private int batchSize = 1000;
/**
* filter
*/
private String filter;
/**
* retry count when error occurred
*/
private int retryCount = 5;
/**
* interval of the message-acquiring
*/
private long acquireInterval = 1000;
public Instance() {}
public boolean isClusterEnabled() {
return clusterEnabled;
}
public void setClusterEnabled(boolean clusterEnabled) {
this.clusterEnabled = clusterEnabled;
}
public Set<String> getZookeeperAddress() {
return zookeeperAddress;
}
public void setZookeeperAddress(Set<String> zookeeperAddress) {
this.zookeeperAddress = zookeeperAddress;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getBatchSize() {
return batchSize;
}
public void setBatchSize(int batchSize) {
this.batchSize = batchSize;
}
public String getFilter() {
return filter;
}
public void setFilter(String filter) {
this.filter = filter;
}
public int getRetryCount() {
return retryCount;
}
public void setRetryCount(int retryCount) {
this.retryCount = retryCount;
}
public long getAcquireInterval() {
return acquireInterval;
}
public void setAcquireInterval(long acquireInterval) {
this.acquireInterval = acquireInterval;
}
}
}
import com.alibaba.otter.canal.protocol.CanalEntry;
public interface CanalEventListener {
/**
* run when event was fired
*
* @param eventType eventType
* @param rowData rowData
*/
void onEvent(CanalEntry.EventType eventType, CanalEntry.Entry rowData);
}
import com.alibaba.otter.canal.protocol.CanalEntry;
import java.util.Objects;
public interface DmlCanalEventListener extends CanalEventListener {
@Override
default void onEvent(CanalEntry.EventType eventType, CanalEntry.Entry rowData) {
Objects.requireNonNull(eventType);
switch (eventType) {
case INSERT:
onInsert(rowData);
break;
case UPDATE:
onUpdate(rowData);
break;
case DELETE:
onDelete(rowData);
break;
default:
break;
}
}
/**
* fired on insert event
*
* @param rowData rowData
*/
void onInsert(CanalEntry.Entry rowData);
/**
* fired on update event
*
* @param rowData rowData
*/
void onUpdate(CanalEntry.Entry rowData);
/**
* fired on delete event
*
* @param rowData rowData
*/
void onDelete(CanalEntry.Entry rowData);
}
import com.alibaba.fastjson.JSON;
import com.alibaba.otter.canal.protocol.CanalEntry;
import com.google.protobuf.InvalidProtocolBufferException;
import com.yyq.common.canal.annotation.CanalEventListener;
import com.yyq.common.canal.annotation.ListenPoint;
import com.yyq.common.canal.util.CanalEventType;
import com.yyq.common.canal.util.RequestUtils;
import com.yyq.common.canal.vo.CanalVO;
import com.yyq.common.canal.vo.ConditionVo;
import org.apache.commons.lang.StringUtils;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@CanalEventListener
public class CanalDataEventListener {
private static List<ConditionVo> conditionVoList = new ArrayList<>();
public static void init(List<ConditionVo> list){
//先清空
conditionVoList.clear();
//再添加
conditionVoList.addAll(list);
}
/**
* destination 指定监听实例
* schema 指定监听数据库名称
* table 指定表名 {"user"} || {"user","user_info"}
* eventType 指定监听CURD类型
*/
@ListenPoint(eventType = {CanalEntry.EventType.INSERT,CanalEntry.EventType.UPDATE,CanalEntry.EventType.DELETE})
public void listen(CanalEntry.EventType eventType, CanalEntry.Entry entry) {
//修改
List<ConditionVo> eventUpdateList = conditionVoList.stream().filter(item -> Objects.equals(item.getType(), CanalEventType.UPDATE_VALUE)).collect(Collectors.toList());
//添加
List<ConditionVo> eventInsertList = conditionVoList.stream().filter(item -> Objects.equals(item.getType(), CanalEventType.INSERT_VALUE)).collect(Collectors.toList());
//删除
List<ConditionVo> eventDeleteList = conditionVoList.stream().filter(item -> Objects.equals(item.getType(), CanalEventType.DELETE_VALUE)).collect(Collectors.toList());
if (eventType == CanalEntry.EventType.DELETE) { //删除类
saveDeleteSql(entry,eventDeleteList);
} else if (eventType == CanalEntry.EventType.UPDATE) { //修改类
saveUpdateSql(entry,eventUpdateList);
} else if (eventType == CanalEntry.EventType.INSERT) { // 新增
saveInsertSql(entry,eventInsertList);
}
}
/**
* 监听修改事件
* @param entry
*/
private void saveUpdateSql(CanalEntry.Entry entry,List<ConditionVo> conditionVoList) {
try {
CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
for (CanalEntry.RowData rowData : rowDatasList) {
//数据变更前
List<CanalEntry.Column> beforeColumnsList2 = rowData.getBeforeColumnsList();
//数据变更后
List<CanalEntry.Column> afterColumnsList2 = rowData.getAfterColumnsList();
Map<String, String> data1 = new HashMap<>();
afterColumnsList2.stream().forEach(item -> {
if(StringUtils.isNotEmpty(item.getValue())){
data1.put(lineToHump(item.getName()), item.getValue());
}
});
Map<String, String> data2 = new HashMap<>();
beforeColumnsList2.stream().forEach(item -> {
if(StringUtils.isNotEmpty(item.getValue())){
data2.put(lineToHump(item.getName()), item.getValue());
}
});
//修改后的数据
List<CanalEntry.Column> afterColumnsList = rowData.getAfterColumnsList();
System.out.println("afterColumnsList.get(0).getValue() = " + afterColumnsList.get(0).getValue());
CanalVO afterUpdate = CanalVO.ok(entry.getHeader().getSchemaName(),entry.getHeader().getTableName(), "UPDATE", data1, afterColumnsList.get(0).getValue());
//修改前的数据
List<CanalEntry.Column> oldColumnList = rowData.getAfterColumnsList();
CanalVO before = CanalVO.ok(entry.getHeader().getSchemaName(),entry.getHeader().getTableName(),"UPDATE", data2, oldColumnList.get(0).getValue());
//进行规则校验
RequestUtils.updateRequest(before,afterUpdate,conditionVoList);
System.out.println(JSON.toJSONString(before));
}
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
/**
* 监听删除事件
*
* @param entry
*/
private void saveDeleteSql(CanalEntry.Entry entry,List<ConditionVo> conditionVoList) {
try {
CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
for (CanalEntry.RowData rowData : rowDatasList) {
List<CanalEntry.Column> oldColumnList = rowData.getBeforeColumnsList();
CanalVO delete = CanalVO.ok(entry.getHeader().getSchemaName(),entry.getHeader().getTableName(), "DELETE", null, oldColumnList.get(0).getValue());
System.out.println("删除返回 : " + JSON.toJSONString(delete));
}
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
/**
* 监听插入事件
*
* @param entry
*/
private void saveInsertSql(CanalEntry.Entry entry,List<ConditionVo> conditionVoList) {
try {
CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
List<CanalEntry.RowData> rowDatasList = rowChange.getRowDatasList();
for (CanalEntry.RowData rowData : rowDatasList) {
List<CanalEntry.Column> columnList = rowData.getAfterColumnsList();
Map<String, String> data = new HashMap<>();
columnList.stream().forEach(item -> {
if(StringUtils.isNotEmpty(item.getValue())){
data.put(lineToHump(item.getName()), item.getValue());
}
});
CanalVO insert = CanalVO.ok(entry.getHeader().getSchemaName(),entry.getHeader().getTableName(), "INSERT", data, null);
System.out.println("插入返回 : " + JSON.toJSONString(insert));
}
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
private static Pattern linePattern = Pattern.compile("_(\\w)");
public static String lineToHump(String str) {
str = str.toLowerCase();
Matcher matcher = linePattern.matcher(str);
StringBuffer sb = new StringBuffer();
while (matcher.find()) {
matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
}
matcher.appendTail(sb);
return sb.toString();
}
}
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Component
public class BeanUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
BeanUtil.applicationContext = applicationContext;
}
public static <T> T getBean(Class<T> clazz) {
T obj;
try {
obj = applicationContext.getBean(clazz);
} catch (Exception e) {
obj = null;
}
return obj;
}
public static <T> List<T> getBeansOfType(Class<T> clazz) {
Map<String, T> map;
try {
map = applicationContext.getBeansOfType(clazz);
} catch (Exception e) {
map = null;
}
return map == null ? null : new ArrayList<>(map.values());
}
public static Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> anno) {
Map<String, Object> map;
try {
map = applicationContext.getBeansWithAnnotation(anno);
} catch (Exception e) {
map = null;
}
return map;
}
}
public enum CanalEventType {
INSERT(0, 1),
UPDATE(1, 2),
DELETE(2, 3);
public static final int INSERT_VALUE = 1;
public static final Integer UPDATE_VALUE = 2;
public static final Integer DELETE_VALUE = 3;
private final Integer index;
private final Integer value;
CanalEventType(Integer index, Integer value) {
this.index = index;
this.value = value;
}
public Integer getIndex() {
return index;
}
public Integer getValue() {
return value;
}
}
import com.yyq.common.canal.client.CanalClient;
import com.yyq.common.canal.client.SimpleCanalClient;
import com.yyq.common.canal.client.transfer.MessageTransponders;
import com.yyq.common.canal.config.CanalConfig;
public class CanalUtil {
public static void start(CanalConfig canalConfig){
CanalClient canalClient = new SimpleCanalClient(canalConfig, MessageTransponders.defaultMessageTransponder());
canalClient.start();
}
public static void stop(CanalConfig canalConfig){
CanalClient canalClient = new SimpleCanalClient(canalConfig, MessageTransponders.defaultMessageTransponder());
canalClient.stop();
}
}
public enum ConditionType {
DA(0, ">"),
XIAO(1, "<"),
DENG(2, "="),
IN(2, "in");
public static final String DA_VALUE = ">";
public static final String XIAO_VALUE = "<";
public static final String DENG_VALUE = "=";
public static final String IN_VALUE = "in";
private final Integer index;
private final String value;
ConditionType(Integer index, String value) {
this.index = index;
this.value = value;
}
public Integer getIndex() {
return index;
}
public String getValue() {
return value;
}
}
import com.yyq.common.canal.vo.CanalVO;
import com.yyq.common.canal.vo.ConditionVo;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import static com.yyq.common.canal.util.ConditionType.*;
public class RequestUtils {
public static void updateRequest(CanalVO before,CanalVO after,List<ConditionVo> conditionVos){
for (ConditionVo conditionVo : conditionVos) {
if (conditionVo.getTableName().equals(after.getTableName())){
if (conditionVo.getId() != null){
//ID存在的情况
if (after.getId().equals(conditionVo.getId())){
String oldValue = before.getData().get(conditionVo.getFieldName());
String changeValue = after.getData().get(conditionVo.getFieldName());
if (null != changeValue && !oldValue.equals(changeValue)){
updateType(conditionVo.getCondition().toString(),changeValue,conditionVo.getChangeFieldValue(),conditionVo.getId());
}
}
}else{
// ID不存在的情况
String oldValue = before.getData().get(conditionVo.getFieldName());
String changeValue = after.getData().get(conditionVo.getFieldName());
if (null != changeValue && !oldValue.equals(changeValue)){
updateType(conditionVo.getCondition().toString(),changeValue,conditionVo.getChangeFieldValue(),null);
}
}
// 不存在ID的情况
// if (after.getId().equals(conditionVo.getId())){
// }
}
}
}
public static void insertRequest(CanalVO insert,List<ConditionVo> conditionVoList){
}
public static void deleteRequest(CanalVO delete,List<ConditionVo> conditionVoList){
}
private static void updateType(String condition, String oldValue, String newValue,String id) {
if (condition.equals(DA_VALUE)){
if (Integer.parseInt(oldValue) > Integer.parseInt(newValue)){
//发起请求
}
}else if (condition.equals(XIAO_VALUE)){
if (Integer.parseInt(oldValue) < Integer.parseInt(newValue)){
System.out.println("发送请求 <<<<<<<<<<");
}
}else if (condition.equals(DENG_VALUE)){
if (oldValue.equals(newValue)){
System.out.println("发送请求 ==========");
}
}else if (condition.equals(IN_VALUE)){
if (oldValue.contains(newValue)){
//发起请求
}
}
}
private static void insertType(String condition, String oldValue, String newValue) {
if (condition.equals(DA_VALUE)){
if (Integer.parseInt(oldValue) > Integer.parseInt(newValue)){
//发起请求
}
}else if (condition.equals(XIAO_VALUE)){
if (Integer.parseInt(oldValue) < Integer.parseInt(newValue)){
System.out.println("发送请求 <<<<<<<<<<");
}
}else if (condition.equals(DENG_VALUE)){
if (oldValue.equals(newValue)){
System.out.println("发送请求 ==========");
}
}else if (condition.equals(IN_VALUE)){
if (oldValue.contains(newValue)){
//发起请求
}
}
}
private static void deleteType(String condition, String oldValue, String newValue) {
if (condition.equals(DA_VALUE)){
if (Integer.parseInt(oldValue) > Integer.parseInt(newValue)){
//发起请求
}
}else if (condition.equals(XIAO_VALUE)){
if (Integer.parseInt(oldValue) < Integer.parseInt(newValue)){
System.out.println("发送请求 <<<<<<<<<<");
}
}else if (condition.equals(DENG_VALUE)){
if (oldValue.equals(newValue)){
System.out.println("发送请求 ==========");
}
}else if (condition.equals(IN_VALUE)){
if (oldValue.contains(newValue)){
//发起请求
}
}
}
}
import java.util.Map;
public class CanalVO {
private String schemaName;
private String tableName; // 表名
private String type; // 类型(更新、删除、插入)
private Map<String,String> data; // 数据JSON 自己转对应表格实体类
private String id; // 更新或删除都是根据ID来
public static CanalVO ok(String schemaName,String tableName,String type,Map<String,String> data,String id){
CanalVO canalVO=new CanalVO();
canalVO.setSchemaName(schemaName);
canalVO.setId(id);
canalVO.setTableName(tableName);
canalVO.setType(type);
canalVO.setData(data);
return canalVO;
}
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Map<String, String> getData() {
return data;
}
public void setData(Map<String, String> data) {
this.data = data;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSchemaName() {
return schemaName;
}
public void setSchemaName(String schemaName) {
this.schemaName = schemaName;
}
}
@Data
public class ConditionVo {
private String schemaName;
/**
* 监听的表名
*/
private String tableName;
/**
* 数据列ID 若为空则全监听,不为空则指定监听
*/
private String id;
/**
* 监听字段名
*/
private String fieldName;
/**
* 条件
*/
private Object condition;
/**
* 改变的监听字段值
*/
private String changeFieldValue;
/**
* 增:1,删:2,改:3
*/
private Integer type;
}
@EnableCanal标注在启动类上方则会自动启动Canal监听,当然也可以使用CanalUtil工具类,自定义启动Canal监听
@SpringBootApplication
@ComponentScan("com.yyq")
@EnableCanalClient
public class CanalApplication {
public static void main(String[] args) {
SpringApplication.run(CanalApplication.class,args);
}
}
编码到此结束~
引用博客