spring boot + mqtt 物联网开发
最近这一年里,在项目实战的时候,遇到了mqtt开发,今天我就大致的来总结下,mqtt在spring boot的使用
1、引用jar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <!-- mqtt --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-integration</artifactId> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-stream</artifactId> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-mqtt</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional> true </optional> </dependency> |
2.项目启动建立链接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component; import com.slife.cws.mqtt.component.MqttPushClient; import com.slife.cws.mqtt.config.MqttConfig; import lombok.extern.slf4j.Slf4j; @Component @Slf4j public class MqttApplicationRunner implements ApplicationRunner { @Autowired private MqttConfig mqttConfig; @Override public void run(ApplicationArguments args) throws Exception { if (log.isInfoEnabled()) { log.info( "===============>>>Mqtt is run starting:<<==================" ); } MqttPushClient mqttPushClient = new MqttPushClient(); mqttPushClient.connect(mqttConfig); // 订阅主题 mqttPushClient.subscribe(mqttConfig.getTopic(), mqttConfig.getQos()); } } |
3.相关配置及实现类
①、配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | #spring.mqtt.url=tcp: //127.0.0.1 spring.mqtt.url=tcp: //127.0.0.1 spring.mqtt.username= nbew spring.mqtt.password= 123456 spring.mqtt.client-id= 100201101 spring.mqtt.topics= top spring.mqtt.completion-timeout= 3000 spring.mqtt.timeout= 120 spring.mqtt.keep-alive= 20 spring.mqtt.qos= 1 , 1 spring: mqtt: url: tcp: //mqtt.rootcloudapp.com:1883 username: 0ba851e2e83609b9 password: 81757448a4df0d73 client-id: 0ba851e2e83609b9 id: test10000011 topics: v4/p/post/thing/live/json/ 1.1 completion-timeout: 3000 timeout: 30 keep-alive: 60 qos: 1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; import lombok.Data; //@ConfigurationProperties(prefix = "spring.mqtt") @Data @Component @Configuration public class MqttConfig { /** * 链接url */ @Value ( "${spring.mqtt.url}" ) private String url; /** * 用户名 */ @Value ( "${spring.mqtt.username}" ) private String username; /** * 密码 */ @Value ( "${spring.mqtt.password}" ) private String password; /** * 客户端id */ @Value ( "${spring.mqtt.client-id}" ) private String clientId; /** * 通讯标识 id */ @Value ( "${spring.mqtt.id}" ) private String id; /** * 主题 */ @Value ( "${spring.mqtt.topics}" ) private String[] topic; /** * 超时时间 */ @Value ( "${spring.mqtt.timeout}" ) private int timeout; /** * 心跳检测时间 */ @Value ( "${spring.mqtt.keep-alive}" ) private int keepAlive; /** * 心跳包级别 */ @Value ( "${spring.mqtt.qos}" ) private int [] qos; private int completionTimeout; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; /** * @Package com.slife.cws.mqtt.component * @ClassName: Mqttbean * @Description: 客户端 * @Author youli * @date 2021年2月16日 * @CopyRight:上海成生科技有限公司 */ @Component public class Mqttbean { @Bean ( "mqttPushClient" ) public MqttPushClient getMqttPushClient() { MqttPushClient mqttPushClient = new MqttPushClient(); return mqttPushClient; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.MqttPersistenceException; import org.eclipse.paho.client.mqttv3.MqttTopic; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; import com.slife.cws.mqtt.config.MqttConfig; import lombok.extern.slf4j.Slf4j; /** * @Package com.shhw.mqtt.component * @ClassName: MqttPushClient * @Description: MqttClient客户端代码 * @Author youli * @date 2020年10月16日 * @CopyRight:上海成生科技有限公司 */ @Slf4j public class MqttPushClient { private static MqttClient client; public static MqttClient getClient() { return client; } public static void setClient(MqttClient client) { MqttPushClient.client = client; } private MqttConnectOptions getOption(String userName, String password, int outTime, int KeepAlive) { // MQTT连接设置 MqttConnectOptions option = new MqttConnectOptions(); // 设置是否清空session,false表示服务器会保留客户端的连接记录,true表示每次连接到服务器都以新的身份连接 option.setCleanSession( false ); // 设置连接的用户名 option.setUserName(userName); // 设置连接的密码 option.setPassword(password.toCharArray()); // 设置超时时间 单位为秒 option.setConnectionTimeout(outTime); // 设置会话心跳时间 单位为秒 服务器会每隔(1.5*keepTime)秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制 option.setKeepAliveInterval(KeepAlive); // setWill方法,如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息 // option.setWill(topic, "close".getBytes(), 2, true); option.setMaxInflight( 1000 ); log.info( "================>>>MQTT连接认证成功<<======================" ); return option; } /** * 连接 */ public void connect(MqttConfig mqttConfig) { MqttClient client; try { String clientId = mqttConfig.getClientId(); clientId += System.currentTimeMillis(); client = new MqttClient(mqttConfig.getUrl(), clientId, new MemoryPersistence()); MqttConnectOptions options = getOption(mqttConfig.getUsername(), mqttConfig.getPassword(), mqttConfig.getTimeout(), mqttConfig.getKeepAlive()); MqttPushClient.setClient(client); try { client.setCallback( new PushCallback<Object>( this , mqttConfig)); if (!client.isConnected()) { client.connect(options); log.info( "================>>>MQTT连接成功<<======================" ); //订阅主题 subscribe(mqttConfig.getTopic(), mqttConfig.getQos()); } else { // 这里的逻辑是如果连接不成功就重新连接 client.disconnect(); client.connect(options); log.info( "===================>>>MQTT断连成功<<<======================" ); } } catch (Exception e) { e.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); } } /** * 断线重连 * * @throws Exception */ public Boolean reConnect() throws Exception { Boolean isConnected = false ; if ( null != client) { client.connect(); if (client.isConnected()) { isConnected = true ; } } return isConnected; } /** * 发布,默认qos为0,非持久化 * * @param topic * @param pushMessage */ public void publish(String topic, String pushMessage) { publish( 0 , false , topic, pushMessage); } /** * 发布 * * @param qos * @param retained * @param topic * @param pushMessage */ public void publish( int qos, boolean retained, String topic, String pushMessage) { MqttMessage message = new MqttMessage(); message.setQos(qos); message.setRetained(retained); message.setPayload(pushMessage.getBytes()); MqttTopic mTopic = MqttPushClient.getClient().getTopic(topic); if ( null == mTopic) { log.error( "===============>>>MQTT topic 不存在<<=======================" ); } MqttDeliveryToken token; try { token = mTopic.publish(message); token.waitForCompletion(); } catch (MqttPersistenceException e) { e.printStackTrace(); } catch (MqttException e) { e.printStackTrace(); } } /** * 发布消息的服务质量(推荐为:2-确保消息到达一次。0-至多一次到达;1-至少一次到达,可能重复), retained * 默认:false-非持久化(是指一条消息消费完,就会被删除;持久化,消费完,还会保存在服务器中,当新的订阅者出现,继续给新订阅者消费) * * @param topic * @param pushMessage */ public void publish( int qos, String topic, String pushMessage) { publish(qos, false , topic, pushMessage); } /** * 订阅某个主题,qos默认为0 * * @param topic */ public void subscribe(String[] topic) { subscribe(topic, null ); } /** * 订阅某个主题 * * @param topic * @param qos */ public void subscribe(String[] topic, int [] qos) { try { MqttPushClient.getClient().unsubscribe(topic); MqttPushClient.getClient().subscribe(topic, qos); } catch (MqttException e) { e.printStackTrace(); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | import org.eclipse.paho.client.mqttv3.MqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.MqttPersistenceException; import org.eclipse.paho.client.mqttv3.MqttTopic; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import lombok.extern.slf4j.Slf4j; /** * @Package com.shhw.mqtt.component * @ClassName: MqttSender * @Description: 主题发布 * @Author youli * @date 2020年10月16日 * @CopyRight:上海成生科技有限公司 */ @Component (value = "mqttSender" ) @Slf4j public class MqttSender { @Async public void send(String queueName, String msg) { log.debug( "=====================>>>>发送主题:{}, msg:{}" , queueName,msg); publish( 2 , queueName, msg); } /** * 发布,默认qos为0,非持久化 * * @param topic * @param pushMessage */ public void publish(String topic, String pushMessage) { publish( 1 , false , topic, pushMessage); } /** * 发布 * * @param qos * @param retained * @param topic * @param pushMessage */ public void publish( int qos, boolean retained, String topic, String pushMessage) { MqttMessage message = new MqttMessage(); message.setQos(qos); message.setRetained(retained); message.setPayload(pushMessage.getBytes()); MqttTopic mTopic = MqttPushClient.getClient().getTopic(topic); if ( null == mTopic) { log.error( "===================>>>MQTT topic 不存在<<=================" ); } MqttDeliveryToken token; try { token = mTopic.publish(message); token.waitForCompletion(); } catch (MqttPersistenceException e) { log.error( "============>>>publish fail" , e); e.printStackTrace(); } catch (MqttException e) { e.printStackTrace(); } } /** * 发布消息的服务质量(推荐为:2-确保消息到达一次。0-至多一次到达;1-至少一次到达,可能重复), retained * 默认:false-非持久化(是指一条消息消费完,就会被删除;持久化,消费完,还会保存在服务器中,当新的订阅者出现,继续给新订阅者消费) * * @param topic * @param pushMessage */ public void publish( int qos, String topic, String pushMessage) { publish(qos, false , topic, pushMessage); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | import javax.annotation.Resource; import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.springframework.stereotype.Component; import com.slife.cws.mqtt.config.MqttConfig; import com.slife.cws.mqtt.pojo.MqttResponseBody; import com.slife.cws.mqtt.pojo.MqttResponseHeartbeat; import com.slife.cws.mqtt.service.MqttService; import com.slife.cws.mqtt.service.impl.MqttServiceImpl; import com.slife.cws.utils.JSONUtils; import com.slife.cws.utils.SpringUtil; import lombok.extern.slf4j.Slf4j; /** * @Package com.shhw.mqtt.component * @ClassName: PushCallback * @Description: 进行双向通信的时候,监听订阅的客户端和主题是否处于连接状态 * @Author youli * @date 2020年10月16日 * @CopyRight:上海成生科技有限公司 */ @Slf4j @Component public class PushCallback<component> implements MqttCallback { private MqttPushClient client; private MqttConfig mqttConfiguration; @Resource MqttService mqttService; public PushCallback(MqttPushClient client, MqttConfig mqttConfiguration) { this .client = client; this .mqttConfiguration = mqttConfiguration; } @Override public void connectionLost(Throwable cause) { /** 连接丢失后,一般在这里面进行重连 **/ if (client != null ) { while ( true ) { try { log.info( "==============》》》[MQTT] 连接丢失,尝试重连..." ); MqttPushClient mqttPushClient = new MqttPushClient(); mqttPushClient.connect(mqttConfiguration); if (MqttPushClient.getClient().isConnected()) { log.info( "=============>>重连成功" ); } break ; } catch (Exception e) { log.error( "=============>>>[MQTT] 连接断开,重连失败!<<=============" ); continue ; } } } log.info(cause.getMessage()); } @Override public void deliveryComplete(IMqttDeliveryToken token) { // publish后会执行到这里 log.info( "pushComplete==============>>>" + token.isComplete()); } /** * 监听对应的主题消息 * * @param topic * @param message * @throws Exception */ @Override public void messageArrived(String topic, MqttMessage message) throws Exception { // subscribe后得到的消息会执行到这里面 log.info( "============》》接收消息主题 : " + topic); log.info( "============》》接收消息Qos : " + message.getQos()); log.info( "============》》接收消息内容原始内容 : " + new String(message.getPayload())); log.info( "============》》接收消息内容GB2312 : " + new String(message.getPayload(), "GB2312" )); log.info( "============》》接收消息内容UTF-8 : " + new String(message.getPayload(), "UTF-8" )); try { if (topic.equals( "datapoint" )) { MqttResponseBody mqttResponseBody = JSONUtils.jsonToBean( new String(message.getPayload(), "UTF-8" ), MqttResponseBody. class ); MqttService mqttService = SpringUtil.getBean(MqttServiceImpl. class ); mqttService.messageArrived(mqttResponseBody); } else if (topic.equals( "heartbeat" )) { MqttResponseHeartbeat mqttResponseHeartbeat = JSONUtils .jsonToBean( new String(message.getPayload(), "UTF-8" ), MqttResponseHeartbeat. class ); MqttService mqttService = SpringUtil.getBean(MqttServiceImpl. class ); mqttService.messageHeartbeat(mqttResponseHeartbeat); } } catch (Exception e) { e.printStackTrace(); log.info( "============》》接收消息主题异常 : " + e.getMessage()); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | import com.slife.cws.mqtt.pojo.MqttResponseBody; import com.slife.cws.mqtt.pojo.MqttResponseHeartbeat; public interface MqttService { /** * @Title: sendMessage * @Description: 发送消息 * @Author youli * @date 2020年11月9日 * @param gpsMsg */ void sendMessage(); /** * @Title: messageArrived * @Description: 监听发送消息 * @Author youli * @date 2021年2月16日 * @param mqttResponseBody */ void messageArrived(MqttResponseBody mqttResponseBody); /** * @Title: messageHeartbeat * @Description: 设备心跳监听 * @Author youli * @date 2021年7月4日 * @param mqttResponseHeartbeat */ void messageHeartbeat(MqttResponseHeartbeat mqttResponseHeartbeat); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.slife.cws.mqtt.component.MqttSender; import com.slife.cws.mqtt.config.MqttConfig; import com.slife.cws.mqtt.pojo.MqttResponseBody; import com.slife.cws.mqtt.pojo.MqttResponseHeartbeat; import com.slife.cws.mqtt.service.MqttService; import com.slife.cws.utils.JSONUtils; import com.slife.ews.service.DatumrealtimeService; import lombok.extern.slf4j.Slf4j; @Service @Slf4j public class MqttServiceImpl implements MqttService { @Autowired MqttConfig mqttConfig; @Autowired private MqttSender mqttSender; @Autowired DataService dataService; @Override public void sendMessage() { String jsonStr = null ; mqttSender.send(mqttConfig.getTopic()[ 0 ], jsonStr); } @Override public void messageArrived(MqttResponseBody mqttResponseBody) { log.info( "接口的消息:{}" , JSONUtils.beanToJson(mqttResponseBody)); dataService.save(mqttResponseBody); } @Override public void messageHeartbeat(MqttResponseHeartbeat mqttResponseHeartbeat) { log.info( "监听心跳信息:{}" , JSONUtils.beanToJson(mqttResponseHeartbeat)); dataService.heartbeat(mqttResponseHeartbeat); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.slife.cws.mqtt.component.MqttPushClient; import com.slife.cws.mqtt.component.MqttSender; import com.slife.cws.mqtt.config.MqttConfig; @RestController public class MessageController { @Autowired MqttConfig mqttConfig; @Autowired private MqttSender mqttSender; @Autowired private MqttPushClient mqttPushClient; /*** * 发布消息,用于其他客户端消息接收测试 */ @RequestMapping ( "/sendMqttMessage" ) public String sendMqttMessage(String topic) { String jsonStr = "{\"truckPic\":\"20201029111.jpg\",\"httpRootPic\":\"url\"}" ; mqttSender.send(topic, jsonStr); return "ok" ; } @RequestMapping ( "/mqttop" ) public String mqttop() { String TOPIC1 = "test_topic1" ; String TOPIC2 = "test_topic2" ; String TOPIC3 = "test_topic3" ; String TOPIC4 = "test_topic4" ; int Qos1 = 1 ; int Qos2 = 1 ; int Qos3 = 1 ; int Qos4 = 1 ; String[] topics = { TOPIC1, TOPIC2, TOPIC3, TOPIC4 }; int [] qos = { Qos1, Qos2, Qos3, Qos4 }; mqttPushClient.subscribe(topics, qos); return "订阅主题" ; } public static void main(String[] args) { long time = System.currentTimeMillis(); System.out.println(time); } } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步