1use std::collections::HashMap;
6use std::sync::Arc;
7use std::time::Duration;
8
9use async_trait::async_trait;
10use chrono::{DateTime, Utc};
11use serde::{Deserialize, Serialize};
12use tokio::sync::RwLock;
13
14use crate::dao::SaTokenDao;
15use crate::error::SaTokenResult;
16use crate::keys::{LOGIN_TYPE_DEFAULT, SaKeys};
17use crate::online::OnlineUser;
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct StoredOnlineUser {
23 #[serde(default = "default_login_type")]
25 pub login_type: String,
26 pub login_id: String,
28 pub token: String,
30 pub device: String,
32 pub connect_time: DateTime<Utc>,
34 pub last_activity: DateTime<Utc>,
36 pub metadata: HashMap<String, String>,
38}
39
40fn default_login_type() -> String {
41 LOGIN_TYPE_DEFAULT.to_string()
42}
43
44impl From<OnlineUser> for StoredOnlineUser {
45 fn from(u: OnlineUser) -> Self {
46 Self {
47 login_type: u.login_type,
48 login_id: u.login_id,
49 token: u.token,
50 device: u.device,
51 connect_time: u.connect_time,
52 last_activity: u.last_activity,
53 metadata: u.metadata,
54 }
55 }
56}
57
58impl From<StoredOnlineUser> for OnlineUser {
59 fn from(s: StoredOnlineUser) -> Self {
60 OnlineUser {
61 login_type: s.login_type,
62 login_id: s.login_id,
63 token: s.token,
64 device: s.device,
65 connect_time: s.connect_time,
66 last_activity: s.last_activity,
67 metadata: s.metadata,
68 }
69 }
70}
71
72#[async_trait]
75pub trait OnlineStore: Send + Sync {
76 async fn mark_online(&self, user: OnlineUser) -> SaTokenResult<()>;
78 async fn mark_offline(
80 &self,
81 login_type: &str,
82 login_id: &str,
83 token: &str,
84 ) -> SaTokenResult<()>;
85 async fn mark_offline_all(&self, login_type: &str, login_id: &str) -> SaTokenResult<()>;
87 async fn is_online(&self, login_type: &str, login_id: &str) -> SaTokenResult<bool>;
89 async fn get_online_count(&self) -> SaTokenResult<usize>;
91 async fn get_online_users(&self) -> SaTokenResult<Vec<String>>;
93 async fn get_user_sessions(
95 &self,
96 login_type: &str,
97 login_id: &str,
98 ) -> SaTokenResult<Vec<OnlineUser>>;
99 async fn update_activity(
101 &self,
102 login_type: &str,
103 login_id: &str,
104 token: &str,
105 ) -> SaTokenResult<()>;
106 async fn prune_index(&self, login_type: &str, login_id: &str) -> SaTokenResult<usize>;
109}
110
111pub struct DistributedOnlineStore {
114 dao: Arc<SaTokenDao>,
115 entry_ttl: Option<Duration>,
118}
119
120impl std::fmt::Debug for DistributedOnlineStore {
121 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 f.write_str("DistributedOnlineStore { .. }")
123 }
124}
125
126impl DistributedOnlineStore {
127 pub fn new(dao: Arc<SaTokenDao>, entry_ttl: Option<Duration>) -> Self {
129 Self { dao, entry_ttl }
130 }
131
132 fn keys(&self) -> &SaKeys {
133 self.dao.keys()
134 }
135
136 fn item_key(&self, login_type: &str, login_id: &str, token: &str) -> String {
137 if SaKeys::is_default_login_type(login_type) {
138 self.keys().online(login_id, token)
139 } else {
140 self.keys().online_with_type(login_type, login_id, token)
141 }
142 }
143
144 fn index_key(&self, login_type: &str, login_id: &str) -> String {
145 if SaKeys::is_default_login_type(login_type) {
146 self.keys().online_index(login_id)
147 } else {
148 self.keys().online_index_with_type(login_type, login_id)
149 }
150 }
151}
152
153#[async_trait]
154impl OnlineStore for DistributedOnlineStore {
155 async fn mark_online(&self, user: OnlineUser) -> SaTokenResult<()> {
156 let stored = StoredOnlineUser::from(user);
157 let item_key = self.item_key(&stored.login_type, &stored.login_id, &stored.token);
158 let idx_key = self.index_key(&stored.login_type, &stored.login_id);
159
160 self.dao
161 .set_object(&item_key, &stored, self.entry_ttl)
162 .await?;
163 self.dao
166 .list_push_unique(&idx_key, &stored.token, self.entry_ttl)
167 .await?;
168 self.dao
169 .list_push_unique(&self.keys().online_users_set(), &stored.login_id, None)
170 .await?;
171 Ok(())
172 }
173
174 async fn mark_offline(
175 &self,
176 login_type: &str,
177 login_id: &str,
178 token: &str,
179 ) -> SaTokenResult<()> {
180 let item_key = self.item_key(login_type, login_id, token);
181 let idx_key = self.index_key(login_type, login_id);
182 self.dao.delete(&item_key).await?;
183 self.dao.list_remove(&idx_key, token).await?;
184 if self.dao.list_len(&idx_key).await? == 0 {
185 self.dao.delete(&idx_key).await?;
186 self.dao
187 .list_remove(&self.keys().online_users_set(), login_id)
188 .await?;
189 }
190 Ok(())
191 }
192
193 async fn mark_offline_all(&self, login_type: &str, login_id: &str) -> SaTokenResult<()> {
194 let idx_key = self.index_key(login_type, login_id);
195 let tokens = self.dao.list_range(&idx_key, 0, None).await?;
196 for token in &tokens {
197 let _ = self
198 .dao
199 .delete(&self.item_key(login_type, login_id, token))
200 .await;
201 }
202 self.dao.delete(&idx_key).await?;
203 self.dao
204 .list_remove(&self.keys().online_users_set(), login_id)
205 .await?;
206 Ok(())
207 }
208
209 async fn is_online(&self, login_type: &str, login_id: &str) -> SaTokenResult<bool> {
210 let sessions = self.get_user_sessions(login_type, login_id).await?;
211 Ok(!sessions.is_empty())
212 }
213
214 async fn get_online_count(&self) -> SaTokenResult<usize> {
215 self.dao.list_len(&self.keys().online_users_set()).await
216 }
217
218 async fn get_online_users(&self) -> SaTokenResult<Vec<String>> {
219 self.dao
220 .list_range(&self.keys().online_users_set(), 0, None)
221 .await
222 }
223
224 async fn get_user_sessions(
225 &self,
226 login_type: &str,
227 login_id: &str,
228 ) -> SaTokenResult<Vec<OnlineUser>> {
229 let _ = self.prune_index(login_type, login_id).await;
230 let idx_key = self.index_key(login_type, login_id);
231 let tokens = self.dao.list_range(&idx_key, 0, None).await?;
232 let mut out = Vec::with_capacity(tokens.len());
233 for token in tokens {
234 let key = self.item_key(login_type, login_id, &token);
235 if let Some(stored) = self.dao.get_object::<StoredOnlineUser>(&key).await? {
236 out.push(stored.into());
237 }
238 }
239 Ok(out)
240 }
241
242 async fn update_activity(
243 &self,
244 login_type: &str,
245 login_id: &str,
246 token: &str,
247 ) -> SaTokenResult<()> {
248 let key = self.item_key(login_type, login_id, token);
249 if let Some(mut stored) = self.dao.get_object::<StoredOnlineUser>(&key).await? {
250 stored.last_activity = Utc::now();
251 self.dao.set_object(&key, &stored, self.entry_ttl).await?;
252 }
253 Ok(())
254 }
255
256 async fn prune_index(&self, login_type: &str, login_id: &str) -> SaTokenResult<usize> {
257 let idx_key = self.index_key(login_type, login_id);
258 let tokens = self.dao.list_range(&idx_key, 0, None).await?;
259 let mut removed = 0usize;
260 for token in &tokens {
261 let key = self.item_key(login_type, login_id, token);
262 if self.dao.get_string(&key).await?.is_none() {
263 self.dao.list_remove(&idx_key, token).await?;
264 removed += 1;
265 }
266 }
267 if self.dao.list_len(&idx_key).await? == 0 {
268 self.dao.delete(&idx_key).await?;
269 self.dao
270 .list_remove(&self.keys().online_users_set(), login_id)
271 .await?;
272 }
273 Ok(removed)
274 }
275}
276
277pub struct LocalOnlineStore {
280 inner: Arc<RwLock<HashMap<String, Vec<OnlineUser>>>>,
281}
282
283impl std::fmt::Debug for LocalOnlineStore {
284 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285 f.write_str("LocalOnlineStore { .. }")
286 }
287}
288
289fn local_map_key(login_type: &str, login_id: &str) -> String {
290 format!("{login_type}\u{1}{login_id}")
291}
292
293impl LocalOnlineStore {
294 pub fn new() -> Self {
296 Self {
297 inner: Arc::new(RwLock::new(HashMap::new())),
298 }
299 }
300}
301
302impl Default for LocalOnlineStore {
303 fn default() -> Self {
304 Self::new()
305 }
306}
307
308#[async_trait]
309impl OnlineStore for LocalOnlineStore {
310 async fn mark_online(&self, user: OnlineUser) -> SaTokenResult<()> {
311 let mut map = self.inner.write().await;
312 let list = map
313 .entry(local_map_key(&user.login_type, &user.login_id))
314 .or_default();
315 list.retain(|u| u.token != user.token);
318 list.push(user);
319 Ok(())
320 }
321
322 async fn mark_offline(
323 &self,
324 login_type: &str,
325 login_id: &str,
326 token: &str,
327 ) -> SaTokenResult<()> {
328 let mut map = self.inner.write().await;
329 let k = local_map_key(login_type, login_id);
330 if let Some(list) = map.get_mut(&k) {
331 list.retain(|u| u.token != token);
332 if list.is_empty() {
333 map.remove(&k);
334 }
335 }
336 Ok(())
337 }
338
339 async fn mark_offline_all(&self, login_type: &str, login_id: &str) -> SaTokenResult<()> {
340 self.inner
341 .write()
342 .await
343 .remove(&local_map_key(login_type, login_id));
344 Ok(())
345 }
346
347 async fn is_online(&self, login_type: &str, login_id: &str) -> SaTokenResult<bool> {
348 let map = self.inner.read().await;
349 Ok(map
350 .get(&local_map_key(login_type, login_id))
351 .is_some_and(|v| !v.is_empty()))
352 }
353
354 async fn get_online_count(&self) -> SaTokenResult<usize> {
355 Ok(self.inner.read().await.len())
356 }
357
358 async fn get_online_users(&self) -> SaTokenResult<Vec<String>> {
359 let map = self.inner.read().await;
360 Ok(map
361 .keys()
362 .filter_map(|k| k.split('\u{1}').nth(1).map(str::to_string))
363 .collect())
364 }
365
366 async fn get_user_sessions(
367 &self,
368 login_type: &str,
369 login_id: &str,
370 ) -> SaTokenResult<Vec<OnlineUser>> {
371 let map = self.inner.read().await;
372 Ok(map
373 .get(&local_map_key(login_type, login_id))
374 .cloned()
375 .unwrap_or_default())
376 }
377
378 async fn update_activity(
379 &self,
380 login_type: &str,
381 login_id: &str,
382 token: &str,
383 ) -> SaTokenResult<()> {
384 let mut map = self.inner.write().await;
385 if let Some(list) = map.get_mut(&local_map_key(login_type, login_id)) {
386 if let Some(u) = list.iter_mut().find(|u| u.token == token) {
387 u.last_activity = Utc::now();
388 }
389 }
390 Ok(())
391 }
392
393 async fn prune_index(&self, _login_type: &str, _login_id: &str) -> SaTokenResult<usize> {
394 Ok(0)
395 }
396}