use async_trait::async_trait;
use std::sync::Arc;
use std::sync::RwLock;
use chrono::{DateTime, Utc};
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum SaTokenEventType {
Login,
Logout,
KickOut,
RenewTimeout,
Replaced,
Banned,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SaTokenEvent {
pub event_type: SaTokenEventType,
pub login_id: String,
pub token: String,
pub login_type: String,
pub timestamp: DateTime<Utc>,
pub extra: Option<serde_json::Value>,
}
impl SaTokenEvent {
pub fn login(login_id: impl Into<String>, token: impl Into<String>) -> Self {
Self {
event_type: SaTokenEventType::Login,
login_id: login_id.into(),
token: token.into(),
login_type: "default".to_string(),
timestamp: Utc::now(),
extra: None,
}
}
pub fn logout(login_id: impl Into<String>, token: impl Into<String>) -> Self {
Self {
event_type: SaTokenEventType::Logout,
login_id: login_id.into(),
token: token.into(),
login_type: "default".to_string(),
timestamp: Utc::now(),
extra: None,
}
}
pub fn kick_out(login_id: impl Into<String>, token: impl Into<String>) -> Self {
Self {
event_type: SaTokenEventType::KickOut,
login_id: login_id.into(),
token: token.into(),
login_type: "default".to_string(),
timestamp: Utc::now(),
extra: None,
}
}
pub fn renew_timeout(login_id: impl Into<String>, token: impl Into<String>) -> Self {
Self {
event_type: SaTokenEventType::RenewTimeout,
login_id: login_id.into(),
token: token.into(),
login_type: "default".to_string(),
timestamp: Utc::now(),
extra: None,
}
}
pub fn replaced(login_id: impl Into<String>, token: impl Into<String>) -> Self {
Self {
event_type: SaTokenEventType::Replaced,
login_id: login_id.into(),
token: token.into(),
login_type: "default".to_string(),
timestamp: Utc::now(),
extra: None,
}
}
pub fn banned(login_id: impl Into<String>) -> Self {
Self {
event_type: SaTokenEventType::Banned,
login_id: login_id.into(),
token: String::new(),
login_type: "default".to_string(),
timestamp: Utc::now(),
extra: None,
}
}
pub fn with_login_type(mut self, login_type: impl Into<String>) -> Self {
self.login_type = login_type.into();
self
}
pub fn with_extra(mut self, extra: serde_json::Value) -> Self {
self.extra = Some(extra);
self
}
}
#[async_trait]
pub trait SaTokenListener: Send + Sync {
async fn on_login(&self, login_id: &str, token: &str, login_type: &str) {
let _ = (login_id, token, login_type);
}
async fn on_logout(&self, login_id: &str, token: &str, login_type: &str) {
let _ = (login_id, token, login_type);
}
async fn on_kick_out(&self, login_id: &str, token: &str, login_type: &str) {
let _ = (login_id, token, login_type);
}
async fn on_renew_timeout(&self, login_id: &str, token: &str, login_type: &str) {
let _ = (login_id, token, login_type);
}
async fn on_replaced(&self, login_id: &str, token: &str, login_type: &str) {
let _ = (login_id, token, login_type);
}
async fn on_banned(&self, login_id: &str, login_type: &str) {
let _ = (login_id, login_type);
}
async fn on_event(&self, event: &SaTokenEvent) {
let _ = event;
}
}
#[derive(Clone)]
pub struct SaTokenEventBus {
listeners: Arc<RwLock<Vec<Arc<dyn SaTokenListener>>>>,
}
impl SaTokenEventBus {
pub fn new() -> Self {
Self {
listeners: Arc::new(RwLock::new(Vec::new())),
}
}
pub fn register(&self, listener: Arc<dyn SaTokenListener>) {
let mut listeners = self.listeners.write().unwrap();
listeners.push(listener);
}
pub async fn register_async(&self, listener: Arc<dyn SaTokenListener>) {
self.register(listener);
}
pub fn clear(&self) {
let mut listeners = self.listeners.write().unwrap();
listeners.clear();
}
pub fn listener_count(&self) -> usize {
let listeners = self.listeners.read().unwrap();
listeners.len()
}
pub async fn publish(&self, event: SaTokenEvent) {
let listeners = {
let guard = self.listeners.read().unwrap();
guard.clone()
};
for listener in listeners.iter() {
listener.on_event(&event).await;
match event.event_type {
SaTokenEventType::Login => {
listener.on_login(&event.login_id, &event.token, &event.login_type).await;
}
SaTokenEventType::Logout => {
listener.on_logout(&event.login_id, &event.token, &event.login_type).await;
}
SaTokenEventType::KickOut => {
listener.on_kick_out(&event.login_id, &event.token, &event.login_type).await;
}
SaTokenEventType::RenewTimeout => {
listener.on_renew_timeout(&event.login_id, &event.token, &event.login_type).await;
}
SaTokenEventType::Replaced => {
listener.on_replaced(&event.login_id, &event.token, &event.login_type).await;
}
SaTokenEventType::Banned => {
listener.on_banned(&event.login_id, &event.login_type).await;
}
}
}
}
}
impl Default for SaTokenEventBus {
fn default() -> Self {
Self::new()
}
}
pub struct LoggingListener;
#[async_trait]
impl SaTokenListener for LoggingListener {
async fn on_login(&self, login_id: &str, token: &str, login_type: &str) {
tracing::info!(
login_id = %login_id,
token = %token,
login_type = %login_type,
"用户登录"
);
}
async fn on_logout(&self, login_id: &str, token: &str, login_type: &str) {
tracing::info!(
login_id = %login_id,
token = %token,
login_type = %login_type,
"用户登出"
);
}
async fn on_kick_out(&self, login_id: &str, token: &str, login_type: &str) {
tracing::warn!(
login_id = %login_id,
token = %token,
login_type = %login_type,
"用户被踢出下线"
);
}
async fn on_renew_timeout(&self, login_id: &str, token: &str, login_type: &str) {
tracing::debug!(
login_id = %login_id,
token = %token,
login_type = %login_type,
"Token 续期"
);
}
async fn on_replaced(&self, login_id: &str, token: &str, login_type: &str) {
tracing::warn!(
login_id = %login_id,
token = %token,
login_type = %login_type,
"用户被顶下线"
);
}
async fn on_banned(&self, login_id: &str, login_type: &str) {
tracing::warn!(
login_id = %login_id,
login_type = %login_type,
"用户被封禁"
);
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestListener {
login_count: Arc<RwLock<i32>>,
}
impl TestListener {
fn new() -> Self {
Self {
login_count: Arc::new(RwLock::new(0)),
}
}
}
#[async_trait]
impl SaTokenListener for TestListener {
async fn on_login(&self, _login_id: &str, _token: &str, _login_type: &str) {
let mut count = self.login_count.write().unwrap();
*count += 1;
}
}
#[tokio::test]
async fn test_event_bus() {
let bus = SaTokenEventBus::new();
let listener = Arc::new(TestListener::new());
let login_count = Arc::clone(&listener.login_count);
bus.register(listener);
let event = SaTokenEvent::login("user_123", "token_abc");
bus.publish(event).await;
let count = login_count.read().unwrap();
assert_eq!(*count, 1);
}
#[test]
fn test_event_creation() {
let event = SaTokenEvent::login("user_123", "token_abc");
assert_eq!(event.event_type, SaTokenEventType::Login);
assert_eq!(event.login_id, "user_123");
assert_eq!(event.token, "token_abc");
}
}