sa_token_core/session/
mod.rs1use chrono::{DateTime, Utc};
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9pub mod terminal;
10pub use terminal::SaTerminalInfo;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct SaSession {
34 pub id: String,
36
37 pub create_time: DateTime<Utc>,
39
40 #[serde(default)]
43 pub terminal_list: Vec<SaTerminalInfo>,
44
45 #[serde(default)]
48 pub history_terminal_count: i32,
49
50 #[serde(flatten)]
52 pub data: HashMap<String, serde_json::Value>,
53}
54
55impl SaSession {
56 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 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 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 pub fn remove(&mut self, key: &str) -> Option<serde_json::Value> {
109 self.data.remove(key)
110 }
111
112 pub fn clear(&mut self) {
116 self.data.clear();
117 }
118
119 pub fn has(&self, key: &str) -> bool {
128 self.data.contains_key(key)
129 }
130
131 pub fn keys(&self) -> Vec<String> {
133 self.data.keys().cloned().collect()
134 }
135
136 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 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 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 pub fn terminal_list_copy(&self) -> Vec<SaTerminalInfo> {
165 self.terminal_list.clone()
166 }
167
168 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 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 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}