增加站内信未读提醒和后台通知中心+txt 校验
This commit is contained in:
@@ -20,3 +20,7 @@ type PaginatedResult struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
type UnreadCountDTO struct {
|
||||
UnreadCount int64 `json:"unread_count"`
|
||||
}
|
||||
|
||||
@@ -48,6 +48,34 @@ func (h *Handler) List(c *gin.Context) {
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
func (h *Handler) UnreadCount(c *gin.Context) {
|
||||
userID, ok := currentUserID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少用户上下文")
|
||||
return
|
||||
}
|
||||
result, err := h.service.UnreadCount(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
writeNotificationError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, result)
|
||||
}
|
||||
|
||||
func (h *Handler) MarkAllRead(c *gin.Context) {
|
||||
userID, ok := currentUserID(c)
|
||||
if !ok {
|
||||
response.Unauthorized(c, "缺少用户上下文")
|
||||
return
|
||||
}
|
||||
count, err := h.service.MarkAllRead(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
writeNotificationError(c, err)
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"read_count": count})
|
||||
}
|
||||
|
||||
func (h *Handler) MarkRead(c *gin.Context) {
|
||||
userID, ok := currentUserID(c)
|
||||
if !ok {
|
||||
@@ -79,6 +107,8 @@ func writeNotificationError(c *gin.Context, err error) {
|
||||
switch {
|
||||
case errors.Is(err, ErrDependencyUnavailable):
|
||||
response.ServiceUnavailable(c, "数据库未连接")
|
||||
case errors.Is(err, ErrNotificationNotFound):
|
||||
response.NotFound(c, "通知不存在")
|
||||
default:
|
||||
response.ServiceUnavailable(c, "通知服务暂时不可用")
|
||||
}
|
||||
|
||||
@@ -45,9 +45,40 @@ func (r *Repository) List(ctx context.Context, userID uint64, page, pageSize int
|
||||
|
||||
func (r *Repository) MarkRead(ctx context.Context, userID uint64, id uint64) error {
|
||||
now := time.Now()
|
||||
return r.db.WithContext(ctx).Model(&model.Notification{}).
|
||||
result := r.db.WithContext(ctx).Model(&model.Notification{}).
|
||||
Where("id = ? AND user_id = ?", id, userID).
|
||||
Update("read_at", now).Error
|
||||
Update("read_at", now)
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
var total int64
|
||||
if err := r.db.WithContext(ctx).Model(&model.Notification{}).
|
||||
Where("id = ? AND user_id = ?", id, userID).
|
||||
Count(&total).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if total == 0 {
|
||||
return ErrNotificationNotFound
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) UnreadCount(ctx context.Context, userID uint64) (int64, error) {
|
||||
var total int64
|
||||
err := r.db.WithContext(ctx).Model(&model.Notification{}).
|
||||
Where("user_id = ? AND read_at IS NULL", userID).
|
||||
Count(&total).Error
|
||||
return total, err
|
||||
}
|
||||
|
||||
func (r *Repository) MarkAllRead(ctx context.Context, userID uint64) (int64, error) {
|
||||
now := time.Now()
|
||||
result := r.db.WithContext(ctx).Model(&model.Notification{}).
|
||||
Where("user_id = ? AND read_at IS NULL", userID).
|
||||
Update("read_at", now)
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
func Append(tx *gorm.DB, entries ...Entry) error {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"hfb_sys/backend/internal/database"
|
||||
"hfb_sys/backend/internal/model"
|
||||
)
|
||||
|
||||
func TestRepositoryUnreadCountAndMarkRead(t *testing.T) {
|
||||
db := database.NewTestDB()
|
||||
if err := db.AutoMigrate(&model.Notification{}); err != nil {
|
||||
t.Fatalf("AutoMigrate() error = %v", err)
|
||||
}
|
||||
repo := NewRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
if err := db.Create(&model.Notification{
|
||||
UserID: 10,
|
||||
Type: "order",
|
||||
Title: "订单通知",
|
||||
Content: "请处理订单",
|
||||
}).Error; err != nil {
|
||||
t.Fatalf("Create() error = %v", err)
|
||||
}
|
||||
|
||||
count, err := repo.UnreadCount(ctx, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("UnreadCount() error = %v", err)
|
||||
}
|
||||
if count != 1 {
|
||||
t.Fatalf("UnreadCount() = %d, want 1", count)
|
||||
}
|
||||
|
||||
if err := repo.MarkRead(ctx, 10, 1); err != nil {
|
||||
t.Fatalf("MarkRead() error = %v", err)
|
||||
}
|
||||
// 重复标记应保持幂等,不应误报不存在。
|
||||
if err := repo.MarkRead(ctx, 10, 1); err != nil {
|
||||
t.Fatalf("MarkRead() repeat error = %v", err)
|
||||
}
|
||||
|
||||
count, err = repo.UnreadCount(ctx, 10)
|
||||
if err != nil {
|
||||
t.Fatalf("UnreadCount() after read error = %v", err)
|
||||
}
|
||||
if count != 0 {
|
||||
t.Fatalf("UnreadCount() after read = %d, want 0", count)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepositoryMarkReadNotFound(t *testing.T) {
|
||||
db := database.NewTestDB()
|
||||
if err := db.AutoMigrate(&model.Notification{}); err != nil {
|
||||
t.Fatalf("AutoMigrate() error = %v", err)
|
||||
}
|
||||
repo := NewRepository(db)
|
||||
|
||||
err := repo.MarkRead(context.Background(), 10, 99)
|
||||
if !errors.Is(err, ErrNotificationNotFound) {
|
||||
t.Fatalf("MarkRead() error = %v, want ErrNotificationNotFound", err)
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
)
|
||||
|
||||
var ErrDependencyUnavailable = errors.New("dependency unavailable")
|
||||
var ErrNotificationNotFound = errors.New("notification not found")
|
||||
|
||||
type Service struct {
|
||||
repo *Repository
|
||||
@@ -28,3 +29,21 @@ func (s *Service) MarkRead(ctx context.Context, userID uint64, id uint64) error
|
||||
}
|
||||
return s.repo.MarkRead(ctx, userID, id)
|
||||
}
|
||||
|
||||
func (s *Service) UnreadCount(ctx context.Context, userID uint64) (*UnreadCountDTO, error) {
|
||||
if s.repo == nil {
|
||||
return nil, ErrDependencyUnavailable
|
||||
}
|
||||
count, err := s.repo.UnreadCount(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &UnreadCountDTO{UnreadCount: count}, nil
|
||||
}
|
||||
|
||||
func (s *Service) MarkAllRead(ctx context.Context, userID uint64) (int64, error) {
|
||||
if s.repo == nil {
|
||||
return 0, ErrDependencyUnavailable
|
||||
}
|
||||
return s.repo.MarkAllRead(ctx, userID)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user