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    /// 主键
15    pub id: i64,
16    /// 租户 ID(多租户隔离)
17    pub tenant_id: i64,
18    /// 登录用户名
19    pub username: String,
20    /// 显示名称
21    pub display_name: String,
22    /// 密码哈希(敏感字段,序列化跳过)
23    #[serde(skip_serializing)]
24    pub password_hash: String,
25    /// 邮箱(可选)
26    pub email: Option<String>,
27    /// 手机号(可选)
28    pub phone: Option<String>,
29    /// 账号状态(active/disabled)
30    pub status: String,
31    /// 插件扩展字段(JSON)
32    pub extra: serde_json::Value,
33    /// 创建时间
34    pub created_at: chrono::DateTime<chrono::Utc>,
35    /// 更新时间
36    pub updated_at: chrono::DateTime<chrono::Utc>,
37}
38
39/// 系统权限(共享 Schema)。
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct SysPermission {
42    /// 主键
43    pub id: i64,
44    /// 租户 ID(多租户隔离)
45    pub tenant_id: i64,
46    /// 权限名称
47    pub name: String,
48    /// 权限描述
49    pub description: String,
50    /// 资源标识(如 order)
51    pub resource: String,
52    /// 操作(create/read/update/delete)
53    pub action: String,
54    /// 数据权限条件(JSON,可选)
55    pub conditions: Option<serde_json::Value>,
56    /// 创建时间
57    pub created_at: chrono::DateTime<chrono::Utc>,
58}
59
60/// 系统事件(共享 Schema)。
61///
62/// 事件总线持久化事件记录,支持至少一次投递。
63/// `delivered`/`delivered_at`/`retry_count` 跟踪投递状态。
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct SysEvent {
66    /// 主键
67    pub id: i64,
68    /// 租户 ID(多租户隔离)
69    pub tenant_id: i64,
70    /// 事件类型(如 order.created)
71    pub event_type: String,
72    /// 来源插件名
73    pub source_plugin: String,
74    /// 事件负载(JSON)
75    pub payload: serde_json::Value,
76    /// 是否已投递
77    pub delivered: bool,
78    /// 投递时间(可选)
79    pub delivered_at: Option<chrono::DateTime<chrono::Utc>>,
80    /// 重试次数
81    pub retry_count: i32,
82    /// 最大重试次数
83    pub max_retries: i32,
84    /// 创建时间
85    pub created_at: chrono::DateTime<chrono::Utc>,
86    /// 更新时间
87    pub updated_at: chrono::DateTime<chrono::Utc>,
88}
89
90impl SysUser {
91    /// 返回系统用户表名
92    pub fn table_name() -> &'static str {
93        "sys_users"
94    }
95}
96
97impl SysPermission {
98    /// 返回系统权限表名
99    pub fn table_name() -> &'static str {
100        "sys_permissions"
101    }
102}
103
104impl SysEvent {
105    /// 返回系统事件表名
106    pub fn table_name() -> &'static str {
107        "sys_events"
108    }
109
110    /// 创建新事件
111    pub fn new(
112        tenant_id: i64,
113        event_type: impl Into<String>,
114        source_plugin: impl Into<String>,
115        payload: serde_json::Value,
116    ) -> Self {
117        let now = chrono::Utc::now();
118        Self {
119            id: 0,
120            tenant_id,
121            event_type: event_type.into(),
122            source_plugin: source_plugin.into(),
123            payload,
124            delivered: false,
125            delivered_at: None,
126            retry_count: 0,
127            max_retries: 3,
128            created_at: now,
129            updated_at: now,
130        }
131    }
132
133    /// 标记已投递
134    pub fn mark_delivered(&mut self) {
135        self.delivered = true;
136        self.delivered_at = Some(chrono::Utc::now());
137        self.updated_at = chrono::Utc::now();
138    }
139
140    /// 增加重试计数
141    pub fn increment_retry(&mut self) {
142        self.retry_count += 1;
143        self.updated_at = chrono::Utc::now();
144    }
145
146    /// 是否已耗尽重试次数
147    pub fn is_exhausted(&self) -> bool {
148        self.retry_count >= self.max_retries
149    }
150}
151
152#[cfg(test)]
153mod tests {
154    use super::*;
155
156    #[test]
157    fn test_sys_event_new() {
158        let event = SysEvent::new(1, "order.created", "shop", serde_json::json!({"id": 1}));
159        assert_eq!(event.tenant_id, 1);
160        assert_eq!(event.event_type, "order.created");
161        assert!(!event.delivered);
162        assert_eq!(event.retry_count, 0);
163        assert_eq!(event.max_retries, 3);
164    }
165
166    #[test]
167    fn test_mark_delivered() {
168        let mut event = SysEvent::new(1, "test", "test", serde_json::json!({}));
169        event.mark_delivered();
170        assert!(event.delivered);
171        assert!(event.delivered_at.is_some());
172    }
173
174    #[test]
175    fn test_retry_exhaustion() {
176        let mut event = SysEvent::new(1, "test", "test", serde_json::json!({}));
177        assert!(!event.is_exhausted());
178        event.increment_retry();
179        event.increment_retry();
180        event.increment_retry();
181        assert!(event.is_exhausted());
182    }
183
184    #[test]
185    fn test_password_hash_skip_serializing() {
186        let user = SysUser {
187            id: 1,
188            tenant_id: 1,
189            username: "admin".to_string(),
190            display_name: "Admin".to_string(),
191            password_hash: "secret_hash".to_string(),
192            email: None,
193            phone: None,
194            status: "active".to_string(),
195            extra: serde_json::json!({}),
196            created_at: chrono::Utc::now(),
197            updated_at: chrono::Utc::now(),
198        };
199        let json = serde_json::to_string(&user).expect("序列化失败");
200        assert!(
201            !json.contains("password_hash"),
202            "敏感字段不应出现在 JSON 中"
203        );
204        assert!(!json.contains("secret_hash"), "敏感值不应出现在 JSON 中");
205    }
206}