一、 导入依赖
<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>
二、Yml文件配置
mqtt:
client-id-prefix: dev
server-uris: tcp://192.168.1.16:1883
username: admin
password: admin123
keep-alive-interval: 2
message-mapper: com.panda.mqtt.component.support.MqttJsonMessageMapper #自定义消息转换器
model-packages: com.panda.mqtt.model.toserver #
sub-topics:
- $SYS/brokers/+/clients/#
- iot_server/#
- iot_client/test123/#
三、 Config文件配置
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import com.panda.mqtt.component.support.BytesMessageConverter;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.mapping.BytesMessageMapper;
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.MqttMessageConverter;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
@EnableIntegration
@Configuration
@ConfigurationProperties(prefix = "mqtt")
@IntegrationComponentScan(basePackages = "com.panda.mqtt")
public class MqttConfig {
private String[] serverUris;
private String username;
private char[] password;
private int keepAliveInterval;
private String[] subTopics;
private Class<? extends BytesMessageMapper> messageMapper;
private String clientIdPrefix;
private String modelPackages;
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(serverUris);
options.setUserName(username);
options.setPassword(password);
options.setKeepAliveInterval(keepAliveInterval);
factory.setConnectionOptions(options);
return factory;
}
@Bean
public MqttMessageConverter bytesMessageConverter() throws NoSuchMethodException {
BytesMessageMapper bytesMessageMapper = BeanUtils.instantiateClass(messageMapper.getConstructor(String.class), modelPackages);
return new BytesMessageConverter(bytesMessageMapper);
}
@Bean
@ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound(MqttMessageConverter mqttMessageConverter) {
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(clientIdPrefix + "_outbound", mqttClientFactory());
messageHandler.setConverter(mqttMessageConverter);
messageHandler.setCompletionTimeout(5000);
messageHandler.setAsync(true);
return messageHandler;
}
/**
* 在默认连接地址动态订阅主题
* @param mqttMessageConverter 自定义转换器
* 使用说明:
* 注入对应类
* @Autowired
* private MqttPahoMessageDrivenChannelAdapter messageProducer.
* 添加订阅:messageProducer.addTopic( topic:"/max/a",qos:2):
* 移除订阅:messageProducer.removeTopic( topic:"/max/a"):
* @return MqttPahoMessageDrivenChannelAdapter
*/
@Bean
public MqttPahoMessageDrivenChannelAdapter inbound(MqttMessageConverter mqttMessageConverter) {
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(clientIdPrefix + "_inbound_add", mqttClientFactory());
adapter.setConverter(mqttMessageConverter);
adapter.setOutputChannel(mqttInboundChannel());
adapter.setQos(1);
return adapter;
}
@Bean
public MessageProducer mqttInbound(MqttMessageConverter mqttMessageConverter) {
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(clientIdPrefix + "_inbound", mqttClientFactory(), subTopics);
adapter.setConverter(mqttMessageConverter);
adapter.setOutputChannel(mqttInboundChannel());
adapter.setCompletionTimeout(5000);
adapter.setQos(1);
return adapter;
}
@Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
@Bean
public MessageChannel mqttInboundChannel() {
return new DirectChannel();
}
public String[] getServerUris() {
return serverUris;
}
public void setServerUris(String[] serverUris) {
this.serverUris = serverUris;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public char[] getPassword() {
return password;
}
public void setPassword(char[] password) {
this.password = password;
}
public int getKeepAliveInterval() {
return keepAliveInterval;
}
public void setKeepAliveInterval(int keepAliveInterval) {
this.keepAliveInterval = keepAliveInterval;
}
public String[] getSubTopics() {
return subTopics;
}
public void setSubTopics(String[] subTopics) {
this.subTopics = subTopics;
}
public Class<? extends BytesMessageMapper> getMessageMapper() {
return messageMapper;
}
public void setMessageMapper(Class<? extends BytesMessageMapper> messageMapper) {
this.messageMapper = messageMapper;
}
public String getClientIdPrefix() {
return clientIdPrefix;
}
public void setClientIdPrefix(String clientIdPrefix) {
this.clientIdPrefix = clientIdPrefix;
}
public String getModelPackages() {
return modelPackages;
}
public void setModelPackages(String modelPackages) {
this.modelPackages = modelPackages;
}
}
四、消息回调
import com.panda.mqtt.commons.JsonUtils;
import com.panda.mqtt.component.model.Connected;
import com.panda.mqtt.component.model.Disconnected;
import com.panda.mqtt.component.model.MqttRequest;
import com.panda.mqtt.component.model.MqttResponse;
import com.panda.mqtt.model.toclient.SettingUpdate;
import com.panda.mqtt.model.toserver.CommonResponse;
import com.panda.mqtt.service.impl.MessageService;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
import org.springframework.stereotype.Service;
/**
* 默认连接回调处理
*/
@Service
public class MqttHandler implements MessageHandler {
private static final Logger log = LoggerFactory.getLogger(MqttHandler.class);
@Autowired
private MessageService messageService;
@ServiceActivator(inputChannel = "mqttInboundChannel")
@Override
public void handleMessage(Message<?> message) throws MessagingException {
String topic = message.getHeaders().get(MqttHeaders.TOPIC, String.class);
Object payload = message.getPayload();
System.out.println("topic:" + topic + "\t" + "payload:" + payload + "\n");
}
}
动态连接回调
import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.stereotype.Service;
/**
* 动态订阅主题回调实现
*/
@Service
public class SubHandler implements IMqttMessageListener {
/**
* 接收到消息
* @param s 主题名称
* @param mqttMessage 消息内容
* @throws Exception 异常
*/
@Override
public void messageArrived(String s, MqttMessage mqttMessage) throws Exception {
System.out.println("messageArrived: " + mqttMessage);
System.out.println("s:" + s);
}
}
五、 配置动态连接/断开以及动态订阅/移除订阅
import com.panda.mqtt.component.model.DynamicConnected;
import com.panda.mqtt.component.model.DynamicSubscribe;
import com.panda.mqtt.endpoint.SubHandler;
import com.panda.mqtt.service.impl.MqttService;
import org.eclipse.paho.client.mqttv3.IMqttClient;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.stereotype.Service;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class MqttServiceImpl implements MqttService {
/**
* 存放的已连接的设备
*/
private final ConcurrentHashMap<String, IMqttClient> mqttClientMap = new ConcurrentHashMap<>();
/**
* 连接
* @param connected 连接参数
*/
@Override
public void connect(DynamicConnected connected) {
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[]{getMqttUrl(connected.getIp(), connected.getPort())});
options.setUserName(connected.getUsername());
options.setPassword(connected.getPassword());
options.setCleanSession(connected.getCleanSession());
options.setKeepAliveInterval(connected.getKeepAliveInterval());
String key = getKey(connected.getIp(), connected.getPort(), connected.getClientId(), connected.getUsername());
mqttClientMap.computeIfAbsent(key,k->{
try {
MqttClient mqttClient = new MqttClient(getMqttUrl(connected.getIp(), connected.getPort()), connected.getClientId());
mqttClient.connect(options);
if (mqttClient.isConnected()) {
System.out.println("连接成功");
return mqttClient;
}
System.out.println(getMqttUrl(connected.getIp(), connected.getPort()));
System.out.println("灭联系"+mqttClient.isConnected());
} catch (MqttException e) {
throw new RuntimeException(e);
}
return null;
});
}
/**
* 断开连接
* @param connected 断开参数
*/
@Override
public void disconnect(DynamicConnected connected) {
String key = getKey(connected.getIp(), connected.getPort(), connected.getClientId(), connected.getUsername());
if (!key.isEmpty()) {
IMqttClient iMqttClient = mqttClientMap.get(key);
if (iMqttClient != null && iMqttClient.isConnected()) {
try {
iMqttClient.disconnect();
} catch (MqttException e) {
throw new RuntimeException(e);
}
}
}
}
/**
* 订阅主题
* @param subscribe 订阅参数
*/
@Override
public void subscribe(DynamicSubscribe subscribe) {
String key = getKey(subscribe.getIp(), subscribe.getPort(), subscribe.getClientId(), subscribe.getUsername());
if (!key.isEmpty()) {
IMqttClient iMqttClient = mqttClientMap.get(key);
if (iMqttClient != null && iMqttClient.isConnected()) {
try {
iMqttClient.subscribe(subscribe.getTopic(), subscribe.getQos(),new SubHandler());
} catch (MqttException e) {
throw new RuntimeException(e);
}
}
}
}
/**
* 移除订阅
* @param subscribe 移除参数
*/
@Override
public void unsubscribe(DynamicSubscribe subscribe) {
String key = getKey(subscribe.getIp(), subscribe.getPort(), subscribe.getClientId(), subscribe.getUsername());
if (!key.isEmpty()) {
IMqttClient iMqttClient = mqttClientMap.get(key);
if (iMqttClient != null && iMqttClient.isConnected()) {
try {
iMqttClient.unsubscribe(subscribe.getTopic());
} catch (MqttException e) {
throw new RuntimeException(e);
}
}
}
}
/**
* 获取MQTT连接地址
* @param ip IP
* @param port 端口
* @return 地址
*/
private String getMqttUrl(String ip, Integer port) {
return "tcp://"+ip+":"+port;
}
/**
* 获取存储的Key
* @param ip IP
* @param port 端口
* @param clientId 唯一ID
* @param username 用户名
* @return Key
*/
private String getKey(String ip, Integer port, String clientId,String username) {
return ip+"_"+port+"_"+clientId+"_"+username;
}
}
六、 配置默认连接发送
import com.panda.mqtt.component.annotations.Topic;
import com.panda.mqtt.component.model.MqttRequest;
import com.panda.mqtt.component.model.MqttResponse;
import com.panda.mqtt.endpoint.MqttProducer;
import com.panda.mqtt.service.impl.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoSink;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.RejectedExecutionException;
@Service
public class MessageServiceImpl implements MessageService {
private static final Mono<MqttResponse> Rejected = Mono.error(new RejectedExecutionException("客户端暂未响应,请勿重复发送"));
private Map<String, MonoSink<MqttResponse>> topicSubscribers = new ConcurrentHashMap<>();
@Autowired
private MqttProducer mqttProducer;
@Override
public void notify(String deviceId, Object payload) {
Topic annotation = payload.getClass().getAnnotation(Topic.class);
String value = annotation.value();
String topic = value.replace("{deviceId}", deviceId);
mqttProducer.sendTo(topic, payload);
}
@Override
public Mono<MqttResponse> request(String deviceId, MqttRequest payload) {
Class<? extends MqttRequest> clazz = payload.getClass();
Topic annotation = clazz.getAnnotation(Topic.class);
String value = annotation.value();
String topic = value.replace("{deviceId}", deviceId);
long messageId = payload.getMessageId();
if (messageId == 0L)
payload.setMessageId(messageId = System.currentTimeMillis());
String key = getKey(deviceId, messageId);
Mono<MqttResponse> receive = subscribe(key);
if (receive == null)
return Rejected;
return Mono.create(sink -> {
mqttProducer.sendTo(topic, payload);
sink.success();
}).then(receive).doFinally(signal -> unSubscribe(key));
}
@Override
public boolean response(Message<MqttResponse> message) {
MqttResponse payload = message.getPayload();
MonoSink<MqttResponse> sink = topicSubscribers.get(getKey(payload.getDeviceId(), payload.getMessageId()));
if (sink != null) {
sink.success(payload);
return true;
}
return false;
}
private String getKey(String deviceId, long messageId) {
return deviceId + "/" + messageId;
}
private Mono<MqttResponse> subscribe(String key) {
if (!topicSubscribers.containsKey(key)) {
return Mono.create(sink -> topicSubscribers.put(key, sink));
}
return null;
}
private void unSubscribe(String key) {
topicSubscribers.remove(key);
}
}
七、测试
@RestController
@RequestMapping(path = "mqtt", method = {RequestMethod.GET})
public class MqttController {
@Autowired
private MessageService messageService;
@RequestMapping(path = "send")
public Mono<MqttResponse> sendMessage(@RequestParam(defaultValue = "test123") String deviceId,
SettingUpdate settingUpdate) {
Mono<MqttResponse> result = messageService.request(deviceId, settingUpdate);
//TODO 模拟客户端回复消息
Mono.create(monoSink -> {
CommonResponse commonResponse = new CommonResponse(settingUpdate.getMessageId(), deviceId, (byte) 9);
messageService.notify(deviceId, commonResponse);
}).delaySubscription(Duration.ofMillis(10L)).subscribe();
return result.timeout(Duration.ofSeconds(10L));
}
@RequestMapping(path = "send/notice")
public void sendNotice(@RequestParam(defaultValue = "test123") String deviceId,
Location location) {
messageService.notify(deviceId, location);
System.out.println("发送成功-----info");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
以下是动态添加部分
@RestController
@RequestMapping(path = "dy", method = {RequestMethod.GET})
public class DyController {
@Autowired
private MqttService mqttService;
/**
* 连接
*/
@GetMapping("/con")
public void connect(){
DynamicConnected dynamicConnected = new DynamicConnected();
dynamicConnected.setIp("IP");
dynamicConnected.setPort(1883);
dynamicConnected.setUsername("admin");
dynamicConnected.setPassword("public");
dynamicConnected.setKeepAliveInterval(2);
dynamicConnected.setClientId("cliendTd");
dynamicConnected.setCleanSession(true);
mqttService.connect(dynamicConnected);
};
/**
* 断开连接
*/
@GetMapping("/dis")
public void disconnect(){
DynamicConnected dynamicConnected = new DynamicConnected();
dynamicConnected.setIp("IP");
dynamicConnected.setPort(1883);
dynamicConnected.setUsername("admin");
dynamicConnected.setPassword("public");
dynamicConnected.setKeepAliveInterval(2);
dynamicConnected.setClientId("cliendTd");
dynamicConnected.setCleanSession(true);
mqttService.disconnect(dynamicConnected);
};
/**
* 订阅主题
*/
@GetMapping("/sub")
public void subscribe(){
DynamicSubscribe dynamicSubscribe = new DynamicSubscribe();
dynamicSubscribe.setIp("IP");
dynamicSubscribe.setPort(1883);
dynamicSubscribe.setUsername("admin");
dynamicSubscribe.setPassword("public");
dynamicSubscribe.setKeepAliveInterval(2);
dynamicSubscribe.setClientId("cliendTd");
dynamicSubscribe.setCleanSession(true);
dynamicSubscribe.setTopic("/max/a");
dynamicSubscribe.setQos(2);
mqttService.subscribe(dynamicSubscribe);
};
/**
* 移除订阅
*/
@GetMapping("/unsub")
public void unsubscribe(){
DynamicSubscribe dynamicSubscribe = new DynamicSubscribe();
dynamicSubscribe.setIp("IP");
dynamicSubscribe.setPort(1883);
dynamicSubscribe.setUsername("admin");
dynamicSubscribe.setPassword("public");
dynamicSubscribe.setKeepAliveInterval(2);
dynamicSubscribe.setClientId("cliendTd");
dynamicSubscribe.setCleanSession(true);
dynamicSubscribe.setTopic("/max/a");
dynamicSubscribe.setQos(2);
mqttService.unsubscribe(dynamicSubscribe);
};
到此就结束了,非常的好用,完美测试完成!!!
如需完整源码,请留言!!!