Skip to main content

sa_token_core/session/
mod.rs

1// Author: 金书记
2//
3//! Session 管理模块
4
5use std::collections::HashMap;
6use serde::{Deserialize, Serialize};
7use chrono::{DateTime, Utc};
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    /// 已登录设备终端列表(对齐 Java SaSession.terminalList)
41    #[serde(default)]
42    pub terminal_list: Vec<SaTerminalInfo>,
43
44    /// 历史累计登录设备数,仅增不减,用于生成终端 index(对齐 Java historyTerminalCount)
45    #[serde(default)]
46    pub history_terminal_count: i32,
47    
48    /// 数据存储 | Data storage
49    #[serde(flatten)]
50    pub data: HashMap<String, serde_json::Value>,
51}
52
53impl SaSession {
54    pub fn new(id: impl Into<String>) -> Self {
55        Self {
56            id: id.into(),
57            create_time: Utc::now(),
58            terminal_list: Vec::new(),
59            history_terminal_count: 0,
60            data: HashMap::new(),
61        }
62    }
63    
64    /// 设置值 | Set Value
65    /// 
66    /// # 参数 | Parameters
67    /// - `key`: 键名 | Key name
68    /// - `value`: 要存储的值 | Value to store
69    /// 
70    /// # 返回 | Returns
71    /// - `Ok(())`: 设置成功 | Set successfully
72    /// - `Err`: 序列化失败 | Serialization failed
73    pub fn set<T: Serialize>(&mut self, key: impl Into<String>, value: T) -> Result<(), serde_json::Error> {
74        let json_value = serde_json::to_value(value)?;
75        self.data.insert(key.into(), json_value);
76        Ok(())
77    }
78    
79    /// 获取值 | Get Value
80    /// 
81    /// # 参数 | Parameters
82    /// - `key`: 键名 | Key name
83    /// 
84    /// # 返回 | Returns
85    /// - `Some(value)`: 找到值并成功反序列化 | Found value and deserialized successfully
86    /// - `None`: 键不存在或反序列化失败 | Key not found or deserialization failed
87    pub fn get<T: for<'de> Deserialize<'de>>(&self, key: &str) -> Option<T> {
88        self.data.get(key)
89            .and_then(|v| serde_json::from_value(v.clone()).ok())
90    }
91    
92    /// 删除值 | Remove Value
93    /// 
94    /// # 参数 | Parameters
95    /// - `key`: 键名 | Key name
96    /// 
97    /// # 返回 | Returns
98    /// 被删除的值,如果键不存在则返回 None
99    /// Removed value, or None if key doesn't exist
100    pub fn remove(&mut self, key: &str) -> Option<serde_json::Value> {
101        self.data.remove(key)
102    }
103    
104    /// 清空 session | Clear Session
105    /// 
106    /// 删除所有存储的数据 | Remove all stored data
107    pub fn clear(&mut self) {
108        self.data.clear();
109    }
110    
111    /// 检查 key 是否存在 | Check if Key Exists
112    /// 
113    /// # 参数 | Parameters
114    /// - `key`: 键名 | Key name
115    /// 
116    /// # 返回 | Returns
117    /// - `true`: 键存在 | Key exists
118    /// - `false`: 键不存在 | Key doesn't exist
119    pub fn has(&self, key: &str) -> bool {
120        self.data.contains_key(key)
121    }
122
123    /// 新增一个终端:自动分配 index = history_terminal_count + 1,并累加历史计数
124    pub fn add_terminal(&mut self, mut terminal: SaTerminalInfo) {
125        self.history_terminal_count += 1;
126        terminal.index = self.history_terminal_count;
127        self.terminal_list.push(terminal);
128    }
129
130    /// 按 token 移除终端;返回被移除的终端(不存在则 None)
131    pub fn remove_terminal(&mut self, token_value: &str) -> Option<SaTerminalInfo> {
132        if let Some(pos) = self.terminal_list.iter().position(|t| t.token_value == token_value) {
133            Some(self.terminal_list.remove(pos))
134        } else {
135            None
136        }
137    }
138
139    /// 按 token 获取终端引用
140    pub fn get_terminal(&self, token_value: &str) -> Option<&SaTerminalInfo> {
141        self.terminal_list.iter().find(|t| t.token_value == token_value)
142    }
143
144    /// 终端列表副本
145    pub fn terminal_list_copy(&self) -> Vec<SaTerminalInfo> {
146        self.terminal_list.clone()
147    }
148
149    /// 按设备类型筛选终端;device_type 传 None 表示不限设备类型
150    pub fn get_terminal_list_by_device_type(&self, device_type: Option<&str>) -> Vec<SaTerminalInfo> {
151        match device_type {
152            None => self.terminal_list.clone(),
153            Some(dt) => self
154                .terminal_list
155                .iter()
156                .filter(|t| t.device_type == dt)
157                .cloned()
158                .collect(),
159        }
160    }
161
162    /// 按设备类型提取 token 列表
163    pub fn get_token_value_list_by_device_type(&self, device_type: Option<&str>) -> Vec<String> {
164        self.get_terminal_list_by_device_type(device_type)
165            .into_iter()
166            .map(|t| t.token_value)
167            .collect()
168    }
169
170    /// 终端数量
171    pub fn terminal_count(&self) -> usize {
172        self.terminal_list.len()
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn test_terminal_index_increments() {
182        let mut session = SaSession::new("u1");
183        session.add_terminal(SaTerminalInfo::new("t1", "PC"));
184        session.add_terminal(SaTerminalInfo::new("t2", "APP"));
185        session.add_terminal(SaTerminalInfo::new("t3", "WEB"));
186        assert_eq!(session.terminal_count(), 3);
187        assert_eq!(session.history_terminal_count, 3);
188        assert_eq!(session.terminal_list[0].index, 1);
189        assert_eq!(session.terminal_list[1].index, 2);
190        assert_eq!(session.terminal_list[2].index, 3);
191
192        session.remove_terminal("t2");
193        session.add_terminal(SaTerminalInfo::new("t4", "PC"));
194        assert_eq!(session.terminal_list.last().unwrap().index, 4);
195    }
196
197    #[test]
198    fn test_filter_by_device_type() {
199        let mut session = SaSession::new("u1");
200        session.add_terminal(SaTerminalInfo::new("t1", "PC"));
201        session.add_terminal(SaTerminalInfo::new("t2", "PC"));
202        session.add_terminal(SaTerminalInfo::new("t3", "APP"));
203        assert_eq!(session.get_terminal_list_by_device_type(Some("PC")).len(), 2);
204        assert_eq!(session.get_terminal_list_by_device_type(None).len(), 3);
205        assert_eq!(
206            session.get_token_value_list_by_device_type(Some("APP")),
207            vec!["t3".to_string()]
208        );
209    }
210
211    #[test]
212    fn test_deserialize_legacy_session_without_terminals() {
213        let json = r#"{"id":"u1","create_time":"2024-01-01T00:00:00Z","foo":"bar"}"#;
214        let session: SaSession = serde_json::from_str(json).unwrap();
215        assert!(session.terminal_list.is_empty());
216        assert_eq!(session.history_terminal_count, 0);
217    }
218}