文章目录
springboot + MQTTv3
最近项目使用到 MQTT,在引入 mica-mqtt 尝试并发发送消息出现消息丢失的情况,QoS =0,1,2 存在消息丢失情况,QoS=2最严重,所以只好切换到
org.eclipse.paho.client.mqttv3, 整合时候借鉴 mica-mqtt 开源部分思路;
引入相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.dromara.dynamictp</groupId>
<artifactId>dynamic-tp-core</artifactId>
</dependency>
</dependencies>
期待效果
// @Payload 可以byte[]、String、DevicePayload自定义类
@MqttSubscribe(value = "device/{deviceId}/{type}/report")
public void handleReportData(String topic
@Variable(value = "deviceId") int deviceId,
@Variable(value = "type") int type,
@Payload DevicePayload payload) {
}
以下相关代码,全部贴出
MqttAutoConfiguration
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.module.SimpleModule;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dromara.dynamictp.common.em.QueueTypeEnum;
import org.dromara.dynamictp.core.executor.DtpExecutor;
import org.dromara.dynamictp.core.support.ThreadPoolBuilder;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.ExecutorChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import javax.net.ssl.SSLSocketFactory;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.TimeZone;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 参考官方: https://docs.spring.io/spring-integration/reference/mqtt.html
*/
@Slf4j
@Configuration
@RequiredArgsConstructor
@EnableConfigurationProperties(MqttProperties.class)
public class MqttAutoConfiguration {
// 默认MQTT线程名称
public static final String DEFAULT_THREAD_POOL_NAME = "mqttThreadPoolExecutor";
public static final String DEFAULT_OBJECT_MAPPER = "mqttObjectMapper";
public static final String DEFAULT_TOPIC = "default/topic";
public static final String MQTT_INBOUND_CHANNEL = "mqttInboundChannel";
public static final String MQTT_OUTBOUND_CHANNEL = "mqttOutboundChannel";
private final MqttProperties properties;
@Bean
public DefaultPahoMessageConverter defaultPahoMessageConverter(ObjectMapper objectMapper) {
DefaultPahoMessageConverter converter = new DefaultPahoMessageConverter();
// 消息采用bytes
converter.setPayloadAsBytes(true);
converter.setBytesMessageMapper(new JsonMessageMapper(objectMapper));
return converter;
}
@Bean
public MethodArgumentResolverComposite methodArgumentResolverComposite(@Qualifier(DEFAULT_OBJECT_MAPPER) ObjectMapper objectMapper) {
MethodArgumentResolverComposite resolverComposite = new MethodArgumentResolverComposite();
resolverComposite.addResolver(new VariableMethodArgumentResolver())
.addResolver(new PayloadMethodArgumentResolver(objectMapper))
// 默认参数处理器,必须放在最后
.addResolver(new DefaultMethodArgumentResolver());
return resolverComposite;
}
@Bean
public MqttPahoMessageDrivenChannelAdapter channelAdapter(DefaultPahoMessageConverter converter) {
MqttClientProperties clientProperties = clientPropertiesIsPresent(properties.getConsumer()) ?
properties.getConsumer() : properties;
Objects.requireNonNull(clientProperties, "Mqtt client missing consumer configuration");
String clientId = StringUtils.isNotEmpty(clientProperties.getClientId()) ?
clientProperties.getClientId() : getClientId();
MqttPahoMessageDrivenChannelAdapter adapter = new CustomMqttPahoMessageDrivenChannelAdapter(
clientId, mqttClientFactory(clientProperties), DEFAULT_TOPIC);
adapter.setCompletionTimeout(5000);
adapter.setQos(0);
adapter.setConverter(converter);
// 移除默认
adapter.removeTopic(DEFAULT_TOPIC);
return adapter;
}
@Bean
public MqttClientSubscribeManager mqttClientSubscribeManager(MqttPahoMessageDrivenChannelAdapter channelAdapter) {
return new MqttClientSubscribeManager(channelAdapter);
}
// ========================= 消费者配置 ==========================
/**
* 消费者配置
*/
@Bean(name = DEFAULT_THREAD_POOL_NAME)
@ConditionalOnMissingBean(name = DEFAULT_THREAD_POOL_NAME)
public DtpExecutor mqttThreadPoolExecutor() {
int maximumPoolSize = 128;
return ThreadPoolBuilder.newBuilder()
.threadPoolName(DEFAULT_THREAD_POOL_NAME)
.threadFactory("mq-consume")
.corePoolSize(8)
.maximumPoolSize(maximumPoolSize)
.keepAliveTime(60)
.allowCoreThreadTimeOut(Boolean.FALSE)
.workQueue(QueueTypeEnum.SYNCHRONOUS_QUEUE.getName(), maximumPoolSize, Boolean.FALSE)
.rejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy())
.buildDynamic();
}
/**
* 通道
*/
@Bean(name = MQTT_INBOUND_CHANNEL)
public MessageChannel inboundMessageChannel(@Qualifier(DEFAULT_THREAD_POOL_NAME) DtpExecutor executor) {
return new ExecutorChannel(executor);
}
/**
* 入站适配器
*
* @see org.springframework.integration.dsl.context.IntegrationFlowBeanPostProcessor
*/
@Bean
public IntegrationFlow inboundIntegrationFlow(