Skip to main content

privchat_protocol/
notification.rs

1// Copyright 2025 Shanghai Boyu Information Technology Co., Ltd.
2// https://privchat.dev
3//
4// Author: zoujiaqing <zoujiaqing@gmail.com>
5//
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10//     http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use serde::{Deserialize, Serialize};
19
20/// 系统通知类型枚举
21///
22/// 用于各种会话中的系统通知消息,如好友请求、群组操作、红包等
23#[derive(Debug, Clone, Serialize, Deserialize)]
24#[serde(tag = "type", content = "data")]
25pub enum NotificationType {
26    // ========== 好友相关 ==========
27    /// 好友请求已发送
28    FriendRequestSent {
29        request_id: u64,
30        from_user_id: u64,
31        to_user_id: u64,
32        message: String,
33    },
34
35    /// 好友请求被接受
36    FriendRequestAccepted {
37        request_id: u64,
38        user_id: u64,
39        username: String,
40        avatar: Option<String>,
41    },
42
43    /// 好友请求被拒绝
44    FriendRequestRejected { request_id: u64, user_id: u64 },
45
46    /// 好友被删除
47    FriendDeleted { user_id: u64, username: String },
48
49    // ========== 群组相关 ==========
50    /// 群组创建
51    GroupCreated {
52        group_id: u64,
53        group_name: String,
54        creator_id: u64,
55        creator_name: String,
56        member_count: u32,
57    },
58
59    /// 成员加入群组
60    GroupMemberJoined {
61        group_id: u64,
62        group_name: String,
63        user_id: u64,
64        username: String,
65        invited_by: Option<u64>,
66        inviter_name: Option<String>,
67    },
68
69    /// 成员离开群组
70    GroupMemberLeft {
71        group_id: u64,
72        group_name: String,
73        user_id: u64,
74        username: String,
75    },
76
77    /// 成员被踢出群组
78    GroupMemberKicked {
79        group_id: u64,
80        group_name: String,
81        user_id: u64,
82        username: String,
83        kicked_by: u64,
84        kicker_name: String,
85        reason: Option<String>,
86    },
87
88    /// 群组名称修改
89    GroupNameChanged {
90        group_id: u64,
91        old_name: String,
92        new_name: String,
93        changed_by: u64,
94        changer_name: String,
95    },
96
97    /// 群组头像修改
98    GroupAvatarChanged {
99        group_id: u64,
100        group_name: String,
101        changed_by: u64,
102        changer_name: String,
103        new_avatar_url: String,
104    },
105
106    /// 群组公告修改
107    GroupAnnouncementChanged {
108        group_id: u64,
109        group_name: String,
110        announcement: String,
111        changed_by: u64,
112        changer_name: String,
113    },
114
115    /// 群主转让
116    GroupOwnerTransferred {
117        group_id: u64,
118        group_name: String,
119        old_owner_id: u64,
120        old_owner_name: String,
121        new_owner_id: u64,
122        new_owner_name: String,
123    },
124
125    /// 管理员添加
126    GroupAdminAdded {
127        group_id: u64,
128        group_name: String,
129        user_id: u64,
130        username: String,
131        added_by: u64,
132        adder_name: String,
133    },
134
135    /// 管理员移除
136    GroupAdminRemoved {
137        group_id: u64,
138        group_name: String,
139        user_id: u64,
140        username: String,
141        removed_by: u64,
142        remover_name: String,
143    },
144
145    /// 成员被禁言
146    GroupMemberMuted {
147        group_id: u64,
148        group_name: String,
149        user_id: u64,
150        username: String,
151        duration_seconds: u64,
152        muted_by: u64,
153        muter_name: String,
154        reason: Option<String>,
155    },
156
157    /// 成员解除禁言
158    GroupMemberUnmuted {
159        group_id: u64,
160        group_name: String,
161        user_id: u64,
162        username: String,
163        unmuted_by: u64,
164        unmuter_name: String,
165    },
166
167    /// 群组被解散
168    GroupDismissed {
169        group_id: u64,
170        group_name: String,
171        dismissed_by: u64,
172        dismisser_name: String,
173    },
174
175    // ========== 红包相关 ==========
176    /// 红包发送
177    RedPacketSent {
178        red_packet_id: String,
179        from_user_id: u64,
180        from_username: String,
181        total_amount: i64, // 单位:分
182        count: u32,        // 红包个数
183        message: String,   // 祝福语
184        red_packet_type: RedPacketType,
185    },
186
187    /// 红包被领取
188    RedPacketReceived {
189        red_packet_id: String,
190        user_id: u64,
191        username: String,
192        amount: i64, // 领取金额(分)
193        timestamp: i64,
194    },
195
196    /// 红包已抢完
197    RedPacketEmpty {
198        red_packet_id: String,
199        total_received: u32, // 总共被领取数量
200        total_amount: i64,   // 总共被领取金额
201    },
202
203    /// 红包过期
204    RedPacketExpired {
205        red_packet_id: String,
206        remaining_count: u32,  // 剩余个数
207        remaining_amount: i64, // 剩余金额
208    },
209
210    // ========== 消息相关 ==========
211    /// 消息被撤回
212    MessageRevoked {
213        server_message_id: u64,
214        channel_id: u64,
215        revoked_by: u64,
216        revoker_name: String,
217        revoked_at: i64,
218    },
219
220    /// 消息被置顶
221    MessagePinned {
222        server_message_id: u64,
223        channel_id: u64,
224        pinned_by: u64,
225        pinner_name: String,
226        pinned_at: i64,
227    },
228
229    /// 消息取消置顶
230    MessageUnpinned {
231        server_message_id: u64,
232        channel_id: u64,
233        unpinned_by: u64,
234        unpinner_name: String,
235        unpinned_at: i64,
236    },
237
238    /// 消息已读(已读回执)
239    MessageRead {
240        server_message_id: u64,
241        channel_id: u64,
242        reader_id: u64,
243        reader_name: String,
244        read_at: i64,
245    },
246
247    /// 消息被编辑
248    MessageEdited {
249        server_message_id: u64,
250        channel_id: u64,
251        editor_id: u64,
252        editor_name: String,
253        old_content: String,
254        new_content: String,
255        edited_at: i64,
256    },
257
258    // ========== 系统相关 ==========
259    /// 系统维护通知
260    SystemMaintenance {
261        title: String,
262        content: String,
263        start_time: i64,
264        end_time: i64,
265        level: MaintenanceLevel,
266    },
267
268    /// 系统公告
269    SystemAnnouncement {
270        announcement_id: u64,
271        title: String,
272        content: String,
273        level: AnnouncementLevel,
274        published_at: i64,
275    },
276
277    /// 版本更新通知
278    SystemVersionUpdate {
279        version: String,
280        description: String,
281        update_url: String,
282        force_update: bool,
283    },
284}
285
286/// 红包类型
287#[derive(Debug, Clone, Serialize, Deserialize)]
288pub enum RedPacketType {
289    /// 普通红包(固定金额)
290    Normal,
291    /// 拼手气红包(随机金额)
292    Lucky,
293    /// 专属红包(指定接收人)
294    Exclusive { target_user_ids: Vec<u64> },
295}
296
297/// 维护级别
298#[derive(Debug, Clone, Serialize, Deserialize)]
299pub enum MaintenanceLevel {
300    /// 常规维护
301    Normal,
302    /// 紧急维护
303    Urgent,
304    /// 计划维护
305    Scheduled,
306}
307
308/// 公告级别
309#[derive(Debug, Clone, Serialize, Deserialize)]
310pub enum AnnouncementLevel {
311    /// 信息
312    Info,
313    /// 警告
314    Warning,
315    /// 重要
316    Important,
317    /// 紧急
318    Critical,
319}
320
321/// 通知消息结构
322///
323/// 封装通知类型和相关元数据,用于在会话中显示系统通知
324#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct NotificationMessage {
326    /// 通知ID(可选,用于去重)
327    pub notification_id: Option<u64>,
328
329    /// 通知类型
330    pub notification_type: NotificationType,
331
332    /// 显示文本(用于UI展示,可以根据语言本地化)
333    pub display_text: String,
334
335    /// 时间戳
336    pub timestamp: i64,
337
338    /// 所属会话ID
339    pub channel_id: u64,
340
341    /// 会话类型(1=私聊, 2=群聊)
342    pub channel_type: u8,
343
344    /// 是否需要持久化存储
345    pub should_persist: bool,
346
347    /// 额外元数据(可选)
348    pub metadata: Option<serde_json::Value>,
349}
350
351impl NotificationMessage {
352    /// 创建新的通知消息
353    pub fn new(
354        notification_type: NotificationType,
355        display_text: String,
356        channel_id: u64,
357        channel_type: u8,
358    ) -> Self {
359        Self {
360            notification_id: None,
361            notification_type,
362            display_text,
363            timestamp: chrono::Utc::now().timestamp(),
364            channel_id,
365            channel_type,
366            should_persist: true,
367            metadata: None,
368        }
369    }
370
371    /// 设置通知ID
372    pub fn with_notification_id(mut self, id: u64) -> Self {
373        self.notification_id = Some(id);
374        self
375    }
376
377    /// 设置是否持久化
378    pub fn with_persist(mut self, persist: bool) -> Self {
379        self.should_persist = persist;
380        self
381    }
382
383    /// 设置额外元数据
384    pub fn with_metadata(mut self, metadata: serde_json::Value) -> Self {
385        self.metadata = Some(metadata);
386        self
387    }
388
389    /// 获取通知类型的字符串表示
390    pub fn type_str(&self) -> &'static str {
391        match &self.notification_type {
392            NotificationType::FriendRequestSent { .. } => "friend_request_sent",
393            NotificationType::FriendRequestAccepted { .. } => "friend_request_accepted",
394            NotificationType::FriendRequestRejected { .. } => "friend_request_rejected",
395            NotificationType::FriendDeleted { .. } => "friend_deleted",
396            NotificationType::GroupCreated { .. } => "group_created",
397            NotificationType::GroupMemberJoined { .. } => "group_member_joined",
398            NotificationType::GroupMemberLeft { .. } => "group_member_left",
399            NotificationType::GroupMemberKicked { .. } => "group_member_kicked",
400            NotificationType::GroupNameChanged { .. } => "group_name_changed",
401            NotificationType::GroupAvatarChanged { .. } => "group_avatar_changed",
402            NotificationType::GroupAnnouncementChanged { .. } => "group_announcement_changed",
403            NotificationType::GroupOwnerTransferred { .. } => "group_owner_transferred",
404            NotificationType::GroupAdminAdded { .. } => "group_admin_added",
405            NotificationType::GroupAdminRemoved { .. } => "group_admin_removed",
406            NotificationType::GroupMemberMuted { .. } => "group_member_muted",
407            NotificationType::GroupMemberUnmuted { .. } => "group_member_unmuted",
408            NotificationType::GroupDismissed { .. } => "group_dismissed",
409            NotificationType::RedPacketSent { .. } => "red_packet_sent",
410            NotificationType::RedPacketReceived { .. } => "red_packet_received",
411            NotificationType::RedPacketEmpty { .. } => "red_packet_empty",
412            NotificationType::RedPacketExpired { .. } => "red_packet_expired",
413            NotificationType::MessageRevoked { .. } => "message_revoked",
414            NotificationType::MessagePinned { .. } => "message_pinned",
415            NotificationType::MessageUnpinned { .. } => "message_unpinned",
416            NotificationType::MessageRead { .. } => "message_read",
417            NotificationType::MessageEdited { .. } => "message_edited",
418            NotificationType::SystemMaintenance { .. } => "system_maintenance",
419            NotificationType::SystemAnnouncement { .. } => "system_announcement",
420            NotificationType::SystemVersionUpdate { .. } => "system_version_update",
421        }
422    }
423}
424
425/// 辅助函数:生成显示文本
426impl NotificationMessage {
427    /// 根据通知类型生成默认的显示文本(中文)
428    pub fn generate_display_text_cn(notification_type: &NotificationType) -> String {
429        match notification_type {
430            NotificationType::FriendRequestAccepted { username, .. } => {
431                format!("{} 接受了你的好友请求", username)
432            }
433            NotificationType::FriendDeleted { username, .. } => {
434                format!("{} 删除了你的好友关系", username)
435            }
436            NotificationType::GroupMemberJoined {
437                username,
438                inviter_name,
439                ..
440            } => {
441                if let Some(inviter) = inviter_name {
442                    format!("{} 邀请 {} 加入了群聊", inviter, username)
443                } else {
444                    format!("{} 加入了群聊", username)
445                }
446            }
447            NotificationType::GroupMemberLeft { username, .. } => {
448                format!("{} 离开了群聊", username)
449            }
450            NotificationType::GroupMemberKicked {
451                username,
452                kicker_name,
453                reason,
454                ..
455            } => {
456                if let Some(r) = reason {
457                    format!("{} 将 {} 移出了群聊(原因:{})", kicker_name, username, r)
458                } else {
459                    format!("{} 将 {} 移出了群聊", kicker_name, username)
460                }
461            }
462            NotificationType::GroupNameChanged {
463                old_name,
464                new_name,
465                changer_name,
466                ..
467            } => {
468                format!(
469                    "{} 将群名称从「{}」改为「{}」",
470                    changer_name, old_name, new_name
471                )
472            }
473            NotificationType::GroupOwnerTransferred {
474                old_owner_name,
475                new_owner_name,
476                ..
477            } => {
478                format!("{} 将群主转让给 {}", old_owner_name, new_owner_name)
479            }
480            NotificationType::GroupAdminAdded {
481                username,
482                adder_name,
483                ..
484            } => {
485                format!("{} 将 {} 设置为管理员", adder_name, username)
486            }
487            NotificationType::GroupMemberMuted {
488                username,
489                muter_name,
490                duration_seconds,
491                ..
492            } => {
493                let duration_text = format_duration(*duration_seconds);
494                format!("{} 禁言了 {}({})", muter_name, username, duration_text)
495            }
496            NotificationType::RedPacketSent {
497                from_username,
498                message,
499                ..
500            } => {
501                format!("{} 发送了红包「{}」", from_username, message)
502            }
503            NotificationType::RedPacketReceived {
504                username, amount, ..
505            } => {
506                format!("{} 领取了红包({:.2}元)", username, *amount as f64 / 100.0)
507            }
508            NotificationType::RedPacketEmpty { .. } => "红包已被抢完".to_string(),
509            NotificationType::MessageRevoked { revoker_name, .. } => {
510                format!("{} 撤回了一条消息", revoker_name)
511            }
512            NotificationType::MessagePinned { pinner_name, .. } => {
513                format!("{} 置顶了一条消息", pinner_name)
514            }
515            NotificationType::MessageRead { reader_name, .. } => {
516                format!("{} 已读", reader_name)
517            }
518            NotificationType::SystemMaintenance { title, .. } => {
519                format!("系统维护通知:{}", title)
520            }
521            NotificationType::SystemAnnouncement { title, .. } => {
522                format!("系统公告:{}", title)
523            }
524            _ => "系统通知".to_string(),
525        }
526    }
527}
528
529/// 格式化时长
530fn format_duration(seconds: u64) -> String {
531    if seconds < 60 {
532        format!("{}秒", seconds)
533    } else if seconds < 3600 {
534        format!("{}分钟", seconds / 60)
535    } else if seconds < 86400 {
536        format!("{}小时", seconds / 3600)
537    } else {
538        format!("{}天", seconds / 86400)
539    }
540}
541
542#[cfg(test)]
543mod tests {
544    use super::*;
545
546    #[test]
547    fn test_notification_serialization() {
548        let notification = NotificationType::GroupMemberJoined {
549            group_id: 123,
550            group_name: "测试群".to_string(),
551            user_id: 456,
552            username: "张三".to_string(),
553            invited_by: Some(789),
554            inviter_name: Some("李四".to_string()),
555        };
556
557        let json = serde_json::to_string(&notification).unwrap();
558        assert!(json.contains("group_member_joined"));
559        assert!(json.contains("张三"));
560
561        let deserialized: NotificationType = serde_json::from_str(&json).unwrap();
562        assert!(matches!(
563            deserialized,
564            NotificationType::GroupMemberJoined { .. }
565        ));
566    }
567
568    #[test]
569    fn test_display_text_generation() {
570        let notification = NotificationType::GroupMemberJoined {
571            group_id: 123,
572            group_name: "测试群".to_string(),
573            user_id: 456,
574            username: "张三".to_string(),
575            invited_by: Some(789),
576            inviter_name: Some("李四".to_string()),
577        };
578
579        let text = NotificationMessage::generate_display_text_cn(&notification);
580        assert_eq!(text, "李四 邀请 张三 加入了群聊");
581    }
582
583    #[test]
584    fn test_notification_message_builder() {
585        let notification_type = NotificationType::FriendRequestAccepted {
586            request_id: 1,
587            user_id: 123,
588            username: "Alice".to_string(),
589            avatar: None,
590        };
591
592        let msg = NotificationMessage::new(
593            notification_type.clone(),
594            "Alice 接受了你的好友请求".to_string(),
595            456,
596            1,
597        )
598        .with_notification_id(789)
599        .with_persist(true);
600
601        assert_eq!(msg.notification_id, Some(789));
602        assert_eq!(msg.channel_id, 456);
603        assert!(msg.should_persist);
604    }
605
606    #[test]
607    fn test_format_duration() {
608        assert_eq!(format_duration(30), "30秒");
609        assert_eq!(format_duration(120), "2分钟");
610        assert_eq!(format_duration(3600), "1小时");
611        assert_eq!(format_duration(86400), "1天");
612    }
613}