2026-04-28 14:53:04 +08:00
|
|
|
|
package service
|
2026-03-09 21:28:58 +08:00
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
2026-04-22 16:01:59 +08:00
|
|
|
|
"with_you/internal/cache"
|
|
|
|
|
|
apperrors "with_you/internal/errors"
|
|
|
|
|
|
"with_you/internal/model"
|
|
|
|
|
|
"with_you/internal/pkg/cursor"
|
|
|
|
|
|
"with_you/internal/repository"
|
2026-03-09 21:28:58 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 缓存TTL常量
|
|
|
|
|
|
const (
|
|
|
|
|
|
NotificationUnreadCountTTL = 30 * time.Second // 通知未读数缓存30秒
|
|
|
|
|
|
NotificationNullTTL = 5 * time.Second
|
|
|
|
|
|
NotificationCacheJitter = 0.1
|
|
|
|
|
|
)
|
|
|
|
|
|
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
// 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 {
|
2026-03-26 18:14:16 +08:00
|
|
|
|
notificationRepo repository.NotificationRepository
|
2026-03-09 21:28:58 +08:00
|
|
|
|
cache cache.Cache
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewNotificationService 创建通知服务
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func NewNotificationService(notificationRepo repository.NotificationRepository, cacheBackend cache.Cache) NotificationService {
|
|
|
|
|
|
return ¬ificationService{
|
2026-03-09 21:28:58 +08:00
|
|
|
|
notificationRepo: notificationRepo,
|
2026-03-13 09:38:18 +08:00
|
|
|
|
cache: cacheBackend,
|
2026-03-09 21:28:58 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Create 创建通知
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *notificationService) Create(ctx context.Context, userID string, notificationType model.NotificationType, title, content string) (*model.Notification, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
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 获取用户通知
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *notificationService) GetByUserID(ctx context.Context, userID string, page, pageSize int, unreadOnly bool) ([]*model.Notification, int64, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
return s.notificationRepo.GetByUserID(userID, page, pageSize, unreadOnly)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-30 12:26:25 +08:00
|
|
|
|
// MarkAsReadWithUserID 标记为已读(带用户ID,验证所有权)
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *notificationService) MarkAsReadWithUserID(ctx context.Context, id, userID string) error {
|
2026-04-30 12:26:25 +08:00
|
|
|
|
notification, err := s.notificationRepo.GetByID(id)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-04-30 12:26:25 +08:00
|
|
|
|
if notification.UserID != userID {
|
|
|
|
|
|
return apperrors.ErrForbidden
|
|
|
|
|
|
}
|
2026-03-09 21:28:58 +08:00
|
|
|
|
|
2026-04-30 12:26:25 +08:00
|
|
|
|
err = s.notificationRepo.MarkAsRead(id)
|
2026-03-09 21:28:58 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
cache.InvalidateUnreadSystem(s.cache, userID)
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MarkAllAsRead 标记所有为已读
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *notificationService) MarkAllAsRead(ctx context.Context, userID string) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
err := s.notificationRepo.MarkAllAsRead(userID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 失效未读数缓存
|
|
|
|
|
|
cache.InvalidateUnreadSystem(s.cache, userID)
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetUnreadCount 获取未读数量(带缓存)
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *notificationService) GetUnreadCount(ctx context.Context, userID string) (int64, error) {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
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 删除通知(带用户验证)
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *notificationService) DeleteNotification(ctx context.Context, id, userID string) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
// 先检查通知是否属于该用户
|
|
|
|
|
|
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 清空所有通知
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *notificationService) ClearAllNotifications(ctx context.Context, userID string) error {
|
2026-03-09 21:28:58 +08:00
|
|
|
|
err := s.notificationRepo.DeleteAllByUserID(userID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 失效未读数缓存
|
|
|
|
|
|
cache.InvalidateUnreadSystem(s.cache, userID)
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat(api): add cursor-based pagination for posts, comments, messages, groups, and notifications
Implement cursor-based pagination across multiple API endpoints to improve performance and enable efficient infinite scrolling. This replaces traditional offset-based pagination for high-volume data retrieval.
Changes:
- Add cursor pagination DTOs for all entity types (Post, Comment, Message, Conversation, Group, Notification, GroupMember, GroupAnnouncement)
- Implement cursor pagination methods in repositories with support for various sort orders (created_at, updated_at, join_time, seq)
- Add cursor pagination handlers that maintain backward compatibility with existing offset pagination
- Add new API routes for cursor-based endpoints (/cursor suffix)
- Add helper converter functions for pointer slice types in DTO conversions
2026-03-20 23:03:23 +08:00
|
|
|
|
// GetNotificationsByCursor 游标分页获取用户通知
|
refactor(server): decouple services and improve architecture
- Introduce interfaces for all major services (JWT, PostAI, Comment, Message, Notification, QRCodeLogin, Upload, Vote, etc.) to support dependency inversion.
- Move query parameters from `internal/dto` to a new `internal/query` package to separate request payloads from data transfer objects.
- Refactor `internal/router` to use embedded `RouterDeps` for cleaner dependency management.
- Decouple handlers from repositories by injecting services instead of direct repository access, ensuring proper layering.
- Improve database initialization by moving it from `internal/model` to `internal/database`.
- Optimize message decryption by implementing a more efficient `BatchDecrypt` method in `MessageEncryptor` using a worker pool.
- Enhance error handling and security by implementing fail-fast checks for encryption key length during startup.
- Clean up unused code, including the `avatar` package and several unused DTOs.
2026-06-15 03:41:59 +08:00
|
|
|
|
func (s *notificationService) GetNotificationsByCursor(ctx context.Context, userID string, unreadOnly bool, req *cursor.PageRequest) (*cursor.CursorPageResult[*model.Notification], error) {
|
feat(api): add cursor-based pagination for posts, comments, messages, groups, and notifications
Implement cursor-based pagination across multiple API endpoints to improve performance and enable efficient infinite scrolling. This replaces traditional offset-based pagination for high-volume data retrieval.
Changes:
- Add cursor pagination DTOs for all entity types (Post, Comment, Message, Conversation, Group, Notification, GroupMember, GroupAnnouncement)
- Implement cursor pagination methods in repositories with support for various sort orders (created_at, updated_at, join_time, seq)
- Add cursor pagination handlers that maintain backward compatibility with existing offset pagination
- Add new API routes for cursor-based endpoints (/cursor suffix)
- Add helper converter functions for pointer slice types in DTO conversions
2026-03-20 23:03:23 +08:00
|
|
|
|
return s.notificationRepo.GetNotificationsByCursor(ctx, userID, unreadOnly, req)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 21:28:58 +08:00
|
|
|
|
// 错误定义
|
2026-03-13 09:38:18 +08:00
|
|
|
|
var ErrUnauthorizedNotification = apperrors.ErrUnauthorizedNotification
|