新增: df-relay Phase2 WS中继实现(Hello握手+鉴权+ConnRegistry配对路由+Event/Command方向分发)

This commit is contained in:
2026-06-22 01:47:01 +08:00
parent 2fe1fe57c8
commit 2b8b30ef41
5 changed files with 638 additions and 121 deletions

View File

@@ -2,18 +2,21 @@
//!
//! 设计依据:设计文档「Layer2」—— 云后端纯转发,无业务逻辑。
//! 中继消息骨架定义「谁发给谁」的路由语义:
//! - `DeviceEvent`:桌面端事件 → 广播给该 device 绑定的小程序
//! - `MiniappCommand`:小程序指令 → 转发给该 device 绑定的桌面端
//! - `Event`:桌面端事件 → 广播给该 device 绑定的小程序
//! - `Command`:小程序指令 → 转发给该 device 绑定的桌面端
//! - `Control`:控制面消息(配对/心跳/在线状态)
//!
//! 与 df-tunnel::TunnelMessage 区分:
//! - TunnelMessage 是隧道两端(桌面↔云)的传输单元
//! - BroadcastMessage 是云后端内部的路由单元(标识来源/去向/广播范围)
//!
//! 脚手架阶段仅定义 enum,Phase2 填路由表 + 广播实现。
//! AiChatEvent JSON 透传:relay 不解析 AiChatEvent(类型在 src-tauri/df-types,
//! relay 不依赖避跨 crate 强耦合),payload 用 serde_json::Value 透传。
use serde::{Deserialize, Serialize};
use crate::conn::ConnId;
/// 客户端类型(用于路由判断)
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
@@ -24,27 +27,52 @@ pub enum ClientKind {
Miniapp,
}
/// 中继路由消息骨架
/// 中继路由消息语义分类
///
/// - `Event`:桌面端 → 小程序(渲染事件,AiChatEvent 透传)
/// - `Command`:小程序 → 桌面端(操作指令,触发 Tauri command)
/// - `Control`:控制面(配对/心跳/Ping-Pong,不进业务流)
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "snake_case")]
pub enum MessageKind {
Event,
Command,
Control,
}
/// 中继路由消息骨架(云后端内部的路由单元)
///
/// 每条消息携带 `device_id`(配对绑定键),云后端按 device_id
/// 找到绑定的对端连接进行转发/广播。
/// 找到绑定的对端连接进行转发/广播。payload 为原始 JSON,转发时不解析业务字段,
/// 保持轻量。
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BroadcastMessage {
/// 配对绑定的设备 ID(桌面端首次配置时生成)
pub device_id: String,
/// 消息来源(用于鉴权校验:device 不能发 MiniappCommand)
/// 消息语义(Event / Command / Control)
pub kind: MessageKind,
/// 消息来源连接(用于鉴权校验:device 不能发 Command)
pub source: ConnId,
/// 来源客户端类型
pub from: ClientKind,
/// 消息载荷(原始 JSON,转发时不解析业务字段,保持轻量)
/// 消息载荷(原始 JSON,透传不解析)
pub payload: serde_json::Value,
/// 时间戳(毫秒,用于冲突处理 —— 桌面端为真相源,但保留时间戳辅助判断)
/// 时间戳(毫秒,用于冲突判断 —— 桌面端为真相源,但保留时间戳辅助)
pub ts: i64,
}
impl BroadcastMessage {
/// 便捷构造:device 发出的事件
pub fn from_device(device_id: impl Into<String>, payload: serde_json::Value, ts: i64) -> Self {
pub fn from_device(
device_id: impl Into<String>,
source: ConnId,
payload: serde_json::Value,
ts: i64,
) -> Self {
Self {
device_id: device_id.into(),
kind: MessageKind::Event,
source,
from: ClientKind::Device,
payload,
ts,
@@ -52,14 +80,39 @@ impl BroadcastMessage {
}
/// 便捷构造:小程序发出的指令
pub fn from_miniapp(device_id: impl Into<String>, payload: serde_json::Value, ts: i64) -> Self {
pub fn from_miniapp(
device_id: impl Into<String>,
source: ConnId,
payload: serde_json::Value,
ts: i64,
) -> Self {
Self {
device_id: device_id.into(),
kind: MessageKind::Command,
source,
from: ClientKind::Miniapp,
payload,
ts,
}
}
/// 便捷构造:控制面消息
pub fn control(
device_id: impl Into<String>,
source: ConnId,
from: ClientKind,
payload: serde_json::Value,
ts: i64,
) -> Self {
Self {
device_id: device_id.into(),
kind: MessageKind::Control,
source,
from,
payload,
ts,
}
}
}
/// 客户端在线状态(用于离线降级提示)
@@ -73,8 +126,11 @@ pub enum PresenceState {
}
/// 控制面消息(配对绑定 / 心跳 / 在线状态广播)
///
/// 仅作为 payload 的语义约定,relay 对 Control 消息同样透传;部分控制语义
/// (如 Ping/Pong 心跳)由 WS 连接层直接处理,不进 BroadcastMessage 流。
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(tag = "control_kind", rename_all = "snake_case")]
pub enum ControlMessage {
/// 配对绑定请求(小程序持 token 申请绑定 device_id)
Pair { device_id: String, token: String },
@@ -82,4 +138,8 @@ pub enum ControlMessage {
Heartbeat { device_id: String },
/// 在线状态变更通知
Presence { device_id: String, state: PresenceState },
/// 心跳 Ping(WS 应用层心跳,与协议层 Ping 区分)
Ping,
/// 心跳 Pong
Pong,
}