• Canal 投递至 Kafka 的数据消费


    Canal 扔进 Kafka 的消息源

    package com.seliote.twowaysync.domain.kafka;
    
    import lombok.Data;
    
    import java.util.List;
    import java.util.Map;
    
    /**
     * Kafka 中接收到的源数据
     *
     * @author Li Yangdi
     * @since 2022-04-18
     */
    @Data
    public class FlatMsg {
    
        /**
         * 操作 ID
         */
        private Long id;
    
        /**
         * Execute time,毫秒
         */
        private String es;
    
        /**
         * Build timestamp,毫秒
         */
        private Long ts;
    
        /**
         * 是否为 DDL
         */
        private Boolean isDdl;
    
        /**
         * 数据库名称
         */
        private String database;
    
        /**
         * 操作的表名
         */
        private String table;
    
        /**
         * 操作类型
         * INSERT: 新增
         * DELETE: 删除
         * UPDATE: 更新
         */
        private String type;
    
        /**
         * SQL
         */
        private String sql;
    
        /**
         * MySQL 数据类型
         */
        private Map<String, String> mysqlType;
    
        /**
         * SQL 数据类型
         */
        private Map<String, Integer> sqlType;
    
        /**
         * 主键名称
         */
        private List<String> pkNames;
    
        /**
         * 源数据部分,即数据库修改后的数据
         */
        private List<Map<String, String>> data;
    
        /**
         * 旧数据
         */
        private List<Map<String, String>> old;
    }
    

    消费者:

    package com.seliote.twowaysync.listener;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.seliote.twowaysync.domain.kafka.FlatMsg;
    import com.seliote.twowaysync.entity.jd.*;
    import com.seliote.twowaysync.entity.zg.ZgAccessLog;
    import com.seliote.twowaysync.service.DataSync;
    import com.seliote.twowaysync.service.jd.*;
    import com.seliote.twowaysync.service.zg.ZgAccessLogSyncService;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.kafka.clients.consumer.ConsumerRecord;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.ApplicationContext;
    import org.springframework.data.util.Pair;
    import org.springframework.kafka.annotation.KafkaListener;
    import org.springframework.stereotype.Service;
    import com.seliote.twowaysync.entity.tws.OperateLog;
    import com.seliote.twowaysync.repo.tws.OperateLogRepo;
    
    import java.lang.reflect.InvocationTargetException;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    /**
     * 增量数据监听
     *
     * @author Li Yangdi
     * @since 2022-04-16
     */
    @Slf4j
    @Service
    public class DataChangeListener {
    
        // 机电需要同步的数据库名称
        public final String JD_DATABASE_NAME = "jd3";
        // 机电需要同步的表名称到实体与服务的映射
        public final Map<String, Pair<Class<?>, Class<? extends DataSync<?>>>> JD_TABLE_MAPPING = new HashMap<>() {{
            put("outsourcing_company", Pair.of(JdOutsourcingCompany.class, JdOutsourcingCompanySyncService.class));
            put("outsourcing_employee", Pair.of(JdOutsourcingEmployee.class, JdOutsourcingEmployeeSyncService.class));
            put("oc_proj_relate", Pair.of(JdOcProjRelate.class, JdOcProjRelateSyncService.class));
            put("supervision_employee", Pair.of(JdSupervisionEmployee.class, JdSupervisionEmployeeSyncService.class));
            put("supervision_org", Pair.of(JdSupervisionOrg.class, JdSupervisionOrgSyncService.class));
            put("sc_proj_relate", Pair.of(JdScProjRelate.class, JdScProjRelateSyncService.class));
            put("employee", Pair.of(JdEmployee.class, JdEmployeeSycnService.class));
            put("company_inout_auth", Pair.of(JdCompanyInoutAuth.class, JdCompanyInoutAuthSycnService.class));
        }};
    
        // 机电需要同步的数据库名称
        public final String ZG_DATABASE_NAME = "jd4";
        // 机电需要同步的表名称到实体与服务的映射
        public final Map<String, Pair<Class<?>, Class<? extends DataSync<?>>>> ZG_TABLE_MAPPING = new HashMap<>() {{
            put("access_log", Pair.of(ZgAccessLog.class, ZgAccessLogSyncService.class));
        }};
    
        private final ApplicationContext applicationContext;
        private final ObjectMapper objectMapper;
        private final OperateLogRepo operateLogRepo;
    
        @Autowired
        public DataChangeListener(ApplicationContext applicationContext,
                                  ObjectMapper objectMapper,
                                  OperateLogRepo operateLogRepo) {
            this.applicationContext = applicationContext;
            this.objectMapper = objectMapper;
            this.operateLogRepo = operateLogRepo;
        }
    
        /**
         * Kafka 监听机电变化
         *
         * @param msg 收到的消息
         */
        @KafkaListener(topics = "jd-data", groupId = "tws")
        public void jdDataListener(ConsumerRecord<String, String> msg) {
            consume(msg, JD_DATABASE_NAME, JD_TABLE_MAPPING);
        }
    
        /**
         * Kafka 监听综管变化
         *
         * @param msg 收到的消息
         */
        @KafkaListener(topics = "zg-data", groupId = "tws")
        public void zgDataListener(ConsumerRecord<String, String> msg) {
            consume(msg, ZG_DATABASE_NAME, ZG_TABLE_MAPPING);
        }
    
        /**
         * 消息消费
         *
         * @param msg          消息自身
         * @param tableMapping 表名到实体以及 Bean 映射
         */
        private void consume(ConsumerRecord<String, String> msg,
                             String table,
                             Map<String, Pair<Class<?>, Class<? extends DataSync<?>>>> tableMapping) {
            var operateLog = new OperateLog();
            try {
                log.info("Receive message from topic: '{}', key: '{}', value: '{}'",
                        msg.topic(), msg.key(), msg.value());
                var flatMsg = objectMapper.readValue(msg.value(), FlatMsg.class);
                if (flatMsg.getIsDdl()
                        || !table.equals(flatMsg.getDatabase())
                        || !tableMapping.containsKey(flatMsg.getTable())) {
                    log.warn("Message will dropped(maybe this need filtered before income kafka): '{}'", msg.value());
                    return;
                }
                operateLog.setTopic(msg.topic());
                operateLog.setMsgId(flatMsg.getId());
                operateLog.setMsgContent(msg.value());
                Integer count = operateLogRepo.countByTopicAndMsgId(msg.topic(), flatMsg.getId());
                if (count > 0) {
                    operateLog.setNormalFinished(false);
                    operateLog.setRemark("Msg had handled");
                    operateLogRepo.save(operateLog);
                    log.info("Msg {} of {} had handled, will ignore", flatMsg.getId(), msg.topic());
                    return;
                }
                var pair = tableMapping.get(flatMsg.getTable());
                router(flatMsg, pair.getFirst(), pair.getSecond());
                log.info("Success handle message {} of {}", flatMsg.getId(), msg.topic());
                operateLog.setNormalFinished(true);
                operateLogRepo.save(operateLog);
            } catch (Exception e) {
                operateLog.setNormalFinished(false);
                operateLog.setRemark(e.getMessage());
                operateLogRepo.save(operateLog);
                log.error("Catch an exception when handle msg, ", e);
            }
        }
    
        /**
         * 操作路由
         * 调用的对象为表名为 key 查找到的 TABLE_MAPPING.value.second Service
         * 调用的方法为 Kafka 消息中 type.toLowerCase 名方法,方法参数为 List
         *
         * @param flatMsg 消息实体
         */
        private void router(FlatMsg flatMsg, Class<?> entityClass, Class<? extends DataSync<?>> beanClass)
                throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            // 操作
            var operation = flatMsg.getType();
            // 操作的数据对象
            List<?> data = flatMsg.getData().stream().map(d -> objectMapper.convertValue(d, entityClass))
                    .collect(Collectors.toList());
            // 注册的服务
            var bean = applicationContext.getBean(beanClass);
            // 获取方法
            var method = beanClass.getMethod(operation.toLowerCase(), List.class);
            log.info("Routing to {}.{}", beanClass.getCanonicalName(), method.getName());
            method.invoke(bean, data);
            log.info("Routing success");
        }
    }
    
    
  • 相关阅读:
    POJ 3672 水题......
    POJ 3279 枚举?
    STL
    241. Different Ways to Add Parentheses
    282. Expression Add Operators
    169. Majority Element
    Weekly Contest 121
    927. Three Equal Parts
    910. Smallest Range II
    921. Minimum Add to Make Parentheses Valid
  • 原文地址:https://www.cnblogs.com/seliote/p/16160845.html
Copyright © 2020-2023  润新知