Files
Meshray-Manager/internal/service/notification.go
T
zkcoi e9ca2f7d70 fix: rename manager module to git.zkcoi.com/zkcoi/Meshray-Manager
- go.mod module path matches repo zkcoi/Meshray-Manager (case-distinct from zkcoi/Meshray/core)
- rewrite internal imports meshray/{internal,web,pkg} -> Meshray-Manager/... (core refs kept)
- sync README.md / install.sh repo URLs; add CHANGELOG entry
2026-07-15 16:25:46 +08:00

244 lines
6.0 KiB
Go

package service
import (
"encoding/json"
"sync"
"time"
"git.zkcoi.com/zkcoi/Meshray-Manager/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(&notification).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(&notification)
}
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
}