Skip to main content

sa_token_core/session/
terminal.rs

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