Skip to main content

flare_core/server/connection/
device_handler.rs

1//! 设备冲突处理模块
2//!
3//! 处理用户多设备在线时的冲突策略
4
5use crate::common::MessageParser;
6use crate::common::device::{DeviceInfo, DevicePlatform};
7use crate::common::error::Result;
8use crate::common::protocol::{Reliability, builder::kicked, frame_with_system_command};
9use crate::server::connection::ConnectionManagerTrait;
10use crate::server::device::DeviceManager;
11use std::sync::Arc;
12use tracing::{debug, error, info, warn};
13
14/// 设备冲突处理结果
15#[derive(Debug)]
16pub struct DeviceConflictResult {
17    /// 需要被踢掉的连接 ID 列表
18    pub conflict_connections: Vec<String>,
19}
20
21/// 处理设备冲突
22///
23/// 通过 user_id 和 DevicePlatform 来确定冲突规则,不再依赖完整的 DeviceInfo
24///
25/// # 参数
26/// - `device_manager`: 设备管理器(可选)
27/// - `user_id`: 用户 ID(用于确定冲突规则)
28/// - `connection_id`: 新连接的连接 ID
29/// - `platform`: 设备平台(用于确定冲突规则)
30/// - `device_info`: 设备信息(完整信息,用于记录)
31/// - `manager`: 连接管理器(用于断开冲突连接)
32///
33/// # 返回
34/// 设备冲突处理结果,包含需要被踢掉的连接 ID 列表
35pub async fn handle_device_conflict(
36    device_manager: Option<Arc<DeviceManager>>,
37    user_id: &str,
38    connection_id: &str,
39    platform: &DevicePlatform,
40    device_info: &DeviceInfo,
41    manager: Arc<dyn ConnectionManagerTrait>,
42) -> Result<DeviceConflictResult> {
43    let mut conflict_connections = Vec::new();
44
45    info!(
46        "[DeviceHandler] 开始处理设备冲突: user_id={}, connection_id={}, platform={:?}",
47        user_id, connection_id, platform
48    );
49
50    let device_manager_clone = device_manager.clone();
51    if let Some(device_mgr) = &device_manager {
52        // 使用 user_id 和 platform 来确定冲突规则,然后添加设备
53        match device_mgr
54            .add_device(user_id, connection_id.to_string(), device_info.clone())
55            .await
56        {
57            Ok(conflicts) => {
58                conflict_connections = conflicts;
59                if !conflict_connections.is_empty() {
60                    info!(
61                        "[DeviceHandler] ✅ 设备冲突检测: 用户 {} 的新平台 {:?} 将踢掉 {} 个旧连接: {:?}",
62                        user_id,
63                        platform,
64                        conflict_connections.len(),
65                        conflict_connections
66                    );
67                } else {
68                    debug!(
69                        "[DeviceHandler] 无设备冲突: 用户 {} 的新平台 {:?} 可以正常添加",
70                        user_id, platform
71                    );
72                }
73            }
74            Err(e) => {
75                error!("[DeviceHandler] 设备管理器错误: {}", e);
76            }
77        }
78    } else {
79        debug!("[DeviceHandler] 未配置设备管理器,跳过设备冲突检测");
80    }
81
82    // 断开冲突的连接(在断开前发送被踢消息)
83    if !conflict_connections.is_empty() {
84        info!(
85            "[DeviceHandler] 准备踢掉 {} 个冲突连接: {:?}",
86            conflict_connections.len(),
87            conflict_connections
88        );
89    }
90
91    if let Some(device_mgr) = device_manager_clone {
92        info!(
93            "[DeviceHandler] 🔍 开始处理 {} 个冲突连接,新连接ID: {}",
94            conflict_connections.len(),
95            connection_id
96        );
97
98        for conflict_conn_id in &conflict_connections {
99            // 确保不处理新连接本身(防御性检查)
100            if conflict_conn_id == connection_id {
101                error!(
102                    "[DeviceHandler] ❌ 严重错误:冲突连接列表包含新连接ID!跳过处理: connection_id={}",
103                    connection_id
104                );
105                continue;
106            }
107
108            info!(
109                "[DeviceHandler] 📤 准备踢掉旧连接: connection_id={} (新连接ID: {})",
110                conflict_conn_id, connection_id
111            );
112
113            // 1. 获取连接信息(包括协商后的序列化格式)
114            if let Some((conn, conn_info)) = manager.get_connection(conflict_conn_id).await {
115                info!(
116                    "[DeviceHandler] 找到冲突连接: connection_id={}, user_id={:?}, format={:?}",
117                    conflict_conn_id, conn_info.user_id, conn_info.serialization_format
118                );
119                // 2. 创建被踢消息(使用连接的协商格式)
120                let reason = format!(
121                    "设备冲突:同一用户 ({}) 的同一平台 ({:?}) 已有其他设备在线,当前设备将被踢下线",
122                    user_id, platform
123                );
124
125                let mut metadata = std::collections::HashMap::new();
126                metadata.insert("reason".to_string(), "device_conflict".as_bytes().to_vec());
127                metadata.insert(
128                    "platform".to_string(),
129                    format!("{:?}", platform).as_bytes().to_vec(),
130                );
131                metadata.insert(
132                    "new_device_id".to_string(),
133                    device_info.device_id.as_bytes().to_vec(),
134                );
135
136                let kick_cmd = kicked(reason.clone(), Some(metadata));
137                let kick_frame = frame_with_system_command(kick_cmd, Reliability::AtLeastOnce);
138
139                // 3. 使用连接的协商格式序列化并发送被踢消息
140                let parser = MessageParser::new(
141                    conn_info.serialization_format,
142                    conn_info.compression,
143                    crate::common::encryption::EncryptionAlgorithm::None,
144                );
145
146                match parser.serialize(&kick_frame) {
147                    Ok(kick_data) => {
148                        // 再次验证:确保发送给的是旧连接,不是新连接
149                        if conflict_conn_id == connection_id {
150                            error!(
151                                "[DeviceHandler] ❌ 严重错误:尝试发送KICKED消息给新连接!跳过: connection_id={}",
152                                connection_id
153                            );
154                            continue;
155                        }
156
157                        let mut c = conn.lock().await;
158                        if let Err(e) = c.send(&kick_data).await {
159                            error!(
160                                "[DeviceHandler] 发送被踢消息失败给旧连接 {}: {}",
161                                conflict_conn_id, e
162                            );
163                        } else {
164                            info!(
165                                "[DeviceHandler] ✅ 已发送被踢消息给旧连接 {} (新连接: {}): {}",
166                                conflict_conn_id, connection_id, reason
167                            );
168                            info!("[DeviceHandler] 💡 客户端收到 KICKED 消息后将主动断开连接");
169                        }
170                    }
171                    Err(e) => {
172                        error!(
173                            "[DeviceHandler] 序列化被踢消息失败 (旧连接: {}): {}",
174                            conflict_conn_id, e
175                        );
176                    }
177                }
178
179                // 从设备管理器中移除旧连接的设备
180                // 注意:连接断开事件会在 DefaultServerMessageObserver 中处理连接管理器的清理
181                if conflict_conn_id == connection_id {
182                    error!(
183                        "[DeviceHandler] ❌ 严重错误:尝试从设备管理器移除新连接的设备!跳过: connection_id={}",
184                        connection_id
185                    );
186                    continue;
187                }
188
189                match device_mgr.remove_device(user_id, conflict_conn_id).await {
190                    Ok(_) => {
191                        info!(
192                            "[DeviceHandler] ✅ 旧连接的设备已从设备管理器移除: {} (新连接: {})",
193                            conflict_conn_id, connection_id
194                        );
195                    }
196                    Err(e) => {
197                        debug!(
198                            "[DeviceHandler] 旧连接的设备 {} 已不存在于设备管理器: {}",
199                            conflict_conn_id, e
200                        );
201                    }
202                }
203            } else {
204                warn!(
205                    "[DeviceHandler] 无法获取旧连接信息: {} (连接可能已断开)",
206                    conflict_conn_id
207                );
208            }
209        }
210
211        info!(
212            "[DeviceHandler] ✅ 设备冲突处理完成: 新连接 {} 已保留,已向 {} 个旧连接发送 KICKED 消息",
213            connection_id,
214            conflict_connections.len()
215        );
216    } else {
217        // 如果没有设备管理器,无法发送被踢消息(因为需要设备管理器来管理冲突)
218        warn!(
219            "[DeviceHandler] ⚠️  无设备管理器,无法处理设备冲突: {} 个冲突连接",
220            conflict_connections.len()
221        );
222    }
223
224    info!(
225        "[DeviceHandler] 🎯 设备冲突处理完成: 新连接 {} 保留,已通知 {} 个旧连接断开(客户端将主动断开)",
226        connection_id,
227        conflict_connections.len()
228    );
229
230    Ok(DeviceConflictResult {
231        conflict_connections,
232    })
233}