refactor(core): 拆分 model/mqtt/update 为单一职责子模块

- model/ 按领域拆为 connection/subscription/message/publish/variable/log/settings
- mqtt/ 拆为门面(引擎与事件)、tls、v5、v4 四块
- update/ 拆为门面(版本工具与命名约定)、check(检查)、install(下载/安装/清理)
- sysmon 只保留进程资源采样,窗口置顶迁至新 platform.rs
- 对外路径经 mod.rs 再导出保持不变,测试随门面保留(33 通过)
This commit is contained in:
2026-09-29 22:20:53 +08:00
parent 303b348ffe
commit b5089f5898
21 changed files with 3393 additions and 3252 deletions
+317
View File
@@ -0,0 +1,317 @@
//! MQTT 3.1.1:MqttOptions/last-will 组装与事件循环泵。
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use rumqttc::mqttbytes::v5::{
ConnectReturnCode as V5ReturnCode, Packet as V5Packet,
};
use rumqttc::Event as V5Event;
use rumqttc_v4 as rv4;
use tokio::sync::watch;
use crate::model::{
ConnectionConfig, ConnectionStatus, Direction, LogLevel, MqttRecord, TransportKind,
};// ─── 引擎事件 ────────────────────────────────────────────────────────────────
use super::*;
pub(super) fn build_v4(
cfg: &ConnectionConfig,
) -> Result<(rv4::AsyncClient, rv4::EventLoop), String> {
use rv4::{Broker as V4Broker, MqttOptions as V4Options, Transport as V4Transport};
let custom_tls = resolve_tls_config(cfg)?;
let mut opts = match cfg.transport {
TransportKind::Tcp | TransportKind::Tls => {
V4Options::new(cfg.client_id.clone(), V4Broker::tcp(&cfg.host, cfg.port))
}
TransportKind::Ws => {
let url = format!("ws://{}:{}{}", cfg.host, cfg.port, normalize_path(&cfg.path));
V4Options::new(cfg.client_id.clone(), V4Broker::websocket(url).map_err(|e| format!("{e:?}"))?)
}
TransportKind::Wss => {
let url = format!("wss://{}:{}{}", cfg.host, cfg.port, normalize_path(&cfg.path));
match &custom_tls {
Some(tls) => V4Options::websocket_with_tls_config(
cfg.client_id.clone(),
url,
tls.clone(),
)
.map_err(|e| format!("{e:?}"))?,
None => V4Options::try_websocket_with_default_tls(cfg.client_id.clone(), url)
.map_err(|e| format!("{e:?}"))?,
}
}
};
if cfg.transport == TransportKind::Tls {
match &custom_tls {
Some(tls) => {
opts.set_transport(V4Transport::tls_with_config(tls.clone()));
}
None => {
opts.set_transport(
V4Transport::try_tls_with_default_config()
.map_err(|e| format!("TLS 初始化失败: {e:?}"))?,
);
}
};
}
opts.set_keep_alive(cfg.keep_alive);
opts.set_clean_session(cfg.clean_start);
if !cfg.username.is_empty() {
opts.set_credentials(cfg.username.clone(), cfg.password.clone());
}
if let Some(will) = &cfg.last_will
&& !will.topic.is_empty() {
// 3.1.1 的遗嘱没有 content_type / response_topic 属性,忽略
let lw = rv4::mqttbytes::v4::LastWill::new(
will.topic.clone(),
will.payload.as_bytes().to_vec(),
v4_qos(will.qos),
will.retain,
);
opts.set_last_will(lw);
}
let (client, mut eventloop) = rv4::AsyncClient::builder(opts).build();
// v4 的连接超时(含 CONNACK 等待)由 EventLoop 的 NetworkOptions 控制
let mut network_options = rv4::NetworkOptions::new();
network_options.set_connection_timeout(connection_timeout_secs(cfg));
eventloop.set_network_options(network_options);
Ok((client, eventloop))
}
pub(super) fn v4_qos(qos: u8) -> rv4::QoS {
match qos {
1 => rv4::QoS::AtLeastOnce,
2 => rv4::QoS::ExactlyOnce,
_ => rv4::QoS::AtMostOnce,
}
}
// ─── 测试连接握手 ────────────────────────────────────────────────────────────
pub(super) async fn test_handshake(cfg_in: &ConnectionConfig) -> Result<(), String> {
// 握手测试必须使用独立 client_id,否则会把使用相同 client_id 的现有连接
// 从 broker 顶下线(MQTT 同 client_id 单会话规则)。
let mut cfg = cfg_in.clone();
cfg.client_id = format!("mqttx_test_{}", &uuid::Uuid::new_v4().simple().to_string()[..8]);
let cfg = &cfg;
if cfg.protocol.is_v5() {
let (client, mut eventloop) = build_v5(cfg)?;
loop {
match eventloop.poll().await {
Ok(V5Event::Incoming(V5Packet::ConnAck(ack))) => {
if ack.code == V5ReturnCode::Success {
// 测试完成,主动断开避免在 broker 侧残留会话。
// disconnect() 只是把请求入队,需再 poll 才会把
// DISCONNECT 真正写上网络。
let _ = client.disconnect().await;
for _ in 0..3 {
if eventloop.poll().await.is_err() {
break;
}
}
return Ok(());
}
return Err(format!("连接被拒绝: {:?}", ack.code));
}
Ok(V5Event::Incoming(V5Packet::Disconnect(d))) => {
return Err(format!("服务端断开: {:?}", d.reason_code));
}
Err(e) => return Err(format!("连接失败: {e}")),
_ => {}
}
}
} else {
let (client, mut eventloop) = build_v4(cfg)?;
loop {
match eventloop.poll().await {
Ok(rv4::Event::Incoming(rv4::Packet::ConnAck(ack))) => {
if ack.code == rv4::ConnectReturnCode::Success {
// 同 v5:disconnect() 只入队,需 poll 把报文冲出
let _ = client.disconnect().await;
for _ in 0..3 {
if eventloop.poll().await.is_err() {
break;
}
}
return Ok(());
}
return Err(format!("连接被拒绝: {:?}", ack.code));
}
Ok(rv4::Event::Incoming(rv4::Packet::Disconnect)) => {
return Err("服务端主动断开".into());
}
Err(e) => return Err(format!("连接失败: {e}")),
_ => {}
}
}
}
}
// ─── 事件循环 ────────────────────────────────────────────────────────────────
pub(super) fn spawn_v4_loop(
engine: Arc<MqttEngine>,
id: String,
mut eventloop: rv4::EventLoop,
mut cancel_rx: watch::Receiver<bool>,
connected: Arc<AtomicBool>,
generation: u64,
auto_reconnect: bool,
max_reconnect_times: u32,
) {
#![allow(clippy::too_many_arguments)]
let runtime = engine.runtime.handle().clone();
runtime.spawn(async move {
let mut first_connect = true;
// 已执行的重连次数,用于 max_reconnect_times(0=无限)限流
let mut reconnect_attempts: u32 = 0;
loop {
tokio::select! {
event = eventloop.poll() => {
match event {
Ok(rv4::Event::Incoming(rv4::Packet::Publish(p))) => {
let (payload, raw_bytes, payload_truncated) =
crate::model::retained_payload(&p.payload);
let record = MqttRecord {
seq: engine.next_seq(),
connection_id: engine.intern(&id),
direction: Direction::Received,
topic: engine.intern(&String::from_utf8_lossy(&p.topic)),
payload,
qos: match p.qos {
rv4::QoS::AtMostOnce => 0,
rv4::QoS::AtLeastOnce => 1,
rv4::QoS::ExactlyOnce => 2,
},
retain: p.retain,
timestamp: now_ms(),
user_properties: Vec::new(),
content_type: None,
response_topic: None,
correlation_data: None,
message_expiry_interval: None,
subscription_identifier: None,
payload_truncated,
raw_bytes,
};
engine.log(
&id, LogLevel::Info, "publish_received",
format!("收到消息: {} ({} 字节)", record.topic, p.payload.len()), None);
engine.emit(EngineEvent::Message(record));
}
Ok(rv4::Event::Incoming(rv4::Packet::ConnAck(ack))) => {
connected.store(ack.code == rv4::ConnectReturnCode::Success, Ordering::SeqCst);
if ack.code == rv4::ConnectReturnCode::Success {
first_connect = false;
// 重连成功后清零计数:max_reconnect_times 限流的是
// 「连续失败次数」,不能把历史错误累计进来
reconnect_attempts = 0;
engine.log(&id, LogLevel::Info, "connack",
format!("连接成功 (session_present: {})", ack.session_present), None);
engine.emit(EngineEvent::Status {
connection_id: id.clone(),
status: ConnectionStatus::Connected,
error: None,
reason_code: Some(0),
session_present: Some(ack.session_present),
});
} else {
engine.log(&id, LogLevel::Error, "connack",
format!("连接被拒绝: {:?}", ack.code), None);
engine.emit(EngineEvent::Status {
connection_id: id.clone(),
status: ConnectionStatus::Error,
error: Some(format!("{:?}", ack.code)),
// 上报真实的 CONNACK 拒绝码,不再硬编码为 1
reason_code: Some(ack.code as u8),
session_present: Some(ack.session_present),
});
break;
}
}
Ok(rv4::Event::Incoming(rv4::Packet::Disconnect)) => {
connected.store(false, Ordering::SeqCst);
engine.emit(EngineEvent::Status {
connection_id: id.clone(),
status: ConnectionStatus::Disconnected,
error: Some("服务端断开".into()),
reason_code: None,
session_present: None,
});
break;
}
Ok(rv4::Event::Incoming(rv4::Packet::SubAck(s))) => {
engine.log(&id, LogLevel::Info, "suback",
format!("订阅确认 (packet {})", s.pkid), None);
}
Ok(rv4::Event::Incoming(rv4::Packet::UnsubAck(a))) => {
engine.log(&id, LogLevel::Info, "unsuback",
format!("取消订阅确认 (packet {})", a.pkid), None);
}
Ok(rv4::Event::Incoming(rv4::Packet::PubAck(a))) => {
engine.log(&id, LogLevel::Debug, "puback",
format!("发布确认 (packet {})", a.pkid), None);
}
Ok(rv4::Event::Outgoing(rv4::Outgoing::Publish(pkid))) => {
engine.log(&id, LogLevel::Debug, "publish_outgoing",
format!("消息已发出 (packet {pkid})"), None);
}
Ok(_) => {}
Err(rv4::ConnectionError::RequestsDone) => {
connected.store(false, Ordering::SeqCst);
engine.emit(EngineEvent::Status {
connection_id: id.clone(),
status: ConnectionStatus::Disconnected,
error: None, reason_code: None, session_present: None,
});
break;
}
Err(e) => {
connected.store(false, Ordering::SeqCst);
engine.emit(EngineEvent::Status {
connection_id: id.clone(),
status: ConnectionStatus::Error,
error: Some(e.to_string()),
reason_code: None, session_present: None,
});
if !auto_reconnect || first_connect {
engine.log(&id, LogLevel::Error, "connection_error",
format!("连接错误: {e}"), None);
break;
}
// max_reconnect_times>0 时按次数停止重试;0 表示无限重连
if max_reconnect_times > 0 && reconnect_attempts >= max_reconnect_times {
engine.log(&id, LogLevel::Error, "reconnect_give_up",
format!("已重连 {reconnect_attempts} 次仍失败,达到最大重连次数 {max_reconnect_times},停止重连: {e}"), None);
break;
}
reconnect_attempts += 1;
engine.log(&id, LogLevel::Warn, "connection_error",
format!("连接错误,2 秒后重连(第 {reconnect_attempts} 次): {e}"), None);
// 退避等待也必须可取消:否则 close() 的取消信号最长
// 2 秒不可见,期间会对用户已关闭的连接继续重连。
tokio::select! {
_ = cancel_rx.changed() => break,
_ = tokio::time::sleep(Duration::from_secs(2)) => {}
}
}
}
}
_ = cancel_rx.changed() => {
engine.log(&id, LogLevel::Info, "disconnect_cancelled", "连接已取消".into(), None);
break;
}
}
}
// 仅当 map 中仍是本代句柄时才移除,避免误删重连后的新句柄
engine.remove_handle_if(&id, generation);
});
}