├── .gitignore ├── src └── main │ ├── resources │ └── META-INF │ │ └── spring.factories │ └── java │ └── cn │ └── knowbox │ └── book │ └── alimq │ ├── utils │ └── HashUtil.java │ ├── producer │ ├── TransactionChecker.java │ ├── TransactionExecuter.java │ ├── MessageOrderTypeEnum.java │ ├── DefaultSendCallback.java │ ├── LocalTransactionExecuterImpl.java │ ├── TransactionMessageTemplate.java │ ├── OrderMessageTemplate.java │ ├── LocalTransactionCheckerImpl.java │ └── RocketMQTemplate.java │ ├── RocketMQProperties.java │ ├── consumer │ ├── AbstractMessageListener.java │ └── MqConsumer.java │ ├── annotation │ └── RocketMQMessageListener.java │ ├── event │ └── MessageEvent.java │ └── RocketMQAutoConfiguration.java ├── pom.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings 4 | .eclipse 5 | target 6 | .idea 7 | *.iml 8 | .DS_Store 9 | logs -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.knowbox.book.alimq.RocketMQAutoConfiguration -------------------------------------------------------------------------------- /src/main/java/cn/knowbox/book/alimq/utils/HashUtil.java: -------------------------------------------------------------------------------- 1 | package cn.knowbox.book.alimq.utils; 2 | 3 | import java.util.zip.CRC32; 4 | 5 | public class HashUtil { 6 | public static long crc32Code(byte[] bytes) { 7 | CRC32 crc32 = new CRC32(); 8 | crc32.update(bytes); 9 | return crc32.getValue(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/cn/knowbox/book/alimq/producer/TransactionChecker.java: -------------------------------------------------------------------------------- 1 | package cn.knowbox.book.alimq.producer; 2 | 3 | import com.aliyun.openservices.ons.api.transaction.TransactionStatus; 4 | 5 | import cn.knowbox.book.alimq.event.MessageEvent; 6 | 7 | /** 8 | * 用作LocalTransactionExecuterImpl的构造器参数,用于执行本地事务操作 9 | * @author hengxi 10 | * 11 | */ 12 | @FunctionalInterface 13 | public interface TransactionChecker { 14 | TransactionStatus check(MessageEvent messageEvent, Long hashValue); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/cn/knowbox/book/alimq/producer/TransactionExecuter.java: -------------------------------------------------------------------------------- 1 | package cn.knowbox.book.alimq.producer; 2 | 3 | import com.aliyun.openservices.ons.api.transaction.TransactionStatus; 4 | 5 | import cn.knowbox.book.alimq.event.MessageEvent; 6 | 7 | /** 8 | * 用作LocalTransactionExecuterImpl的构造器参数,用于执行本地事务操作 9 | * @author hengxi 10 | * 11 | */ 12 | @FunctionalInterface 13 | public interface TransactionExecuter { 14 | TransactionStatus executer(MessageEvent messageEvent, Long hashValue,Object arg); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/cn/knowbox/book/alimq/RocketMQProperties.java: -------------------------------------------------------------------------------- 1 | package cn.knowbox.book.alimq; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import java.util.Properties; 6 | 7 | /** 8 | * @author jibaole 9 | * @version 1.0 10 | * @desc 配置Bean 11 | * @date 2018/7/7 下午5:19 12 | */ 13 | 14 | @ConfigurationProperties(prefix = "aliyun.mq") 15 | @Data 16 | public class RocketMQProperties { 17 | 18 | private String onsAddr; 19 | 20 | private String topic; 21 | 22 | private String accessKey; 23 | 24 | private String secretKey; 25 | 26 | private Properties producer; 27 | 28 | private Properties consumer; 29 | 30 | private String tagSuffix; 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/cn/knowbox/book/alimq/producer/MessageOrderTypeEnum.java: -------------------------------------------------------------------------------- 1 | package cn.knowbox.book.alimq.producer; 2 | 3 | /** 4 | * 顺序消息,消息分区类型 5 | * @author hengxi 6 | * 7 | */ 8 | public enum MessageOrderTypeEnum { 9 | 10 | /** 11 | * 全局顺序消息 12 | */ 13 | GLOBAL((byte)1), 14 | /** 15 | * topic作为分区的顺序消息,即同一topic消息保证为顺序消息 16 | */ 17 | TOPIC((byte)2), 18 | /** 19 | * topic 和tag 作为分区的顺序消息,即同一topic和tag消息保证为顺序消息 20 | */ 21 | TAG((byte)3); 22 | 23 | 24 | private byte code; 25 | 26 | MessageOrderTypeEnum(byte code) { 27 | this.code = code; 28 | } 29 | 30 | public byte getCode() { 31 | return code; 32 | } 33 | 34 | public static MessageOrderTypeEnum valueOf(byte code) { 35 | for(MessageOrderTypeEnum item : MessageOrderTypeEnum.values()) { 36 | if(item.getCode()==code) { 37 | return item; 38 | } 39 | } 40 | return null; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/cn/knowbox/book/alimq/producer/DefaultSendCallback.java: -------------------------------------------------------------------------------- 1 | package cn.knowbox.book.alimq.producer; 2 | 3 | import com.aliyun.openservices.ons.api.OnExceptionContext; 4 | import com.aliyun.openservices.ons.api.SendCallback; 5 | import com.aliyun.openservices.ons.api.SendResult; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * @author jibaole 12 | * @version 1.0 13 | * @desc 消息者监听(订阅消费内容) 14 | * @date 2018/7/7 下午5:29 15 | */ 16 | @Slf4j 17 | public class DefaultSendCallback implements SendCallback { 18 | 19 | 20 | @Override 21 | public void onSuccess(SendResult sendResult) { 22 | log.info("消息发送成功: topic=" + sendResult.getTopic() + ", msgId=" + sendResult.getMessageId()); 23 | } 24 | 25 | @Override 26 | public void onException(OnExceptionContext context) { 27 | log.warn("消息发送失败: topic=" + context.getTopic() + ", msgId=" + context.getMessageId(), context.getException()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/cn/knowbox/book/alimq/consumer/AbstractMessageListener.java: -------------------------------------------------------------------------------- 1 | package cn.knowbox.book.alimq.consumer; 2 | 3 | import com.aliyun.openservices.ons.api.Action; 4 | import com.aliyun.openservices.ons.api.ConsumeContext; 5 | import com.aliyun.openservices.ons.api.Message; 6 | import com.aliyun.openservices.ons.api.MessageListener; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.util.SerializationUtils; 9 | 10 | /** 11 | * @author jibaole 12 | * @version 1.0 13 | * @desc 消息监听者需要继承该抽象类,实现handle方法,消息消费逻辑处理(如果抛出异常,则重新入队列) 14 | * @date 2018/7/7 下午5:19 15 | */ 16 | @Slf4j 17 | public abstract class AbstractMessageListener implements MessageListener { 18 | public abstract void handle(T body); 19 | 20 | @Override 21 | public Action consume(Message message, ConsumeContext context) { 22 | log.info("接收消息:[topic: {}, tag: {}, msgId: {}, startDeliverTime: {}]", message.getTopic(), message.getTag(), message.getMsgID(), message.getStartDeliverTime()); 23 | try { 24 | handle((T)SerializationUtils.deserialize(message.getBody())); 25 | log.info("handle message success. message id:"+message.getMsgID()); 26 | return Action.CommitMessage; 27 | } catch (Exception e) { 28 | //消费失败 29 | log.warn("handle message fail, requeue it. message id:"+message.getMsgID(), e); 30 | return Action.ReconsumeLater; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/cn/knowbox/book/alimq/annotation/RocketMQMessageListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package cn.knowbox.book.alimq.annotation; 19 | 20 | import java.lang.annotation.*; 21 | 22 | /** 23 | * @author jibaole 24 | * @version 1.0 25 | * @desc 消息者监听(订阅消费内容) 26 | * @date 2018/7/7 下午5:19 27 | */ 28 | 29 | @Target(ElementType.TYPE) 30 | @Retention(RetentionPolicy.RUNTIME) 31 | @Documented 32 | public @interface RocketMQMessageListener { 33 | 34 | /** 35 | * Topic name 36 | */ 37 | String topic(); 38 | 39 | /** 40 | * tag name 41 | */ 42 | String[] tag() default "*"; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/cn/knowbox/book/alimq/event/MessageEvent.java: -------------------------------------------------------------------------------- 1 | package cn.knowbox.book.alimq.event; 2 | 3 | import lombok.Data; 4 | import java.io.Serializable; 5 | import java.util.UUID; 6 | 7 | import com.aliyun.openservices.shade.io.netty.util.internal.StringUtil; 8 | 9 | /** 10 | * @author jibaole 11 | * @version 1.0 12 | * @desc MQ统一事件对象,用在跨业务整合中 13 | * @date 2018/6/29 上午10:46 14 | */ 15 | 16 | @Data 17 | public class MessageEvent implements Serializable { 18 | 19 | private static final long serialVersionUID = -2624253925403159396L; 20 | 21 | /** 22 | * 事件序列ID 23 | */ 24 | private String txId; 25 | 26 | /** 27 | * 话题的名字 28 | */ 29 | private String topic; 30 | 31 | /** 32 | * 话题的名字 33 | */ 34 | private String tag; 35 | 36 | /** 37 | * 需要传递的领域对象 38 | */ 39 | private Object domain; 40 | 41 | /** 42 | * 传递的领域对象的唯一标识,用来构建消息的唯一标识,不检测重复,可以为空,不影响消息收发 43 | */ 44 | private String domainKey; 45 | 46 | 47 | /** 48 | * 事件创建时间 49 | */ 50 | private long createdDate = System.currentTimeMillis(); 51 | 52 | 53 | /** 54 | * 方便的生成TxId的方法 55 | * 56 | * @return 57 | */ 58 | public String generateTxId() { 59 | if (null == txId) { 60 | txId = getTopic() + ":" + getTag()+":"; 61 | if(StringUtil.isNullOrEmpty(domainKey)) { 62 | txId = txId + getCreatedDate() + ":" + UUID.randomUUID().toString(); 63 | }else { 64 | txId = txId + domainKey; 65 | } 66 | } 67 | return txId; 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/cn/knowbox/book/alimq/producer/LocalTransactionExecuterImpl.java: -------------------------------------------------------------------------------- 1 | package cn.knowbox.book.alimq.producer; 2 | 3 | import org.springframework.util.SerializationUtils; 4 | import org.springframework.util.StringUtils; 5 | 6 | import com.aliyun.openservices.ons.api.Message; 7 | import com.aliyun.openservices.ons.api.transaction.LocalTransactionExecuter; 8 | import com.aliyun.openservices.ons.api.transaction.TransactionStatus; 9 | import com.aliyun.openservices.shade.com.alibaba.fastjson.JSON; 10 | 11 | import cn.knowbox.book.alimq.event.MessageEvent; 12 | import cn.knowbox.book.alimq.utils.HashUtil; 13 | import lombok.extern.slf4j.Slf4j; 14 | 15 | /** 16 | * 默认的本地事务执行器实现 17 | * @author hengxi 18 | * 19 | */ 20 | @Slf4j 21 | public class LocalTransactionExecuterImpl implements LocalTransactionExecuter { 22 | 23 | private TransactionExecuter transactionExecuter; 24 | public LocalTransactionExecuterImpl(TransactionExecuter transactionExecuter) { 25 | this.transactionExecuter = transactionExecuter; 26 | } 27 | @Override 28 | public TransactionStatus execute(Message msg, Object arg) { 29 | // 消息 ID(有可能消息体一样,但消息 ID 不一样,当前消息 ID 在控制台无法查询) 30 | String msgId = msg.getMsgID(); 31 | // 消息体内容进行 crc32,也可以使用其它的如 MD5 32 | long crc32Id = HashUtil.crc32Code(msg.getBody()); 33 | // 消息 ID 和 crc32id 主要是用来防止消息重复 34 | // 如果业务本身是幂等的,可以忽略,否则需要利用 msgId 或 crc32Id 来做幂等 35 | // 如果要求消息绝对不重复,推荐做法是对消息体 body 使用 crc32或 md5来防止重复消息 36 | TransactionStatus transactionStatus = TransactionStatus.Unknow; 37 | MessageEvent messageEvent = null; 38 | try { 39 | messageEvent = (MessageEvent)SerializationUtils.deserialize(msg.getBody()); 40 | transactionStatus = transactionExecuter.executer(messageEvent,crc32Id,arg); 41 | } catch (Exception e) { 42 | log.error("TransactionChecker.check has error.message id:"+msgId+" , message:"+JSON.toJSONString(messageEvent)+", arg:"+JSON.toJSONString(arg),e); 43 | } 44 | return transactionStatus == null?TransactionStatus.Unknow:transactionStatus; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/cn/knowbox/book/alimq/producer/TransactionMessageTemplate.java: -------------------------------------------------------------------------------- 1 | package cn.knowbox.book.alimq.producer; 2 | 3 | import javax.annotation.Resource; 4 | 5 | import org.springframework.util.SerializationUtils; 6 | import org.springframework.util.StringUtils; 7 | 8 | import com.aliyun.openservices.ons.api.Message; 9 | import com.aliyun.openservices.ons.api.SendResult; 10 | import com.aliyun.openservices.ons.api.bean.TransactionProducerBean; 11 | import com.aliyun.openservices.ons.api.transaction.LocalTransactionChecker; 12 | import com.aliyun.openservices.ons.api.transaction.LocalTransactionExecuter; 13 | 14 | import cn.knowbox.book.alimq.event.MessageEvent; 15 | import lombok.extern.slf4j.Slf4j; 16 | 17 | /** 18 | * @author hengxi 19 | * @version 1.0 20 | * @desc 事务消息生产者 21 | * @date 2018/8/13 22 | */ 23 | @Slf4j 24 | public class TransactionMessageTemplate { 25 | 26 | @Resource 27 | private TransactionProducerBean transactionProducer; 28 | 29 | /** 30 | * 使用前需要调用该方法设置localTransactionChecker 31 | * @param localTransactionChecker 32 | */ 33 | public void init(TransactionChecker transactionCheck) { 34 | LocalTransactionCheckerImpl checkerImpl = (LocalTransactionCheckerImpl)transactionProducer.getLocalTransactionChecker(); 35 | checkerImpl.init(transactionCheck); 36 | } 37 | 38 | /**** 39 | * @Description: 同步发送事务消息 40 | * @Param: [event] 41 | */ 42 | public SendResult send(MessageEvent event,LocalTransactionExecuter executer,Object arg) { 43 | if(event == null) { 44 | throw new RuntimeException("event is null."); 45 | } 46 | log.info("start to send message. [topic: {}, tag: {}]", event.getTopic(), event.getTag()); 47 | if (StringUtils.isEmpty(event.getTopic()) || null == event.getDomain()) { 48 | throw new RuntimeException("topic, or body is null."); 49 | } 50 | Message message = new Message(event.getTopic(), event.getTag(), SerializationUtils.serialize(event)); 51 | message.setKey(event.generateTxId()); 52 | SendResult result = this.transactionProducer.send(message, executer, arg); 53 | log.info("send message success. "+ result.toString()); 54 | return result; 55 | } 56 | 57 | /**** 58 | * @Description: 同步发送事务消息 59 | * @Param: [event] 60 | */ 61 | public SendResult send(MessageEvent event,TransactionExecuter transactionExecuter,Object arg) { 62 | return send(event, new LocalTransactionExecuterImpl(transactionExecuter), arg); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | cn.knowbox.book 8 | spring-boot-starter-alimq 9 | 1.1.0.RELEASE 10 | 11 | 12 | 13 | org.apache.maven.plugins 14 | maven-compiler-plugin 15 | 3.7.0 16 | 17 | 1.8 18 | 1.8 19 | UTF-8 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-autoconfigure 29 | 30 | 31 | 32 | com.aliyun.openservices 33 | ons-client 34 | 1.7.8.Final 35 | 36 | 37 | 38 | org.projectlombok 39 | lombok 40 | 1.18.0 41 | provided 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-dependencies 51 | 2.0.2.RELEASE 52 | pom 53 | import 54 | 55 | 56 | 57 | 58 | 59 | 60 | Bukexuetang_Release 61 | http://106.75.63.153:8081/repository/maven-releases 62 | 63 | 64 | Bukexuetang_Snapshot 65 | http://106.75.63.153:8081/repository/maven-snapshots 66 | 67 | 68 | -------------------------------------------------------------------------------- /src/main/java/cn/knowbox/book/alimq/producer/OrderMessageTemplate.java: -------------------------------------------------------------------------------- 1 | package cn.knowbox.book.alimq.producer; 2 | 3 | import javax.annotation.Resource; 4 | 5 | import org.springframework.util.SerializationUtils; 6 | import org.springframework.util.StringUtils; 7 | 8 | import com.aliyun.openservices.ons.api.Message; 9 | import com.aliyun.openservices.ons.api.SendResult; 10 | import com.aliyun.openservices.ons.api.bean.OrderProducerBean; 11 | 12 | import cn.knowbox.book.alimq.event.MessageEvent; 13 | import lombok.extern.slf4j.Slf4j; 14 | 15 | /** 16 | * @author hengxi 17 | * @version 1.0 18 | * @desc 顺序消息生产者 19 | * @date 2018/8/13 20 | */ 21 | @Slf4j 22 | public class OrderMessageTemplate { 23 | 24 | @Resource 25 | private OrderProducerBean orderProducer; 26 | 27 | /**** 28 | * @Description: 同步发送顺序消息 29 | * @Param: [event] 30 | * @param: sharding 分区顺序消息中区分不同分区的关键字段,sharding key 于普通消息的 key 是完全不同的概念。 31 | */ 32 | public SendResult send(MessageEvent event,String sharding) { 33 | if(event == null) { 34 | throw new RuntimeException("event is null."); 35 | } 36 | log.info("start to send message. [topic: {}, tag: {}]", event.getTopic(), event.getTag()); 37 | if (StringUtils.isEmpty(event.getTopic()) || null == event.getDomain()) { 38 | throw new RuntimeException("topic, or body is null."); 39 | } 40 | Message message = new Message(event.getTopic(), event.getTag(), SerializationUtils.serialize(event)); 41 | message.setKey(event.generateTxId()); 42 | SendResult result = this.orderProducer.send(message,sharding); 43 | log.info("send message success. "+ result.toString()); 44 | return result; 45 | } 46 | 47 | /**** 48 | * @Description: 同步发送顺序消息 49 | * @Param: [event] 50 | * @param: sharding 分区顺序消息中区分不同分区的关键字段,sharding key 于普通消息的 key 是完全不同的概念。 51 | */ 52 | public SendResult send(MessageEvent event) { 53 | return send(event, MessageOrderTypeEnum.GLOBAL); 54 | } 55 | 56 | /**** 57 | * @Description: 同步发送顺序消息 58 | * @Param: [event] 59 | * @param: sharding 分区顺序消息中区分不同分区的关键字段,sharding key 于普通消息的 key 是完全不同的概念。 60 | * @Author: jibaole 61 | */ 62 | public SendResult send(MessageEvent event,MessageOrderTypeEnum orderType) { 63 | String sharding = "#global#"; 64 | switch(orderType) { 65 | case GLOBAL: 66 | sharding = "#global#"; 67 | break; 68 | case TOPIC: 69 | sharding = "#"+event.getTopic()+"#"; 70 | break; 71 | case TAG: 72 | sharding = "#"+event.getTopic()+"#"+event.getTag()+"#"; 73 | break; 74 | } 75 | return send(event, sharding); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/cn/knowbox/book/alimq/consumer/MqConsumer.java: -------------------------------------------------------------------------------- 1 | package cn.knowbox.book.alimq.consumer; 2 | 3 | import cn.knowbox.book.alimq.annotation.RocketMQMessageListener; 4 | import com.aliyun.openservices.ons.api.Consumer; 5 | import com.aliyun.openservices.ons.api.ONSFactory; 6 | import com.aliyun.openservices.ons.api.PropertyKeyConst; 7 | import com.aliyun.openservices.ons.api.exception.ONSClientException; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.aop.support.AopUtils; 10 | import org.springframework.beans.BeansException; 11 | import org.springframework.beans.factory.config.BeanPostProcessor; 12 | import org.springframework.util.StringUtils; 13 | 14 | import java.util.Properties; 15 | 16 | /** 17 | * @author jibaole 18 | * @version 1.0 19 | * @desc 消费者 20 | * @date 2018/7/7 下午5:19 21 | */ 22 | @Slf4j 23 | public class MqConsumer implements BeanPostProcessor { 24 | 25 | private Properties properties; 26 | private Consumer consumer; 27 | 28 | public MqConsumer(Properties properties) { 29 | if (properties == null || properties.get(PropertyKeyConst.ConsumerId) == null 30 | || properties.get(PropertyKeyConst.AccessKey) == null 31 | || properties.get(PropertyKeyConst.SecretKey) == null 32 | || properties.get(PropertyKeyConst.ONSAddr) == null) { 33 | throw new ONSClientException("consumer properties not set properly."); 34 | } 35 | this.properties = properties; 36 | } 37 | 38 | public void start() { 39 | this.consumer = ONSFactory.createConsumer(properties); 40 | this.consumer.start(); 41 | } 42 | 43 | public void shutdown() { 44 | if (this.consumer != null) { 45 | this.consumer.shutdown(); 46 | } 47 | } 48 | 49 | @Override 50 | public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 51 | return bean; 52 | } 53 | 54 | /** 55 | * @Description: 获取所有消费者订阅内容(Topic、Tag) 56 | * @Param: [bean, beanName] 57 | * @Author: jibaole 58 | */ 59 | @Override 60 | public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { 61 | Class clazz = AopUtils.getTargetClass(bean); 62 | RocketMQMessageListener annotation = clazz.getAnnotation(RocketMQMessageListener.class); 63 | if (null != annotation) { 64 | @SuppressWarnings("rawtypes") 65 | AbstractMessageListener listener = (AbstractMessageListener) bean; 66 | String subExpression = StringUtils.arrayToDelimitedString(annotation.tag(), " || "); 67 | consumer.subscribe(annotation.topic(), subExpression, listener); 68 | } 69 | return bean; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/main/java/cn/knowbox/book/alimq/producer/LocalTransactionCheckerImpl.java: -------------------------------------------------------------------------------- 1 | package cn.knowbox.book.alimq.producer; 2 | 3 | import org.springframework.util.SerializationUtils; 4 | 5 | import com.aliyun.openservices.ons.api.Message; 6 | import com.aliyun.openservices.ons.api.transaction.LocalTransactionChecker; 7 | import com.aliyun.openservices.ons.api.transaction.TransactionStatus; 8 | import com.aliyun.openservices.shade.com.alibaba.fastjson.JSON; 9 | 10 | import cn.knowbox.book.alimq.event.MessageEvent; 11 | import cn.knowbox.book.alimq.utils.HashUtil; 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | /** 15 | * 默认的本地事务状态检测器实现 16 | * @author hengxi 17 | * 18 | */ 19 | @Slf4j 20 | public class LocalTransactionCheckerImpl implements LocalTransactionChecker { 21 | 22 | /** 23 | * 根据消息体和消息体hash值判断事务执行是否成功 24 | */ 25 | private TransactionChecker transactionCheck; 26 | 27 | public LocalTransactionCheckerImpl(TransactionChecker transactionCheck) { 28 | this.transactionCheck = transactionCheck; 29 | } 30 | 31 | public void init(TransactionChecker transactionCheck) { 32 | this.transactionCheck = transactionCheck; 33 | } 34 | 35 | @Override 36 | public TransactionStatus check(Message msg) { 37 | if(transactionCheck == null) { 38 | log.warn("mq Check 被回调但 LocalTransactionCheckerImpl 需要的 TransactionChecker尚未设置,原因:TransactionMessageTemplate的init方法尚未被调用"); 39 | return TransactionStatus.Unknow; 40 | } 41 | //消息 ID(有可能消息体一样,但消息 ID 不一样,当前消息属于 Half 消息,所以消息 ID 在控制台无法查询) 42 | String msgId = msg.getMsgID(); 43 | //消息体内容进行 crc32,也可以使用其它的方法如 MD5 44 | long crc32Id = HashUtil.crc32Code(msg.getBody()); 45 | //消息 ID、消息本 crc32Id 主要是用来防止消息重复 46 | //如果业务本身是幂等的,可以忽略,否则需要利用 msgId 或 crc32Id 来做幂等 47 | //如果要求消息绝对不重复,推荐做法是对消息体使用 crc32 或 md5 来防止重复消息 48 | TransactionStatus transactionStatus = TransactionStatus.Unknow; 49 | MessageEvent messageEvent = null; 50 | Object object = null; 51 | try { 52 | object = SerializationUtils.deserialize(msg.getBody()); 53 | if(object instanceof MessageEvent) { 54 | messageEvent = (MessageEvent)object; 55 | }else { 56 | log.error("数据异常,回滚事务,message body:"+JSON.toJSONString(object)); 57 | return TransactionStatus.RollbackTransaction; 58 | } 59 | transactionStatus = transactionCheck.check(messageEvent,crc32Id); 60 | }catch (IllegalArgumentException e) { 61 | if(object == null) { 62 | log.error("SerializationUtils.deserialize 反序列化失败,自动回滚事务,message body:"+(msg.getBody() == null?null:new String(msg.getBody()))); 63 | return TransactionStatus.RollbackTransaction; 64 | } 65 | } catch (Exception e) { 66 | log.error("TransactionChecker.check has error.message id:"+msgId+" , message:"+JSON.toJSONString(messageEvent),e); 67 | } 68 | return transactionStatus == null?TransactionStatus.Unknow:transactionStatus; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/cn/knowbox/book/alimq/RocketMQAutoConfiguration.java: -------------------------------------------------------------------------------- 1 | package cn.knowbox.book.alimq; 2 | 3 | import cn.knowbox.book.alimq.consumer.MqConsumer; 4 | import cn.knowbox.book.alimq.producer.LocalTransactionCheckerImpl; 5 | import cn.knowbox.book.alimq.producer.OrderMessageTemplate; 6 | import cn.knowbox.book.alimq.producer.RocketMQTemplate; 7 | import cn.knowbox.book.alimq.producer.TransactionMessageTemplate; 8 | 9 | import com.aliyun.openservices.ons.api.PropertyKeyConst; 10 | import com.aliyun.openservices.ons.api.bean.OrderProducerBean; 11 | import com.aliyun.openservices.ons.api.bean.ProducerBean; 12 | import com.aliyun.openservices.ons.api.bean.TransactionProducerBean; 13 | 14 | import lombok.extern.slf4j.Slf4j; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 17 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 18 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 19 | import org.springframework.context.annotation.Bean; 20 | import org.springframework.context.annotation.Configuration; 21 | import java.util.Properties; 22 | 23 | /** 24 | * @author jibaole 25 | * @version 1.0 26 | * @desc 初始化(生成|消费)相关配置 27 | * @date 2018/7/7 下午5:19 28 | */ 29 | @Configuration 30 | @EnableConfigurationProperties(RocketMQProperties.class) 31 | @Slf4j 32 | public class RocketMQAutoConfiguration { 33 | @Autowired 34 | private RocketMQProperties propConfig; 35 | 36 | 37 | @Bean(name = "producer",initMethod = "start", destroyMethod = "shutdown") 38 | @ConditionalOnMissingBean 39 | @ConditionalOnProperty(prefix = "aliyun.mq.producer",value = "enabled",havingValue = "true") 40 | public ProducerBean producer() { 41 | ProducerBean producerBean = new ProducerBean(); 42 | Properties properties = new Properties(); 43 | log.info("执行producer初始化……"); 44 | properties.put(PropertyKeyConst.ProducerId, propConfig.getProducer().getProperty("producerId")); 45 | properties.put(PropertyKeyConst.AccessKey, propConfig.getAccessKey()); 46 | properties.put(PropertyKeyConst.SecretKey, propConfig.getSecretKey()); 47 | properties.put(PropertyKeyConst.ONSAddr, propConfig.getOnsAddr()); 48 | producerBean.setProperties(properties); 49 | producerBean.start(); 50 | return producerBean; 51 | } 52 | 53 | @Bean(name = "orderProducer",initMethod = "start", destroyMethod = "shutdown") 54 | @ConditionalOnMissingBean 55 | @ConditionalOnProperty(prefix = "aliyun.mq.producer",value = "enabled",havingValue = "true") 56 | public OrderProducerBean orderProducer() { 57 | OrderProducerBean producerBean = new OrderProducerBean(); 58 | Properties properties = new Properties(); 59 | log.info("执行producer初始化……"); 60 | properties.put(PropertyKeyConst.ProducerId, propConfig.getProducer().getProperty("producerId")); 61 | properties.put(PropertyKeyConst.AccessKey, propConfig.getAccessKey()); 62 | properties.put(PropertyKeyConst.SecretKey, propConfig.getSecretKey()); 63 | properties.put(PropertyKeyConst.ONSAddr, propConfig.getOnsAddr()); 64 | producerBean.setProperties(properties); 65 | producerBean.start(); 66 | return producerBean; 67 | } 68 | 69 | @Bean(name = "transactionProducer",initMethod = "start", destroyMethod = "shutdown") 70 | @ConditionalOnMissingBean 71 | @ConditionalOnProperty(prefix = "aliyun.mq.producer",value = "enabled",havingValue = "true") 72 | public TransactionProducerBean transactionProducer() { 73 | TransactionProducerBean producerBean = new TransactionProducerBean(); 74 | Properties properties = new Properties(); 75 | log.info("执行producer初始化……"); 76 | properties.put(PropertyKeyConst.ProducerId, propConfig.getProducer().getProperty("producerId")); 77 | properties.put(PropertyKeyConst.AccessKey, propConfig.getAccessKey()); 78 | properties.put(PropertyKeyConst.SecretKey, propConfig.getSecretKey()); 79 | properties.put(PropertyKeyConst.ONSAddr, propConfig.getOnsAddr()); 80 | producerBean.setProperties(properties); 81 | //LocalTransactionCheckerImpl必须在start方法调用前设置 82 | producerBean.setLocalTransactionChecker(new LocalTransactionCheckerImpl(null)); 83 | producerBean.start(); 84 | return producerBean; 85 | } 86 | 87 | 88 | @Bean(initMethod="start", destroyMethod = "shutdown") 89 | @ConditionalOnMissingBean 90 | @ConditionalOnProperty(prefix = "aliyun.mq.consumer",value = "enabled",havingValue = "true") 91 | public MqConsumer mqConsumer(){ 92 | Properties properties = new Properties(); 93 | log.info("执行consumer初始化……"); 94 | properties.setProperty(PropertyKeyConst.ConsumerId, propConfig.getConsumer().getProperty("consumerId")); 95 | properties.setProperty(PropertyKeyConst.AccessKey, propConfig.getAccessKey()); 96 | properties.setProperty(PropertyKeyConst.SecretKey, propConfig.getSecretKey()); 97 | properties.setProperty(PropertyKeyConst.ONSAddr, propConfig.getOnsAddr()); 98 | return new MqConsumer(properties); 99 | } 100 | 101 | @Bean 102 | @ConditionalOnMissingBean 103 | @ConditionalOnProperty(prefix = "aliyun.mq.producer",value = "enabled",havingValue = "true") 104 | public RocketMQTemplate rocketMQTemplate(){ 105 | RocketMQTemplate rocketMQTemplate = new RocketMQTemplate(); 106 | return rocketMQTemplate; 107 | } 108 | 109 | @Bean 110 | @ConditionalOnMissingBean 111 | @ConditionalOnProperty(prefix = "aliyun.mq.producer",value = "enabled",havingValue = "true") 112 | public OrderMessageTemplate orderMessageTemplate(){ 113 | OrderMessageTemplate orderMessageTemplate = new OrderMessageTemplate(); 114 | return orderMessageTemplate; 115 | } 116 | 117 | @Bean 118 | @ConditionalOnMissingBean 119 | @ConditionalOnProperty(prefix = "aliyun.mq.producer",value = "enabled",havingValue = "true") 120 | public TransactionMessageTemplate transactionMessageTemplate(){ 121 | TransactionMessageTemplate transactionMessageTemplate = new TransactionMessageTemplate(); 122 | return transactionMessageTemplate; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/java/cn/knowbox/book/alimq/producer/RocketMQTemplate.java: -------------------------------------------------------------------------------- 1 | package cn.knowbox.book.alimq.producer; 2 | 3 | import com.aliyun.openservices.ons.api.*; 4 | import com.aliyun.openservices.ons.api.bean.ProducerBean; 5 | import cn.knowbox.book.alimq.event.MessageEvent; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.springframework.util.SerializationUtils; 8 | import org.springframework.util.StringUtils; 9 | 10 | import java.text.SimpleDateFormat; 11 | import java.time.LocalDateTime; 12 | import java.time.ZoneId; 13 | import java.util.Date; 14 | 15 | import javax.annotation.Resource; 16 | 17 | /** 18 | * @author jibaole 19 | * @version 1.0 20 | * @desc 普通消息,定时消息,延迟消息生产者 21 | * @date 2018/7/7 下午5:19 22 | */ 23 | @Slf4j 24 | @SuppressWarnings({"WeakerAccess", "unused"}) 25 | public class RocketMQTemplate { 26 | 27 | @Resource 28 | private ProducerBean producer; 29 | 30 | private Message getMessage(MessageEvent event) { 31 | if(event == null) { 32 | throw new RuntimeException("event is null."); 33 | } 34 | log.info("start to send message. [topic: {}, tag: {}]", event.getTopic(), event.getTag()); 35 | if (StringUtils.isEmpty(event.getTopic()) || null == event.getDomain()) { 36 | throw new RuntimeException("topic, or body is null."); 37 | } 38 | Message message = new Message(event.getTopic(), event.getTag(), SerializationUtils.serialize(event)); 39 | message.setKey(event.generateTxId()); 40 | return message; 41 | } 42 | 43 | /**** 44 | * @Description: 同步发送 45 | * @Param: [event] 46 | * @Author: jibaole 47 | */ 48 | public SendResult send(MessageEvent event) { 49 | Message message = getMessage(event); 50 | SendResult result = this.producer.send(message); 51 | log.info("send message success. {}", result.toString()); 52 | return result; 53 | } 54 | 55 | /**** 56 | * @Description: 同步发送(带延迟时间) 57 | * @Param: [event, delay] 58 | * @Author: jibaole 59 | */ 60 | public SendResult send(MessageEvent event, long delay) { 61 | Message message = getMessage(event); 62 | message.setStartDeliverTime(System.currentTimeMillis() + delay); 63 | SendResult result = this.producer.send(message); 64 | log.info("send message success. {}", result.toString()); 65 | return result; 66 | } 67 | 68 | /** 69 | * @Description: 单向发送,单向(Oneway)发送特点为发送方只负责发送消息,不等待服务器回应且没有回调函数触发,即只发送请求不等待应答。 70 | * 此方式发送消息的过程耗时非常短,一般在微秒级别。适用于某些耗时非常短,但对可靠性要求并不高的场景,例如日志收集。 71 | * @Param: [event] 72 | * @Author: jibaole 73 | */ 74 | public void sendOneway(MessageEvent event) { 75 | Message message = getMessage(event); 76 | this.producer.sendOneway(message); 77 | log.info("send message success. "); 78 | } 79 | 80 | /** 81 | * @Description: 异步发送 82 | * @Param: [event] 83 | * @Author: jibaole 84 | */ 85 | public void sendAsync(MessageEvent event) { 86 | Message message = getMessage(event); 87 | this.producer.sendAsync(message, new DefaultSendCallback()); 88 | } 89 | 90 | 91 | /** 92 | * @Description: 异步发送(带延迟时间) 93 | * @Param: [event, delay] 94 | * @Author: jibaole 95 | */ 96 | public void sendAsync(MessageEvent event, long delay) { 97 | Message message = getMessage(event); 98 | message.setStartDeliverTime(System.currentTimeMillis() + delay); 99 | this.producer.sendAsync(message, new DefaultSendCallback()); 100 | } 101 | 102 | /** 103 | * @Description: 异步发送 104 | * @Param: [event] 105 | * @Author: jibaole 106 | */ 107 | public void sendAsync(MessageEvent event,SendCallback callback) { 108 | Message message = getMessage(event); 109 | this.producer.sendAsync(message, callback); 110 | } 111 | 112 | 113 | /** 114 | * @Description: 异步发送(带延迟时间) 115 | * @Param: [event, delay] 116 | * @Author: jibaole 117 | */ 118 | public void sendAsync(MessageEvent event, long delay,SendCallback callback) { 119 | Message message = getMessage(event); 120 | message.setStartDeliverTime(System.currentTimeMillis() + delay); 121 | this.producer.sendAsync(message, callback); 122 | } 123 | 124 | /**** 125 | * @Description: 同步发送(延迟定时发送,请注意保证时间正确) 126 | * @Param: [event, delay] 127 | * @Author: jibaole 128 | */ 129 | public SendResult send(MessageEvent event, Date date) { 130 | long delay = getDelay(date); 131 | return send(event,delay); 132 | } 133 | 134 | private long getDelay(Date date) { 135 | Date now = new Date(); 136 | long delay = date.getTime()-now.getTime(); 137 | if(delay<= 0) { 138 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 139 | log.warn("消息发送时间:"+sdf.format(date)+" 小于当前时间:"+sdf.format(now)); 140 | } 141 | return delay; 142 | } 143 | 144 | /** 145 | * @Description: 异步发送(延迟定时发送,请注意保证时间正确) 146 | * @Param: [event, delay] 147 | * @Author: jibaole 148 | */ 149 | public void sendAsync(MessageEvent event, Date date,SendCallback callback) { 150 | long delay = getDelay(date); 151 | sendAsync(event,delay,callback); 152 | } 153 | 154 | /**** 155 | * @Description: 同步发送(延迟定时发送,请注意保证时间正确) 156 | * @Param: [event, delay] 157 | * @Author: jibaole 158 | */ 159 | public SendResult send(MessageEvent event, LocalDateTime date) { 160 | long delay = getDelay(date); 161 | return send(event,delay); 162 | } 163 | 164 | private long getDelay(LocalDateTime date) { 165 | ZoneId zone = ZoneId.systemDefault(); 166 | LocalDateTime now = LocalDateTime.now(); 167 | //时间间隔秒数 168 | long delay = date.atZone(zone).toInstant().getEpochSecond() - now.atZone(zone).toInstant().getEpochSecond(); 169 | if(delay<= 0) { 170 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 171 | log.warn("消息发送时间:"+sdf.format(date)+" 小于当前时间:"+sdf.format(now)); 172 | } 173 | return delay * 1000; 174 | } 175 | 176 | /** 177 | * @Description: 异步发送(延迟定时发送,请注意保证时间正确) 178 | * @Param: [event, delay] 179 | * @Author: jibaole 180 | */ 181 | public void sendAsync(MessageEvent event, LocalDateTime date,SendCallback callback) { 182 | long delay = getDelay(date); 183 | sendAsync(event,delay,callback); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 基于aliyun MQ服务封装的SpringBoot starter 2 | 3 | 4 | ### 一、RocketMQ相关说明 5 | 6 | * 快速入门: 7 | * [收发顺序消息](https://help.aliyun.com/document_detail/49323.html?spm=a2c4g.11186623.6.602.HOqOX3) 8 | * [收发事务消息](https://help.aliyun.com/document_detail/29548.html?spm=a2c4g.11186623.6.603.LFUtTE) 9 | * [收发延时消息](https://help.aliyun.com/document_detail/29549.html?spm=a2c4g.11186623.6.604.sCq6lR) 10 | * [收发定时消息](https://help.aliyun.com/document_detail/29550.html?spm=a2c4g.11186623.6.605.Pxqkpc) 11 | 12 | 13 | > RocketMQ 物理部署结构 14 | 15 | ![](https://ws2.sinaimg.cn/large/006tNc79ly1ft2r4zpl2aj31kw0xnq65.jpg) 16 | > RocketMQ 网络部署特点 17 | * Name Server 是一个几乎无状态节点,可集群部署,节点之间无任何信息同步。 18 | * Broker 部署相对复杂,Broker 分为 Master 与 Slave,一个 Master 可以对应多个 Slave,但是一个 Slave 只能对应一个 Master,Master 与 Slave 19 | 的对应关系通过指定相同的 BrokerName,不同的 BrokerId 来定义,BrokerId为 0 表示 Master,非 0 表示 Slave。Master 也可以部署多个。每个 Broker 20 | 与Name Server 集群中的所有节点建立长连接,定时注册 Topic 信息到所有 Name Server。 21 | 22 | * Producer 与 Name Server 集群中的其中一个节点(随机选择)建立长连接,定期从 Name Server 取 Topic 路由信息,并向提供 Topic 服务的 Master 23 | 建立长连接,且定时向 Master 发送心跳。Producer 完全无状态,可集群部署。 24 | 25 | * Consumer 与 Name Server 集群中的其中一个节点(随机选择)建立长连接,定期从 Name Server 取 Topic 路由信息,并向提供 Topic 服务的 Master、 26 | Slave 建立长连接,且定时向 Master、Slave 发送心跳。Consumer 既可以从 Master 订阅消息,也可以从 Slave 订阅消息,订阅规则由 Broker 配置决定。 27 | 28 | ### 二、接入概要说明 29 | 30 | > 1、通用参数说明 31 | 32 | | 参数名 | 参数说明 | 33 | | -------- | ----- | 34 | | onsAddr | 设置 MQ TCP 协议接入点,参考上面表格(推荐) | 35 | | AccessKey | 您在阿里云账号管理控制台中创建的 AccessKey,用于身份认证 | 36 | | SecretKey | 您在阿里云账号管理控制台中创建的 SecretKey,用于身份认证 | 37 | | producerId | 您在阿里云账号管理控制台中创建的producerId,用户发送消息 | 38 | | consumerId | 您在阿里云账号管理控制台中创建的consumerId,用户订阅消息 | 39 | 40 | 41 | 42 | 43 | > 2、发送消息参数说明 44 | 45 | |参数名| 参数说明| 46 | | -------- | ----- | 47 | |ProducerId| 您在控制台创建的 Producer ID| 48 | |SendMsgTimeoutMillis |设置消息发送的超时时间,单位(毫秒),默认:3000| 49 | |CheckImmunityTimeInSeconds(事务消息)| 设置事务消息第一次回查的最快时间,单位(秒)| 50 | |shardingKey(顺序消息)| 顺序消息中用来计算不同分区的值| 51 | 52 | ![image](http://om9j2ardo.bkt.clouddn.com/QQ%E5%9B%BE%E7%89%8720180607122953.png) 53 | 54 | > 3、订阅消息参数说明 55 | 56 | |参数名| 参数说明| 57 | | -------- | ----- | 58 | |ConsumerId |您在 MQ 控制台上创建的 Consumer ID| 59 | |MessageModel|设置 Consumer 实例的消费模式,默认为集群消费(值:CLUSTERING);广播消费(BROADCASTING)| 60 | |ConsumeThreadNums |设置 Consumer 实例的消费线程数,默认:64| 61 | |MaxReconsumeTimes| 设置消息消费失败的最大重试次数,默认:16| 62 | |ConsumeTimeout |设置每条消息消费的最大超时时间,超过设置时间则被视为消费失败,等下次重新投递再次消费。每个业务需要设置一个合理的值,单位(分钟)。默认:15| 63 | |suspendTimeMillis|(顺序消息) 只适用于顺序消息,设置消息消费失败的重试间隔时间| 64 | 65 | ![image](http://om9j2ardo.bkt.clouddn.com/QQ%E5%9B%BE%E7%89%8720180607122207.png) 66 | 67 | > 4、TCP协议接入域名 68 | 69 | |环境说明 |接入点| 70 | | -------- | ----- | 71 | |公共云内网接入|(阿里云经典网络/VPC): 72 | 华东1、华东2、华北1、华北2、华南1、香港| http://onsaddr-internal.aliyun.com:8080/rocketmq/nsaddr4client-internal| 73 | |公共云公网接入(非阿里云选择这个)| http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet| 74 | |公共云 Region:亚太东南1(新加坡) |http://ap-southeastaddr-internal.aliyun.com:8080/rocketmq/nsaddr4client-internal| 75 | |公共云 Region:亚太东南3(吉隆坡) |http://ons-ap-southeast-3-internal.aliyun.com:8080/rocketmq/nsaddr4client-internal| 76 | |公共云 Region:亚太东北 1 (东京)| http://ons-ap-northeast-1-internal.aliyun.com:8080/rocketmq/nsaddr4client-internal| 77 | |金融云 Region:华东1 |http://jbponsaddr-internal.aliyun.com:8080/rocketmq/nsaddr4client-internal| 78 | |金融云 Region:华东2、华南1| http://mq4finance-sz.addr.aliyun.com:8080/rocketmq/nsaddr4client-internal| 79 | 80 | > 5、产品费用详情 81 | 82 | ![MQ费用详细](http://pacssnntv.bkt.clouddn.com/20180709100117.png) 83 | 84 | 85 | ### 三、starter的基本用法: 86 | 87 | > 1、pom.xml引入依赖 88 | 89 | ```xml 90 | 91 | cn.knowbox.book 92 | spring-boot-starter-alimq 93 | 1.0.0.RELEASE 94 | 95 | ``` 96 | 97 | 98 | > 2、application配置文件中添加相应配置 99 | 100 | 101 | ```properties 102 | 103 | aliyun.mq.onsAddr=http://onsaddr-internet.aliyun.com/rocketmq/nsaddr4client-internet 104 | aliyun.mq.accessKey=xxxxxxx 105 | aliyun.mq.secretKey=xxxxxx 106 | #为false表示不引入producer,为true则producerId必须提供 107 | aliyun.mq.producer.enabled=true 108 | aliyun.mq.producer.producerId=PID_PRODUCER_SMS 109 | #为false表示不引入consumer,为true则consumerId必须提供 110 | aliyun.mq.consumer.enabled=true 111 | aliyun.mq.consumer.consumerId=CID_CONSUMER_SMS 112 | 113 | ``` 114 | 115 | 116 | 117 | 118 | 119 | > 3、 使用producer只需要在相应类中依需要注入对应实例 120 | 121 | ```java 122 | 123 | /** 124 | * @author jibaole 125 | * @version 1.0 126 | * @desc 生产者-通用方法 127 | * @date 2018/7/7 下午5:19 128 | */ 129 | @Slf4j 130 | @SuppressWarnings({"WeakerAccess", "unused"}) 131 | public class RocketMQTemplate { 132 | 133 | @Resource 134 | private ProducerBean producer; 135 | 136 | /**** 137 | * @Description: 同步发送 138 | * @Param: [event] 139 | * @Author: jibaole 140 | */ 141 | public SendResult send(MessageEvent event) {} 142 | 143 | /**** 144 | * @Description: 同步发送(带延迟时间) 145 | * @Param: [event, delay] 146 | * @Author: jibaole 147 | */ 148 | public SendResult send(MessageEvent event, long delay) {} 149 | 150 | /** 151 | * @Description: 单向发送 152 | * @Param: [event] 153 | * @Author: jibaole 154 | */ 155 | public void sendOneway(MessageEvent event) {} 156 | 157 | /** 158 | * @Description: 异步发送 159 | * @Param: [event] 160 | * @Author: jibaole 161 | */ 162 | public void sendAsync(MessageEvent event) {} 163 | 164 | 165 | /** 166 | * @Description: 异步发送(带延迟时间) 167 | * @Param: [event, delay] 168 | * @Author: jibaole 169 | */ 170 | public void sendAsync(MessageEvent event, long delay) {} 171 | 172 | 173 | } 174 | 175 | ``` 176 | 177 | ```java 178 | 179 | 180 | @Service 181 | public class ALiService { 182 | @Autowired 183 | private RocketMQTemplate rocketMQTemplate; 184 | 185 | /** 186 | * 普通消息生产者-调用 187 | */ 188 | public void sentMsg() { 189 | /**封装消息*/ 190 | MessageEvent event = new MessageEvent(); 191 | event.setTopic("base_sms"); 192 | event.setTag("Tag_user"); 193 | 194 | User user = new User(); 195 | user.setName("Paul"); 196 | user.setAdds("北京市 昌平区 龙锦苑东二区"); 197 | /**封装任意类型领域对象*/ 198 | event.setDomain(user); 199 | 200 | rocketMQTemplate.send(event); 201 | } 202 | } 203 | 204 | ``` 205 | ```java 206 | 207 | 208 | @Service 209 | public class ALiService { 210 | @Autowired 211 | private OrderMessageTemplate rocketMQTemplate; 212 | 213 | /** 214 | * 顺序消息生产者-调用 215 | */ 216 | public void sentMsg() { 217 | /**封装消息*/ 218 | MessageEvent event = new MessageEvent(); 219 | event.setTopic("base_sms"); 220 | event.setTag("Tag_user"); 221 | 222 | User user = new User(); 223 | user.setName("Paul"); 224 | user.setAdds("北京市 昌平区 龙锦苑东二区"); 225 | /**封装任意类型领域对象*/ 226 | event.setDomain(user); 227 | 228 | rocketMQTemplate.send(event); 229 | } 230 | } 231 | 232 | ``` 233 | ```java 234 | 235 | 236 | @Service 237 | public class ALiService { 238 | @Autowired 239 | private TransactionMessageTemplate transactionMessageTemplate; 240 | 241 | @PostConstruct 242 | public void init() { 243 | //发事务消息前需要初始化LocalTransactionCheckerImpl 244 | transactionMessageTemplate.init(new TransactionChecker() { 245 | 246 | @Override 247 | public TransactionStatus check(MessageEvent messageEvent, Long hashValue) { 248 | TransactionStatus status = TransactionDemo.checker(); 249 | System.out.println("sysout TransactionChecker.check 方法被调用:"+JSON.toJSONString(messageEvent)); 250 | return status; 251 | } 252 | }); 253 | log.info("初始化 LocalTransactionChecker 完成"); 254 | } 255 | 256 | /** 257 | * 事务消息生产者-调用 258 | */ 259 | public void sentMsg() { 260 | /**封装消息*/ 261 | MessageEvent event = new MessageEvent(); 262 | event.setTopic("base_sms"); 263 | event.setTag("Tag_user"); 264 | 265 | User user = new User(); 266 | user.setName("Paul"); 267 | user.setAdds("北京市 昌平区 龙锦苑东二区"); 268 | /**封装任意类型领域对象*/ 269 | event.setDomain(user); 270 | 271 | transactionMessageTemplate.send(event,new TransactionExecuter() { 272 | 273 | @Override 274 | public TransactionStatus executer(MessageEvent messageEvent, Long hashValue, Object arg) { 275 | String transactionId = TransactionDemo.createTransaction(); 276 | TransactionStatus status = TransactionDemo.checker(); 277 | return status; 278 | } 279 | ),"参数对象,以本字符串示例,会传递给TransactionExecuter.executer"); 280 | } 281 | } 282 | 283 | ``` 284 | > 4、 consumer监听处理类实现,继承AbstractMessageListener类,实现handle方法即可,如: 285 | 286 | ```java 287 | 288 | 289 | /** 290 | * @author jibaole 291 | * @version 1.0 292 | * @desc 用户事件监听处理 293 | * @date 2018/7/5 上午11:18 294 | */ 295 | 296 | @Service 297 | @RocketMQMessageListener(topic = "base_sms") 298 | public class UserMessageListener extends AbstractMessageListener { 299 | /** 300 | * 消息处理 301 | */ 302 | @Override 303 | public void handle(User user) { 304 | System.out.println(user instanceof User); 305 | 306 | System.out.println(user.toString()); 307 | } 308 | } 309 | 310 | ``` 311 | * topic为必填,可以配置多个不同topic监听业务处理 312 | 313 | 314 | > 5、优点 315 | * 1、 使用简单:开箱即用,只需要简单配置 316 | * 2、分业务处理:针对同一topic、通过tag区分具体业务场景。 317 | 318 | 319 | 320 | 321 | ### 相关参考 322 | ---- 323 | * 官网Demo地址: 324 | * Apache的starter: --------------------------------------------------------------------------------