244 lines
6.0 KiB
Go
244 lines
6.0 KiB
Go
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
|
|
}
|