use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SysUser {
pub id: i64,
pub tenant_id: i64,
pub username: String,
pub display_name: String,
#[serde(skip_serializing)]
pub password_hash: String,
pub email: Option<String>,
pub phone: Option<String>,
pub status: String,
pub extra: serde_json::Value,
pub created_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SysPermission {
pub id: i64,
pub tenant_id: i64,
pub name: String,
pub description: String,
pub resource: String,
pub action: String,
pub conditions: Option<serde_json::Value>,
pub created_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SysEvent {
pub id: i64,
pub tenant_id: i64,
pub event_type: String,
pub source_plugin: String,
pub payload: serde_json::Value,
pub delivered: bool,
pub delivered_at: Option<chrono::DateTime<chrono::Utc>>,
pub retry_count: i32,
pub max_retries: i32,
pub created_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
}
impl SysUser {
pub fn table_name() -> &'static str {
"sys_users"
}
}
impl SysPermission {
pub fn table_name() -> &'static str {
"sys_permissions"
}
}
impl SysEvent {
pub fn table_name() -> &'static str {
"sys_events"
}
pub fn new(
tenant_id: i64,
event_type: impl Into<String>,
source_plugin: impl Into<String>,
payload: serde_json::Value,
) -> Self {
let now = chrono::Utc::now();
Self {
id: 0,
tenant_id,
event_type: event_type.into(),
source_plugin: source_plugin.into(),
payload,
delivered: false,
delivered_at: None,
retry_count: 0,
max_retries: 3,
created_at: now,
updated_at: now,
}
}
pub fn mark_delivered(&mut self) {
self.delivered = true;
self.delivered_at = Some(chrono::Utc::now());
self.updated_at = chrono::Utc::now();
}
pub fn increment_retry(&mut self) {
self.retry_count += 1;
self.updated_at = chrono::Utc::now();
}
pub fn is_exhausted(&self) -> bool {
self.retry_count >= self.max_retries
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sys_event_new() {
let event = SysEvent::new(1, "order.created", "shop", serde_json::json!({"id": 1}));
assert_eq!(event.tenant_id, 1);
assert_eq!(event.event_type, "order.created");
assert!(!event.delivered);
assert_eq!(event.retry_count, 0);
assert_eq!(event.max_retries, 3);
}
#[test]
fn test_mark_delivered() {
let mut event = SysEvent::new(1, "test", "test", serde_json::json!({}));
event.mark_delivered();
assert!(event.delivered);
assert!(event.delivered_at.is_some());
}
#[test]
fn test_retry_exhaustion() {
let mut event = SysEvent::new(1, "test", "test", serde_json::json!({}));
assert!(!event.is_exhausted());
event.increment_retry();
event.increment_retry();
event.increment_retry();
assert!(event.is_exhausted());
}
#[test]
fn test_password_hash_skip_serializing() {
let user = SysUser {
id: 1,
tenant_id: 1,
username: "admin".to_string(),
display_name: "Admin".to_string(),
password_hash: "secret_hash".to_string(),
email: None,
phone: None,
status: "active".to_string(),
extra: serde_json::json!({}),
created_at: chrono::Utc::now(),
updated_at: chrono::Utc::now(),
};
let json = serde_json::to_string(&user).expect("序列化失败");
assert!(
!json.contains("password_hash"),
"敏感字段不应出现在 JSON 中"
);
assert!(!json.contains("secret_hash"), "敏感值不应出现在 JSON 中");
}
}