Commit 7138cab3c0a172c1b8f64d8f848a3d01e053d59c
1 parent
54a08b97
feat:【IoT 物联网】更新 IotGatewayProperties 配置
Showing
5 changed files
with
160 additions
and
13 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
| ... | ... | @@ -123,16 +123,11 @@ public class IotGatewayProperties { |
| 123 | 123 | */ |
| 124 | 124 | @NotNull(message = "是否开启不能为空") |
| 125 | 125 | private Boolean enabled; |
| 126 | - // TODO @haohao:加个默认值? | |
| 127 | - /** | |
| 128 | - * 服务端口 | |
| 129 | - */ | |
| 130 | - private Integer serverPort; | |
| 131 | - // TODO @haohao:应该不用?一般都监听 0.0.0.0 哈; | |
| 126 | + | |
| 132 | 127 | /** |
| 133 | - * 服务主机 | |
| 128 | + * 服务端口(默认:8093) | |
| 134 | 129 | */ |
| 135 | - private String serverHost; | |
| 130 | + private Integer serverPort = 8093; | |
| 136 | 131 | |
| 137 | 132 | } |
| 138 | 133 | |
| ... | ... | @@ -211,6 +206,94 @@ public class IotGatewayProperties { |
| 211 | 206 | */ |
| 212 | 207 | private Long reconnectDelayMs = 5000L; |
| 213 | 208 | |
| 209 | + /** | |
| 210 | + * 是否启用 Clean Session (清理会话) | |
| 211 | + * true: 每次连接都是新会话,Broker 不保留离线消息和订阅关系。 | |
| 212 | + * 对于网关这类“永远在线”且会主动重新订阅的应用,建议为 true。 | |
| 213 | + */ | |
| 214 | + private Boolean cleanSession = true; | |
| 215 | + | |
| 216 | + /** | |
| 217 | + * 心跳间隔(秒) | |
| 218 | + * 用于保持连接活性,及时发现网络中断。 | |
| 219 | + */ | |
| 220 | + private Integer keepAliveIntervalSeconds = 60; | |
| 221 | + | |
| 222 | + /** | |
| 223 | + * 最大未确认消息队列大小 | |
| 224 | + * 限制已发送但未收到 Broker 确认的 QoS 1/2 消息数量,用于流量控制。 | |
| 225 | + */ | |
| 226 | + private Integer maxInflightQueue = 10000; | |
| 227 | + | |
| 228 | + /** | |
| 229 | + * 是否信任所有 SSL 证书 | |
| 230 | + * 警告:此配置会绕过证书验证,仅建议在开发和测试环境中使用! | |
| 231 | + * 在生产环境中,应设置为 false,并配置正确的信任库。 | |
| 232 | + */ | |
| 233 | + private Boolean trustAll = false; | |
| 234 | + | |
| 235 | + /** | |
| 236 | + * 遗嘱消息配置 (用于网关异常下线时通知其他系统) | |
| 237 | + */ | |
| 238 | + private final Will will = new Will(); | |
| 239 | + | |
| 240 | + /** | |
| 241 | + * 高级 SSL/TLS 配置 (用于生产环境) | |
| 242 | + */ | |
| 243 | + private final Ssl sslOptions = new Ssl(); | |
| 244 | + | |
| 245 | + /** | |
| 246 | + * 遗嘱消息 (Last Will and Testament) | |
| 247 | + */ | |
| 248 | + @Data | |
| 249 | + public static class Will { | |
| 250 | + /** | |
| 251 | + * 是否启用遗嘱消息 | |
| 252 | + */ | |
| 253 | + private boolean enabled = false; | |
| 254 | + /** | |
| 255 | + * 遗嘱消息主题 | |
| 256 | + */ | |
| 257 | + private String topic; | |
| 258 | + /** | |
| 259 | + * 遗嘱消息内容 | |
| 260 | + */ | |
| 261 | + private String payload; | |
| 262 | + /** | |
| 263 | + * 遗嘱消息 QoS 等级 | |
| 264 | + */ | |
| 265 | + private Integer qos = 1; | |
| 266 | + /** | |
| 267 | + * 遗嘱消息是否作为保留消息发布 | |
| 268 | + */ | |
| 269 | + private boolean retain = true; | |
| 270 | + } | |
| 271 | + | |
| 272 | + /** | |
| 273 | + * 高级 SSL/TLS 配置 | |
| 274 | + */ | |
| 275 | + @Data | |
| 276 | + public static class Ssl { | |
| 277 | + /** | |
| 278 | + * 密钥库(KeyStore)路径,例如:classpath:certs/client.jks | |
| 279 | + * 包含客户端自己的证书和私钥,用于向服务端证明身份(双向认证)。 | |
| 280 | + */ | |
| 281 | + private String keyStorePath; | |
| 282 | + /** | |
| 283 | + * 密钥库密码 | |
| 284 | + */ | |
| 285 | + private String keyStorePassword; | |
| 286 | + /** | |
| 287 | + * 信任库(TrustStore)路径,例如:classpath:certs/trust.jks | |
| 288 | + * 包含服务端信任的 CA 证书,用于验证服务端的身份,防止中间人攻击。 | |
| 289 | + */ | |
| 290 | + private String trustStorePath; | |
| 291 | + /** | |
| 292 | + * 信任库密码 | |
| 293 | + */ | |
| 294 | + private String trustStorePassword; | |
| 295 | + } | |
| 296 | + | |
| 214 | 297 | } |
| 215 | 298 | |
| 216 | 299 | } | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxAuthEventProtocol.java
| ... | ... | @@ -71,6 +71,7 @@ public class IotEmqxAuthEventProtocol { |
| 71 | 71 | router.post(IotMqttTopicUtils.MQTT_AUTH_PATH).handler(handler::handleAuth); |
| 72 | 72 | router.post(IotMqttTopicUtils.MQTT_EVENT_PATH).handler(handler::handleEvent); |
| 73 | 73 | // TODO @haohao:/mqtt/acl 需要处理么? |
| 74 | + // TODO @芋艿:已在 EMQX 处理,如果是“设备直连”模式需要处理 | |
| 74 | 75 | |
| 75 | 76 | // 3. 启动 HTTP 服务器 |
| 76 | 77 | try { | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxUpstreamProtocol.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.gateway.protocol.emqx; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.lang.Assert; | |
| 4 | +import cn.hutool.core.util.StrUtil; | |
| 3 | 5 | import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; |
| 4 | 6 | import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; |
| 5 | 7 | import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.router.IotEmqxUpstreamHandler; |
| 6 | 8 | import io.netty.handler.codec.mqtt.MqttQoS; |
| 7 | 9 | import io.vertx.core.Vertx; |
| 8 | 10 | import io.vertx.core.buffer.Buffer; |
| 11 | +import io.vertx.core.net.JksOptions; | |
| 9 | 12 | import io.vertx.mqtt.MqttClient; |
| 10 | 13 | import io.vertx.mqtt.MqttClientOptions; |
| 11 | 14 | import jakarta.annotation.PostConstruct; |
| ... | ... | @@ -127,7 +130,6 @@ public class IotEmqxUpstreamProtocol { |
| 127 | 130 | // 1. 连接 MQTT Broker |
| 128 | 131 | CountDownLatch latch = new CountDownLatch(1); |
| 129 | 132 | AtomicBoolean success = new AtomicBoolean(false); |
| 130 | - // TODO @haohao:要不要加 MqttClientOptions 参数?1)setCleanSession true;2)setMaxInflightQueue 10000;3)setKeepAliveInterval 60;4)setSsl/setTrustAll | |
| 131 | 133 | mqttClient.connect(port, host, connectResult -> { |
| 132 | 134 | if (connectResult.succeeded()) { |
| 133 | 135 | log.info("[connectMqttSync][MQTT 客户端连接成功, host: {}, port: {}]", host, port); |
| ... | ... | @@ -252,11 +254,53 @@ public class IotEmqxUpstreamProtocol { |
| 252 | 254 | * 创建 MQTT 客户端 |
| 253 | 255 | */ |
| 254 | 256 | private void createMqttClient() { |
| 255 | - MqttClientOptions options = new MqttClientOptions() | |
| 257 | + // 1. 创建基础配置 | |
| 258 | + MqttClientOptions options = (MqttClientOptions) new MqttClientOptions() | |
| 256 | 259 | .setClientId(emqxProperties.getMqttClientId()) |
| 257 | 260 | .setUsername(emqxProperties.getMqttUsername()) |
| 258 | 261 | .setPassword(emqxProperties.getMqttPassword()) |
| 259 | - .setSsl(emqxProperties.getMqttSsl()); | |
| 262 | + .setSsl(emqxProperties.getMqttSsl()) | |
| 263 | + .setCleanSession(emqxProperties.getCleanSession()) | |
| 264 | + .setKeepAliveInterval(emqxProperties.getKeepAliveIntervalSeconds()) | |
| 265 | + .setMaxInflightQueue(emqxProperties.getMaxInflightQueue()) | |
| 266 | + .setConnectTimeout(emqxProperties.getConnectTimeoutSeconds() * 1000) // Vert.x 需要毫秒 | |
| 267 | + .setTrustAll(emqxProperties.getTrustAll()); | |
| 268 | + | |
| 269 | + // 2. 配置遗嘱消息 | |
| 270 | + IotGatewayProperties.EmqxProperties.Will will = emqxProperties.getWill(); | |
| 271 | + if (will.isEnabled()) { | |
| 272 | + Assert.notBlank(will.getTopic(), "遗嘱消息主题(will.topic)不能为空"); | |
| 273 | + Assert.notNull(will.getPayload(), "遗嘱消息内容(will.payload)不能为空"); | |
| 274 | + options.setWillFlag(true) | |
| 275 | + .setWillTopic(will.getTopic()) | |
| 276 | + .setWillMessageBytes(Buffer.buffer(will.getPayload())) | |
| 277 | + .setWillQoS(will.getQos()) | |
| 278 | + .setWillRetain(will.isRetain()); | |
| 279 | + } | |
| 280 | + | |
| 281 | + // 3. 配置高级 SSL/TLS (仅在启用 SSL 且不信任所有证书时生效) | |
| 282 | + if (Boolean.TRUE.equals(emqxProperties.getMqttSsl()) && !Boolean.TRUE.equals(emqxProperties.getTrustAll())) { | |
| 283 | + IotGatewayProperties.EmqxProperties.Ssl sslOptions = emqxProperties.getSslOptions(); | |
| 284 | + // 配置信任库 (用于验证服务端证书) | |
| 285 | + if (StrUtil.isNotBlank(sslOptions.getTrustStorePath())) { | |
| 286 | + options.setTrustStoreOptions(new JksOptions() | |
| 287 | + .setPath(sslOptions.getTrustStorePath()) | |
| 288 | + .setPassword(sslOptions.getTrustStorePassword())); | |
| 289 | + } | |
| 290 | + // 配置密钥库 (用于客户端双向认证) | |
| 291 | + if (StrUtil.isNotBlank(sslOptions.getKeyStorePath())) { | |
| 292 | + options.setKeyStoreOptions(new JksOptions() | |
| 293 | + .setPath(sslOptions.getKeyStorePath()) | |
| 294 | + .setPassword(sslOptions.getKeyStorePassword())); | |
| 295 | + } | |
| 296 | + } | |
| 297 | + | |
| 298 | + // 4. 安全警告日志 | |
| 299 | + if (Boolean.TRUE.equals(emqxProperties.getTrustAll())) { | |
| 300 | + log.warn("[createMqttClient][安全警告:当前配置信任所有 SSL 证书(trustAll=true),这在生产环境中存在严重安全风险!]"); | |
| 301 | + } | |
| 302 | + | |
| 303 | + // 5. 创建客户端实例 | |
| 260 | 304 | this.mqttClient = MqttClient.create(vertx, options); |
| 261 | 305 | } |
| 262 | 306 | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpUpstreamProtocol.java
| ... | ... | @@ -54,7 +54,7 @@ public class IotTcpUpstreamProtocol { |
| 54 | 54 | }); |
| 55 | 55 | |
| 56 | 56 | // 3. 启动 TCP 服务器 |
| 57 | - netServer.listen(tcpProperties.getServerPort(), tcpProperties.getServerHost()) | |
| 57 | + netServer.listen(tcpProperties.getServerPort(), "0.0.0.0") | |
| 58 | 58 | .onSuccess(server -> log.info("[start][IoT 网关 TCP 服务启动成功,端口:{}]", server.actualPort())) |
| 59 | 59 | .onFailure(e -> log.error("[start][IoT 网关 TCP 服务启动失败]", e)); |
| 60 | 60 | } | ... | ... |
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml
| ... | ... | @@ -55,13 +55,32 @@ yudao: |
| 55 | 55 | mqtt-ssl: false # 是否开启 SSL |
| 56 | 56 | mqtt-topics: |
| 57 | 57 | - "/sys/#" # 系统主题 |
| 58 | + clean-session: true # 是否启用 Clean Session (默认: true) | |
| 59 | + keep-alive-interval-seconds: 60 # 心跳间隔,单位秒 (默认: 60) | |
| 60 | + max-inflight-queue: 10000 # 最大飞行消息队列,单位:条 | |
| 61 | + connect-timeout-seconds: 10 # 连接超时,单位:秒 | |
| 62 | + # 是否信任所有 SSL 证书 (默认: false)。警告:生产环境必须为 false! | |
| 63 | + # 仅在开发环境或内网测试时,如果使用了自签名证书,可以临时设置为 true | |
| 64 | + trust-all: true # 在 dev 环境可以设为 true | |
| 65 | + # 遗嘱消息配置 (用于网关异常下线时通知其他系统) | |
| 66 | + will: | |
| 67 | + enabled: true # 生产环境强烈建议开启 | |
| 68 | + topic: "gateway/status/${yudao.iot.gateway.emqx.mqtt-client-id}" # 遗嘱消息主题 | |
| 69 | + payload: "offline" # 遗嘱消息负载 | |
| 70 | + qos: 1 # 遗嘱消息 QoS | |
| 71 | + retain: true # 遗嘱消息是否保留 | |
| 72 | + # 高级 SSL/TLS 配置 (当 trust-all: false 且 mqtt-ssl: true 时生效) | |
| 73 | + ssl-options: | |
| 74 | + key-store-path: "classpath:certs/client.jks" # 客户端证书库路径 | |
| 75 | + key-store-password: "your-keystore-password" # 客户端证书库密码 | |
| 76 | + trust-store-path: "classpath:certs/trust.jks" # 信任的 CA 证书库路径 | |
| 77 | + trust-store-password: "your-truststore-password" # 信任的 CA 证书库密码 | |
| 58 | 78 | # ==================================== |
| 59 | 79 | # 针对引入的 TCP 组件的配置 |
| 60 | 80 | # ==================================== |
| 61 | 81 | tcp: |
| 62 | 82 | enabled: true |
| 63 | 83 | server-port: 8093 |
| 64 | - server-host: 0.0.0.0 | |
| 65 | 84 | |
| 66 | 85 | --- #################### 日志相关配置 #################### |
| 67 | 86 | ... | ... |