Files
timeline-server/timeline-user-service/REACTIONS_WEBSOCKET_GUIDE.md
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

242 lines
6.9 KiB
Markdown

# 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
```json
{
"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
```typescript
// 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
```typescript
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:
```typescript
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