Commit 890d304340f1d20c52cf5288d47956eaed5814ee
1 parent
deab8c1c
【功能完善】IoT: 更新 Vert.x 版本至 4.5.1,新增 EMQX 插件及其相关配置,重构 MQTT 插件以支持 Vert.x MQTT 服务器,优化插件启动和停止逻辑,更新插件描述信息。
Showing
10 changed files
with
508 additions
and
41 deletions
yudao-dependencies/pom.xml
| ... | ... | @@ -626,6 +626,12 @@ |
| 626 | 626 | <artifactId>vertx-web</artifactId> |
| 627 | 627 | <version>${vertx.version}</version> |
| 628 | 628 | </dependency> |
| 629 | + <!-- Vert.x MQTT 模块 --> | |
| 630 | + <dependency> | |
| 631 | + <groupId>io.vertx</groupId> | |
| 632 | + <artifactId>vertx-mqtt</artifactId> | |
| 633 | + <version>${vertx.version}</version> | |
| 634 | + </dependency> | |
| 629 | 635 | |
| 630 | 636 | </dependencies> |
| 631 | 637 | </dependencyManagement> | ... | ... |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-emqx-plugin/plugin.properties
0 → 100644
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-emqx-plugin/pom.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
| 3 | + xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation=" | |
| 4 | + http://maven.apache.org/POM/4.0.0 | |
| 5 | + http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |
| 6 | + <parent> | |
| 7 | + <artifactId>yudao-module-iot-plugin</artifactId> | |
| 8 | + <groupId>cn.iocoder.boot</groupId> | |
| 9 | + <version>${revision}</version> | |
| 10 | + </parent> | |
| 11 | + <modelVersion>4.0.0</modelVersion> | |
| 12 | + <packaging>jar</packaging> | |
| 13 | + | |
| 14 | + <artifactId>yudao-module-iot-emqx-plugin</artifactId> | |
| 15 | + | |
| 16 | + <name>${project.artifactId}</name> | |
| 17 | + <description> | |
| 18 | + 物联网 插件模块 - emqx 插件 | |
| 19 | + </description> | |
| 20 | + | |
| 21 | + <properties> | |
| 22 | + <!-- 插件相关 --> | |
| 23 | + <plugin.id>emqx-plugin</plugin.id> | |
| 24 | + <plugin.class>cn.iocoder.yudao.module.iot.plugin.EmqxPlugin</plugin.class> | |
| 25 | + <plugin.version>0.0.1</plugin.version> | |
| 26 | + <plugin.provider>ahh</plugin.provider> | |
| 27 | + <plugin.description>emqx-plugin-0.0.1</plugin.description> | |
| 28 | + <plugin.dependencies/> | |
| 29 | + </properties> | |
| 30 | + | |
| 31 | + <build> | |
| 32 | + <plugins> | |
| 33 | + <!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section) | |
| 34 | + <plugin> | |
| 35 | + <groupId>org.codehaus.mojo</groupId> | |
| 36 | + <artifactId>properties-maven-plugin</artifactId> | |
| 37 | + <version>1.0-alpha-2</version> | |
| 38 | + <executions> | |
| 39 | + <execution> | |
| 40 | + <phase>initialize</phase> | |
| 41 | + <goals> | |
| 42 | + <goal>read-project-properties</goal> | |
| 43 | + </goals> | |
| 44 | + <configuration> | |
| 45 | + <files> | |
| 46 | + <file>plugin.properties</file> | |
| 47 | + </files> | |
| 48 | + </configuration> | |
| 49 | + </execution> | |
| 50 | + </executions> | |
| 51 | + </plugin> | |
| 52 | + --> | |
| 53 | + | |
| 54 | + <plugin> | |
| 55 | + <groupId>org.apache.maven.plugins</groupId> | |
| 56 | + <artifactId>maven-antrun-plugin</artifactId> | |
| 57 | + <version>1.6</version> | |
| 58 | + <executions> | |
| 59 | + <execution> | |
| 60 | + <id>unzip jar file</id> | |
| 61 | + <phase>package</phase> | |
| 62 | + <configuration> | |
| 63 | + <target> | |
| 64 | + <unzip src="target/${project.artifactId}-${project.version}.${project.packaging}" | |
| 65 | + dest="target/plugin-classes"/> | |
| 66 | + </target> | |
| 67 | + </configuration> | |
| 68 | + <goals> | |
| 69 | + <goal>run</goal> | |
| 70 | + </goals> | |
| 71 | + </execution> | |
| 72 | + </executions> | |
| 73 | + </plugin> | |
| 74 | + | |
| 75 | + <plugin> | |
| 76 | + <artifactId>maven-assembly-plugin</artifactId> | |
| 77 | + <version>2.3</version> | |
| 78 | + <configuration> | |
| 79 | + <descriptors> | |
| 80 | + <descriptor> | |
| 81 | + src/main/assembly/assembly.xml | |
| 82 | + </descriptor> | |
| 83 | + </descriptors> | |
| 84 | + <appendAssemblyId>false</appendAssemblyId> | |
| 85 | + </configuration> | |
| 86 | + <executions> | |
| 87 | + <execution> | |
| 88 | + <id>make-assembly</id> | |
| 89 | + <phase>package</phase> | |
| 90 | + <goals> | |
| 91 | + <goal>attached</goal> | |
| 92 | + </goals> | |
| 93 | + </execution> | |
| 94 | + </executions> | |
| 95 | + </plugin> | |
| 96 | + | |
| 97 | + <plugin> | |
| 98 | + <groupId>org.apache.maven.plugins</groupId> | |
| 99 | + <artifactId>maven-jar-plugin</artifactId> | |
| 100 | + <version>2.4</version> | |
| 101 | + <configuration> | |
| 102 | + <archive> | |
| 103 | + <manifestEntries> | |
| 104 | + <Plugin-Id>${plugin.id}</Plugin-Id> | |
| 105 | + <Plugin-Class>${plugin.class}</Plugin-Class> | |
| 106 | + <Plugin-Version>${plugin.version}</Plugin-Version> | |
| 107 | + <Plugin-Provider>${plugin.provider}</Plugin-Provider> | |
| 108 | + <Plugin-Description>${plugin.description}</Plugin-Description> | |
| 109 | + <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies> | |
| 110 | + </manifestEntries> | |
| 111 | + </archive> | |
| 112 | + </configuration> | |
| 113 | + </plugin> | |
| 114 | + | |
| 115 | + <plugin> | |
| 116 | + <artifactId>maven-deploy-plugin</artifactId> | |
| 117 | + <configuration> | |
| 118 | + <skip>true</skip> | |
| 119 | + </configuration> | |
| 120 | + </plugin> | |
| 121 | + </plugins> | |
| 122 | + </build> | |
| 123 | + | |
| 124 | + <dependencies> | |
| 125 | + <!-- 其他依赖项 --> | |
| 126 | + <dependency> | |
| 127 | + <groupId>org.springframework.boot</groupId> | |
| 128 | + <artifactId>spring-boot-starter-web</artifactId> | |
| 129 | + </dependency> | |
| 130 | + <!-- PF4J Spring 集成 --> | |
| 131 | + <dependency> | |
| 132 | + <groupId>org.pf4j</groupId> | |
| 133 | + <artifactId>pf4j-spring</artifactId> | |
| 134 | + <scope>provided</scope> | |
| 135 | + </dependency> | |
| 136 | + <!-- 项目依赖 --> | |
| 137 | + <dependency> | |
| 138 | + <groupId>cn.iocoder.boot</groupId> | |
| 139 | + <artifactId>yudao-module-iot-api</artifactId> | |
| 140 | + <version>${revision}</version> | |
| 141 | + </dependency> | |
| 142 | + <dependency> | |
| 143 | + <groupId>org.projectlombok</groupId> | |
| 144 | + <artifactId>lombok</artifactId> | |
| 145 | + <version>${lombok.version}</version> | |
| 146 | + <scope>provided</scope> | |
| 147 | + </dependency> | |
| 148 | + <!-- Vert.x 核心依赖 --> | |
| 149 | + <dependency> | |
| 150 | + <groupId>io.vertx</groupId> | |
| 151 | + <artifactId>vertx-core</artifactId> | |
| 152 | + </dependency> | |
| 153 | + <!-- Vert.x Web 模块 --> | |
| 154 | + <dependency> | |
| 155 | + <groupId>io.vertx</groupId> | |
| 156 | + <artifactId>vertx-web</artifactId> | |
| 157 | + </dependency> | |
| 158 | + <!-- MQTT --> | |
| 159 | + <dependency> | |
| 160 | + <groupId>org.eclipse.paho</groupId> | |
| 161 | + <artifactId>org.eclipse.paho.client.mqttv3</artifactId> | |
| 162 | + </dependency> | |
| 163 | + </dependencies> | |
| 164 | +</project> | |
| 0 | 165 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-emqx-plugin/src/main/assembly/assembly.xml
0 → 100644
| 1 | +<assembly> | |
| 2 | + <id>plugin</id> | |
| 3 | + <formats> | |
| 4 | + <format>zip</format> | |
| 5 | + </formats> | |
| 6 | + <includeBaseDirectory>false</includeBaseDirectory> | |
| 7 | + <dependencySets> | |
| 8 | + <dependencySet> | |
| 9 | + <useProjectArtifact>false</useProjectArtifact> | |
| 10 | + <scope>runtime</scope> | |
| 11 | + <outputDirectory>lib</outputDirectory> | |
| 12 | + <includes> | |
| 13 | + <include>*:jar:*</include> | |
| 14 | + </includes> | |
| 15 | + </dependencySet> | |
| 16 | + </dependencySets> | |
| 17 | + <!-- | |
| 18 | + <fileSets> | |
| 19 | + <fileSet> | |
| 20 | + <directory>target/classes</directory> | |
| 21 | + <outputDirectory>classes</outputDirectory> | |
| 22 | + </fileSet> | |
| 23 | + </fileSets> | |
| 24 | + --> | |
| 25 | + <fileSets> | |
| 26 | + <fileSet> | |
| 27 | + <directory>target/plugin-classes</directory> | |
| 28 | + <outputDirectory>classes</outputDirectory> | |
| 29 | + </fileSet> | |
| 30 | + </fileSets> | |
| 31 | +</assembly> | ... | ... |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-emqx-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/EmqxPlugin.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.plugin; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.module.iot.api.ServiceRegistry; | |
| 4 | +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; | |
| 5 | +import lombok.extern.slf4j.Slf4j; | |
| 6 | +import org.pf4j.Plugin; | |
| 7 | +import org.pf4j.PluginWrapper; | |
| 8 | + | |
| 9 | +import javax.annotation.Resource; | |
| 10 | +import java.util.concurrent.ExecutorService; | |
| 11 | +import java.util.concurrent.Executors; | |
| 12 | + | |
| 13 | +@Slf4j | |
| 14 | +public class EmqxPlugin extends Plugin { | |
| 15 | + | |
| 16 | + private ExecutorService executorService; | |
| 17 | + @Resource | |
| 18 | + private DeviceDataApi deviceDataApi; | |
| 19 | + | |
| 20 | + public EmqxPlugin(PluginWrapper wrapper) { | |
| 21 | + super(wrapper); | |
| 22 | + this.executorService = Executors.newSingleThreadExecutor(); | |
| 23 | + } | |
| 24 | + | |
| 25 | + @Override | |
| 26 | + public void start() { | |
| 27 | + log.info("EmqxPlugin.start()"); | |
| 28 | + | |
| 29 | + if (executorService.isShutdown() || executorService.isTerminated()) { | |
| 30 | + executorService = Executors.newSingleThreadExecutor(); | |
| 31 | + } | |
| 32 | + | |
| 33 | + deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class); | |
| 34 | + if (deviceDataApi == null) { | |
| 35 | + log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!"); | |
| 36 | + return; | |
| 37 | + } | |
| 38 | + | |
| 39 | + } | |
| 40 | + | |
| 41 | + @Override | |
| 42 | + public void stop() { | |
| 43 | + log.info("EmqxPlugin.stop()"); | |
| 44 | + } | |
| 45 | +} | |
| 0 | 46 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml
| ... | ... | @@ -147,20 +147,11 @@ |
| 147 | 147 | <version>${lombok.version}</version> |
| 148 | 148 | <scope>provided</scope> |
| 149 | 149 | </dependency> |
| 150 | - <!-- Vert.x 核心依赖 --> | |
| 151 | - <dependency> | |
| 152 | - <groupId>io.vertx</groupId> | |
| 153 | - <artifactId>vertx-core</artifactId> | |
| 154 | - </dependency> | |
| 155 | - <!-- Vert.x Web 模块 --> | |
| 150 | + <!-- Vert.x Web --> | |
| 156 | 151 | <dependency> |
| 157 | 152 | <groupId>io.vertx</groupId> |
| 158 | 153 | <artifactId>vertx-web</artifactId> |
| 159 | - </dependency> | |
| 160 | - <!-- MQTT --> | |
| 161 | - <dependency> | |
| 162 | - <groupId>org.eclipse.paho</groupId> | |
| 163 | - <artifactId>org.eclipse.paho.client.mqttv3</artifactId> | |
| 154 | + <version>4.5.11</version> | |
| 164 | 155 | </dependency> |
| 165 | 156 | </dependencies> |
| 166 | 157 | </project> |
| 167 | 158 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/plugin.properties
| 1 | 1 | plugin.id=mqtt-plugin |
| 2 | +plugin.description=Vert.x MQTT plugin | |
| 2 | 3 | plugin.class=cn.iocoder.yudao.module.iot.plugin.MqttPlugin |
| 3 | -plugin.version=0.0.1 | |
| 4 | +plugin.version=1.0.0 | |
| 5 | +plugin.requires= | |
| 4 | 6 | plugin.provider=ahh |
| 5 | -plugin.dependencies= | |
| 6 | -plugin.description=mqtt-plugin-0.0.1 | |
| 7 | +plugin.license=Apache-2.0 | ... | ... |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/pom.xml
| ... | ... | @@ -145,10 +145,11 @@ |
| 145 | 145 | <version>${lombok.version}</version> |
| 146 | 146 | <scope>provided</scope> |
| 147 | 147 | </dependency> |
| 148 | - <!-- MQTT --> | |
| 148 | + <!-- Vert.x MQTT --> | |
| 149 | 149 | <dependency> |
| 150 | - <groupId>org.eclipse.paho</groupId> | |
| 151 | - <artifactId>org.eclipse.paho.client.mqttv3</artifactId> | |
| 150 | + <groupId>io.vertx</groupId> | |
| 151 | + <artifactId>vertx-mqtt</artifactId> | |
| 152 | + <version>4.5.11</version> | |
| 152 | 153 | </dependency> |
| 153 | 154 | </dependencies> |
| 154 | 155 | </project> |
| 155 | 156 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/MqttPlugin.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.plugin; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.iot.api.ServiceRegistry; | |
| 4 | -import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; | |
| 5 | 3 | import lombok.extern.slf4j.Slf4j; |
| 6 | 4 | import org.pf4j.Plugin; |
| 7 | 5 | import org.pf4j.PluginWrapper; |
| 8 | 6 | |
| 9 | -import javax.annotation.Resource; | |
| 10 | -import java.util.concurrent.ExecutorService; | |
| 11 | -import java.util.concurrent.Executors; | |
| 12 | - | |
| 13 | 7 | @Slf4j |
| 14 | 8 | public class MqttPlugin extends Plugin { |
| 15 | 9 | |
| 16 | - private ExecutorService executorService; | |
| 17 | - @Resource | |
| 18 | - private DeviceDataApi deviceDataApi; | |
| 10 | + private MqttServerExtension mqttServerExtension; | |
| 19 | 11 | |
| 20 | 12 | public MqttPlugin(PluginWrapper wrapper) { |
| 21 | 13 | super(wrapper); |
| 22 | - this.executorService = Executors.newSingleThreadExecutor(); | |
| 23 | 14 | } |
| 24 | 15 | |
| 25 | 16 | @Override |
| 26 | 17 | public void start() { |
| 27 | - log.info("MqttPlugin.start()"); | |
| 28 | - | |
| 29 | - if (executorService.isShutdown() || executorService.isTerminated()) { | |
| 30 | - executorService = Executors.newSingleThreadExecutor(); | |
| 31 | - } | |
| 32 | - | |
| 33 | - deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class); | |
| 34 | - if (deviceDataApi == null) { | |
| 35 | - log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!"); | |
| 36 | - return; | |
| 37 | - } | |
| 38 | - | |
| 18 | + log.info("MQTT Plugin started."); | |
| 19 | + mqttServerExtension = new MqttServerExtension(); | |
| 20 | + mqttServerExtension.startMqttServer(); | |
| 39 | 21 | } |
| 40 | 22 | |
| 41 | 23 | @Override |
| 42 | 24 | public void stop() { |
| 43 | - log.info("MqttPlugin.stop()"); | |
| 25 | + log.info("MQTT Plugin stopped."); | |
| 26 | + if (mqttServerExtension != null) { | |
| 27 | + mqttServerExtension.stopMqttServer().onComplete(ar -> { | |
| 28 | + if (ar.succeeded()) { | |
| 29 | + log.info("Stopped MQTT Server successfully"); | |
| 30 | + } else { | |
| 31 | + log.error("Failed to stop MQTT Server: {}", ar.cause().getMessage()); | |
| 32 | + } | |
| 33 | + }); | |
| 34 | + } | |
| 44 | 35 | } |
| 45 | -} | |
| 46 | 36 | \ No newline at end of file |
| 37 | +} | ... | ... |
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-mqtt-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/MqttServerExtension.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.iot.plugin; | |
| 2 | + | |
| 3 | +import io.netty.handler.codec.mqtt.MqttProperties; | |
| 4 | +import io.netty.handler.codec.mqtt.MqttQoS; | |
| 5 | +import io.vertx.core.Future; | |
| 6 | +import io.vertx.core.Vertx; | |
| 7 | +import io.vertx.core.buffer.Buffer; | |
| 8 | +import io.vertx.mqtt.MqttEndpoint; | |
| 9 | +import io.vertx.mqtt.MqttServer; | |
| 10 | +import io.vertx.mqtt.MqttServerOptions; | |
| 11 | +import io.vertx.mqtt.MqttTopicSubscription; | |
| 12 | +import io.vertx.mqtt.messages.MqttDisconnectMessage; | |
| 13 | +import io.vertx.mqtt.messages.MqttPublishMessage; | |
| 14 | +import io.vertx.mqtt.messages.MqttSubscribeMessage; | |
| 15 | +import io.vertx.mqtt.messages.MqttUnsubscribeMessage; | |
| 16 | +import io.vertx.mqtt.messages.codes.MqttSubAckReasonCode; | |
| 17 | +import lombok.extern.slf4j.Slf4j; | |
| 18 | +import org.pf4j.Extension; | |
| 19 | + | |
| 20 | +import java.nio.charset.Charset; | |
| 21 | +import java.util.ArrayList; | |
| 22 | +import java.util.List; | |
| 23 | + | |
| 24 | +/** | |
| 25 | + * 根据官方示例,整合常见 MQTT 功能到 PF4J 的 Extension 类中 | |
| 26 | + */ | |
| 27 | +@Slf4j | |
| 28 | +@Extension | |
| 29 | +public class MqttServerExtension { | |
| 30 | + | |
| 31 | + private Vertx vertx; | |
| 32 | + private MqttServer mqttServer; | |
| 33 | + | |
| 34 | + /** | |
| 35 | + * 启动 MQTT 服务端 | |
| 36 | + * 可根据需要决定是否启用 SSL/TLS、WebSocket、多实例部署等 | |
| 37 | + */ | |
| 38 | + public void startMqttServer() { | |
| 39 | + // 初始化 Vert.x | |
| 40 | + vertx = Vertx.vertx(); | |
| 41 | + | |
| 42 | + // ========== 如果需要 SSL/TLS,请参考下面注释,启用注释并替换端口、证书路径等 ========== | |
| 43 | + // MqttServerOptions options = new MqttServerOptions() | |
| 44 | + // .setPort(8883) | |
| 45 | + // .setKeyCertOptions(new PemKeyCertOptions() | |
| 46 | + // .setKeyPath("./src/test/resources/tls/server-key.pem") | |
| 47 | + // .setCertPath("./src/test/resources/tls/server-cert.pem")) | |
| 48 | + // .setSsl(true); | |
| 49 | + | |
| 50 | + // ========== 如果需要 WebSocket,请设置 setUseWebSocket(true) ========== | |
| 51 | + // options.setUseWebSocket(true); | |
| 52 | + | |
| 53 | + // ========== 默认不启用 SSL 的示例 ========== | |
| 54 | + MqttServerOptions options = new MqttServerOptions() | |
| 55 | + .setPort(1883) | |
| 56 | + .setHost("0.0.0.0") | |
| 57 | + .setUseWebSocket(false); // 如果需要 WebSocket,请改为 true | |
| 58 | + | |
| 59 | + mqttServer = MqttServer.create(vertx, options); | |
| 60 | + | |
| 61 | + // 指定 endpointHandler,处理客户端连接等 | |
| 62 | + mqttServer.endpointHandler(endpoint -> { | |
| 63 | + handleClientConnect(endpoint); | |
| 64 | + handleDisconnect(endpoint); | |
| 65 | + handleSubscribe(endpoint); | |
| 66 | + handleUnsubscribe(endpoint); | |
| 67 | + handlePublish(endpoint); | |
| 68 | + handlePing(endpoint); | |
| 69 | + }); | |
| 70 | + | |
| 71 | + // 启动监听 | |
| 72 | + mqttServer.listen(ar -> { | |
| 73 | + if (ar.succeeded()) { | |
| 74 | + log.info("MQTT server is listening on port {}", mqttServer.actualPort()); | |
| 75 | + } else { | |
| 76 | + log.error("Error on starting the server", ar.cause()); | |
| 77 | + } | |
| 78 | + }); | |
| 79 | + } | |
| 80 | + | |
| 81 | + /** | |
| 82 | + * 优雅关闭 MQTT 服务端 | |
| 83 | + */ | |
| 84 | + public Future<Void> stopMqttServer() { | |
| 85 | + if (mqttServer != null) { | |
| 86 | + return mqttServer.close().onComplete(ar -> { | |
| 87 | + if (ar.succeeded()) { | |
| 88 | + log.info("MQTT server closed."); | |
| 89 | + if (vertx != null) { | |
| 90 | + vertx.close(); | |
| 91 | + log.info("Vert.x instance closed."); | |
| 92 | + } | |
| 93 | + } else { | |
| 94 | + log.error("Failed to close MQTT server: {}", ar.cause().getMessage()); | |
| 95 | + } | |
| 96 | + }); | |
| 97 | + } | |
| 98 | + return Future.succeededFuture(); | |
| 99 | + } | |
| 100 | + | |
| 101 | + // ==================== 以下为官方示例中常见事件的处理封装 ==================== | |
| 102 | + | |
| 103 | + /** | |
| 104 | + * 处理客户端连接 (CONNECT) | |
| 105 | + */ | |
| 106 | + private void handleClientConnect(MqttEndpoint endpoint) { | |
| 107 | + // 打印 CONNECT 的主要信息 | |
| 108 | + log.info("MQTT client [{}] request to connect, clean session = {}", | |
| 109 | + endpoint.clientIdentifier(), endpoint.isCleanSession()); | |
| 110 | + | |
| 111 | + if (endpoint.auth() != null) { | |
| 112 | + log.info("[username = {}, password = {}]", endpoint.auth().getUsername(), endpoint.auth().getPassword()); | |
| 113 | + } | |
| 114 | + log.info("[properties = {}]", endpoint.connectProperties()); | |
| 115 | + | |
| 116 | + if (endpoint.will() != null) { | |
| 117 | + log.info("[will topic = {}, msg = {}, QoS = {}, isRetain = {}]", | |
| 118 | + endpoint.will().getWillTopic(), | |
| 119 | + new String(endpoint.will().getWillMessageBytes()), | |
| 120 | + endpoint.will().getWillQos(), | |
| 121 | + endpoint.will().isWillRetain()); | |
| 122 | + } | |
| 123 | + | |
| 124 | + log.info("[keep alive timeout = {}]", endpoint.keepAliveTimeSeconds()); | |
| 125 | + | |
| 126 | + // 接受远程客户端的连接 | |
| 127 | + endpoint.accept(false); | |
| 128 | + } | |
| 129 | + | |
| 130 | + /** | |
| 131 | + * 处理客户端主动断开 (DISCONNECT) | |
| 132 | + */ | |
| 133 | + private void handleDisconnect(MqttEndpoint endpoint) { | |
| 134 | + endpoint.disconnectMessageHandler((MqttDisconnectMessage disconnectMessage) -> { | |
| 135 | + log.info("Received disconnect from client [{}], reason code = {}", | |
| 136 | + endpoint.clientIdentifier(), disconnectMessage.code()); | |
| 137 | + }); | |
| 138 | + } | |
| 139 | + | |
| 140 | + /** | |
| 141 | + * 处理客户端订阅 (SUBSCRIBE) | |
| 142 | + */ | |
| 143 | + private void handleSubscribe(MqttEndpoint endpoint) { | |
| 144 | + endpoint.subscribeHandler((MqttSubscribeMessage subscribe) -> { | |
| 145 | + List<MqttSubAckReasonCode> reasonCodes = new ArrayList<>(); | |
| 146 | + for (MqttTopicSubscription s : subscribe.topicSubscriptions()) { | |
| 147 | + log.info("Subscription for {} with QoS {}", s.topicName(), s.qualityOfService()); | |
| 148 | + // 将客户端请求的 QoS 转换为返回给客户端的 reason code(可能是错误码或实际 granted QoS) | |
| 149 | + reasonCodes.add(MqttSubAckReasonCode.qosGranted(s.qualityOfService())); | |
| 150 | + } | |
| 151 | + // 回复 SUBACK,MQTT 5.0 时可指定 reasonCodes、properties | |
| 152 | + endpoint.subscribeAcknowledge(subscribe.messageId(), reasonCodes, MqttProperties.NO_PROPERTIES); | |
| 153 | + }); | |
| 154 | + } | |
| 155 | + | |
| 156 | + /** | |
| 157 | + * 处理客户端取消订阅 (UNSUBSCRIBE) | |
| 158 | + */ | |
| 159 | + private void handleUnsubscribe(MqttEndpoint endpoint) { | |
| 160 | + endpoint.unsubscribeHandler((MqttUnsubscribeMessage unsubscribe) -> { | |
| 161 | + for (String topic : unsubscribe.topics()) { | |
| 162 | + log.info("Unsubscription for {}", topic); | |
| 163 | + } | |
| 164 | + // 回复 UNSUBACK,MQTT 5.0 时可指定 reasonCodes、properties | |
| 165 | + endpoint.unsubscribeAcknowledge(unsubscribe.messageId()); | |
| 166 | + }); | |
| 167 | + } | |
| 168 | + | |
| 169 | + /** | |
| 170 | + * 处理客户端发布的消息 (PUBLISH) | |
| 171 | + */ | |
| 172 | + private void handlePublish(MqttEndpoint endpoint) { | |
| 173 | + // 接收 PUBLISH 消息 | |
| 174 | + endpoint.publishHandler((MqttPublishMessage message) -> { | |
| 175 | + String payload = message.payload().toString(Charset.defaultCharset()); | |
| 176 | + log.info("Received message [{}] on topic [{}] with QoS [{}]", | |
| 177 | + payload, message.topicName(), message.qosLevel()); | |
| 178 | + | |
| 179 | + // 根据不同 QoS,回复客户端 | |
| 180 | + if (message.qosLevel() == MqttQoS.AT_LEAST_ONCE) { | |
| 181 | + endpoint.publishAcknowledge(message.messageId()); | |
| 182 | + } else if (message.qosLevel() == MqttQoS.EXACTLY_ONCE) { | |
| 183 | + endpoint.publishReceived(message.messageId()); | |
| 184 | + } | |
| 185 | + }); | |
| 186 | + | |
| 187 | + // 如果 QoS = 2,需要处理 PUBREL | |
| 188 | + endpoint.publishReleaseHandler(messageId -> { | |
| 189 | + endpoint.publishComplete(messageId); | |
| 190 | + }); | |
| 191 | + } | |
| 192 | + | |
| 193 | + /** | |
| 194 | + * 处理客户端 PINGREQ | |
| 195 | + */ | |
| 196 | + private void handlePing(MqttEndpoint endpoint) { | |
| 197 | + endpoint.pingHandler(v -> { | |
| 198 | + // 这里仅做日志, PINGRESP 已自动发送 | |
| 199 | + log.info("Ping received from client [{}]", endpoint.clientIdentifier()); | |
| 200 | + }); | |
| 201 | + } | |
| 202 | + | |
| 203 | + // ==================== 如果需要服务端向客户端发布消息,可用以下示例 ==================== | |
| 204 | + | |
| 205 | + /** | |
| 206 | + * 服务端主动向已连接的某个 endpoint 发布消息的示例 | |
| 207 | + * 如果使用 MQTT 5.0,可以传递更多消息属性 | |
| 208 | + */ | |
| 209 | + public void publishToClient(MqttEndpoint endpoint, String topic, String content) { | |
| 210 | + endpoint.publish(topic, | |
| 211 | + Buffer.buffer(content), | |
| 212 | + MqttQoS.AT_LEAST_ONCE, // QoS 自行选择 | |
| 213 | + false, | |
| 214 | + false); | |
| 215 | + | |
| 216 | + // 处理 QoS 1 和 QoS 2 的 ACK | |
| 217 | + endpoint.publishAcknowledgeHandler(messageId -> { | |
| 218 | + log.info("Received PUBACK from client [{}] for messageId = {}", endpoint.clientIdentifier(), messageId); | |
| 219 | + }).publishReceivedHandler(messageId -> { | |
| 220 | + endpoint.publishRelease(messageId); | |
| 221 | + }).publishCompletionHandler(messageId -> { | |
| 222 | + log.info("Received PUBCOMP from client [{}] for messageId = {}", endpoint.clientIdentifier(), messageId); | |
| 223 | + }); | |
| 224 | + } | |
| 225 | + | |
| 226 | + // ==================== 如果需要多实例部署,用于多核扩展,可参考以下思路 ==================== | |
| 227 | + // 例如,在宿主应用或插件中循环启动多个 MqttServerExtension 实例,或使用 Vert.x 的 deployVerticle: | |
| 228 | + // DeploymentOptions options = new DeploymentOptions().setInstances(10); | |
| 229 | + // vertx.deployVerticle(() -> new MyMqttVerticle(), options); | |
| 230 | + | |
| 231 | +} | ... | ... |