package notification import ( "context" "time" "gorm.io/gorm" "github.com/break/junhong_cmp_fiber/internal/model" "github.com/break/junhong_cmp_fiber/internal/model/dto" "github.com/break/junhong_cmp_fiber/pkg/constants" "github.com/break/junhong_cmp_fiber/pkg/errors" ) // ReadService 执行后台账号与个人客户的幂等已读事务脚本。 type ReadService struct { db *gorm.DB now func() time.Time } // NewReadService 创建单条已读用例。 func NewReadService(db *gorm.DB) *ReadService { return &ReadService{db: db, now: time.Now} } // MarkRead 仅首次更新当前接收人的未过期未读通知。 func (s *ReadService) MarkRead(ctx context.Context, recipientID, notificationID uint) error { if recipientID == 0 || notificationID == 0 { return errors.New(errors.CodeInvalidParam) } now := s.now().UTC() result := s.db.WithContext(ctx).Model(&model.Notification{}). Where("id = ? AND recipient_kind = ? AND recipient_id = ? AND is_read = ? AND (expires_at IS NULL OR expires_at > ?)", notificationID, constants.NotificationRecipientKindAccount, recipientID, false, now). Updates(map[string]any{"is_read": true, "read_at": now}) if result.Error != nil { return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新通知已读状态失败") } return nil } // MarkAllRead 将当前后台账号全部或指定类别的未过期通知幂等标记为已读。 func (s *ReadService) MarkAllRead(ctx context.Context, recipientID uint, request dto.NotificationReadAllRequest) (*dto.NotificationReadAllResponse, error) { if recipientID == 0 || !isReadAllCategory(request.Category) { return nil, errors.New(errors.CodeInvalidParam) } now := s.now().UTC() db := s.db.WithContext(ctx).Model(&model.Notification{}). Where("recipient_kind = ? AND recipient_id = ? AND is_read = ? AND (expires_at IS NULL OR expires_at > ?)", constants.NotificationRecipientKindAccount, recipientID, false, now) if request.Category != "" { db = db.Where("category = ?", request.Category) } result := db.Updates(map[string]any{"is_read": true, "read_at": now}) if result.Error != nil { return nil, errors.Wrap(errors.CodeDatabaseError, result.Error, "批量更新通知已读状态失败") } return &dto.NotificationReadAllResponse{UpdatedCount: result.RowsAffected}, nil } func isReadAllCategory(category string) bool { switch category { case "", constants.NotificationCategoryApproval, constants.NotificationCategoryExpiry, constants.NotificationCategorySync, constants.NotificationCategorySystem: return true default: return false } } // MarkPersonalRead 仅首次更新当前个人客户可见的未过期未读通知。 func (s *ReadService) MarkPersonalRead(ctx context.Context, customerID, notificationID uint) error { if customerID == 0 || notificationID == 0 { return errors.New(errors.CodeInvalidParam) } now := s.now().UTC() result := personalReadScope(s.db.WithContext(ctx).Model(&model.Notification{}), customerID, now). Where("id = ? AND is_read = ?", notificationID, false). Updates(map[string]any{"is_read": true, "read_at": now}) if result.Error != nil { return errors.Wrap(errors.CodeDatabaseError, result.Error, "更新个人客户通知已读状态失败") } return nil } // MarkAllPersonalRead 将当前个人客户可见的全部未过期通知幂等标记为已读。 func (s *ReadService) MarkAllPersonalRead(ctx context.Context, customerID uint) (*dto.NotificationReadAllResponse, error) { if customerID == 0 { return nil, errors.New(errors.CodeInvalidParam) } now := s.now().UTC() result := personalReadScope(s.db.WithContext(ctx).Model(&model.Notification{}), customerID, now). Where("is_read = ?", false). Updates(map[string]any{"is_read": true, "read_at": now}) if result.Error != nil { return nil, errors.Wrap(errors.CodeDatabaseError, result.Error, "批量更新个人客户通知已读状态失败") } return &dto.NotificationReadAllResponse{UpdatedCount: result.RowsAffected}, nil } func personalReadScope(db *gorm.DB, customerID uint, now time.Time) *gorm.DB { return db.Where(`recipient_kind = ? AND recipient_id = ? AND category IN ? AND type IN ? AND (expires_at IS NULL OR expires_at > ?)`, constants.NotificationRecipientKindPersonalCustomer, customerID, []string{constants.NotificationCategoryApproval, constants.NotificationCategoryExpiry}, []string{constants.NotificationTypePackageExpiring}, now, ) }