Commit 7138cab3c0a172c1b8f64d8f848a3d01e053d59c

Authored by haohao
1 parent 54a08b97

feat:【IoT 物联网】更新 IotGatewayProperties 配置

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  
... ...