Skip to main content

sz_rust_core/plugin/
schema.rs

1//! 共享 Schema 模型定义 — sys_users / sys_permissions / sys_events。
2//!
3//! 所有模型含 `tenant_id` 字段实现多租户隔离。
4//! 敏感字段标注 `#[serde(skip_serializing)]`(铁律 7)。
5
6use serde::{Deserialize, Serialize};
7
8/// 系统用户(共享 Schema)。
9///
10/// `extra` 为 JSON 类型,供插件存储扩展字段。
11/// `password_hash` 为敏感字段,序列化时跳过(铁律 7)。
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct SysUser {
14    pub id: i64,
15    pub tenant_id: i64,
16    pub username: String,
17    pub display_name: String,
18    #[serde(skip_serializing)]
19    pub password_hash: String,
20    pub email: Option<String>,
21    pub phone: Option<String>,
22    pub status: String,
23    pub extra: serde_json::Value,
24    pub created_at: chrono::DateTime<chrono::Utc>,
25    pub updated_at: chrono::DateTime<chrono::Utc>,
26}
27
28/// 系统权限(共享 Schema)。
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct SysPermission {
31    pub id: i64,
32    pub tenant_id: i64,
33    pub name: String,
34    pub description: String,
35    pub resource: String,
36    pub action: String,
37    pub conditions: Option<serde_json::Value>,
38    pub created_at: chrono::DateTime<chrono::Utc>,
39}
40
41/// 系统事件(共享 Schema)。
42///
43/// 事件总线持久化事件记录,支持至少一次投递。
44/// `delivered`/`delivered_at`/`retry_count` 跟踪投递状态。
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct SysEvent {
47    pub id: i64,
48    pub tenant_id: i64,
49    pub event_type: String,
50    pub source_plugin: String,
51    pub payload: serde_json::Value,
52    pub delivered: bool,
53    pub delivered_at: Option<chrono::DateTime<chrono::Utc>>,
54    pub retry_count: i32,
55    pub max_retries: i32,
56    pub created_at: chrono::DateTime<chrono::Utc>,
57    pub updated_at: chrono::DateTime<chrono::Utc>,
58}
59
60impl SysUser {
61    pub fn table_name() -> &'static str {
62        "sys_users"
63    }
64}
65
66impl SysPermission {
67    pub fn table_name() -> &'static str {
68        "sys_permissions"
69    }
70}
71
72impl SysEvent {
73    pub fn table_name() -> &'static str {
74        "sys_events"
75    }
76
77    /// 创建新事件
78    pub fn new(
79        tenant_id: i64,
80        event_type: impl Into<String>,
81        source_plugin: impl Into<String>,
82        payload: serde_json::Value,
83    ) -> Self {
84        let now = chrono::Utc::now();
85        Self {
86            id: 0,
87            tenant_id,
88            event_type: event_type.into(),
89            source_plugin: source_plugin.into(),
90            payload,
91            delivered: false,
92            delivered_at: None,
93            retry_count: 0,
94            max_retries: 3,
95            created_at: now,
96            updated_at: now,
97        }
98    }
99
100    /// 标记已投递
101    pub fn mark_delivered(&mut self) {
102        self.delivered = true;
103        self.delivered_at = Some(chrono::Utc::now());
104        self.updated_at = chrono::Utc::now();
105    }
106
107    /// 增加重试计数
108    pub fn increment_retry(&mut self) {
109        self.retry_count += 1;
110        self.updated_at = chrono::Utc::now();
111    }
112
113    /// 是否已耗尽重试次数
114    pub fn is_exhausted(&self) -> bool {
115        self.retry_count >= self.max_retries
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122
123    #[test]
124    fn test_sys_event_new() {
125        let event = SysEvent::new(1, "order.created", "shop", serde_json::json!({"id": 1}));
126        assert_eq!(event.tenant_id, 1);
127        assert_eq!(event.event_type, "order.created");
128        assert!(!event.delivered);
129        assert_eq!(event.retry_count, 0);
130        assert_eq!(event.max_retries, 3);
131    }
132
133    #[test]
134    fn test_mark_delivered() {
135        let mut event = SysEvent::new(1, "test", "test", serde_json::json!({}));
136        event.mark_delivered();
137        assert!(event.delivered);
138        assert!(event.delivered_at.is_some());
139    }
140
141    #[test]
142    fn test_retry_exhaustion() {
143        let mut event = SysEvent::new(1, "test", "test", serde_json::json!({}));
144        assert!(!event.is_exhausted());
145        event.increment_retry();
146        event.increment_retry();
147        event.increment_retry();
148        assert!(event.is_exhausted());
149    }
150
151    #[test]
152    fn test_password_hash_skip_serializing() {
153        let user = SysUser {
154            id: 1,
155            tenant_id: 1,
156            username: "admin".to_string(),
157            display_name: "Admin".to_string(),
158            password_hash: "secret_hash".to_string(),
159            email: None,
160            phone: None,
161            status: "active".to_string(),
162            extra: serde_json::json!({}),
163            created_at: chrono::Utc::now(),
164            updated_at: chrono::Utc::now(),
165        };
166        let json = serde_json::to_string(&user).expect("序列化失败");
167        assert!(!json.contains("password_hash"), "敏感字段不应出现在 JSON 中");
168        assert!(!json.contains("secret_hash"), "敏感值不应出现在 JSON 中");
169    }
170}