issue_22 #41
@@ -90,6 +90,7 @@ func NewApplication(configPath string) (*Application, error) {
 | 
				
			|||||||
	pigSickPigLogRepo := repository.NewGormPigSickLogRepository(storage.GetDB())
 | 
						pigSickPigLogRepo := repository.NewGormPigSickLogRepository(storage.GetDB())
 | 
				
			||||||
	medicationLogRepo := repository.NewGormMedicationLogRepository(storage.GetDB())
 | 
						medicationLogRepo := repository.NewGormMedicationLogRepository(storage.GetDB())
 | 
				
			||||||
	rawMaterialRepo := repository.NewGormRawMaterialRepository(storage.GetDB())
 | 
						rawMaterialRepo := repository.NewGormRawMaterialRepository(storage.GetDB())
 | 
				
			||||||
 | 
						notificationRepo := repository.NewGormNotificationRepository(storage.GetDB())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 初始化事务管理器
 | 
						// 初始化事务管理器
 | 
				
			||||||
	unitOfWork := repository.NewGormUnitOfWork(storage.GetDB(), logger)
 | 
						unitOfWork := repository.NewGormUnitOfWork(storage.GetDB(), logger)
 | 
				
			||||||
@@ -123,7 +124,7 @@ func NewApplication(configPath string) (*Application, error) {
 | 
				
			|||||||
	auditService := audit.NewService(userActionLogRepo, logger)
 | 
						auditService := audit.NewService(userActionLogRepo, logger)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 初始化通知服务
 | 
						// 初始化通知服务
 | 
				
			||||||
	notifyService, err := initNotifyService(cfg.Notify, logger, userRepo)
 | 
						notifyService, err := initNotifyService(cfg.Notify, logger, userRepo, notificationRepo)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, fmt.Errorf("初始化通知服务失败: %w", err)
 | 
							return nil, fmt.Errorf("初始化通知服务失败: %w", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -220,6 +221,7 @@ func initNotifyService(
 | 
				
			|||||||
	cfg config.NotifyConfig,
 | 
						cfg config.NotifyConfig,
 | 
				
			||||||
	log *logs.Logger,
 | 
						log *logs.Logger,
 | 
				
			||||||
	userRepo repository.UserRepository,
 | 
						userRepo repository.UserRepository,
 | 
				
			||||||
 | 
						notificationRepo repository.NotificationRepository,
 | 
				
			||||||
) (domain_notify.Service, error) {
 | 
					) (domain_notify.Service, error) {
 | 
				
			||||||
	var availableNotifiers []notify.Notifier
 | 
						var availableNotifiers []notify.Notifier
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -278,13 +280,14 @@ func initNotifyService(
 | 
				
			|||||||
		log.Warnf("配置的首选渠道 '%s' 未启用或未指定,已自动降级使用 '%s' 作为首选渠道。", cfg.Primary, primaryNotifier.Type())
 | 
							log.Warnf("配置的首选渠道 '%s' 未启用或未指定,已自动降级使用 '%s' 作为首选渠道。", cfg.Primary, primaryNotifier.Type())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 4. 使用创建的 Notifier 列表来组装领域服务
 | 
						// 4. 使用创建的 Notifier 列表和 notificationRepo 来组装领域服务
 | 
				
			||||||
	notifyService, err := domain_notify.NewFailoverService(
 | 
						notifyService, err := domain_notify.NewFailoverService(
 | 
				
			||||||
		log,
 | 
							log,
 | 
				
			||||||
		userRepo,
 | 
							userRepo,
 | 
				
			||||||
		availableNotifiers,
 | 
							availableNotifiers,
 | 
				
			||||||
		primaryNotifier.Type(),
 | 
							primaryNotifier.Type(),
 | 
				
			||||||
		cfg.FailureThreshold,
 | 
							cfg.FailureThreshold,
 | 
				
			||||||
 | 
							notificationRepo,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, fmt.Errorf("创建故障转移通知服务失败: %w", err)
 | 
							return nil, fmt.Errorf("创建故障转移通知服务失败: %w", err)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,6 +33,7 @@ type failoverService struct {
 | 
				
			|||||||
	primaryNotifier  notify.Notifier
 | 
						primaryNotifier  notify.Notifier
 | 
				
			||||||
	failureThreshold int
 | 
						failureThreshold int
 | 
				
			||||||
	failureCounters  *sync.Map // 使用 sync.Map 来安全地并发读写失败计数, key: userID (uint), value: counter (int)
 | 
						failureCounters  *sync.Map // 使用 sync.Map 来安全地并发读写失败计数, key: userID (uint), value: counter (int)
 | 
				
			||||||
 | 
						notificationRepo repository.NotificationRepository
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewFailoverService 创建一个新的故障转移通知服务
 | 
					// NewFailoverService 创建一个新的故障转移通知服务
 | 
				
			||||||
@@ -42,6 +43,7 @@ func NewFailoverService(
 | 
				
			|||||||
	notifiers []notify.Notifier,
 | 
						notifiers []notify.Notifier,
 | 
				
			||||||
	primaryNotifierType notify.NotifierType,
 | 
						primaryNotifierType notify.NotifierType,
 | 
				
			||||||
	failureThreshold int,
 | 
						failureThreshold int,
 | 
				
			||||||
 | 
						notificationRepo repository.NotificationRepository,
 | 
				
			||||||
) (Service, error) {
 | 
					) (Service, error) {
 | 
				
			||||||
	notifierMap := make(map[notify.NotifierType]notify.Notifier)
 | 
						notifierMap := make(map[notify.NotifierType]notify.Notifier)
 | 
				
			||||||
	for _, n := range notifiers {
 | 
						for _, n := range notifiers {
 | 
				
			||||||
@@ -60,6 +62,7 @@ func NewFailoverService(
 | 
				
			|||||||
		primaryNotifier:  primaryNotifier,
 | 
							primaryNotifier:  primaryNotifier,
 | 
				
			||||||
		failureThreshold: failureThreshold,
 | 
							failureThreshold: failureThreshold,
 | 
				
			||||||
		failureCounters:  &sync.Map{},
 | 
							failureCounters:  &sync.Map{},
 | 
				
			||||||
 | 
							notificationRepo: notificationRepo,
 | 
				
			||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -128,11 +131,15 @@ func (s *failoverService) sendAlarmToUser(userID uint, content notify.AlarmConte
 | 
				
			|||||||
		primaryType := s.primaryNotifier.Type()
 | 
							primaryType := s.primaryNotifier.Type()
 | 
				
			||||||
		addr := getAddressForNotifier(primaryType, user.Contact)
 | 
							addr := getAddressForNotifier(primaryType, user.Contact)
 | 
				
			||||||
		if addr == "" {
 | 
							if addr == "" {
 | 
				
			||||||
 | 
								// 记录跳过通知
 | 
				
			||||||
 | 
								s.recordNotificationAttempt(userID, primaryType, content, "", models.NotificationStatusSkipped, fmt.Errorf("用户未配置首选通知方式 '%s' 的地址", primaryType))
 | 
				
			||||||
			return fmt.Errorf("用户未配置首选通知方式 '%s' 的地址", primaryType)
 | 
								return fmt.Errorf("用户未配置首选通知方式 '%s' 的地址", primaryType)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		err = s.primaryNotifier.Send(content, addr)
 | 
							err = s.primaryNotifier.Send(content, addr)
 | 
				
			||||||
		if err == nil {
 | 
							if err == nil {
 | 
				
			||||||
 | 
								// 记录成功通知
 | 
				
			||||||
 | 
								s.recordNotificationAttempt(userID, primaryType, content, addr, models.NotificationStatusSuccess, nil)
 | 
				
			||||||
			if failureCount > 0 {
 | 
								if failureCount > 0 {
 | 
				
			||||||
				s.log.Infow("首选渠道发送恢复正常", "userID", userID, "notifierType", primaryType)
 | 
									s.log.Infow("首选渠道发送恢复正常", "userID", userID, "notifierType", primaryType)
 | 
				
			||||||
				s.failureCounters.Store(userID, 0)
 | 
									s.failureCounters.Store(userID, 0)
 | 
				
			||||||
@@ -140,6 +147,8 @@ func (s *failoverService) sendAlarmToUser(userID uint, content notify.AlarmConte
 | 
				
			|||||||
			return nil
 | 
								return nil
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 记录失败通知
 | 
				
			||||||
 | 
							s.recordNotificationAttempt(userID, primaryType, content, addr, models.NotificationStatusFailed, err)
 | 
				
			||||||
		newFailureCount := failureCount + 1
 | 
							newFailureCount := failureCount + 1
 | 
				
			||||||
		s.failureCounters.Store(userID, newFailureCount)
 | 
							s.failureCounters.Store(userID, newFailureCount)
 | 
				
			||||||
		s.log.Warnw("首选渠道发送失败", "userID", userID, "notifierType", primaryType, "error", err, "failureCount", newFailureCount)
 | 
							s.log.Warnw("首选渠道发送失败", "userID", userID, "notifierType", primaryType, "error", err, "failureCount", newFailureCount)
 | 
				
			||||||
@@ -152,13 +161,19 @@ func (s *failoverService) sendAlarmToUser(userID uint, content notify.AlarmConte
 | 
				
			|||||||
		for _, notifier := range s.notifiers {
 | 
							for _, notifier := range s.notifiers {
 | 
				
			||||||
			addr := getAddressForNotifier(notifier.Type(), user.Contact)
 | 
								addr := getAddressForNotifier(notifier.Type(), user.Contact)
 | 
				
			||||||
			if addr == "" {
 | 
								if addr == "" {
 | 
				
			||||||
 | 
									// 记录跳过通知
 | 
				
			||||||
 | 
									s.recordNotificationAttempt(userID, notifier.Type(), content, "", models.NotificationStatusSkipped, fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifier.Type()))
 | 
				
			||||||
				continue
 | 
									continue
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if err := notifier.Send(content, addr); err == nil {
 | 
								if err := notifier.Send(content, addr); err == nil {
 | 
				
			||||||
 | 
									// 记录成功通知
 | 
				
			||||||
 | 
									s.recordNotificationAttempt(userID, notifier.Type(), content, addr, models.NotificationStatusSuccess, nil)
 | 
				
			||||||
				s.log.Infow("广播通知成功", "userID", userID, "notifierType", notifier.Type())
 | 
									s.log.Infow("广播通知成功", "userID", userID, "notifierType", notifier.Type())
 | 
				
			||||||
				s.failureCounters.Store(userID, 0)
 | 
									s.failureCounters.Store(userID, 0)
 | 
				
			||||||
				return nil
 | 
									return nil
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								// 记录失败通知
 | 
				
			||||||
 | 
								s.recordNotificationAttempt(userID, notifier.Type(), content, addr, models.NotificationStatusFailed, err)
 | 
				
			||||||
			lastErr = err
 | 
								lastErr = err
 | 
				
			||||||
			s.log.Warnw("广播通知:渠道发送失败", "userID", userID, "notifierType", notifier.Type(), "error", err)
 | 
								s.log.Warnw("广播通知:渠道发送失败", "userID", userID, "notifierType", notifier.Type(), "error", err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -185,6 +200,13 @@ func (s *failoverService) SendTestMessage(userID uint, notifierType notify.Notif
 | 
				
			|||||||
	addr := getAddressForNotifier(notifierType, user.Contact)
 | 
						addr := getAddressForNotifier(notifierType, user.Contact)
 | 
				
			||||||
	if addr == "" {
 | 
						if addr == "" {
 | 
				
			||||||
		s.log.Warnw("发送测试消息失败:缺少地址", "userID", userID, "notifierType", notifierType)
 | 
							s.log.Warnw("发送测试消息失败:缺少地址", "userID", userID, "notifierType", notifierType)
 | 
				
			||||||
 | 
							// 记录跳过通知
 | 
				
			||||||
 | 
							s.recordNotificationAttempt(userID, notifierType, notify.AlarmContent{
 | 
				
			||||||
 | 
								Title:     "通知服务测试",
 | 
				
			||||||
 | 
								Message:   fmt.Sprintf("这是一条来自【%s】渠道的测试消息。如果您收到此消息,说明您的配置正确。", notifierType),
 | 
				
			||||||
 | 
								Level:     zap.InfoLevel,
 | 
				
			||||||
 | 
								Timestamp: time.Now(),
 | 
				
			||||||
 | 
							}, "", models.NotificationStatusFailed, fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifierType))
 | 
				
			||||||
		return fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifierType)
 | 
							return fmt.Errorf("用户未配置通知方式 '%s' 的地址", notifierType)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -199,10 +221,14 @@ func (s *failoverService) SendTestMessage(userID uint, notifierType notify.Notif
 | 
				
			|||||||
	err = notifier.Send(testContent, addr)
 | 
						err = notifier.Send(testContent, addr)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		s.log.Errorw("发送测试消息失败", "userID", userID, "notifierType", notifierType, "error", err)
 | 
							s.log.Errorw("发送测试消息失败", "userID", userID, "notifierType", notifierType, "error", err)
 | 
				
			||||||
 | 
							// 记录失败通知
 | 
				
			||||||
 | 
							s.recordNotificationAttempt(userID, notifierType, testContent, addr, models.NotificationStatusFailed, err)
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	s.log.Infow("发送测试消息成功", "userID", userID, "notifierType", notifierType)
 | 
						s.log.Infow("发送测试消息成功", "userID", userID, "notifierType", notifierType)
 | 
				
			||||||
 | 
						// 记录成功通知
 | 
				
			||||||
 | 
						s.recordNotificationAttempt(userID, notifierType, testContent, addr, models.NotificationStatusSuccess, nil)
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -221,3 +247,46 @@ func getAddressForNotifier(notifierType notify.NotifierType, contact models.Cont
 | 
				
			|||||||
		return ""
 | 
							return ""
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// recordNotificationAttempt 记录一次通知发送尝试的结果
 | 
				
			||||||
 | 
					// userID: 接收通知的用户ID
 | 
				
			||||||
 | 
					// notifierType: 使用的通知器类型
 | 
				
			||||||
 | 
					// content: 通知内容
 | 
				
			||||||
 | 
					// toAddress: 实际发送到的地址
 | 
				
			||||||
 | 
					// status: 发送尝试的状态 (成功、失败、跳过)
 | 
				
			||||||
 | 
					// err: 如果发送失败,记录的错误信息
 | 
				
			||||||
 | 
					func (s *failoverService) recordNotificationAttempt(
 | 
				
			||||||
 | 
						userID uint,
 | 
				
			||||||
 | 
						notifierType notify.NotifierType,
 | 
				
			||||||
 | 
						content notify.AlarmContent,
 | 
				
			||||||
 | 
						toAddress string,
 | 
				
			||||||
 | 
						status models.NotificationStatus,
 | 
				
			||||||
 | 
						err error,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
						errorMessage := ""
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							errorMessage = err.Error()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						notification := &models.Notification{
 | 
				
			||||||
 | 
							NotifierType:   notifierType,
 | 
				
			||||||
 | 
							UserID:         userID,
 | 
				
			||||||
 | 
							Title:          content.Title,
 | 
				
			||||||
 | 
							Message:        content.Message,
 | 
				
			||||||
 | 
							Level:          content.Level,
 | 
				
			||||||
 | 
							AlarmTimestamp: content.Timestamp,
 | 
				
			||||||
 | 
							ToAddress:      toAddress,
 | 
				
			||||||
 | 
							Status:         status,
 | 
				
			||||||
 | 
							ErrorMessage:   errorMessage,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if saveErr := s.notificationRepo.Create(notification); saveErr != nil {
 | 
				
			||||||
 | 
							s.log.Errorw("无法保存通知发送记录到数据库",
 | 
				
			||||||
 | 
								"userID", userID,
 | 
				
			||||||
 | 
								"notifierType", notifierType,
 | 
				
			||||||
 | 
								"status", status,
 | 
				
			||||||
 | 
								"originalError", errorMessage,
 | 
				
			||||||
 | 
								"saveError", saveErr,
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,16 @@ import (
 | 
				
			|||||||
	"gorm.io/gorm"
 | 
						"gorm.io/gorm"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NotificationStatus 定义了通知发送尝试的状态枚举。
 | 
				
			||||||
 | 
					type NotificationStatus string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						NotificationStatusTest    NotificationStatus = "测试通知" // 测试用通知
 | 
				
			||||||
 | 
						NotificationStatusSuccess NotificationStatus = "发送成功" // 通知已成功发送
 | 
				
			||||||
 | 
						NotificationStatusFailed  NotificationStatus = "发送失败" // 通知发送失败
 | 
				
			||||||
 | 
						NotificationStatusSkipped NotificationStatus = "已跳过"  // 通知因某些原因被跳过(例如:用户未配置联系方式)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Notification 表示已发送或尝试发送的通知记录。
 | 
					// Notification 表示已发送或尝试发送的通知记录。
 | 
				
			||||||
type Notification struct {
 | 
					type Notification struct {
 | 
				
			||||||
	gorm.Model
 | 
						gorm.Model
 | 
				
			||||||
@@ -26,8 +36,8 @@ type Notification struct {
 | 
				
			|||||||
	AlarmTimestamp time.Time `gorm:"primaryKey;not null" json:"alarm_timestamp"`
 | 
						AlarmTimestamp time.Time `gorm:"primaryKey;not null" json:"alarm_timestamp"`
 | 
				
			||||||
	// ToAddress 接收地址 (例如:邮箱地址, 企业微信ID, 日志标识符)
 | 
						// ToAddress 接收地址 (例如:邮箱地址, 企业微信ID, 日志标识符)
 | 
				
			||||||
	ToAddress string `gorm:"type:varchar(255);not null" json:"to_address"`
 | 
						ToAddress string `gorm:"type:varchar(255);not null" json:"to_address"`
 | 
				
			||||||
	// Status 通知发送尝试的状态 (例如:"success", "failed", "pending")
 | 
						// Status 通知发送尝试的状态 (例如:"待发送", "发送成功", "发送失败", "已跳过")
 | 
				
			||||||
	Status string `gorm:"type:varchar(20);not null;default:'pending'" json:"status"`
 | 
						Status NotificationStatus `gorm:"type:varchar(20);not null;default:'待发送'" json:"status"`
 | 
				
			||||||
	// ErrorMessage 如果通知发送失败,此字段存储错误信息
 | 
						// ErrorMessage 如果通知发送失败,此字段存储错误信息
 | 
				
			||||||
	ErrorMessage string `gorm:"type:text" json:"error_message"`
 | 
						ErrorMessage string `gorm:"type:text" json:"error_message"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user