feat(notification): 新增全部标记已读接口

This commit is contained in:
2026-09-16 02:45:36 +08:00
parent c2519c4b6d
commit 89b59c7b91
5 changed files with 76 additions and 2 deletions
+16
View File
@@ -60,6 +60,22 @@ func (d *NotificationDao) NotificationUpdateReadByUUIDIfUnread(ctx context.Conte
return res.RowsAffected > 0, nil
}
// NotificationUpdateReadAllByUserID 将该用户全部未读通知标记为已读,返回影响行数。
// 条件带 is_read = 1,保证只统计本次真正从未读变为已读的条数(幂等,重复调用返回 0)。
func (d *NotificationDao) NotificationUpdateReadAllByUserID(ctx context.Context, userID int64) (int64, error) {
ctx, span := global.Tracer.Start(ctx, "mysqldao.NotificationUpdateReadAllByUserID")
defer span.End()
res := d.db.WithContext(ctx).Model(&mysqlmodel.UserNotification{}).
Where("user_id = ? AND is_read = ?", userID, int8(1)).
Update("is_read", int8(2))
if res.Error != nil {
span.RecordError(res.Error)
return 0, res.Error
}
return res.RowsAffected, nil
}
// NotificationGetByUUID 按 UUID 查询通知
func (d *NotificationDao) NotificationGetByUUID(ctx context.Context, notificationUUID string) (*mysqlmodel.UserNotification, error) {
ctx, span := global.Tracer.Start(ctx, "mysqldao.NotificationGetByUUID")
+16
View File
@@ -77,6 +77,22 @@ func (h *NotificationHandler) Get(c *gin.Context) {
response.ToResponse(out)
}
// ReadAll 将当前用户全部通知标记为已读(前端进入通知页时调用)。
func (h *NotificationHandler) ReadAll(c *gin.Context) {
response := app.NewResponse(c)
userID, ok := middleware.GetUserID(c.Request.Context())
if !ok {
response.ToErrorResponse(errcode.Unauthorized)
return
}
out, e := h.svc.ReadAll(c.Request.Context(), userID)
if e != nil {
response.ToErrorResponse(e)
return
}
response.ToResponse(out)
}
// Read 标记通知已读(uuid 来自路径参数)。
func (h *NotificationHandler) Read(c *gin.Context) {
response := app.NewResponse(c)
+2
View File
@@ -121,6 +121,8 @@ func NewRouter() *gin.Engine {
// 通知
auth.GET("/notification", notificationHandler.List)
auth.GET("/notification/unread", notificationHandler.Unread)
// 静态路径 /notification/read-all 与 /notification/:uuid/read 段数不同,不会互相抢占
auth.POST("/notification/read-all", notificationHandler.ReadAll)
auth.GET("/notification/:uuid", notificationHandler.Get)
auth.POST("/notification/:uuid/read", notificationHandler.Read)
+20
View File
@@ -108,6 +108,26 @@ func (s *NotificationService) Read(ctx context.Context, userID int64, request *d
return &dto.NotificationUnreadResponse{UnreadCount: cnt}, nil
}
// ReadAll 将当前用户全部未读通知标记为已读,并回填未读数缓存。
// 与单条已读一致:以 MySQL 为权威重新统计后返回,避免 Redis 计数偏差。
func (s *NotificationService) ReadAll(ctx context.Context, userID int64) (*dto.NotificationUnreadResponse, *errcode.Error) {
ctx, span := global.Tracer.Start(ctx, "svc.NotificationReadAll")
defer span.End()
affected, err := s.notificationDao.NotificationUpdateReadAllByUserID(ctx, userID)
if err != nil {
slog.ErrorContext(ctx, "mark all notifications read failed", "user_id", userID, "error", err)
return nil, errcode.DatabaseError
}
slog.InfoContext(ctx, "mark all notifications read", "user_id", userID, "affected", affected)
cnt, cerr := s.unreadCount(ctx, userID)
if cerr != nil {
return nil, cerr
}
return &dto.NotificationUnreadResponse{UnreadCount: cnt}, nil
}
// Unread 获取当前用户未读数(以 MySQL 为权威)。
func (s *NotificationService) Unread(ctx context.Context, userID int64) (*dto.NotificationUnreadResponse, *errcode.Error) {
ctx, span := global.Tracer.Start(ctx, "svc.NotificationUnread")