// erp-diary 事件定义 // // DiaryEvent 是日记模块的领域事件枚举,提供类型安全的事件构建。 // 通过 `to_domain_event(tenant_id)` 转换为基座 DomainEvent 后发布到 EventBus。 // // 使用方式(Service 层): // use crate::event::DiaryEvent; // let evt = DiaryEvent::JournalCreated { journal_id, author_id, class_id }; // event_bus.publish(evt.to_domain_event(tenant_id), db).await; use serde_json::json; use uuid::Uuid; use erp_core::events::DomainEvent; /// 日记模块领域事件 #[derive(Debug, Clone)] pub enum DiaryEvent { /// 日记创建 JournalCreated { journal_id: Uuid, author_id: Uuid, class_id: Option, }, /// 日记更新 JournalUpdated { journal_id: Uuid, author_id: Uuid, version: i32, }, /// 日记删除 JournalDeleted { journal_id: Uuid, author_id: Uuid, }, /// 日记分享到班级 JournalShared { journal_id: Uuid, author_id: Uuid, class_id: Uuid, }, /// 班级创建 ClassCreated { class_id: Uuid, teacher_id: Uuid, }, /// 学生加入班级 StudentJoinedClass { class_id: Uuid, student_id: Uuid, }, /// 老师布置主题 TopicAssigned { topic_id: Uuid, class_id: Uuid, teacher_id: Uuid, }, /// 老师点评 CommentCreated { comment_id: Uuid, journal_id: Uuid, teacher_id: Uuid, student_id: Uuid, }, /// 家长绑定孩子 ParentBound { parent_id: Uuid, child_id: Uuid, }, /// 成就解锁 AchievementUnlocked { user_id: Uuid, achievement_id: String, }, } impl DiaryEvent { /// 返回事件类型字符串(用于 DomainEvent.event_type) pub fn event_type(&self) -> &'static str { match self { Self::JournalCreated { .. } => "diary.created", Self::JournalUpdated { .. } => "diary.updated", Self::JournalDeleted { .. } => "diary.deleted", Self::JournalShared { .. } => "diary.shared", Self::ClassCreated { .. } => "diary.class.created", Self::StudentJoinedClass { .. } => "diary.class.student_joined", Self::TopicAssigned { .. } => "diary.topic.assigned", Self::CommentCreated { .. } => "diary.comment.created", Self::ParentBound { .. } => "diary.parent.binding_confirmed", Self::AchievementUnlocked { .. } => "diary.achievement.unlocked", } } /// 返回事件 payload(JSON 格式) pub fn payload(&self) -> serde_json::Value { match self { Self::JournalCreated { journal_id, author_id, class_id, } => json!({ "journal_id": journal_id, "author_id": author_id, "class_id": class_id, }), Self::JournalUpdated { journal_id, author_id, version, } => json!({ "journal_id": journal_id, "author_id": author_id, "version": version, }), Self::JournalDeleted { journal_id, author_id, } => json!({ "journal_id": journal_id, "author_id": author_id, }), Self::JournalShared { journal_id, author_id, class_id, } => json!({ "journal_id": journal_id, "author_id": author_id, "class_id": class_id, }), Self::ClassCreated { class_id, teacher_id, } => json!({ "class_id": class_id, "teacher_id": teacher_id, }), Self::StudentJoinedClass { class_id, student_id, } => json!({ "class_id": class_id, "student_id": student_id, }), Self::TopicAssigned { topic_id, class_id, teacher_id, } => json!({ "topic_id": topic_id, "class_id": class_id, "teacher_id": teacher_id, }), Self::CommentCreated { comment_id, journal_id, teacher_id, student_id, } => json!({ "comment_id": comment_id, "journal_id": journal_id, "teacher_id": teacher_id, "student_id": student_id, }), Self::ParentBound { parent_id, child_id, } => json!({ "parent_id": parent_id, "child_id": child_id, }), Self::AchievementUnlocked { user_id, achievement_id, } => json!({ "user_id": user_id, "achievement_id": achievement_id, }), } } /// 转换为基座 DomainEvent,可直接发布到 EventBus pub fn to_domain_event(&self, tenant_id: Uuid) -> DomainEvent { DomainEvent::new(self.event_type(), tenant_id, self.payload()) } } #[cfg(test)] mod tests { use super::*; #[test] fn journal_created_event_type() { let id = Uuid::now_v7(); let evt = DiaryEvent::JournalCreated { journal_id: id, author_id: id, class_id: None, }; assert_eq!(evt.event_type(), "diary.created"); assert_eq!(evt.payload()["journal_id"], id.to_string()); } #[test] fn class_created_event_type() { let id = Uuid::now_v7(); let evt = DiaryEvent::ClassCreated { class_id: id, teacher_id: id, }; assert_eq!(evt.event_type(), "diary.class.created"); } #[test] fn to_domain_event_preserves_fields() { let tid = Uuid::now_v7(); let jid = Uuid::now_v7(); let aid = Uuid::now_v7(); let de = DiaryEvent::JournalCreated { journal_id: jid, author_id: aid, class_id: Some(tid), } .to_domain_event(tid); assert_eq!(de.event_type, "diary.created"); assert_eq!(de.tenant_id, tid); assert_eq!(de.payload["journal_id"], jid.to_string()); assert_eq!(de.payload["author_id"], aid.to_string()); assert_eq!(de.payload["class_id"], tid.to_string()); } #[test] fn all_variants_have_correct_event_type() { let id = Uuid::now_v7(); let variants: Vec = vec![ DiaryEvent::JournalCreated { journal_id: id, author_id: id, class_id: None }, DiaryEvent::JournalUpdated { journal_id: id, author_id: id, version: 1 }, DiaryEvent::JournalDeleted { journal_id: id, author_id: id }, DiaryEvent::JournalShared { journal_id: id, author_id: id, class_id: id }, DiaryEvent::ClassCreated { class_id: id, teacher_id: id }, DiaryEvent::StudentJoinedClass { class_id: id, student_id: id }, DiaryEvent::TopicAssigned { topic_id: id, class_id: id, teacher_id: id }, DiaryEvent::CommentCreated { comment_id: id, journal_id: id, teacher_id: id, student_id: id }, DiaryEvent::ParentBound { parent_id: id, child_id: id }, DiaryEvent::AchievementUnlocked { user_id: id, achievement_id: "first_diary".into() }, ]; let types: Vec<&str> = variants.iter().map(|v| v.event_type()).collect(); assert!(types.contains(&"diary.created")); assert!(types.contains(&"diary.updated")); assert!(types.contains(&"diary.deleted")); assert!(types.contains(&"diary.shared")); assert!(types.contains(&"diary.class.created")); assert!(types.contains(&"diary.class.student_joined")); assert!(types.contains(&"diary.topic.assigned")); assert!(types.contains(&"diary.comment.created")); assert!(types.contains(&"diary.parent.binding_confirmed")); assert!(types.contains(&"diary.achievement.unlocked")); } }