Skip to main content

sa_token_core/session/
mod.rs

1// Author: 金书记
2//
3//! Session 管理模块
4
5use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9pub mod terminal;
10pub use terminal::SaTerminalInfo;
11
12/// Session 对象 | Session Object
13///
14/// 用于存储用户会话数据的对象
15/// Object for storing user session data
16///
17/// # 字段说明 | Field Description
18/// - `id`: Session 唯一标识 | Session unique identifier
19/// - `create_time`: 创建时间 | Creation time
20/// - `data`: 存储的键值对数据 | Stored key-value data
21///
22/// # 使用示例 | Usage Example
23///
24/// ```rust,ignore
25/// let mut session = SaSession::new("session_123");
26/// session.set("username", "张三")?;
27/// session.set("age", 25)?;
28///
29/// let username: Option<String> = session.get("username");
30/// println!("Username: {:?}", username);
31/// ```
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct SaSession {
34    /// Session ID
35    pub id: String,
36
37    /// 创建时间 | Creation time
38    pub create_time: DateTime<Utc>,
39
40    /// Logged-in device terminal list.
41    /// 已登录设备终端列表。
42    #[serde(default)]
43    pub terminal_list: Vec<SaTerminalInfo>,
44
45    /// Cumulative login-device count (monotonic); used for terminal index.
46    /// 历史累计登录设备数,仅增不减,用于生成终端 index。
47    #[serde(default)]
48    pub history_terminal_count: i32,
49
50    /// 数据存储 | Data storage
51    #[serde(flatten)]
52    pub data: HashMap<String, serde_json::Value>,
53}
54
55impl SaSession {
56    /// Create a new instance | 创建新实例
57    pub fn new(id: impl Into<String>) -> Self {
58        Self {
59            id: id.into(),
60            create_time: Utc::now(),
61            terminal_list: Vec::new(),
62            history_terminal_count: 0,
63            data: HashMap::new(),
64        }
65    }
66
67    /// 设置值 | Set Value
68    ///
69    /// # 参数 | Parameters
70    /// - `key`: 键名 | Key name
71    /// - `value`: 要存储的值 | Value to store
72    ///
73    /// # 返回 | Returns
74    /// - `Ok(())`: 设置成功 | Set successfully
75    /// - `Err`: 序列化失败 | Serialization failed
76    pub fn set<T: Serialize>(
77        &mut self,
78        key: impl Into<String>,
79        value: T,
80    ) -> Result<(), serde_json::Error> {
81        let json_value = serde_json::to_value(value)?;
82        self.data.insert(key.into(), json_value);
83        Ok(())
84    }
85
86    /// 获取值 | Get Value
87    ///
88    /// # 参数 | Parameters
89    /// - `key`: 键名 | Key name
90    ///
91    /// # 返回 | Returns
92    /// - `Some(value)`: 找到值并成功反序列化 | Found value and deserialized successfully
93    /// - `None`: 键不存在或反序列化失败 | Key not found or deserialization failed
94    pub fn get<T: for<'de> Deserialize<'de>>(&self, key: &str) -> Option<T> {
95        self.data
96            .get(key)
97            .and_then(|v| serde_json::from_value(v.clone()).ok())
98    }
99
100    /// 删除值 | Remove Value
101    ///
102    /// # 参数 | Parameters
103    /// - `key`: 键名 | Key name
104    ///
105    /// # 返回 | Returns
106    /// 被删除的值,如果键不存在则返回 None
107    /// Removed value, or None if key doesn't exist
108    pub fn remove(&mut self, key: &str) -> Option<serde_json::Value> {
109        self.data.remove(key)
110    }
111
112    /// 清空 session | Clear Session
113    ///
114    /// 删除所有存储的数据 | Remove all stored data
115    pub fn clear(&mut self) {
116        self.data.clear();
117    }
118
119    /// 检查 key 是否存在 | Check if Key Exists
120    ///
121    /// # 参数 | Parameters
122    /// - `key`: 键名 | Key name
123    ///
124    /// # 返回 | Returns
125    /// - `true`: 键存在 | Key exists
126    /// - `false`: 键不存在 | Key doesn't exist
127    pub fn has(&self, key: &str) -> bool {
128        self.data.contains_key(key)
129    }
130
131    /// 返回会话数据全部键名 | Return all session data keys
132    pub fn keys(&self) -> Vec<String> {
133        self.data.keys().cloned().collect()
134    }
135
136    /// 新增一个终端:自动分配 index = history_terminal_count + 1,并累加历史计数
137    pub fn add_terminal(&mut self, mut terminal: SaTerminalInfo) {
138        self.history_terminal_count += 1;
139        terminal.index = self.history_terminal_count;
140        self.terminal_list.push(terminal);
141    }
142
143    /// 按 token 移除终端;返回被移除的终端(不存在则 None)
144    pub fn remove_terminal(&mut self, token_value: &str) -> Option<SaTerminalInfo> {
145        if let Some(pos) = self
146            .terminal_list
147            .iter()
148            .position(|t| t.token_value == token_value)
149        {
150            Some(self.terminal_list.remove(pos))
151        } else {
152            None
153        }
154    }
155
156    /// 按 token 获取终端引用
157    pub fn get_terminal(&self, token_value: &str) -> Option<&SaTerminalInfo> {
158        self.terminal_list
159            .iter()
160            .find(|t| t.token_value == token_value)
161    }
162
163    /// 终端列表副本
164    pub fn terminal_list_copy(&self) -> Vec<SaTerminalInfo> {
165        self.terminal_list.clone()
166    }
167
168    /// 按设备类型筛选终端;device_type 传 None 表示不限设备类型
169    pub fn get_terminal_list_by_device_type(
170        &self,
171        device_type: Option<&str>,
172    ) -> Vec<SaTerminalInfo> {
173        match device_type {
174            None => self.terminal_list.clone(),
175            Some(dt) => self
176                .terminal_list
177                .iter()
178                .filter(|t| t.device_type == dt)
179                .cloned()
180                .collect(),
181        }
182    }
183
184    /// 按设备类型提取 token 列表
185    pub fn get_token_value_list_by_device_type(&self, device_type: Option<&str>) -> Vec<String> {
186        self.get_terminal_list_by_device_type(device_type)
187            .into_iter()
188            .map(|t| t.token_value)
189            .collect()
190    }
191
192    /// 终端数量
193    pub fn terminal_count(&self) -> usize {
194        self.terminal_list.len()
195    }
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn test_terminal_index_increments() {
204        let mut session = SaSession::new("u1");
205        session.add_terminal(SaTerminalInfo::new("t1", "PC"));
206        session.add_terminal(SaTerminalInfo::new("t2", "APP"));
207        session.add_terminal(SaTerminalInfo::new("t3", "WEB"));
208        assert_eq!(session.terminal_count(), 3);
209        assert_eq!(session.history_terminal_count, 3);
210        assert_eq!(session.terminal_list[0].index, 1);
211        assert_eq!(session.terminal_list[1].index, 2);
212        assert_eq!(session.terminal_list[2].index, 3);
213
214        session.remove_terminal("t2");
215        session.add_terminal(SaTerminalInfo::new("t4", "PC"));
216        assert_eq!(session.terminal_list.last().unwrap().index, 4);
217    }
218
219    #[test]
220    fn test_filter_by_device_type() {
221        let mut session = SaSession::new("u1");
222        session.add_terminal(SaTerminalInfo::new("t1", "PC"));
223        session.add_terminal(SaTerminalInfo::new("t2", "PC"));
224        session.add_terminal(SaTerminalInfo::new("t3", "APP"));
225        assert_eq!(
226            session.get_terminal_list_by_device_type(Some("PC")).len(),
227            2
228        );
229        assert_eq!(session.get_terminal_list_by_device_type(None).len(), 3);
230        assert_eq!(
231            session.get_token_value_list_by_device_type(Some("APP")),
232            vec!["t3".to_string()]
233        );
234    }
235
236    #[test]
237    fn test_deserialize_legacy_session_without_terminals() {
238        let json = r#"{"id":"u1","create_time":"2024-01-01T00:00:00Z","foo":"bar"}"#;
239        let session: SaSession = serde_json::from_str(json).unwrap();
240        assert!(session.terminal_list.is_empty());
241        assert_eq!(session.history_terminal_count, 0);
242    }
243}