package service import ( "encoding/json" "sync" "time" "git.zkcoi.com/zkcoi/meshray/internal/model" "github.com/gin-gonic/gin" "go.uber.org/zap" "gorm.io/gorm" ) // NotificationService 通知服务 type NotificationService struct { db *gorm.DB logger *zap.Logger clients map[uint]*NotificationClient // userID -> client mu sync.RWMutex broadcastCh chan NotificationMessage } // NotificationClient WebSocket 通知客户端 type NotificationClient struct { userID uint username string conn *gin.Context msgCh chan NotificationMessage done chan struct{} } // NotificationMessage 通知消息 type NotificationMessage struct { Type string `json:"type"` // alert, system, update, ddns Priority int `json:"priority"` // 1=low, 2=medium, 3=high Title string `json:"title"` Message string `json:"message"` Data map[string]interface{} `json:"data,omitempty"` Timestamp time.Time `json:"timestamp"` } // NewNotificationService 创建通知服务 func NewNotificationService(db *gorm.DB, logger *zap.Logger) *NotificationService { svc := &NotificationService{ db: db, logger: logger, clients: make(map[uint]*NotificationClient), broadcastCh: make(chan NotificationMessage, 100), } // 启动广播协程 go svc.runBroadcaster() return svc } // runBroadcaster 运行广播协程 func (s *NotificationService) runBroadcaster() { for msg := range s.broadcastCh { s.mu.RLock() for _, client := range s.clients { select { case client.msgCh <- msg: // 发送成功 default: // 通道已满,跳过 s.logger.Warn("通知通道已满", zap.Uint("user_id", client.userID)) } } s.mu.RUnlock() } } // RegisterClient 注册通知客户端 func (s *NotificationService) RegisterClient(userID uint, username string, msgCh chan NotificationMessage, done chan struct{}) { s.mu.Lock() defer s.mu.Unlock() client := &NotificationClient{ userID: userID, username: username, msgCh: msgCh, done: done, } s.clients[userID] = client s.logger.Info("用户已连接通知服务", zap.Uint("user_id", userID), zap.String("username", username)) } // UnregisterClient 注销通知客户端 func (s *NotificationService) UnregisterClient(userID uint) { s.mu.Lock() defer s.mu.Unlock() if client, ok := s.clients[userID]; ok { close(client.done) delete(s.clients, userID) s.logger.Info("用户已断开通知服务", zap.Uint("user_id", userID)) } } // SendToUser 发送通知给指定用户(并保存到数据库) func (s *NotificationService) SendToUser(userID uint, msg NotificationMessage) { // 1. 保存到数据库 notification := model.Notification{ UserID: userID, Type: msg.Type, Priority: msg.Priority, Title: msg.Title, Message: msg.Message, } if msg.Data != nil { dataJSON, _ := json.Marshal(msg.Data) notification.Data = string(dataJSON) } if err := s.db.Create(¬ification).Error; err != nil { s.logger.Error("保存通知失败", zap.Error(err)) } // 2. 发送到 WebSocket 通道 s.mu.RLock() defer s.mu.RUnlock() if client, ok := s.clients[userID]; ok { select { case client.msgCh <- msg: s.logger.Debug("通知已发送给用户", zap.Uint("user_id", userID), zap.String("type", msg.Type)) default: s.logger.Warn("用户通知通道已满", zap.Uint("user_id", userID)) } } } // Broadcast 广播通知给所有在线用户(并保存到数据库) func (s *NotificationService) Broadcast(msg NotificationMessage) { msg.Timestamp = time.Now() // 保存到所有用户的数据库记录 s.mu.RLock() for userID := range s.clients { notification := model.Notification{ UserID: userID, Type: msg.Type, Priority: msg.Priority, Title: msg.Title, Message: msg.Message, } if msg.Data != nil { dataJSON, _ := json.Marshal(msg.Data) notification.Data = string(dataJSON) } s.db.Create(¬ification) } s.mu.RUnlock() // 发送到 WebSocket 通道 s.broadcastCh <- msg s.logger.Debug("通知已广播", zap.String("type", msg.Type), zap.Int("online_users", len(s.clients))) } // SendAlert 发送告警通知 func (s *NotificationService) SendAlert(userID uint, title, message string, data map[string]interface{}) { msg := NotificationMessage{ Type: "alert", Priority: 3, // high priority Title: title, Message: message, Data: data, Timestamp: time.Now(), } s.SendToUser(userID, msg) } // SendSystemNotification 发送系统通知 func (s *NotificationService) SendSystemNotification(userID uint, title, message string) { msg := NotificationMessage{ Type: "system", Priority: 2, // medium priority Title: title, Message: message, Timestamp: time.Now(), } s.SendToUser(userID, msg) } // SendUpdateAvailable 发送更新可用通知 func (s *NotificationService) SendUpdateAvailable(version, notes, downloadURL string) { msg := NotificationMessage{ Type: "update", Priority: 2, Title: "发现新版本", Message: version, Data: map[string]interface{}{ "version": version, "notes": notes, "download_url": downloadURL, }, Timestamp: time.Now(), } s.Broadcast(msg) } // SendDDNSUpdate 发送 DDNS 更新通知 func (s *NotificationService) SendDDNSUpdate(serviceName, oldIP, newIP string) { data, _ := json.Marshal(gin.H{ "service_name": serviceName, "old_ip": oldIP, "new_ip": newIP, }) var dataMap map[string]interface{} json.Unmarshal(data, &dataMap) msg := NotificationMessage{ Type: "ddns", Priority: 1, // low priority Title: "DDNS IP 已更新", Message: serviceName, Data: dataMap, Timestamp: time.Now(), } s.Broadcast(msg) } // GetOnlineUserCount 获取在线用户数 func (s *NotificationService) GetOnlineUserCount() int { s.mu.RLock() defer s.mu.RUnlock() return len(s.clients) } // GetDB 返回数据库实例(用于 Handler 层查询) func (s *NotificationService) GetDB() *gorm.DB { return s.db }