feat: 重写为 GPUI 原生桌面应用并接入 Gitea 软件包发布

- gpui-kit 0.6 原生 UI:连接管理、消息流/日志、发布预设、全局变量、
  设置、阿里云连接预设、原生 Logo
- MQTT 5.0/3.1.1 引擎(rumqttc,TCP/TLS/WS/WSS),独立 tokio runtime
  + 事件泵,真实 broker 端到端测试
- 连接表单:保存前可一键测试连接(宽松校验,自动生成临时 Client ID),
  高级属性(认证/MQTT 5 属性/遗嘱)按需折叠显示,WS 路径仅 WS/WSS 显示
- bug 修复:Hex 奇数长度显式报错、测试连接唯一 Client ID、Base64/Hex
  发布发送真实字节、v5 发布记录携带 Content-Type/用户属性、预设应用
  清除旧 Content-Type、模板时间基准统一;补 8 个单元测试
- Gitea 发布:Generic Package 上传脚本(bash/PowerShell)+ Actions
  release 工作流(tag 触发,双平台构建),README 使用说明
This commit is contained in:
2026-09-28 11:39:47 +08:00
parent 0152a10ee6
commit bf6937b776
25 changed files with 15287 additions and 120 deletions
+152
View File
@@ -0,0 +1,152 @@
//! 阿里云 IoT 平台一键接入预设(对标官方 MQTTX 的阿里云模板)。
//!
//! Token 模式鉴权参数:
//! - clientId: `{GroupId}@@@{DeviceId}`
//! - username: `Signature|{AccessKeyId}|{InstanceId}`
//! - password: base64(HMAC-SHA1(AccessKeySecret, clientId))
use base64::Engine as _;
use hmac::{Hmac, Mac};
use serde::{Deserialize, Serialize};
use sha1::Sha1;
use crate::model::{ConnectionConfig, ProtocolVersion, TransportKind};
type HmacSha1 = Hmac<Sha1>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum AliyunAuthMode {
/// Group ID + 设备 ID,使用账号 AccessKey 签名
#[default]
Token,
/// 一机一密:设备独立 DeviceName + DeviceSecret(直接以设备密钥鉴权)
DeviceCredential,
}
impl AliyunAuthMode {
pub const ALL: [Self; 2] = [Self::Token, Self::DeviceCredential];
pub fn label(self) -> &'static str {
match self {
Self::Token => "Group 令牌 (Token)",
Self::DeviceCredential => "一机一密",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AliyunPreset {
pub id: String,
pub name: String,
/// 分组(仅 UI 分类用)
#[serde(default)]
pub group: String,
/// 实例 ID,如 post-cn-4591dq2ra1i
pub instance_id: String,
/// 地域 host 后缀前的 region,如 cn-shanghai;留空则使用公共接入点
#[serde(default)]
pub region: String,
#[serde(default)]
pub auth_mode: AliyunAuthMode,
// Token 模式
#[serde(default)]
pub access_key_id: String,
#[serde(default)]
pub access_key_secret: String,
#[serde(default)]
pub group_id: String,
// 共用
pub device_id: String,
// 一机一密模式
#[serde(default)]
pub device_secret: String,
}
impl AliyunPreset {
pub fn new() -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
name: "阿里云设备".into(),
group: String::new(),
instance_id: String::new(),
region: String::new(),
auth_mode: AliyunAuthMode::Token,
access_key_id: String::new(),
access_key_secret: String::new(),
group_id: String::new(),
device_id: format!("device_{}", &uuid::Uuid::new_v4().simple().to_string()[..8]),
device_secret: String::new(),
}
}
/// HMAC-SHA1(secret, client_id) 的 Base64 编码。
pub fn signature(secret: &str, client_id: &str) -> Result<String, String> {
let mut mac =
HmacSha1::new_from_slice(secret.as_bytes()).map_err(|e| format!("HMAC 初始化失败: {e}"))?;
mac.update(client_id.as_bytes());
Ok(base64::engine::general_purpose::STANDARD.encode(mac.finalize().into_bytes()))
}
/// 接入点 host:`{instanceId}.mqtt.iothub.aliyuncs.com`
/// (实例级接入点在各区域通用;region 为空时同样可用)
pub fn host(&self) -> String {
if self.region.is_empty() {
format!("{}.mqtt.iothub.aliyuncs.com", self.instance_id)
} else {
format!(
"{}.mqtt.{}.aliyuncs.com",
self.instance_id, self.region
)
}
}
/// 由预设生成一份连接配置(不写入 id/name 之外的覆盖项)。
pub fn to_connection(&self) -> Result<ConnectionConfig, String> {
let mut conn = ConnectionConfig::new();
conn.name = self.name.clone();
conn.host = self.host();
conn.port = TransportKind::Tcp.default_port();
conn.transport = TransportKind::Tcp;
conn.protocol = ProtocolVersion::V311;
conn.clean_start = true;
match self.auth_mode {
AliyunAuthMode::Token => {
if self.group_id.is_empty()
|| self.access_key_id.is_empty()
|| self.access_key_secret.is_empty()
{
return Err("Group ID、AccessKey ID 与 AccessKey Secret 不能为空".into());
}
let client_id = format!("{}@@@{}", self.group_id, self.device_id);
let password = Self::signature(&self.access_key_secret, &client_id)?;
conn.client_id = client_id;
conn.username =
format!("Signature|{}|{}", self.access_key_id, self.instance_id);
conn.password = password;
}
AliyunAuthMode::DeviceCredential => {
if self.device_secret.is_empty() {
return Err("设备密钥 (DeviceSecret) 不能为空".into());
}
// 一机一密三元组:clientId=设备名,username=设备名&实例,password=HMAC-SHA1(DeviceSecret, clientId)
let client_id = self.device_id.clone();
let password = Self::signature(&self.device_secret, &client_id)?;
conn.client_id = client_id;
conn.username = format!("{}&{}", self.device_id, self.instance_id);
conn.password = password;
}
}
Ok(conn)
}
}
impl Default for AliyunPreset {
fn default() -> Self {
Self::new()
}
}