Files
MeBox/internal/service/notify_svc.go
T

187 lines
5.4 KiB
Go

// Package service — 通知服务事件分发引擎。
//
// NotifyService 管理所有通知渠道,根据事件类型将通知分发给
// 订阅了该事件的渠道。支持 4 种内置事件类型和 5 种通知渠道。
package service
import (
"context"
"encoding/json"
"sync"
"go.uber.org/zap"
"github.com/ShukeBta/MediaStationGo/internal/model"
"github.com/ShukeBta/MediaStationGo/internal/repository"
)
// 通知事件类型常量。
const (
EventSubscriptionHit = "subscription_hit"
EventDownloadComplete = "download_complete"
EventScrapeFailed = "scrape_failed"
EventSystemAlert = "system_alert"
EventLibraryIngest = "library_ingest"
)
// NotifyEvent 是通知事件的数据结构。
type NotifyEvent struct {
Type string `json:"type"`
Title string `json:"title"`
Message string `json:"message"`
Data map[string]interface{} `json:"data,omitempty"`
}
// NotifyProvider 定义通知渠道的发送接口。
type NotifyProvider interface {
Send(ctx context.Context, cfg map[string]string, event NotifyEvent) error
ValidateConfig(cfg map[string]string) error
}
// NotifyService 是事件驱动的通知分发引擎。
type NotifyService struct {
log *zap.Logger
repo *repository.Container
crypto *CryptoService
mu sync.RWMutex
providers map[string]NotifyProvider // type -> provider
}
// NewNotifyService 创建通知服务。
func NewNotifyService(log *zap.Logger, repo *repository.Container, crypto *CryptoService) *NotifyService {
ns := &NotifyService{
log: log,
repo: repo,
crypto: crypto,
providers: make(map[string]NotifyProvider),
}
// 注册内置 Provider
ns.registerProviders()
return ns
}
// registerProviders 注册所有内置通知 Provider。
func (s *NotifyService) registerProviders() {
s.mu.Lock()
defer s.mu.Unlock()
s.providers["telegram"] = &TelegramProvider{}
s.providers["wechat"] = &WechatProvider{}
s.providers["bark"] = &BarkProvider{}
s.providers["webhook"] = &WebhookProvider{}
s.providers["email"] = &EmailProvider{}
}
// Dispatch 将事件分发给所有订阅了该事件类型的已启用渠道。
func (s *NotifyService) Dispatch(ctx context.Context, event NotifyEvent) {
channels, err := s.repo.NotifyChannel.ListByEvent(ctx, event.Type)
if err != nil {
s.log.Error("failed to list channels for event",
zap.String("event", event.Type),
zap.Error(err),
)
return
}
for _, ch := range channels {
go func(channel model.NotifyChannel) {
if sendErr := s.sendToChannel(ctx, channel, event); sendErr != nil {
s.log.Error("failed to send notification",
zap.String("channel", channel.Name),
zap.String("type", channel.Type),
zap.String("event", event.Type),
zap.Error(sendErr),
)
}
}(ch)
}
}
// SendTest 向指定渠道发送测试通知。
func (s *NotifyService) SendTest(ctx context.Context, channelID string) error {
ch, err := s.repo.NotifyChannel.FindByID(ctx, channelID)
if err != nil {
return err
}
if ch == nil {
return ErrNotifyChannelNotFound
}
testEvent := NotifyEvent{
Type: "test",
Title: "MediaStationGo 测试通知",
Message: "这是一条测试通知,如果您看到此消息,说明通知渠道配置正确。",
}
return s.sendToChannel(ctx, *ch, testEvent)
}
// ValidateChannelConfig 验证渠道配置是否合法。
func (s *NotifyService) ValidateChannelConfig(channelType string, config map[string]string) error {
s.mu.RLock()
provider, ok := s.providers[channelType]
s.mu.RUnlock()
if !ok {
return ErrUnknownNotifyType
}
return provider.ValidateConfig(config)
}
// GetProviderTypes 返回支持的通知渠道类型列表。
func (s *NotifyService) GetProviderTypes() []NotifyProviderInfo {
return []NotifyProviderInfo{
{Type: "telegram", Name: "Telegram", Description: "通过 Telegram Bot 发送消息"},
{Type: "wechat", Name: "Server酱", Description: "通过 Server酱 推送到微信"},
{Type: "bark", Name: "Bark", Description: "通过 Bark 推送到 iOS"},
{Type: "webhook", Name: "Webhook", Description: "通过自定义 HTTP Webhook 发送"},
{Type: "email", Name: "Email", Description: "通过 SMTP 发送邮件"},
}
}
// NotifyProviderInfo 描述通知渠道类型信息。
type NotifyProviderInfo struct {
Type string `json:"type"`
Name string `json:"name"`
Description string `json:"description"`
}
// sendToChannel 解密渠道配置并通过对应的 Provider 发送通知。
func (s *NotifyService) sendToChannel(ctx context.Context, channel model.NotifyChannel, event NotifyEvent) error {
// 解密配置
configStr := channel.Config
if s.crypto != nil && configStr != "" {
configStr = s.crypto.Decrypt(configStr)
}
var cfg map[string]string
if err := json.Unmarshal([]byte(configStr), &cfg); err != nil {
return err
}
s.mu.RLock()
provider, ok := s.providers[channel.Type]
s.mu.RUnlock()
if !ok {
return ErrUnknownNotifyType
}
return provider.Send(ctx, cfg, event)
}
// 通知服务错误定义。
var (
ErrNotifyChannelNotFound = &NotifyError{Code: "CHANNEL_NOT_FOUND", Message: "notification channel not found"}
ErrUnknownNotifyType = &NotifyError{Code: "UNKNOWN_TYPE", Message: "unknown notification type"}
)
// NotifyError 是通知服务专用错误类型。
type NotifyError struct {
Code string `json:"code"`
Message string `json:"message"`
}
// Error 实现 error 接口。
func (e *NotifyError) Error() string {
return e.Message
}