Files
jhao 10ef5918fc
Some checks failed
test/timeline-server/pipeline/head There was a failure building this commit
feat(user-service): 实现用户服务核心功能与数据同步
- 新增用户资料、偏好设置、自定义字段管理功能
- 实现评论、反应、相册与智能集合的完整业务逻辑
- 添加离线变更记录与数据同步机制支持冲突解决
- 集成 Redis 缓存配置与用户统计数据聚合
- 创建 8 个业务控制器处理用户交互请求
- 新增 Feign 客户端与故事服务集成
- 补充详细的后端实现与 WebSocket 指南文档
- 更新项目依赖配置支持新增功能模块
2026-02-25 15:04:30 +08:00

6.9 KiB

Reactions WebSocket Notifications Guide

Overview

The reactions feature now supports real-time WebSocket notifications. When a reaction is created, updated, or deleted, all subscribers to the entity's reaction topic will receive instant updates.

WebSocket Topic Format

/topic/reactions/{entityType}/{entityId}

Examples:

  • Story reactions: /topic/reactions/STORY_ITEM/story123
  • Photo reactions: /topic/reactions/PHOTO/photo456

Event Types

The system broadcasts three types of reaction events:

  1. CREATED - When a new reaction is added
  2. UPDATED - When an existing reaction is changed to a different type
  3. DELETED - When a reaction is removed

Event Payload Structure

{
  "eventType": "CREATED|UPDATED|DELETED",
  "reaction": {
    "entityType": "STORY_ITEM",
    "entityId": "story456",
    "userId": "user123",
    "userName": "John Doe",
    "userAvatarUrl": "https://...",
    "reactionType": "LIKE",
    "createTime": "2024-01-01T12:00:00"
  },
  "userId": "user123",
  "entityType": "STORY_ITEM",
  "entityId": "story456",
  "timestamp": "2024-01-01T12:00:00"
}

Note: For DELETED events, the reaction field will be null, and only userId will be populated to identify whose reaction was removed.

Reaction Types

The system supports five reaction types:

  • LIKE - Like/thumbs up
  • LOVE - Heart/love
  • LAUGH - Laughing face
  • WOW - Surprised/amazed
  • SAD - Sad face

Frontend Integration

1. Subscribe to Reaction Topic

// Subscribe when viewing an entity (story or photo)
const subscription = stompClient.subscribe(
  `/topic/reactions/${entityType}/${entityId}`,
  (message) => {
    const event = JSON.parse(message.body);
    handleReactionEvent(event);
  }
);

// Unsubscribe when leaving the page
subscription.unsubscribe();

2. Handle Reaction Events

function handleReactionEvent(event: ReactionEventDto) {
  switch (event.eventType) {
    case 'CREATED':
      // Add new reaction to the summary
      addReactionToSummary(event.reaction);
      break;
      
    case 'UPDATED':
      // Update existing reaction in the summary
      updateReactionInSummary(event.reaction);
      break;
      
    case 'DELETED':
      // Remove reaction from the summary
      removeReactionFromSummary(event.userId, event.entityType, event.entityId);
      break;
  }
}

3. Update Reaction Counts

When receiving events, update the reaction counts and user lists:

function addReactionToSummary(reaction: ReactionDto) {
  // Increment count for the reaction type
  counts[reaction.reactionType]++;
  
  // If it's the current user's reaction, update userReaction
  if (reaction.userId === currentUserId) {
    userReaction = reaction.reactionType;
  }
  
  // Add to recent reactions list
  recentReactions.unshift(reaction);
  if (recentReactions.length > 10) {
    recentReactions.pop();
  }
}

function updateReactionInSummary(reaction: ReactionDto) {
  // Find and update the old reaction
  const oldReaction = recentReactions.find(r => r.userId === reaction.userId);
  if (oldReaction) {
    // Decrement old type count
    counts[oldReaction.reactionType]--;
    // Increment new type count
    counts[reaction.reactionType]++;
    // Update the reaction in the list
    Object.assign(oldReaction, reaction);
  }
  
  // If it's the current user's reaction, update userReaction
  if (reaction.userId === currentUserId) {
    userReaction = reaction.reactionType;
  }
}

function removeReactionFromSummary(userId: string, entityType: string, entityId: string) {
  // Find the reaction to remove
  const reactionIndex = recentReactions.findIndex(r => r.userId === userId);
  if (reactionIndex !== -1) {
    const reaction = recentReactions[reactionIndex];
    // Decrement count
    counts[reaction.reactionType]--;
    // Remove from list
    recentReactions.splice(reactionIndex, 1);
  }
  
  // If it's the current user's reaction, clear userReaction
  if (userId === currentUserId) {
    userReaction = null;
  }
}

Backend Implementation Details

Service Layer

The ReactionServiceImpl automatically broadcasts WebSocket events after successful database operations:

  • addOrUpdateReaction() - Broadcasts CREATED event for new reactions or UPDATED event for changed reactions
  • removeReaction() - Broadcasts DELETED event with user ID only

Error Handling

WebSocket broadcasting failures are caught and logged but do not affect the main reaction operation. This ensures that:

  • Reactions are always saved to the database
  • WebSocket issues don't break the API
  • Errors are logged for monitoring

Transaction Safety

All reaction operations are wrapped in @Transactional annotations, ensuring:

  • Database changes are committed before WebSocket broadcast
  • Rollback doesn't trigger WebSocket events
  • Consistency between database and real-time updates

Testing

Unit Tests

Create tests to verify:

  • WebSocket events are broadcast for all operations
  • Topic format is correct (/topic/reactions/{entityType}/{entityId})
  • Event payload contains expected data
  • WebSocket failures don't break main operations

Manual Testing

  1. Open two browser windows with the same story/photo
  2. Add a reaction in window 1
  3. Verify the reaction appears in window 2 without refresh
  4. Change the reaction type in window 1
  5. Verify the change appears in window 2 in real-time
  6. Remove the reaction in window 1
  7. Verify the removal appears in window 2 in real-time

Performance Considerations

  • Topic-based broadcasting: Only users viewing the specific entity receive updates
  • Lightweight payloads: Events contain only necessary data
  • Non-blocking: WebSocket operations don't slow down API responses
  • Graceful degradation: System works without WebSocket (polling fallback)

Security

  • WebSocket connections require authentication
  • Users can subscribe to any public entity's reaction topic
  • Private entities should implement additional authorization checks
  • Reaction types are validated before broadcasting

Monitoring

Key metrics to monitor:

  • WebSocket connection count
  • Message delivery success rate
  • Broadcast latency
  • Failed broadcast attempts (check logs)

Comparison with Comments

Reactions WebSocket implementation follows the same pattern as Comments:

Feature Comments Reactions
Topic Format /topic/comments/{type}/{id} /topic/reactions/{type}/{id}
Event Types CREATED, UPDATED, DELETED CREATED, UPDATED, DELETED
Event DTO CommentEventDto ReactionEventDto
Service Integration CommentServiceImpl ReactionServiceImpl
Error Handling Non-blocking, logged Non-blocking, logged
Transaction Safety @Transactional @Transactional

Future Enhancements

Potential improvements:

  • Personal notification queue for entity owners
  • Reaction analytics (trending reactions)
  • Reaction animations in real-time
  • Bulk reaction updates for performance
  • Reaction history tracking