Some checks failed
test/timeline-server/pipeline/head There was a failure building this commit
- 新增用户资料、偏好设置、自定义字段管理功能 - 实现评论、反应、相册与智能集合的完整业务逻辑 - 添加离线变更记录与数据同步机制支持冲突解决 - 集成 Redis 缓存配置与用户统计数据聚合 - 创建 8 个业务控制器处理用户交互请求 - 新增 Feign 客户端与故事服务集成 - 补充详细的后端实现与 WebSocket 指南文档 - 更新项目依赖配置支持新增功能模块
242 lines
6.9 KiB
Markdown
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
|
|
|