1use async_trait::async_trait;
121use chrono::{DateTime, Utc};
122use serde::{Deserialize, Serialize};
123use std::sync::Arc;
124use std::sync::RwLock;
125use std::time::Duration;
126
127#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
129pub enum SaTokenEventType {
130 Login,
132 Logout,
134 KickOut,
136 RenewTimeout,
138 Replaced,
140 Banned,
142 Unbanned,
144 OpenSafe,
146 CloseSafe,
148 SafeVerify,
150 GrantChanged,
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
162pub enum DispatchMode {
163 #[default]
167 Sequential,
168 Concurrent,
172 Detached,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180pub struct EventBusConfig {
181 pub dispatch_mode: DispatchMode,
183 pub listener_timeout: Option<Duration>,
187}
188
189impl Default for EventBusConfig {
190 fn default() -> Self {
191 Self {
192 dispatch_mode: DispatchMode::Sequential,
193 listener_timeout: Some(Duration::from_secs(5)),
194 }
195 }
196}
197
198impl EventBusConfig {
199 pub fn no_timeout() -> Self {
203 Self {
204 listener_timeout: None,
205 ..Default::default()
206 }
207 }
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct SaTokenEvent {
213 pub event_type: SaTokenEventType,
215 pub login_id: String,
217 pub token: String,
219 pub login_type: String,
221 pub timestamp: DateTime<Utc>,
223 pub extra: Option<serde_json::Value>,
225}
226
227impl SaTokenEvent {
228 pub fn login(login_id: impl Into<String>, token: impl Into<String>) -> Self {
230 Self {
231 event_type: SaTokenEventType::Login,
232 login_id: login_id.into(),
233 token: token.into(),
234 login_type: "default".to_string(),
235 timestamp: Utc::now(),
236 extra: None,
237 }
238 }
239
240 pub fn logout(login_id: impl Into<String>, token: impl Into<String>) -> Self {
242 Self {
243 event_type: SaTokenEventType::Logout,
244 login_id: login_id.into(),
245 token: token.into(),
246 login_type: "default".to_string(),
247 timestamp: Utc::now(),
248 extra: None,
249 }
250 }
251
252 pub fn kick_out(login_id: impl Into<String>, token: impl Into<String>) -> Self {
254 Self {
255 event_type: SaTokenEventType::KickOut,
256 login_id: login_id.into(),
257 token: token.into(),
258 login_type: "default".to_string(),
259 timestamp: Utc::now(),
260 extra: None,
261 }
262 }
263
264 pub fn renew_timeout(
271 login_id: impl Into<String>,
272 token: impl Into<String>,
273 timeout_seconds: i64,
274 ) -> Self {
275 Self {
276 event_type: SaTokenEventType::RenewTimeout,
277 login_id: login_id.into(),
278 token: token.into(),
279 login_type: "default".to_string(),
280 timestamp: Utc::now(),
281 extra: Some(serde_json::json!({ "timeout_seconds": timeout_seconds })),
282 }
283 }
284
285 pub fn replaced(login_id: impl Into<String>, token: impl Into<String>) -> Self {
287 Self {
288 event_type: SaTokenEventType::Replaced,
289 login_id: login_id.into(),
290 token: token.into(),
291 login_type: "default".to_string(),
292 timestamp: Utc::now(),
293 extra: None,
294 }
295 }
296
297 pub fn banned(login_id: impl Into<String>, service: impl Into<String>, level: i32) -> Self {
304 Self {
305 event_type: SaTokenEventType::Banned,
306 login_id: login_id.into(),
307 token: String::new(),
308 login_type: "default".to_string(),
309 timestamp: Utc::now(),
310 extra: Some(serde_json::json!({ "service": service.into(), "level": level })),
311 }
312 }
313
314 pub fn unbanned(login_id: impl Into<String>, service: impl Into<String>) -> Self {
320 Self {
321 event_type: SaTokenEventType::Unbanned,
322 login_id: login_id.into(),
323 token: String::new(),
324 login_type: "default".to_string(),
325 timestamp: Utc::now(),
326 extra: Some(serde_json::json!({ "service": service.into() })),
327 }
328 }
329
330 pub fn open_safe(token: impl Into<String>, service: impl Into<String>) -> Self {
335 let svc = service.into();
336 Self {
337 event_type: SaTokenEventType::OpenSafe,
338 login_id: String::new(),
339 token: token.into(),
340 login_type: "default".to_string(),
341 timestamp: Utc::now(),
342 extra: Some(serde_json::json!({ "service": svc })),
343 }
344 }
345
346 pub fn close_safe(token: impl Into<String>, service: impl Into<String>) -> Self {
348 let svc = service.into();
349 Self {
350 event_type: SaTokenEventType::CloseSafe,
351 login_id: String::new(),
352 token: token.into(),
353 login_type: "default".to_string(),
354 timestamp: Utc::now(),
355 extra: Some(serde_json::json!({ "service": svc })),
356 }
357 }
358
359 pub fn safe_verify(token: impl Into<String>, service: impl Into<String>) -> Self {
361 let svc = service.into();
362 Self {
363 event_type: SaTokenEventType::SafeVerify,
364 login_id: String::new(),
365 token: token.into(),
366 login_type: "default".to_string(),
367 timestamp: Utc::now(),
368 extra: Some(serde_json::json!({ "service": svc })),
369 }
370 }
371
372 pub fn grant_changed(login_id: impl Into<String>, login_type: impl Into<String>) -> Self {
374 Self {
375 event_type: SaTokenEventType::GrantChanged,
376 login_id: login_id.into(),
377 token: String::new(),
378 login_type: login_type.into(),
379 timestamp: Utc::now(),
380 extra: None,
381 }
382 }
383
384 pub fn with_login_type(mut self, login_type: impl Into<String>) -> Self {
386 self.login_type = login_type.into();
387 self
388 }
389
390 pub fn with_extra(mut self, extra: serde_json::Value) -> Self {
392 self.extra = Some(extra);
393 self
394 }
395}
396
397#[async_trait]
419pub trait SaTokenListener: Send + Sync {
420 async fn on_login(&self, login_id: &str, token: &str, login_type: &str) {
422 let _ = (login_id, token, login_type);
423 }
424
425 async fn on_logout(&self, login_id: &str, token: &str, login_type: &str) {
427 let _ = (login_id, token, login_type);
428 }
429
430 async fn on_kick_out(&self, login_id: &str, token: &str, login_type: &str) {
432 let _ = (login_id, token, login_type);
433 }
434
435 async fn on_renew_timeout(
443 &self,
444 login_id: &str,
445 token: &str,
446 login_type: &str,
447 timeout_seconds: i64,
448 ) {
449 let _ = (login_id, token, login_type, timeout_seconds);
450 }
451
452 async fn on_replaced(&self, login_id: &str, token: &str, login_type: &str) {
454 let _ = (login_id, token, login_type);
455 }
456
457 async fn on_banned(&self, login_id: &str, login_type: &str) {
459 let _ = (login_id, login_type);
460 }
461
462 async fn on_unbanned(&self, login_id: &str, service: &str, login_type: &str) {
469 let _ = (login_id, service, login_type);
470 }
471
472 async fn on_open_safe(&self, token: &str, service: &str) {
474 let _ = (token, service);
475 }
476
477 async fn on_close_safe(&self, token: &str, service: &str) {
479 let _ = (token, service);
480 }
481
482 async fn on_safe_verify(&self, token: &str, service: &str) {
488 let _ = (token, service);
489 }
490
491 async fn on_grant_changed(&self, login_id: &str, login_type: &str) {
493 let _ = (login_id, login_type);
494 }
495
496 async fn on_event(&self, event: &SaTokenEvent) {
499 let _ = event;
500 }
501}
502
503type ListenerList = Arc<Vec<Arc<dyn SaTokenListener>>>;
506
507#[derive(Clone)]
514pub struct SaTokenEventBus {
515 listeners: Arc<RwLock<ListenerList>>,
516 config: EventBusConfig,
517}
518
519impl std::fmt::Debug for SaTokenEventBus {
520 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
521 f.write_str("SaTokenEventBus { .. }")
522 }
523}
524
525impl SaTokenEventBus {
526 pub fn new() -> Self {
530 Self::with_config(EventBusConfig::default())
531 }
532
533 pub fn with_config(config: EventBusConfig) -> Self {
537 Self {
538 listeners: Arc::new(RwLock::new(Arc::new(Vec::new()))),
539 config,
540 }
541 }
542
543 pub fn config(&self) -> &EventBusConfig {
545 &self.config
546 }
547
548 fn read_guard(&self) -> std::sync::RwLockReadGuard<'_, Arc<Vec<Arc<dyn SaTokenListener>>>> {
551 self.listeners.read().unwrap_or_else(|poisoned| {
552 tracing::warn!("EventBus RwLock poisoned, recovering");
553 poisoned.into_inner()
554 })
555 }
556
557 fn write_guard(&self) -> std::sync::RwLockWriteGuard<'_, Arc<Vec<Arc<dyn SaTokenListener>>>> {
558 self.listeners.write().unwrap_or_else(|poisoned| {
559 tracing::warn!("EventBus RwLock poisoned during write, recovering");
560 poisoned.into_inner()
561 })
562 }
563
564 fn snapshot(&self) -> Arc<Vec<Arc<dyn SaTokenListener>>> {
567 Arc::clone(&*self.read_guard())
568 }
569
570 pub fn register(&self, listener: Arc<dyn SaTokenListener>) {
572 let mut guard = self.write_guard();
573 let mut next = Vec::with_capacity(guard.len() + 1);
574 next.extend(guard.iter().cloned());
575 next.push(listener);
576 *guard = Arc::new(next);
577 }
578
579 pub async fn register_async(&self, listener: Arc<dyn SaTokenListener>) {
583 self.register(listener);
584 }
585
586 pub fn clear(&self) {
588 *self.write_guard() = Arc::new(Vec::new());
589 }
590
591 pub fn listener_count(&self) -> usize {
594 self.read_guard().len()
595 }
596
597 pub async fn publish(&self, event: SaTokenEvent) {
601 match self.config.dispatch_mode {
602 DispatchMode::Sequential => {
603 self.dispatch_sequential(event).await;
604 }
605 DispatchMode::Concurrent => {
606 self.dispatch_concurrent(event).await;
607 }
608 DispatchMode::Detached => {
609 let bus = self.clone();
610 tokio::spawn(async move {
611 bus.dispatch_sequential(event).await;
612 });
613 }
614 }
615 }
616
617 async fn dispatch_sequential(&self, event: SaTokenEvent) {
621 let listeners = self.snapshot();
622 let timeout = self.config.listener_timeout;
623 for listener in listeners.iter() {
624 Self::invoke_listener_safe(Arc::clone(listener), &event, timeout).await;
625 }
626 }
627
628 async fn dispatch_concurrent(&self, event: SaTokenEvent) {
632 let listeners = self.snapshot();
633 let timeout = self.config.listener_timeout;
634 let mut handles = Vec::with_capacity(listeners.len());
635
636 for listener in listeners.iter() {
637 let listener = Arc::clone(listener);
638 let ev = event.clone();
639 let handle = tokio::spawn(async move {
640 Self::invoke_listener_safe(listener, &ev, timeout).await;
641 });
642 handles.push(handle);
643 }
644
645 for (idx, handle) in handles.into_iter().enumerate() {
646 if let Err(e) = handle.await {
647 if e.is_panic() {
648 tracing::warn!(
649 listener_idx = idx,
650 "listener task panicked in concurrent mode"
651 );
652 } else {
653 tracing::warn!(listener_idx = idx, "listener task cancelled");
654 }
655 }
656 }
657 }
658
659 async fn invoke_listener_safe(
663 listener: Arc<dyn SaTokenListener>,
664 event: &SaTokenEvent,
665 timeout: Option<Duration>,
666 ) {
667 let event_owned = event.clone();
668 let handle = tokio::spawn(async move {
669 let fut = Self::dispatch_to_listener(&listener, &event_owned);
670 match timeout {
671 Some(d) => match tokio::time::timeout(d, fut).await {
672 Ok(()) => Ok(()),
673 Err(_elapsed) => Err("timeout"),
674 },
675 None => {
676 fut.await;
677 Ok(())
678 }
679 }
680 });
681
682 match handle.await {
683 Ok(Ok(())) => {}
684 Ok(Err("timeout")) => {
685 tracing::warn!(
686 event_type = ?event.event_type,
687 "listener timed out during event dispatch"
688 );
689 }
690 Ok(Err(_)) => {}
691 Err(e) if e.is_panic() => {
692 tracing::warn!(
693 event_type = ?event.event_type,
694 "listener panicked during event dispatch"
695 );
696 }
697 Err(e) => {
698 tracing::warn!("listener task cancelled: {:?}", e);
699 }
700 }
701 }
702
703 async fn dispatch_to_listener(listener: &Arc<dyn SaTokenListener>, event: &SaTokenEvent) {
707 listener.on_event(event).await;
708
709 match event.event_type {
710 SaTokenEventType::Login => {
711 listener
712 .on_login(&event.login_id, &event.token, &event.login_type)
713 .await;
714 }
715 SaTokenEventType::Logout => {
716 listener
717 .on_logout(&event.login_id, &event.token, &event.login_type)
718 .await;
719 }
720 SaTokenEventType::KickOut => {
721 listener
722 .on_kick_out(&event.login_id, &event.token, &event.login_type)
723 .await;
724 }
725 SaTokenEventType::RenewTimeout => {
726 let timeout_seconds = event
727 .extra
728 .as_ref()
729 .and_then(|v| v.get("timeout_seconds"))
730 .and_then(|v| v.as_i64())
731 .unwrap_or(0);
732 listener
733 .on_renew_timeout(
734 &event.login_id,
735 &event.token,
736 &event.login_type,
737 timeout_seconds,
738 )
739 .await;
740 }
741 SaTokenEventType::Replaced => {
742 listener
743 .on_replaced(&event.login_id, &event.token, &event.login_type)
744 .await;
745 }
746 SaTokenEventType::Banned => {
747 listener.on_banned(&event.login_id, &event.login_type).await;
748 }
749 SaTokenEventType::Unbanned => {
750 let service = event
751 .extra
752 .as_ref()
753 .and_then(|v| v.get("service"))
754 .and_then(|v| v.as_str())
755 .unwrap_or("");
756 listener
757 .on_unbanned(&event.login_id, service, &event.login_type)
758 .await;
759 }
760 SaTokenEventType::OpenSafe => {
761 let service = event
762 .extra
763 .as_ref()
764 .and_then(|v| v.get("service"))
765 .and_then(|v| v.as_str())
766 .unwrap_or(&event.login_type);
767 listener.on_open_safe(&event.token, service).await;
768 }
769 SaTokenEventType::CloseSafe => {
770 let service = event
771 .extra
772 .as_ref()
773 .and_then(|v| v.get("service"))
774 .and_then(|v| v.as_str())
775 .unwrap_or(&event.login_type);
776 listener.on_close_safe(&event.token, service).await;
777 }
778 SaTokenEventType::SafeVerify => {
779 let service = event
780 .extra
781 .as_ref()
782 .and_then(|v| v.get("service"))
783 .and_then(|v| v.as_str())
784 .unwrap_or("");
785 listener.on_safe_verify(&event.token, service).await;
786 }
787 SaTokenEventType::GrantChanged => {
788 listener
789 .on_grant_changed(&event.login_id, &event.login_type)
790 .await;
791 }
792 }
793 }
794}
795
796impl Default for SaTokenEventBus {
797 fn default() -> Self {
798 Self::new()
799 }
800}
801
802pub struct LoggingListener;
804
805#[async_trait]
806impl SaTokenListener for LoggingListener {
807 async fn on_login(&self, login_id: &str, token: &str, login_type: &str) {
808 tracing::info!(
809 login_id = %login_id,
810 token = %token,
811 login_type = %login_type,
812 "用户登录"
813 );
814 }
815
816 async fn on_logout(&self, login_id: &str, token: &str, login_type: &str) {
817 tracing::info!(
818 login_id = %login_id,
819 token = %token,
820 login_type = %login_type,
821 "用户登出"
822 );
823 }
824
825 async fn on_kick_out(&self, login_id: &str, token: &str, login_type: &str) {
826 tracing::warn!(
827 login_id = %login_id,
828 token = %token,
829 login_type = %login_type,
830 "用户被踢出下线"
831 );
832 }
833
834 async fn on_renew_timeout(
835 &self,
836 login_id: &str,
837 token: &str,
838 login_type: &str,
839 timeout_seconds: i64,
840 ) {
841 tracing::debug!(
842 login_id = %login_id,
843 token = %token,
844 login_type = %login_type,
845 timeout_seconds = timeout_seconds,
846 "Token 续期"
847 );
848 }
849
850 async fn on_replaced(&self, login_id: &str, token: &str, login_type: &str) {
851 tracing::warn!(
852 login_id = %login_id,
853 token = %token,
854 login_type = %login_type,
855 "用户被顶下线"
856 );
857 }
858
859 async fn on_banned(&self, login_id: &str, login_type: &str) {
860 tracing::warn!(
861 login_id = %login_id,
862 login_type = %login_type,
863 "用户被封禁"
864 );
865 }
866
867 async fn on_unbanned(&self, login_id: &str, service: &str, login_type: &str) {
868 tracing::info!(
869 login_id = %login_id,
870 service = %service,
871 login_type = %login_type,
872 "用户被解封"
873 );
874 }
875
876 async fn on_safe_verify(&self, token: &str, service: &str) {
877 tracing::debug!(
878 token = %token,
879 service = %service,
880 "二级认证校验通过"
881 );
882 }
883}
884
885impl std::fmt::Debug for LoggingListener {
886 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
887 f.write_str("LoggingListener { .. }")
888 }
889}
890
891#[cfg(test)]
892mod tests {
893 use super::*;
894
895 struct TestListener {
896 login_count: Arc<RwLock<i32>>,
897 }
898
899 impl TestListener {
900 fn new() -> Self {
901 Self {
902 login_count: Arc::new(RwLock::new(0)),
903 }
904 }
905 }
906
907 #[async_trait]
908 impl SaTokenListener for TestListener {
909 async fn on_login(&self, _login_id: &str, _token: &str, _login_type: &str) {
910 let mut count = self.login_count.write().unwrap();
911 *count += 1;
912 }
913 }
914
915 #[tokio::test]
916 async fn test_event_bus() {
917 let bus = SaTokenEventBus::with_config(EventBusConfig::no_timeout());
918 let listener = Arc::new(TestListener::new());
919 let login_count = Arc::clone(&listener.login_count);
920
921 bus.register(listener);
922
923 let event = SaTokenEvent::login("user_123", "token_abc");
924 bus.publish(event).await;
925
926 let count = login_count.read().unwrap();
927 assert_eq!(*count, 1);
928 }
929
930 #[test]
931 fn test_event_creation() {
932 let event = SaTokenEvent::login("user_123", "token_abc");
933 assert_eq!(event.event_type, SaTokenEventType::Login);
934 assert_eq!(event.login_id, "user_123");
935 assert_eq!(event.token, "token_abc");
936 }
937}