Skip to main content

flare_core/common/
device.rs

1//! 设备平台和设备信息管理
2//!
3//! 支持多端设备管理,包括平台类型、设备标识、冲突策略等
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashSet;
7
8/// 设备平台类型
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub enum DevicePlatform {
11    /// Web 浏览器
12    Web,
13    /// PC 桌面应用(Windows、macOS、Linux)
14    PC,
15    /// H5 移动网页
16    H5,
17    /// Android 应用
18    Android,
19    /// iOS 应用
20    IOS,
21    /// 鸿蒙应用
22    HarmonyOS,
23    /// 其他平台
24    Other(String),
25}
26
27impl DevicePlatform {
28    /// 从字符串转换为平台类型
29    #[allow(clippy::should_implement_trait)]
30    pub fn from_str(s: &str) -> Self {
31        match s {
32            s if s.eq_ignore_ascii_case("web") => DevicePlatform::Web,
33            s if s.eq_ignore_ascii_case("pc") || s.eq_ignore_ascii_case("desktop") => {
34                DevicePlatform::PC
35            }
36            s if s.eq_ignore_ascii_case("h5") || s.eq_ignore_ascii_case("mobile_web") => {
37                DevicePlatform::H5
38            }
39            s if s.eq_ignore_ascii_case("android") => DevicePlatform::Android,
40            s if s.eq_ignore_ascii_case("ios")
41                || s.eq_ignore_ascii_case("iphone")
42                || s.eq_ignore_ascii_case("ipad") =>
43            {
44                DevicePlatform::IOS
45            }
46            s if s.eq_ignore_ascii_case("harmonyos")
47                || s.eq_ignore_ascii_case("harmony")
48                || s.eq_ignore_ascii_case("openharmony") =>
49            {
50                DevicePlatform::HarmonyOS
51            }
52            other => DevicePlatform::Other(other.to_string()),
53        }
54    }
55
56    /// 转换为字符串
57    pub fn as_str(&self) -> &str {
58        match self {
59            DevicePlatform::Web => "web",
60            DevicePlatform::PC => "pc",
61            DevicePlatform::H5 => "h5",
62            DevicePlatform::Android => "android",
63            DevicePlatform::IOS => "ios",
64            DevicePlatform::HarmonyOS => "harmonyos",
65            DevicePlatform::Other(s) => s,
66        }
67    }
68
69    /// 判断是否为移动端平台
70    pub fn is_mobile(&self) -> bool {
71        matches!(
72            self,
73            DevicePlatform::H5
74                | DevicePlatform::Android
75                | DevicePlatform::IOS
76                | DevicePlatform::HarmonyOS
77        )
78    }
79}
80
81/// 设备信息
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct DeviceInfo {
84    /// 设备唯一标识符(由客户端生成)
85    pub device_id: String,
86    /// 设备平台类型
87    pub platform: DevicePlatform,
88    /// 设备型号(可选,如 "iPhone 14", "Samsung Galaxy S23")
89    pub model: Option<String>,
90    /// 应用版本(可选)
91    pub app_version: Option<String>,
92    /// 系统版本(可选)
93    pub system_version: Option<String>,
94    /// 其他自定义元数据
95    pub metadata: std::collections::HashMap<String, String>,
96}
97
98impl DeviceInfo {
99    /// 创建新的设备信息
100    pub fn new(device_id: String, platform: DevicePlatform) -> Self {
101        Self {
102            device_id,
103            platform,
104            model: None,
105            app_version: None,
106            system_version: None,
107            metadata: std::collections::HashMap::new(),
108        }
109    }
110
111    /// 设置设备型号
112    pub fn with_model(mut self, model: String) -> Self {
113        self.model = Some(model);
114        self
115    }
116
117    /// 设置应用版本
118    pub fn with_app_version(mut self, version: String) -> Self {
119        self.app_version = Some(version);
120        self
121    }
122
123    /// 设置系统版本
124    pub fn with_system_version(mut self, version: String) -> Self {
125        self.system_version = Some(version);
126        self
127    }
128
129    /// 添加元数据
130    pub fn with_metadata(mut self, key: String, value: String) -> Self {
131        self.metadata.insert(key, value);
132        self
133    }
134}
135
136/// 设备冲突处理策略
137#[derive(Debug, Clone, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
138pub enum DeviceConflictStrategy {
139    /// 允许所有设备同时在线(不检查冲突)
140    #[default]
141    AllowAll,
142    /// 移动端互斥(同一用户只能有一个移动端设备在线)
143    /// 例如:Android 和 iOS 互斥,但 PC 和移动端可以同时在线
144    MobileExclusive,
145    /// 平台互斥(同一平台只能有一个设备在线)
146    /// 例如:同一用户的 Android 设备之间互斥,但 Android 和 iOS 可以同时在线
147    PlatformExclusive,
148    /// 完全互斥(同一用户只能有一个设备在线)
149    FullyExclusive,
150    /// 移动端和PC端共存(移动端之间互斥,PC端之间互斥,但移动端和PC端可以同时在线)
151    /// 例如:同一用户可以有 1 个移动端 + 1 个 PC 同时在线
152    MobileAndPcCoexist,
153    /// 自定义规则(指定哪些平台可以同时在线)
154    Custom {
155        /// 允许同时在线 platform 组合列表
156        /// 例如:[{Web, PC}] 表示 Web 和 PC 可以同时在线
157        allowed_combinations: Vec<HashSet<DevicePlatform>>,
158        /// 互斥的平台组(组内互斥)
159        /// 例如:[{Android, IOS, HarmonyOS}] 表示移动端互斥
160        exclusive_groups: Vec<HashSet<DevicePlatform>>,
161    },
162}
163
164impl DeviceConflictStrategy {
165    /// 检查新设备是否可以与现有设备同时在线
166    ///
167    /// # 参数
168    /// - `new_device`: 新设备的平台类型
169    /// - `existing_devices`: 现有设备的平台类型集合
170    ///
171    /// # 返回
172    /// - `Ok(())`: 可以同时在线
173    /// - `Err(Vec<DevicePlatform>)`: 需要被踢掉的设备平台列表
174    pub fn check_conflict(
175        &self,
176        new_device: DevicePlatform,
177        existing_devices: &HashSet<DevicePlatform>,
178    ) -> Result<(), Vec<DevicePlatform>> {
179        match self {
180            DeviceConflictStrategy::AllowAll => Ok(()),
181
182            DeviceConflictStrategy::MobileExclusive => {
183                if new_device.is_mobile() {
184                    let mobile_conflicts = self.get_mobile_devices(existing_devices);
185                    if !mobile_conflicts.is_empty() {
186                        return Err(mobile_conflicts);
187                    }
188                }
189                Ok(())
190            }
191
192            DeviceConflictStrategy::PlatformExclusive => {
193                if existing_devices.contains(&new_device) {
194                    return Err(vec![new_device]);
195                }
196                Ok(())
197            }
198
199            DeviceConflictStrategy::FullyExclusive => {
200                if !existing_devices.is_empty() {
201                    return Err(existing_devices.iter().cloned().collect());
202                }
203                Ok(())
204            }
205
206            DeviceConflictStrategy::MobileAndPcCoexist => {
207                // 移动端和PC端共存策略
208                if new_device.is_mobile() {
209                    let mobile_conflicts = self.get_mobile_devices(existing_devices);
210                    if !mobile_conflicts.is_empty() {
211                        return Err(mobile_conflicts);
212                    }
213                } else if matches!(new_device, DevicePlatform::Web | DevicePlatform::PC) {
214                    let pc_conflicts: Vec<DevicePlatform> = existing_devices
215                        .iter()
216                        .filter(|p| matches!(p, DevicePlatform::Web | DevicePlatform::PC))
217                        .cloned()
218                        .collect();
219                    if !pc_conflicts.is_empty() {
220                        return Err(pc_conflicts);
221                    }
222                } else {
223                    // 其他平台(如 Other),检查是否有相同平台
224                    if existing_devices.contains(&new_device) {
225                        return Err(vec![new_device]);
226                    }
227                }
228                Ok(())
229            }
230
231            DeviceConflictStrategy::Custom {
232                allowed_combinations,
233                exclusive_groups,
234            } => {
235                // 检查互斥组
236                for group in exclusive_groups {
237                    if group.contains(&new_device) {
238                        let conflicts: Vec<DevicePlatform> = existing_devices
239                            .iter()
240                            .filter(|p| group.contains(p))
241                            .cloned()
242                            .collect();
243                        if !conflicts.is_empty() {
244                            return Err(conflicts);
245                        }
246                    }
247                }
248
249                // 检查允许的组合
250                let mut combined = existing_devices.clone();
251                combined.insert(new_device.clone());
252
253                if combined.len() > 1 {
254                    let is_allowed = allowed_combinations
255                        .iter()
256                        .any(|allowed| combined.is_subset(allowed));
257
258                    if !is_allowed {
259                        return Err(existing_devices.iter().cloned().collect());
260                    }
261                }
262
263                Ok(())
264            }
265        }
266    }
267
268    /// 获取所有移动端设备(辅助方法)
269    fn get_mobile_devices(&self, devices: &HashSet<DevicePlatform>) -> Vec<DevicePlatform> {
270        devices.iter().filter(|p| p.is_mobile()).cloned().collect()
271    }
272}
273
274/// 设备冲突策略构建器
275pub struct DeviceConflictStrategyBuilder {
276    strategy: DeviceConflictStrategy,
277}
278
279impl DeviceConflictStrategyBuilder {
280    /// 创建新的构建器(默认允许所有)
281    pub fn new() -> Self {
282        Self {
283            strategy: DeviceConflictStrategy::AllowAll,
284        }
285    }
286
287    /// 设置移动端互斥
288    pub fn mobile_exclusive(mut self) -> Self {
289        self.strategy = DeviceConflictStrategy::MobileExclusive;
290        self
291    }
292
293    /// 设置平台互斥
294    pub fn platform_exclusive(mut self) -> Self {
295        self.strategy = DeviceConflictStrategy::PlatformExclusive;
296        self
297    }
298
299    /// 设置完全互斥
300    pub fn fully_exclusive(mut self) -> Self {
301        self.strategy = DeviceConflictStrategy::FullyExclusive;
302        self
303    }
304
305    /// 设置移动端和PC端共存
306    ///
307    /// 移动端之间互斥,PC端之间互斥,但移动端和PC端可以同时在线
308    /// 例如:同一用户可以有 1 个移动端(Android/iOS/HarmonyOS/H5) + 1 个 PC(Web/PC)同时在线
309    pub fn mobile_and_pc_coexist(mut self) -> Self {
310        self.strategy = DeviceConflictStrategy::MobileAndPcCoexist;
311        self
312    }
313
314    /// 设置自定义规则
315    pub fn custom(
316        mut self,
317        allowed_combinations: Vec<HashSet<DevicePlatform>>,
318        exclusive_groups: Vec<HashSet<DevicePlatform>>,
319    ) -> Self {
320        self.strategy = DeviceConflictStrategy::Custom {
321            allowed_combinations,
322            exclusive_groups,
323        };
324        self
325    }
326
327    /// 构建策略
328    pub fn build(self) -> DeviceConflictStrategy {
329        self.strategy
330    }
331}
332
333impl Default for DeviceConflictStrategyBuilder {
334    fn default() -> Self {
335        Self::new()
336    }
337}
338
339#[cfg(test)]
340mod tests {
341    use super::*;
342
343    #[test]
344    fn test_mobile_exclusive() {
345        let strategy = DeviceConflictStrategy::MobileExclusive;
346        let mut existing = HashSet::new();
347        existing.insert(DevicePlatform::Android);
348
349        // 新设备是移动端,应该冲突
350        assert!(
351            strategy
352                .check_conflict(DevicePlatform::IOS, &existing)
353                .is_err()
354        );
355
356        // 新设备是 PC,应该允许
357        assert!(
358            strategy
359                .check_conflict(DevicePlatform::PC, &existing)
360                .is_ok()
361        );
362    }
363
364    #[test]
365    fn test_platform_exclusive() {
366        let strategy = DeviceConflictStrategy::PlatformExclusive;
367        let mut existing = HashSet::new();
368        existing.insert(DevicePlatform::Web);
369
370        // 相同平台应该冲突
371        assert!(
372            strategy
373                .check_conflict(DevicePlatform::Web, &existing)
374                .is_err()
375        );
376
377        // 不同平台应该允许
378        assert!(
379            strategy
380                .check_conflict(DevicePlatform::PC, &existing)
381                .is_ok()
382        );
383    }
384}