Skip to main content

sa_token_core/session/
terminal.rs

1// Author: 金书记
2//
3//! Login device / terminal row for multi-device sessions.
4//! 登录设备终端信息(多端会话)。
5//!
6//! 记录某账号某次登录所用的设备:第几个登录(index)、token、设备类型、设备唯一标识、
7//! 登录时挂载的扩展数据、创建时间。终端信息随 Account-Session 一并持久化。
8
9use serde::{Deserialize, Serialize};
10
11/// 登录设备终端信息
12///
13/// 不 derive Eq——extra_data 含 serde_json::Value(浮点数不满足 Eq)
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
15pub struct SaTerminalInfo {
16    /// 登录会话索引值:该账号第几个登录的设备,从 1 开始
17    pub index: i32,
18    /// 此终端持有的 token 值
19    pub token_value: String,
20    /// 设备类型,例如 PC / WEB / MOBILE / APP;未指定时为空串
21    pub device_type: String,
22    /// 登录设备唯一标识(可选)
23    pub device_id: Option<String>,
24    /// 登录时挂载的扩展数据(只建议登录前设定)
25    pub extra_data: Option<serde_json::Value>,
26    /// Created-at timestamp (Unix millis).
27    /// 创建时间(Unix 毫秒)。
28    pub create_time: i64,
29}
30
31impl SaTerminalInfo {
32    /// 新建终端信息;`index` 由 `SaSession::add_terminal` 自动分配,此处传 0 占位即可
33    pub fn new(token_value: impl Into<String>, device_type: impl Into<String>) -> Self {
34        Self {
35            index: 0,
36            token_value: token_value.into(),
37            device_type: device_type.into(),
38            device_id: None,
39            extra_data: None,
40            create_time: chrono::Utc::now().timestamp_millis(),
41        }
42    }
43
44    /// 链式设置设备唯一标识
45    pub fn with_device_id(mut self, device_id: impl Into<String>) -> Self {
46        self.device_id = Some(device_id.into());
47        self
48    }
49
50    /// 链式设置扩展数据
51    pub fn with_extra_data(mut self, extra: serde_json::Value) -> Self {
52        self.extra_data = Some(extra);
53        self
54    }
55
56    /// True when non-empty extra data is set.
57    /// 是否设置了非空扩展数据。
58    pub fn have_extra_data(&self) -> bool {
59        matches!(&self.extra_data, Some(v) if !v.is_null())
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use serde_json::json;
67
68    #[test]
69    fn test_new_defaults() {
70        let t = SaTerminalInfo::new("tok1", "PC");
71        assert_eq!(t.index, 0);
72        assert_eq!(t.token_value, "tok1");
73        assert_eq!(t.device_type, "PC");
74        assert!(t.device_id.is_none());
75        assert!(!t.have_extra_data());
76        assert!(t.create_time > 0);
77    }
78
79    #[test]
80    fn test_with_extra_data() {
81        let t = SaTerminalInfo::new("tok1", "APP").with_extra_data(json!({"k": 1}));
82        assert!(t.have_extra_data());
83    }
84
85    #[test]
86    fn test_serde_round_trip() {
87        let t = SaTerminalInfo::new("tok1", "PC")
88            .with_device_id("dev-1")
89            .with_extra_data(json!({"ip": "127.0.0.1"}));
90        let json = serde_json::to_string(&t).unwrap();
91        let back: SaTerminalInfo = serde_json::from_str(&json).unwrap();
92        assert_eq!(t, back);
93    }
94}