package service import ( "context" "time" "with_you/internal/cache" apperrors "with_you/internal/errors" "with_you/internal/model" "with_you/internal/pkg/cursor" "with_you/internal/repository" ) // 缓存TTL常量 const ( NotificationUnreadCountTTL = 30 * time.Second // 通知未读数缓存30秒 NotificationNullTTL = 5 * time.Second NotificationCacheJitter = 0.1 ) // NotificationService 通知服务接口 type NotificationService interface { Create(ctx context.Context, userID string, notificationType model.NotificationType, title, content string) (*model.Notification, error) GetByUserID(ctx context.Context, userID string, page, pageSize int, unreadOnly bool) ([]*model.Notification, int64, error) MarkAsReadWithUserID(ctx context.Context, id, userID string) error MarkAllAsRead(ctx context.Context, userID string) error GetUnreadCount(ctx context.Context, userID string) (int64, error) DeleteNotification(ctx context.Context, id, userID string) error ClearAllNotifications(ctx context.Context, userID string) error GetNotificationsByCursor(ctx context.Context, userID string, unreadOnly bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Notification], error) } // notificationService 通知服务实现 type notificationService struct { notificationRepo repository.NotificationRepository cache cache.Cache } // NewNotificationService 创建通知服务 func NewNotificationService(notificationRepo repository.NotificationRepository, cacheBackend cache.Cache) NotificationService { return ¬ificationService{ notificationRepo: notificationRepo, cache: cacheBackend, } } // Create 创建通知 func (s *notificationService) Create(ctx context.Context, userID string, notificationType model.NotificationType, title, content string) (*model.Notification, error) { notification := &model.Notification{ UserID: userID, Type: notificationType, Title: title, Content: content, IsRead: false, } err := s.notificationRepo.Create(notification) if err != nil { return nil, err } // 失效未读数缓存 cache.InvalidateUnreadSystem(s.cache, userID) return notification, nil } // GetByUserID 获取用户通知 func (s *notificationService) GetByUserID(ctx context.Context, userID string, page, pageSize int, unreadOnly bool) ([]*model.Notification, int64, error) { return s.notificationRepo.GetByUserID(userID, page, pageSize, unreadOnly) } // MarkAsReadWithUserID 标记为已读(带用户ID,验证所有权) func (s *notificationService) MarkAsReadWithUserID(ctx context.Context, id, userID string) error { notification, err := s.notificationRepo.GetByID(id) if err != nil { return err } if notification.UserID != userID { return apperrors.ErrForbidden } err = s.notificationRepo.MarkAsRead(id) if err != nil { return err } cache.InvalidateUnreadSystem(s.cache, userID) return nil } // MarkAllAsRead 标记所有为已读 func (s *notificationService) MarkAllAsRead(ctx context.Context, userID string) error { err := s.notificationRepo.MarkAllAsRead(userID) if err != nil { return err } // 失效未读数缓存 cache.InvalidateUnreadSystem(s.cache, userID) return nil } // GetUnreadCount 获取未读数量(带缓存) func (s *notificationService) GetUnreadCount(ctx context.Context, userID string) (int64, error) { cacheSettings := cache.GetSettings() unreadTTL := cacheSettings.UnreadCountTTL if unreadTTL <= 0 { unreadTTL = NotificationUnreadCountTTL } nullTTL := cacheSettings.NullTTL if nullTTL <= 0 { nullTTL = NotificationNullTTL } jitter := cacheSettings.JitterRatio if jitter <= 0 { jitter = NotificationCacheJitter } // 生成缓存键 cacheKey := cache.UnreadSystemKey(userID) return cache.GetOrLoadTyped[int64]( s.cache, cacheKey, unreadTTL, jitter, nullTTL, func() (int64, error) { return s.notificationRepo.GetUnreadCount(userID) }, ) } // DeleteNotification 删除通知(带用户验证) func (s *notificationService) DeleteNotification(ctx context.Context, id, userID string) error { // 先检查通知是否属于该用户 notification, err := s.notificationRepo.GetByID(id) if err != nil { return err } if notification.UserID != userID { return ErrUnauthorizedNotification } err = s.notificationRepo.Delete(id) if err != nil { return err } // 失效未读数缓存 cache.InvalidateUnreadSystem(s.cache, userID) return nil } // ClearAllNotifications 清空所有通知 func (s *notificationService) ClearAllNotifications(ctx context.Context, userID string) error { err := s.notificationRepo.DeleteAllByUserID(userID) if err != nil { return err } // 失效未读数缓存 cache.InvalidateUnreadSystem(s.cache, userID) return nil } // GetNotificationsByCursor 游标分页获取用户通知 func (s *notificationService) GetNotificationsByCursor(ctx context.Context, userID string, unreadOnly bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Notification], error) { return s.notificationRepo.GetNotificationsByCursor(ctx, userID, unreadOnly, req) } // 错误定义 var ErrUnauthorizedNotification = apperrors.ErrUnauthorizedNotification