作者 [wgf]

消息队列数据同步demo

  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4 + <modelVersion>4.0.0</modelVersion>
  5 + <parent>
  6 + <groupId>org.springframework.boot</groupId>
  7 + <artifactId>spring-boot-starter-parent</artifactId>
  8 + <version>2.1.1.RELEASE</version>
  9 + <relativePath/> <!-- lookup parent from repository -->
  10 + </parent>
  11 + <groupId>com.aukey.example</groupId>
  12 + <artifactId>canal-mq-client</artifactId>
  13 + <version>0.0.1</version>
  14 + <name>canal-mq-client</name>
  15 + <description>客户端demo项目</description>
  16 +
  17 + <properties>
  18 + <java.version>1.8</java.version>
  19 + </properties>
  20 +
  21 + <dependencies>
  22 + <dependency>
  23 + <groupId>org.springframework.boot</groupId>
  24 + <artifactId>spring-boot-starter-web</artifactId>
  25 + </dependency>
  26 +
  27 + <dependency>
  28 + <groupId>org.springframework.boot</groupId>
  29 + <artifactId>spring-boot-starter-amqp</artifactId>
  30 + </dependency>
  31 +
  32 + <dependency>
  33 + <groupId>com.alibaba</groupId>
  34 + <artifactId>fastjson</artifactId>
  35 + <version>1.2.68</version>
  36 + </dependency>
  37 +
  38 + <dependency>
  39 + <groupId>org.apache.commons</groupId>
  40 + <artifactId>commons-lang3</artifactId>
  41 + <version>3.4</version>
  42 + </dependency>
  43 +
  44 + <dependency>
  45 + <groupId>org.projectlombok</groupId>
  46 + <artifactId>lombok</artifactId>
  47 + <version>1.18.10</version>
  48 + </dependency>
  49 + </dependencies>
  50 +
  51 + <build>
  52 + <plugins>
  53 + <plugin>
  54 + <groupId>org.springframework.boot</groupId>
  55 + <artifactId>spring-boot-maven-plugin</artifactId>
  56 + </plugin>
  57 + </plugins>
  58 + </build>
  59 +
  60 +</project>
  1 +package com.aukey.example;
  2 +
  3 +import org.springframework.boot.SpringApplication;
  4 +import org.springframework.boot.autoconfigure.SpringBootApplication;
  5 +
  6 +@SpringBootApplication
  7 +public class MqExampleApplication {
  8 +
  9 + public static void main(String[] args) {
  10 + SpringApplication.run(MqExampleApplication.class, args);
  11 + }
  12 +}
  1 +package com.aukey.example.conf;
  2 +
  3 +import com.aukey.example.converter.CharArrayToStringConverter;
  4 +import org.springframework.amqp.core.AcknowledgeMode;
  5 +import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
  6 +import org.springframework.amqp.rabbit.connection.ConnectionFactory;
  7 +import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
  8 +import org.springframework.context.annotation.Bean;
  9 +import org.springframework.context.annotation.Configuration;
  10 +
  11 +/**
  12 + * @author: wgf
  13 + * @create: 2020-06-10 18:52
  14 + * @description:
  15 + **/
  16 +@Configuration
  17 +public class RabbitConf {
  18 +
  19 + /**
  20 + * spring boot 在2.2.7.RELEASE 及以上版本不用配置,因为新版本amqp兼容content-type为空的消息
  21 + * @param connectionFactory
  22 + * @return
  23 + */
  24 + @Bean
  25 + public RabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
  26 + SimpleRabbitListenerContainerFactory listenerContainerFactory = new SimpleRabbitListenerContainerFactory();
  27 + listenerContainerFactory.setConnectionFactory(connectionFactory);
  28 + //--加上这句
  29 + listenerContainerFactory.setMessageConverter(new CharArrayToStringConverter());
  30 + listenerContainerFactory.setAcknowledgeMode(AcknowledgeMode.MANUAL);
  31 + return listenerContainerFactory;
  32 + }
  33 +}
  1 +package com.aukey.example.constant;
  2 +
  3 +/**
  4 + * @author: wgf
  5 + * @create: 2020-06-09 15:03
  6 + * @description: Msql数据库事件类型枚举
  7 + * mysql binlog启用的是row模式,只有C,U,D操作
  8 + **/
  9 +public enum EventType {
  10 + INSERT,
  11 + UPDATE,
  12 + DELETE,
  13 + CREATE,
  14 + ALTER,
  15 + ERASE,
  16 + QUERY,
  17 + TRUNCATE,
  18 + RENAME,
  19 + CINDEX,
  20 + DINDEX,
  21 + GTID,
  22 + XACOMMIT,
  23 + XAROLLBACK,
  24 + MHEARTBEAT;
  25 +}
  1 +package com.aukey.example.constant;
  2 +
  3 +/**
  4 + * @author: wgf
  5 + * @create: 2020-06-09 14:35
  6 + * @description: 消息队列配置
  7 + **/
  8 +public interface MQConst {
  9 +
  10 + /**
  11 + * 测试队列
  12 + */
  13 + String TEST = "";
  14 +}
  1 +package com.aukey.example.converter;
  2 +
  3 +import org.apache.commons.lang3.StringUtils;
  4 +import org.springframework.amqp.core.Message;
  5 +import org.springframework.amqp.core.MessageProperties;
  6 +import org.springframework.amqp.support.converter.MessageConversionException;
  7 +import org.springframework.amqp.support.converter.MessageConverter;
  8 +
  9 +import java.io.UnsupportedEncodingException;
  10 +
  11 +/**
  12 + * @author: wgf
  13 + * @create: 2020-06-10 22:39
  14 + * @description: 消息类型转换器
  15 + * 如果 spring boot 用的是 2.2.7.RELEASE以上的版本则不需要使用此转换器
  16 + **/
  17 +public class CharArrayToStringConverter implements MessageConverter {
  18 +
  19 + private String defaultCharset = "utf-8";
  20 +
  21 + @Override
  22 + public Message toMessage(Object object, MessageProperties messageProperties) throws MessageConversionException {
  23 + // TODO 不发消息不实现转换
  24 + return null;
  25 + }
  26 +
  27 + @Override
  28 + public Object fromMessage(Message message) throws MessageConversionException {
  29 + Object content = null;
  30 + MessageProperties properties = message.getMessageProperties();
  31 + if (properties != null) {
  32 + String contentType = properties.getContentType();
  33 + if (StringUtils.isBlank(contentType) || contentType.startsWith("text")) {
  34 + String encoding = properties.getContentEncoding();
  35 + if (encoding == null) {
  36 + encoding = this.defaultCharset;
  37 + }
  38 + try {
  39 + content = new String(message.getBody(), encoding);
  40 + } catch (UnsupportedEncodingException e) {
  41 + throw new MessageConversionException(
  42 + "failed to convert text-based Message content", e);
  43 + }
  44 + }
  45 + } else {
  46 + content = message.getBody();
  47 + }
  48 + return content;
  49 + }
  50 +}
  1 +package com.aukey.example.entity;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.util.Date;
  6 +
  7 +/**
  8 + * @author: wgf
  9 + * @create: 2020-06-09 11:30
  10 + * @description:
  11 + **/
  12 +@Data
  13 +public class AmazonOrder {
  14 + /**
  15 + * 自增主键
  16 + */
  17 + private Integer aid;
  18 +
  19 + /**
  20 + * 亚马逊订单ID
  21 + */
  22 + private String amazonOrderId;
  23 +
  24 + /**
  25 + * 卖家订单ID
  26 + */
  27 + private String sellerOrderId;
  28 +
  29 + /**
  30 + * 购买日期
  31 + */
  32 + private Date purchaseDate;
  33 +
  34 + /**
  35 + * 付款日期
  36 + */
  37 + private Date paymentsDate;
  38 +
  39 + /**
  40 + * 最后更新日期
  41 + */
  42 + private Date lastUpdateDate;
  43 +
  44 + /**
  45 + * 订单状态:1、Pending Availability等待订单生效;2、Pending待定;3、Unshipped未发货;4、Partially Shipped部分发货;5、Shipped已发货;6、Invoice Unconfirmed发票未确认;7、Canceled已取消;8、Unfulfillable无法发货
  46 + */
  47 + private String orderStatus;
  48 +
  49 + /**
  50 + * 发货渠道(AFN、MFN)
  51 + */
  52 + private String fulfillmentChannel;
  53 +
  54 + /**
  55 + * 销售渠道(站点)
  56 + */
  57 + private String salesChannel;
  58 +
  59 + /**
  60 + * 运输服务等级
  61 + */
  62 + private String shipServiceLevel;
  63 +
  64 + /**
  65 + * 收货地址-城市
  66 + */
  67 + private String shippingAddressCity;
  68 +
  69 + /**
  70 + * 收货地址-县
  71 + */
  72 + private String shippingAddressCounty;
  73 +
  74 + /**
  75 + * 收货地址-地区
  76 + */
  77 + private String shippingAddressDistrict;
  78 +
  79 + /**
  80 + * 收货地址-州
  81 + */
  82 + private String shippingAddressStateOrRegion;
  83 +
  84 + /**
  85 + * 收货地址-邮政编码
  86 + */
  87 + private String shippingAddressPostalCode;
  88 +
  89 + /**
  90 + * 收货地址-国家编码
  91 + */
  92 + private String shippingAddressCountryCode;
  93 +
  94 + private String shippingAddressPhone;
  95 +
  96 + /**
  97 + * 订单总额(币种)
  98 + */
  99 + private String orderTotalCurrencyCode;
  100 +
  101 + /**
  102 + * 订单总额
  103 + */
  104 + private Double orderTotalAmount;
  105 +
  106 + /**
  107 + * 邮费总额
  108 + */
  109 + private Double postageTotal;
  110 +
  111 + /**
  112 + * 折扣总额
  113 + */
  114 + private Double discountTotal;
  115 +
  116 + /**
  117 + * 发货数量
  118 + */
  119 + private Integer numberOfItemsShipped;
  120 +
  121 + /**
  122 + * 未发货数量
  123 + */
  124 + private Integer numberOfItemsUnshipped;
  125 +
  126 + /**
  127 + * 付款方式
  128 + */
  129 + private String paymentMethod;
  130 +
  131 + /**
  132 + * 市场ID
  133 + */
  134 + private String marketplaceId;
  135 +
  136 + /**
  137 + * 买家邮箱
  138 + */
  139 + private String buyerEmail;
  140 +
  141 + /**
  142 + * 买家名称
  143 + */
  144 + private String buyerName;
  145 +
  146 + /**
  147 + * 出货服务等级类别
  148 + */
  149 + private String shipmentServiceLevelCategory;
  150 +
  151 + private String shippedByAmazonTfm;
  152 +
  153 + private String tfmShipmentStatus;
  154 +
  155 + private String cbaDisplayableShippingLabel;
  156 +
  157 + /**
  158 + * 订单类型
  159 + */
  160 + private String orderType;
  161 +
  162 + /**
  163 + * 最早发货日期
  164 + */
  165 + private Date earliestShipDate;
  166 +
  167 + /**
  168 + * 最晚发货日期
  169 + */
  170 + private Date latestShipDate;
  171 +
  172 + /**
  173 + * 最早交货日期
  174 + */
  175 + private Date earliestDeliveryDate;
  176 +
  177 + /**
  178 + * 最晚交货日期
  179 + */
  180 + private Date latestDeliveryDate;
  181 +
  182 + private String isBusinessOrder;
  183 +
  184 + /**
  185 + * 买家采购订单编号
  186 + */
  187 + private String purchaseOrderNumber;
  188 +
  189 + private String isPrime;
  190 +
  191 + private String isPremiumOrder;
  192 +
  193 +
  194 + /**
  195 + * 抓单时间
  196 + */
  197 + private Date importSysDate;
  198 +
  199 + /**
  200 + * 店铺ID
  201 + */
  202 + private Integer accountId;
  203 +
  204 + /**
  205 + * 店铺简码
  206 + */
  207 + private String accountCode;
  208 +
  209 + /**
  210 + * 区域ID
  211 + */
  212 + private Integer areaId;
  213 +
  214 + /**
  215 + * 区域
  216 + */
  217 + private String area;
  218 +
  219 + /**
  220 + * 记录创建时间
  221 + */
  222 + private Date createDate;
  223 +
  224 + /**
  225 + * 记录更新时间
  226 + */
  227 + private Date updateDate;
  228 +
  229 + /**
  230 + * item表处理状态
  231 + */
  232 + private String orderItemStatus;
  233 +
  234 + private Integer siteId;
  235 +
  236 + private String site;
  237 +
  238 + /**
  239 + * 店铺站点ID
  240 + */
  241 + private String authId;
  242 +
  243 + private String dataDigest;
  244 +
  245 + /**
  246 + * 是否补发订单
  247 + */
  248 + private String reissue;
  249 +
  250 + /**
  251 + * 收件人姓名
  252 + */
  253 + private String shippingAddressName;
  254 +
  255 + /**
  256 + * 收货地址-街道1
  257 + */
  258 + private String shippingAddressLine1;
  259 +
  260 + /**
  261 + * 收货地址-街道2
  262 + */
  263 + private String shippingAddressLine2;
  264 +
  265 + /**
  266 + * 收货地址-街道3
  267 + */
  268 + private String shippingAddressLine3;
  269 +
  270 + private String skus;
  271 +
  272 + private String asins;
  273 +}
  1 +package com.aukey.example.listener;
  2 +
  3 +import com.alibaba.fastjson.JSON;
  4 +import com.alibaba.fastjson.TypeReference;
  5 +import com.aukey.example.constant.EventType;
  6 +import com.aukey.example.constant.MQConst;
  7 +import com.aukey.example.entity.AmazonOrder;
  8 +import com.aukey.example.vo.MessageVo;
  9 +import com.rabbitmq.client.Channel;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +import org.springframework.amqp.core.Message;
  12 +import org.springframework.amqp.rabbit.annotation.RabbitListener;
  13 +import org.springframework.stereotype.Component;
  14 +
  15 +import java.io.IOException;
  16 +
  17 +/**
  18 + * @author: wgf
  19 + * @create: 2020-06-09 14:30
  20 + * @description: CanalUser监听器
  21 + **/
  22 +@Slf4j
  23 +@Component
  24 +public class CanalUserListener {
  25 +
  26 + @RabbitListener(queues = "polaris_order_center.amazon_order")
  27 + public void receive(String message, Channel channel, Message messageEntity) throws IOException {
  28 +
  29 + try {
  30 +
  31 + log.info("接收到队列: {} 消息:{}", MQConst.TEST, message);
  32 +
  33 + MessageVo<AmazonOrder> messageVo = JSON.parseObject(message, new TypeReference<MessageVo<AmazonOrder>>() {
  34 + });
  35 +
  36 + EventType eventType = EventType.valueOf(messageVo.getType());
  37 +
  38 + log.info("当前监听binlog 数据库:{}, 数据表:{}", messageVo.getDatabase(), messageVo.getTable());
  39 +
  40 + // 数据同步只关注这三种事件
  41 + switch (eventType) {
  42 + case INSERT:
  43 + log.info("触发 INSERT 事件");
  44 + // messageVo.getData(); 获取数据变更
  45 + // TODO 自定义实现
  46 + break;
  47 + case UPDATE:
  48 + log.info("触发 UPDATE 事件");
  49 + // TODO 自定义实现
  50 + break;
  51 + case DELETE:
  52 + log.info("触发 DELETE 事件");
  53 + // TODO 自定义实现
  54 + break;
  55 + default:
  56 + log.info("其他事件类型:{}, 过滤不处理", eventType);
  57 + }
  58 +
  59 + log.info("消息长度:{}", messageVo.getData().size());
  60 +
  61 + /**
  62 + * 消息消费确认
  63 + * 如果客户端在线没有签收没有签收这条Message,则此消息进入Unacked状态,此时监听器阻塞等待消息确认,不推送新Message
  64 + * 如果待消息确认并且客户端下线,下次客户端上线重新推送上次Unacked状态Message
  65 + */
  66 + channel.basicAck(messageEntity.getMessageProperties().getDeliveryTag(), false);
  67 + } catch (Exception e) {
  68 + /**
  69 + * 第一个参数deliveryTag:发布的每一条消息都会获得一个唯一的deliveryTag,deliveryTag在channel范围内是唯一的
  70 + * 第二个参数multiple:批量确认标志。如果值为true,包含本条消息在内的、所有比该消息deliveryTag值小的 消息都被拒绝了(除了已经被 ack 的以外);如果值为false,只拒绝三本条消息
  71 + * 第三个参数requeue:表示如何处理这条消息,如果值为true,则重新放入RabbitMQ的发送队列,如果值为false,则通知RabbitMQ销毁这条消息
  72 + */
  73 + //channel.basicNack(messageEntity.getMessageProperties().getDeliveryTag(), false,true);
  74 + e.printStackTrace();
  75 + }
  76 + }
  77 +}
  1 +package com.aukey.example.vo;
  2 +
  3 +import lombok.Data;
  4 +import lombok.ToString;
  5 +
  6 +import java.util.List;
  7 +import java.util.Map;
  8 +
  9 +/**
  10 + * @author: wgf
  11 + * @create: 2020-06-09 10:44
  12 + * @description: 消息实体
  13 + **/
  14 +@Data
  15 +@ToString
  16 +public class MessageVo<T> {
  17 +
  18 + // 数据库
  19 + private String database;
  20 +
  21 + // 数据表
  22 + private String table;
  23 +
  24 + // INSERT,DELETE,UPDATE
  25 + private String type;
  26 +
  27 + // 最新版本binlog数据
  28 + private List<T> data;
  29 +
  30 + // 旧版本binlog数据,只有UPDATE时才有值
  31 + private List<T> old;
  32 +
  33 + // 主键字段
  34 + private String[] pkNames;
  35 +
  36 + /**
  37 + * value 每个字段对应的sql规范数据枚举类型
  38 + * 参考 {@link java.sql.Types}
  39 + */
  40 + private Map<String, Integer> sqlType;
  41 +
  42 + // 每个字段对应的Mysql数据类型
  43 + private Map<String, String> mysqlType;
  44 +}
  1 +spring:
  2 + rabbitmq:
  3 + host: 121.37.17.48
  4 + port: 5666
  5 + virtual-host: canal
  6 + username: canal_read
  7 + password: uC4235OY@4
  8 + publisher-confirm-type: correlated # 开启发送确认
  9 + publisher-returns: true # 开启发送失败退回
  10 + listener: # 开启ack消费确认
  11 + direct:
  12 + acknowledge-mode: manual
  13 + simple:
  14 + acknowledge-mode: manual