Files
junhong_cmp_fiber/internal/application/notification/read.go
2026-07-24 16:07:18 +08:00

111 lines
4.3 KiB
Go

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,
)
}