refactor(push): normalize notification payload and avoid reconnection replay
All checks were successful
Build Backend / build (push) Successful in 3m30s
Build Backend / build-docker (push) Successful in 1m19s

- Change type field to always be "notification" and add system_type field
- Rename "extra" and "data" fields to "extra_data" for consistency
- Add "is_read" field to notification payload
- Use PublishToUserOnline instead of PublishToUser to prevent system
  messages from being stored in reconnection history replay
- System notifications are already persisted to database, clients can
  fetch via REST API on reconnect
This commit is contained in:
lafay
2026-04-25 15:59:36 +08:00
parent 3215039ff6
commit 52f62ef230

View File

@@ -149,16 +149,18 @@ func (s *pushServiceImpl) pushViaWebSocket(ctx context.Context, userID string, m
content := dto.ExtractTextContentFromModel(message.Segments) content := dto.ExtractTextContentFromModel(message.Segments)
notification := map[string]any{ notification := map[string]any{
"id": fmt.Sprintf("%s", message.ID), "id": fmt.Sprintf("%s", message.ID),
"type": string(message.SystemType), "type": "notification",
"content": content, "system_type": string(message.SystemType),
"extra": map[string]any{}, "content": content,
"created_at": message.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), "is_read": false,
"extra_data": map[string]any{},
"created_at": message.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
} }
// 填充额外数据 // 填充额外数据
if message.ExtraData != nil { if message.ExtraData != nil {
extra := notification["extra"].(map[string]any) extra := notification["extra_data"].(map[string]any)
extra["actor_id"] = message.ExtraData.ActorID extra["actor_id"] = message.ExtraData.ActorID
extra["actor_name"] = message.ExtraData.ActorName extra["actor_name"] = message.ExtraData.ActorName
extra["avatar_url"] = message.ExtraData.AvatarURL extra["avatar_url"] = message.ExtraData.AvatarURL
@@ -174,7 +176,7 @@ func (s *pushServiceImpl) pushViaWebSocket(ctx context.Context, userID string, m
} }
} }
} }
s.wsHub.PublishToUser(userID, "system_notification", notification) s.wsHub.PublishToUserOnline(userID, "system_notification", notification)
return true return true
} }
@@ -444,19 +446,22 @@ func (s *pushServiceImpl) PushSystemMessage(ctx context.Context, userID string,
} }
// pushSystemViaWebSocket 通过WebSocket推送系统消息 // pushSystemViaWebSocket 通过WebSocket推送系统消息
// 使用 PublishToUserOnline 而非 PublishToUser避免将系统消息存入断线重连历史回放
func (s *pushServiceImpl) pushSystemViaWebSocket(ctx context.Context, userID string, msgType, title, content string, data map[string]any) bool { func (s *pushServiceImpl) pushSystemViaWebSocket(ctx context.Context, userID string, msgType, title, content string, data map[string]any) bool {
if s.wsHub == nil || !s.wsHub.HasClients(userID) { if s.wsHub == nil || !s.wsHub.HasClients(userID) {
return false return false
} }
sysMsg := map[string]any{ sysMsg := map[string]any{
"type": msgType, "type": "notification",
"system_type": msgType,
"title": title, "title": title,
"content": content, "content": content,
"data": data, "is_read": false,
"extra_data": data,
"created_at": time.Now().Format("2006-01-02T15:04:05Z07:00"), "created_at": time.Now().Format("2006-01-02T15:04:05Z07:00"),
} }
s.wsHub.PublishToUser(userID, "system_notification", sysMsg) s.wsHub.PublishToUserOnline(userID, "system_notification", sysMsg)
return true return true
} }
@@ -472,24 +477,27 @@ func (s *pushServiceImpl) PushSystemNotification(ctx context.Context, userID str
} }
// pushSystemNotificationViaWebSocket 通过WebSocket推送系统通知 // pushSystemNotificationViaWebSocket 通过WebSocket推送系统通知
// 使用 PublishToUserOnline 而非 PublishToUser避免将通知存入断线重连历史回放
// 系统通知已持久化到数据库,客户端可通过 REST API 拉取,无需在重连时回放
func (s *pushServiceImpl) pushSystemNotificationViaWebSocket(ctx context.Context, userID string, notification *model.SystemNotification) bool { func (s *pushServiceImpl) pushSystemNotificationViaWebSocket(ctx context.Context, userID string, notification *model.SystemNotification) bool {
if s.wsHub == nil || !s.wsHub.HasClients(userID) { if s.wsHub == nil || !s.wsHub.HasClients(userID) {
return false return false
} }
wsNotification := map[string]any{ wsNotification := map[string]any{
"id": fmt.Sprintf("%d", notification.ID), "id": fmt.Sprintf("%d", notification.ID),
"type": string(notification.Type), "type": "notification",
"title": notification.Title, "system_type": string(notification.Type),
"content": notification.Content, "title": notification.Title,
"is_read": notification.IsRead, "content": notification.Content,
"extra": map[string]any{}, "is_read": notification.IsRead,
"created_at": notification.CreatedAt.Format("2006-01-02T15:04:05Z07:00"), "extra_data": map[string]any{},
"created_at": notification.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
} }
// 填充额外数据 // 填充额外数据
if notification.ExtraData != nil { if notification.ExtraData != nil {
extra := wsNotification["extra"].(map[string]any) extra := wsNotification["extra_data"].(map[string]any)
extra["actor_id_str"] = notification.ExtraData.ActorIDStr extra["actor_id_str"] = notification.ExtraData.ActorIDStr
extra["actor_name"] = notification.ExtraData.ActorName extra["actor_name"] = notification.ExtraData.ActorName
extra["avatar_url"] = notification.ExtraData.AvatarURL extra["avatar_url"] = notification.ExtraData.AvatarURL
@@ -508,6 +516,6 @@ func (s *pushServiceImpl) pushSystemNotificationViaWebSocket(ctx context.Context
} }
} }
s.wsHub.PublishToUser(userID, "system_notification", wsNotification) s.wsHub.PublishToUserOnline(userID, "system_notification", wsNotification)
return true return true
} }