在当今快速发展的物联网(IoT)时代,设备间的即时通信变得至关重要。Spring Boot以其简化配置和快速部署的特性,成为构建企业级应用的首选框架。Vert.x作为一个响应式应用平台,以其轻量级和高性能的特点,为构建高并发的分布式系统提供了强大支持。结合EMQ X Broker,我们能够实现一个基于Spring Boot和Vert.x的MQTT客户端,它将为微服务架构下的设备通信提供高效、可靠的解决方案。通过本实现指南,我们将一步步引导开发者如何从零开始构建这一组件,探索其在现代应用开发中的无限可能。
技术框架
为什么选择SpringBoot及 Vert.x 作为组件主要技术框架?
我想当我们聊到Java项目实践时候,往往离不开Spring及SpringBoot 框架:
- Spring Boot简化了基于Spring的应用开发过程,自动配置和微服务支持使其成为快速开发和部署微服务的理想选择。
- Spring Boot拥有庞大的社区和丰富的生态系统,提供了大量的库和工具,方便开发者快速解决问题和扩展功能。
- Spring Boot的应用结构清晰,易于理解和维护。
- Spring Boot可以轻松地扩展应用,无论是水平扩展还是垂直扩展。
Vert.x它的核心是一个轻量级的、多语言的、响应式的应用程序运行时,专为构建响应式、高性能、可伸缩的应用程序而设计。
- Vert.x提供了一个事件驱动的非阻塞编程模型,非常适合处理大量并发连接,这对于需要高吞吐量和低延迟的MQTT通信至关重要。
- Vert.x都拥有庞大的社区和丰富的生态系统,提供了大量的库和工具,方便开发者快速解决问题和扩展功能。
- Vert.x的模块化设计也有助于保持代码的整洁和可维护性。
- Vert.x可以轻松地扩展应用,无论是水平扩展还是垂直扩展。
当Spring Boot遇上Vert.x,它们的结合带来了更多的可能性:
- Spring Boot和Vert.x可以很好地集成,Spring Boot提供了Vert.x的集成支持,使得在Spring Boot应用中使用Vert.x变得简单。
- Vert.x的高性能特性,结合Spring Boot的轻量级特性,可以构建出既快速又高效的应用。
创建项目
源码地址Gitee
工具:idea、maven
环境:jdk17
创建项目步骤
- 选择创建新项目: 在启动界面,点击“Create New Project”(创建新项目)。
- 选择 Maven: 在左侧的项目类型列表中,选择“Maven”。
- 配置 JDK: 确保 Maven 项目使用的 JDK 版本与你的 Maven 配置匹配。你可以通过点击右侧的“JDK”下拉菜单来选择。
- 添加 Maven 架构: 如果你需要特定的 Maven 架构,可以在右侧的“Add Archetype”(添加架构)部分搜索并选择一个架构,例如
maven-archetype-quickstart
。 - 填写项目信息: 在右侧的表单中,填写项目的基本设置,包括:
- Group Id(组 ID):通常是一个反向域名形式的标识符,如
com.gitee.xmhzzz
. - Artifact Id(构件 ID):项目的唯一名称,如
emqx-practice
. - Version(版本):项目的版本号,如
1.0.0-SNAPSHOT
. - Package(包):项目的根包名,通常是组 ID 的全小写形式。
- Group Id(组 ID):通常是一个反向域名形式的标识符,如
创建模块
- 选择创建新项目: 在启动界面,点击“New Module”(创建项目的模块)。
- 选择 Maven: 在左侧的项目类型列表中,选择“Maven”。
- 配置 JDK: “JDK”选择JDK17以上。
- 添加 Maven 架构: 如果你需要特定的 Maven 架构,可以在右侧的“Add Archetype”(添加架构)部分搜索并选择一个架构,例如
maven-archetype-quickstart
。 - 添加Parent:
emqx-practice
. - 填写项目信息: 在右侧的表单中,填写项目的基本设置,包括:
- Group Id(组 ID):通常是一个反向域名形式的标识符,如
com.gitee.xmhzzz
. - Artifact Id(构件 ID):项目的唯一名称,如
emqx-client
. - Version(版本):项目的版本号,如
1.0.0-SNAPSHOT
. - Package(包):项目的根包名,通常是组 ID 的全小写形式。
- Group Id(组 ID):通常是一个反向域名形式的标识符,如
创建三个模块emqx-client、emqx-client-starter、emqx-example-service
maven依赖
parent项目emqx-practice
maven依赖
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-stack-depchain</artifactId>
<version>${vertx.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>${okhttp.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
版本管理
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring-boot.version>3.0.2</spring-boot.version>
<vertx.version>4.2.3</vertx.version>
<fastjson2.version>2.0.33</fastjson2.version>
<okhttp.version>3.14.9</okhttp.version>
<guava.version>32.0.1-jre</guava.version>
<hutool.version>5.8.20</hutool.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
</properties>
Module模块emqx-client
<dependencies>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-mqtt</artifactId>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
Module模块emqx-client-starter
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.gitee.xmhzzz</groupId>
<artifactId>emqx-client</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
Module模块emqx-example-service
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mqtt client -->
<dependency>
<groupId>com.gitee.xmhzzz</groupId>
<artifactId>emqx-client-starter</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
emqx-client组件实现
emqx-client主要划分四部分
MQTT客户端上下文、MQTT配置信息、MQTT消息监听接口、MQTT消息推送
EMQX客户端上下文
● 连接管理:管理MQTT连接及断开连接功能。
● 订阅列表:客户端订阅的所有主题及其对应的QoS(服务质量)级别。
● 推送消息:发送消息到EMQX中。
接口定义
/**
* @ClassName
* @Description emqx-client 上下文
* @Author wzq
* @Date 2024/1/22 14:07
* @Version 1.0
*/
public interface IMqttClient {
/**
* Description: EMQX 建立连接
* @Author: xmh
* @Create: 2024/7/13-14:49
* @Version: v1.0
**/
boolean connect();
/**
* Description: EMQX 关闭连接
* @Author: xmh
* @Create: 2024/7/13-14:50
* @Version: v1.0
**/
boolean disconnect();
/**
* Description: EMQX-CLIENT 是否在线
* @Author: xmh
* @Create: 2024/7/13-14:49
* @Version: v1.0
**/
boolean isConnect();
/**
* Description: EMQX-CLIENT 推送消息
* @Author: xmh
* @Create: 2024/7/13-14:49
* @Version: v1.0
**/
IMqttResp publish(IMqttReq request);
/**
* Description: 关闭 EMQX-CLIENT
* @Author: xmh
* @Create: 2024/7/13-14:49
* @Version: v1.0
**/
void mqttStop();
}
EMQX-CLIENT上下文接口实现
EMQX-CLIENT技术框架选择Vert.x.
创建MqttClientContext类并继承Vert.x框架AbstractVerticle和实现接口IMqttClient
/**
* @ClassName MqttClientContext emqx-client 上下文
* @Description
* @Author wzq
* @Date 2024/7/9 17:16
* @Version 1.0
*/
@Slf4j
public class MqttClientContext extends AbstractVerticle implements IMqttClient{
public static final Long RECONNECT_INTERVAL = 3600L;
private static final String allWildcard = "#";
private static final String singleWildcard = "+";
private final static String SMART_UP_TOPIC_FORMAT = "$share/%s/%s";
private final static List<String> topicFormatList = Lists.newArrayList(
SMART_UP_TOPIC_FORMAT);
private final IMqttConfig mqttConfig;
private final List<IMqttListener> IMqttListenerList;
private CountDownLatch countDownLatch;
private MqttClient mqttClient;
private String subscribeGroup;
public MqttClientContext(IMqttConfig mqttConfig, List<IMqttListener> IMqttListenerList, String subscribeGroup) {
this.mqttConfig = mqttConfig;
this.IMqttListenerList = IMqttListenerList;
this.subscribeGroup = subscribeGroup;
}
/**
* Description:在 Vert.x 中,start 方法是 Verticle 类的一个生命周期方法,它在 verticle 被部署并准备好开始执行时被调用。如果你的 verticle 在启动时需要执行一些简单的同步操作,你可以重写这个方法来放置你的启动代码。
* @Author: xmh
**/
@Override
public void start() {
if (Objects.isNull(this.mqttClient)) {
this.mqttClient = MqttClient.create(vertx, buildMqttClientOptions());
}
//接收服务端消息处理handler
mqttClient.publishHandler(pub -> {
Buffer buffer = pub.payload();
String topicName = pub.topicName();
String[] split = topicName.split("/");
String string = buffer.toString(StandardCharsets.UTF_8);
CommonMessage message = new CommonMessage();
HashMap<String, Object> headers = Maps.newHashMap();
headers.put("topic",topicName);
headers.put("qos",pub.qosLevel().value());
message.setHeaders(headers);
message.setMessageContent(string);
IMqttListenerList.forEach(f -> {
String topic = f.getTopic();
if (topicRute(topic, split)){
f.onMessage(message);
}
});
});
mqttClient.closeHandler(unused -> getVertx().setTimer(RECONNECT_INTERVAL, h -> start()));
mqttClient.connect(mqttConfig.getTcpPort(), mqttConfig.getHost(),
s -> {
if (s.succeeded()) {
log.info("IMqttClient connect success.");
subscribe();
countDownLatch.countDown();
} else {
log.error("IMqttClient connect fail: ", s.cause());
if (s.cause() != null) {
vertx.setTimer(RECONNECT_INTERVAL, handler -> this.start());
}
}
});
}
/**
* Description: 在 Vert.x 中,如果你的 verticle 需要在停止时执行一些简单的同步清理任务,你可以重写 stop 方法来放置你的清理代码。这个方法在 Vert.x 准备停止 verticle 时调用,通常用于关闭资源,如数据库连接、网络连接、文件句柄等。
* @Author: xmh
**/
@Override
public void stop() {
mqttClient.closeHandler(null);
mqttClient.disconnect((handler) -> {
if (handler.succeeded()) {
vertx.close();
log.info("IMqttClient disConnect success.");
} else {
log.error("IMqttClient disConnect fail: ", handler.cause());
}
}).exceptionHandler(event -> {
log.error("IMqttClient fail: ", event.getCause());
});
}
/**
* Description: EMQX 建立连接
* @Author: xmh
**/
@Override
public boolean connect() {
log.info("emqx connect ing ......");
countDownLatch = new CountDownLatch(1);
Vertx.vertx().deployVerticle(this);
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return true;
}
/**
* Description: EMQX 关闭连接
* @Author: xmh
**/
@Override
public boolean disconnect() {
if (!isConnect()) {
log.warn("IMqttClient no connect");
return false;
}
vertx.undeploy(deploymentID(), (handler) -> {
if (handler.succeeded()) {
log.info("undeploy success");
} else {
log.warn("undeploy fail, {}. ", handler.cause().getMessage(), handler.cause());
}
});
return true;
}
/**
* Description: EMQX-CLIENT 是否在线
* @Author: xmh
**/
@Override
public boolean isConnect() {
return Optional.ofNullable(mqttClient).map(MqttClient::isConnected).orElse(false);
}
/**
* Description: EMQX-CLIENT 推送消息
* @Author: xmh
**/
@Override
public IMqttResp publish(IMqttReq request) {
MqttResp response = new MqttResp();
Buffer payload = Buffer.buffer(request.getMessageContent());
mqttClient.publish(request.getTopic(), payload, MqttQoS.valueOf(request.getQos()), false, false, s -> {
if (s.succeeded()) {
log.info("===>IMqttClient publish success[{}]", s.result());
} else {
log.error("===>IMqttClient publish fail.", s.cause());
}
});
response.setCode(200);
return response;
}
/**
* Description: 关闭 EMQX-CLIENT
* @Author: xmh
**/
@Override
public void mqttStop() {
this.stop();
}
private MqttClientOptions buildMqttClientOptions() {
MqttClientOptions options = new MqttClientOptions();
options.setClientId("micro-client-" + RandomStringUtils.randomAlphanumeric(17));
options.setMaxMessageSize(100_000_000);
options.setKeepAliveInterval(60);
options.setPassword(mqttConfig.getPassword());
options.setUsername(mqttConfig.getUsername());
options.setSsl(mqttConfig.getSsl());
options.setReconnectInterval(RECONNECT_INTERVAL);
options.setReconnectAttempts(Integer.MAX_VALUE);
return options;
}
private boolean topicRute(String topic, String[] split) {
String[] listenerTopic = topic.split("/");
boolean flag = true;
for (int i = 0; i < split.length; i++) {
if (allWildcard.equals(listenerTopic[i])) {
break;
}
if (singleWildcard.equals(listenerTopic[i])) {
continue;
}
if (!split[i].equals(listenerTopic[i])) {
flag = false;
break;
}
}
return flag;
}
private void subscribe() {
List<String> subscribeTopicSuffix = mqttConfig.getSysSubscribeTopics();
if (subscribeTopicSuffix == null || subscribeTopicSuffix.isEmpty()) {
throw new IllegalArgumentException("subscribe topic is empty");
}
List<String> subscribeTopic = subscribeTopicSuffix.stream()
.flatMap(topicSuffix -> topicFormatList.stream().map(t -> String.format(t,subscribeGroup, topicSuffix)))
.toList();
subscribeTopic.forEach(topic -> mqttClient.subscribe(topic, 0, s -> {
if (s.succeeded()) {
log.info("===>IMqttClient subscribe success. result[{}] topic[{}]", s.result(), topic);
} else {
log.error("===>IMqttClient subscribe fail. topic[{}] ", topic, s.cause());
}
}));
}
}
EMQX配置信息
接口定义
public interface IMqttConfig {
String getHost();
Integer getTcpPort();
Integer getHttpPort();
String getUsername();
String getPassword();
Boolean getSsl();
//订阅的系统topic
List<String> getSysSubscribeTopics();
String getAppId();
String getAppSecret();
}
Config接口实现
/**
* @ClassName MqttConfig
* @Description
* @Author wzq
* @Date 2024/7/9 9:36
* @Version 1.0
*/
@Data
public class MqttConfig implements IMqttConfig{
/**
* EMQX 地址
*/
private String host;
/**
* EMQX tcp端口
*/
private Integer tcpPort;
/**
* EMQX Http端口
*/
private Integer httpPort;
/**
* EMQX 登录名称
*/
private String username;
/**
* EMQX 登录名称
*/
private String password;
private String appId;
private String appSecret;
private Boolean ssl = false;
//订阅的系统topic
private List<String> sysSubscribeTopics;
}
EMQX-CLIENT消息推送
接口定义
/**
* @InterfaceName IMqttClientApi
* @Description
* @Author wzq
* @Date 2024/7/8 17:04
* @Version 1.0
*/
public interface IMqttClientApi {
/**
* @Description Http 方式推送消息
*/
IMqttResp httpPublish(IMqttReq request);
/**
* @Description TCP 方式推送消息
*/
IMqttResp tcpPublish(IMqttReq request);
}
model定义
public interface IMqttReq {
String getTopic();
Integer getQos();
String getMessageContent();
}
@Data
@Accessors(chain = true)
public class MqttReq implements IMqttReq{
private String topic;
private Integer qos;
private String messageContent;
}
public interface IMqttResp {
Integer getCode();
String getMessage();
Map getData();
}
@Data
@Accessors(chain = true)
public class MqttResp implements IMqttResp{
private Integer code;
private String message;
private Map data;
}
api接口实现
/**
* @ClassName MqttApiImpl
* @Description
* @Author wzq
* @Date 2024/7/9 19:23
* @Version 1.0
*/
@Slf4j
public class MqttApiImpl implements IMqttClientApi{
private final IMqttConfig mqttConfig;
private final IMqttClient mqttClient;
public MqttApiImpl(IMqttConfig mqttConfig, IMqttClient mqttClient) {
this.mqttConfig = mqttConfig;
this.mqttClient = mqttClient;
}
private static final MediaType HTTP_MEDIA_TYPE_JSON_UTF8 = MediaType.parse("application/json; charset=utf-8");
@Override
public IMqttResp httpPublish(IMqttReq request) {
return BeanUtil.toBeanIgnoreError(this.callHttp(request), MqttResp.class);
}
@Override
public IMqttResp tcpPublish(IMqttReq request) {
return mqttClient.publish(request);
}
private Map<String, ?> callHttp(IMqttReq params) {
String path = "";
String url = mqttConfig.getHost() + path;
log.debug("http url[{}] requestBodyStr[{}]", url, params.getMessageContent());
Dict dict = Dict.create();
dict.set("topic", params.getTopic()); //订阅主题
dict.set("payload", params.getMessageContent()); //内容
dict.set("qos", 0); //质量
dict.set("retain",false); //是否保存
String requestBodyStr = JSON.toJSONString(dict);
RequestBody requestBody = RequestBody.create(HTTP_MEDIA_TYPE_JSON_UTF8, requestBodyStr);
Request request = new Request.Builder()
.url(url)
.post(requestBody)
.header("Content-Type", "application/json")
.header("Authorization", Credentials.basic(mqttConfig.getAppId(), mqttConfig.getAppSecret()))
.build();
try (Response response = getHttpClientInstance().newCall(request).execute()) {
log.debug("Call http success. url[{}] response[{}]", url, response);
if (response.code() == 404) {
return ImmutableMap.of("code", 404, "Message", "404 Not Found");
} else if (!response.isSuccessful()) {
return ImmutableMap.of("code", response.code(), "Message", "Server Error");
}
// 输出响应内容
assert response.body() != null;
String string = response.body().string();
return JSON.parseObject(string);
} catch (IOException e) {
log.warn("Call http failed, {}. url[{}] requestBodyStr[{}]", e.getMessage(), url, requestBodyStr);
}
return Collections.emptyMap();
}
private OkHttpClient getHttpClientInstance() {
return HttpClientHolder.HTTP_CLIENT_INSTANCE;
}
private static class HttpClientHolder {
private static final OkHttpClient HTTP_CLIENT_INSTANCE = new OkHttpClient.Builder()
.connectTimeout(Duration.ofSeconds(3))
.readTimeout(Duration.ofSeconds(3))
.writeTimeout(Duration.ofSeconds(3))
.dispatcher(new Dispatcher(Executors.newFixedThreadPool(8)))
.build();
}
}
EMQX-CLIENT消息监听接口
接口定义
public interface IMqttListener {
/**
* Description: 路由topic
**/
String getTopic();
/**
* Description: 消费类型 广播、集群模式
**/
String getType();
/**
* Description: 消息处理
**/
void onMessage(IMessage message);
}
model定义
public interface IMessage {
String getDeviceId();
String getProductId();
Map<String, Object> getHeaders();
String getMessageContent();
}
@Data
@Accessors(chain = true)
public class CommonMessage implements IMessage{
private String deviceId;
private String productId;
private String messageContent;
protected volatile Map<String, Object> headers = Maps.newConcurrentMap();
}
emqx-client-starter模块实现
emqx-client-starter 是一个用于将emqx-client集成到 Spring Boot 应用中的自动配置启动器(starter)。在 Spring Boot 中,一个 starter 是一个包含特定库依赖和自动配置类的 POM 文件,它简化了应用程序的依赖管理和配置。
自动加载类EmqxClientAutoConfiguration
- 定义自动加载类条件
- 自动加载emqx-client模块的配置类MqttConfig
- 在没有IMqttListener类型的类加载则默认实现IMqttListener接口
- 自动加载emqx-client模块的配置类MqttClientContext
- 自动加载emqx-client模块的配置类IMqttClientApi
@Slf4j
@Configuration
@ConditionalOnProperty(value = "emqx.sdk.enable", havingValue = "true")
public class EmqxClientAutoConfiguration {
@Bean
@ConfigurationProperties(prefix = "emqx.sdk")
public IMqttConfig mqttConfig() {
return new MqttConfig();
}
@Bean
@ConditionalOnMissingBean
public IMqttListener mqttListener() {
return new IMqttListener() {
@Override
public String getTopic() {
return "#";
}
@Override
public String getType(){
return "";
}
@Override
public void onMessage(IMessage message) {
LoggerFactory.getLogger(IMqttListener.class)
.info("[默认消息监听器]接收到消息. Message[{}]", JSON.toJSONString(message));
}
};
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean({IMqttConfig.class, IMqttListener.class})
public IMqttClient mqttClient(MqttConfig emqXConfig, List<IMqttListener> IMqttListenerList, Environment environment) {
String serviceName = environment.getProperty("spring.application.name");
String env = environment.getProperty("spring.profiles.active");
IMqttClient mqttClient = new MqttClientContext(emqXConfig, IMqttListenerList,serviceName+"-"+env);
Executors.newSingleThreadExecutor().execute(mqttClient::connect);
Runtime.getRuntime().addShutdownHook(new Thread(mqttClient::mqttStop));
return mqttClient;
}
@Bean("mqttExecutorService")
@ConditionalOnBean({IMqttClient.class, IMqttListener.class})
public ExecutorService mqttExecutorService() {
ExecutorService executorService = new ThreadPoolExecutor(
1, 8,
1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(1024),
new ThreadFactoryBuilder().setNameFormat("mqttlogin-task-%d").build(),
(r, executor) -> log.warn("MQTT task executor rejectedExecution , Runnable Class : {}",
r.getClass().getSimpleName()));
return executorService;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean({IMqttConfig.class, IMqttClient.class})
public IMqttClientApi mqttApi(IMqttClient client,IMqttConfig config) {
return new MqttApiImpl(config,client);
}
}
自定义META-INF
Sping Boot提供了一种方便的方式来将自定义的配置文件放置在 META.NF目录下,并将其添加到classpath中。
在SpringBoot2.7.x版本之后,慢慢不支持META-INF/spring.factories文件了,需要导入的自动配置类可以放在/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中
com.gitee.xmhzzz.emqx.strater.EmqxClientAutoConfiguration
emqx-example-service模块代码实现
emqx-example-service模块是Spring Boot Web 项目,主要实现 EMQX-CLIENT生命周期管理及消息的发送和消费。
添加web项目启动类
@SpringBootApplication
public class EmqxExampleApplication {
private static final Logger logger = LoggerFactory.getLogger(EmqxExampleApplication.class);
public static void main(String[] args) {
SpringApplication.run(EmqxExampleApplication.class, args);
logger.info("==================== emqx-example 启动完成 ====================");
}
}
业务代码
controller层消息推送
@RequestMapping("/web/emqx")
@RestController
public class EmqxController {
@Autowired
private EmqxPublishService emqxPublishService;
@PostMapping("/tcp/publish")
public Result<Void> tcpPublish(@RequestBody MqttReq req){
emqxPublishService.tcpPublish(req);
return Result.ok();
}
@PostMapping("/http/publish")
public Result<Void> httpPublish(@RequestBody MqttReq req){
emqxPublishService.httpPublish(req);
return Result.ok();
}
}
service层消息推送
@Service
public class EmqxPublishService {
@Autowired(required = false)
private IMqttClientApi mqttClientApi;
public IMqttResp httpPublish(IMqttReq request){
IMqttResp iMqttResp = mqttClientApi.httpPublish(request);
return iMqttResp;
}
public IMqttResp tcpPublish(IMqttReq request){
IMqttResp iMqttResp = mqttClientApi.tcpPublish(request);
return iMqttResp;
}
}
HTTP公共返回类
@Data
public class Result<T> {
/**
* 状态码
*/
private Integer code;
/**
* 状态描述
*/
private String message;
/**
* 数据
*/
private T body;
public Result() {
this(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMsg());
}
public Result(Integer code, String message) {
this(code, message, null);
}
public Result(Integer code, String message, T body) {
this.code = code;
this.message = message;
this.body = body;
}
/**
* 返回成功消息
*/
public static <T> Result<T> ok() {
return ok(null);
}
/**
* 返回成功消息
*
* @param body 数据
* @return 成功消息
*/
public static <T> Result<T> ok(T body) {
return ok(ResultCode.SUCCESS.getMsg(), body);
}
/**
* 返回成功消息
*
* @param message 状态描述
* @param body 数据
* @return 成功消息
*/
public static <T> Result<T> ok(String message, T body) {
return new Result<>(ResultCode.SUCCESS.getCode(), message, body);
}
/**
* 返回失败消息
*
* @param message 状态描述
* @return 失败消息
*/
public static <T> Result<T> fail(String message) {
return fail(ResultCode.FAIL.getCode(), message, null);
}
/**
* 返回失败消息
*
* @param message 状态描述
* @param body 数据
* @return 失败消息
*/
public static <T> Result<T> fail(String message, T body) {
return fail(ResultCode.FAIL.getCode(), message, body);
}
/**
* 返回失败消息
*
* @param code 状态码
* @param message 状态描述
* @return 失败消息
*/
public static <T> Result<T> fail(Integer code, String message) {
return fail(code, message, null);
}
/**
* 返回失败消息
*
* @param resultCode 错误码枚举
* @return
*/
public static <T> Result<T> fail(ResultCode resultCode) {
return fail(resultCode.getCode(), resultCode.getMsg());
}
/**
* 返回失败消息
*
* @param resultCode 错误码枚举
* @param body 数据
* @return
*/
public static <T> Result<T> fail(ResultCode resultCode, T body) {
return fail(resultCode.getCode(), resultCode.getMsg(), body);
}
/**
* 返回失败消息
*
* @param code 状态码
* @param message 状态描述
* @param body 数据
* @return
*/
public static <T> Result<T> fail(Integer code, String message, T body) {
return new Result<>(code, message, body);
}
/**
* 是否成功
*
* @return 结果
*/
public boolean isSuccess() {
return Objects.equals(this.code, ResultCode.SUCCESS.getCode());
}
}
@Getter
@AllArgsConstructor
public enum ResultCode
{
/**
*
*/
SUCCESS(200, "成功"),
FAIL(101, "失败"),
SYSTEM_BUSY(1000, "系统繁忙"),
UNKNOWN(9999999, "未知错误");
/**
* 状态码
*/
private final Integer code;
/**
* 状态描述
*/
private final String msg;
public static ResultCode of(Integer code) {
return Arrays.stream(ResultCode.values()).filter(values -> values.code.equals(code)).findFirst().orElse(UNKNOWN);
}
public static ResultCode ofNull(Integer code) {
return Arrays.stream(ResultCode.values()).filter(values -> values.code.equals(code)).findFirst().orElse(null);
}
}
配置信息
application.yml
spring:
application:
name: emqx-example-service
server:
port: 8080
servlet:
context-path: /emqx-example-service
application-dev.yml
emqx:
sdk:
enable: true
host: 127.0.0.1
tcpPort: 1883
httpPort: 18083
username: emqx-client
password: a123456
appId: 0c94e0423b409483
appSecret: e379f64e96d383ced430804512c3e7f1
ssl: false
sysSubscribeTopics:
- sys/+/+/thing/event/property/post
其它
源码地址Gitee
关注公众号【 java程序猿技术】获取EMQX实践系列文章