Initial commit
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user