feat(websocket): integrate WebSocket support and refactor SSE handling
- Added WebSocket support by introducing a new WSHandler and related infrastructure. - Replaced SSE implementations with WebSocket equivalents across message and QR code handlers. - Updated service and repository layers to utilize WebSocket for real-time messaging. - Removed obsolete SSE hub and related code, streamlining the application for WebSocket usage. - Enhanced router to include WebSocket endpoints for real-time communication.
This commit is contained in:
@@ -10,7 +10,7 @@ import (
|
||||
"carrot_bbs/internal/cache"
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/sse"
|
||||
"carrot_bbs/internal/pkg/ws"
|
||||
"carrot_bbs/internal/repository"
|
||||
|
||||
"go.uber.org/zap"
|
||||
@@ -59,7 +59,7 @@ type chatServiceImpl struct {
|
||||
repo repository.MessageRepository
|
||||
userRepo repository.UserRepository
|
||||
sensitive SensitiveService
|
||||
sseHub *sse.Hub
|
||||
wsHub *ws.Hub
|
||||
|
||||
// 缓存相关字段
|
||||
conversationCache *cache.ConversationCache
|
||||
@@ -71,7 +71,7 @@ func NewChatService(
|
||||
repo repository.MessageRepository,
|
||||
userRepo repository.UserRepository,
|
||||
sensitive SensitiveService,
|
||||
sseHub *sse.Hub,
|
||||
wsHub *ws.Hub,
|
||||
cacheBackend cache.Cache,
|
||||
uploadService *UploadService,
|
||||
) ChatService {
|
||||
@@ -91,17 +91,17 @@ func NewChatService(
|
||||
repo: repo,
|
||||
userRepo: userRepo,
|
||||
sensitive: sensitive,
|
||||
sseHub: sseHub,
|
||||
wsHub: wsHub,
|
||||
conversationCache: conversationCache,
|
||||
uploadService: uploadService,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *chatServiceImpl) publishSSEToUsers(userIDs []string, event string, payload interface{}) {
|
||||
if s.sseHub == nil || len(userIDs) == 0 {
|
||||
func (s *chatServiceImpl) publishToUsers(userIDs []string, event string, payload interface{}) {
|
||||
if s.wsHub == nil || len(userIDs) == 0 {
|
||||
return
|
||||
}
|
||||
s.sseHub.PublishToUsers(userIDs, event, payload)
|
||||
s.wsHub.PublishToUsers(userIDs, event, payload)
|
||||
}
|
||||
|
||||
// GetOrCreateConversation 获取或创建私聊会话
|
||||
@@ -314,12 +314,12 @@ func (s *chatServiceImpl) SendMessage(ctx context.Context, senderID string, conv
|
||||
}
|
||||
}()
|
||||
|
||||
// 获取会话中的参与者并发送 SSE
|
||||
// 获取会话中的参与者并发送消息
|
||||
participants, err := s.getParticipants(ctx, conversationID)
|
||||
if err == nil {
|
||||
targetIDs := make([]string, 0, len(participants))
|
||||
for _, p := range participants {
|
||||
// 私聊场景下,发送者已经从 HTTP 响应拿到消息,避免再通过 SSE 回推导致本端重复展示。
|
||||
// 私聊场景下,发送者已经从 HTTP 响应拿到消息,避免再通过 WebSocket 回推导致本端重复展示。
|
||||
if conv.Type == model.ConversationTypePrivate && p.UserID == senderID {
|
||||
continue
|
||||
}
|
||||
@@ -329,7 +329,7 @@ func (s *chatServiceImpl) SendMessage(ctx context.Context, senderID string, conv
|
||||
if conv.Type == model.ConversationTypeGroup {
|
||||
detailType = "group"
|
||||
}
|
||||
s.publishSSEToUsers(targetIDs, "chat_message", map[string]interface{}{
|
||||
s.publishToUsers(targetIDs, "chat_message", map[string]interface{}{
|
||||
"detail_type": detailType,
|
||||
"message": dto.ConvertMessageToResponse(message),
|
||||
})
|
||||
@@ -342,7 +342,7 @@ func (s *chatServiceImpl) SendMessage(ctx context.Context, senderID string, conv
|
||||
s.conversationCache.InvalidateUnreadCount(p.UserID, conversationID)
|
||||
}
|
||||
if totalUnread, uErr := s.repo.GetAllUnreadCount(p.UserID); uErr == nil {
|
||||
s.publishSSEToUsers([]string{p.UserID}, "conversation_unread", map[string]interface{}{
|
||||
s.publishToUsers([]string{p.UserID}, "conversation_unread", map[string]interface{}{
|
||||
"conversation_id": conversationID,
|
||||
"total_unread": totalUnread,
|
||||
})
|
||||
@@ -490,7 +490,7 @@ func (s *chatServiceImpl) MarkAsRead(ctx context.Context, conversationID string,
|
||||
for _, p := range participants {
|
||||
targetIDs = append(targetIDs, p.UserID)
|
||||
}
|
||||
s.publishSSEToUsers(targetIDs, "message_read", map[string]interface{}{
|
||||
s.publishToUsers(targetIDs, "message_read", map[string]interface{}{
|
||||
"detail_type": detailType,
|
||||
"conversation_id": conversationID,
|
||||
"group_id": groupID,
|
||||
@@ -499,7 +499,7 @@ func (s *chatServiceImpl) MarkAsRead(ctx context.Context, conversationID string,
|
||||
})
|
||||
}
|
||||
if totalUnread, uErr := s.repo.GetAllUnreadCount(userID); uErr == nil {
|
||||
s.publishSSEToUsers([]string{userID}, "conversation_unread", map[string]interface{}{
|
||||
s.publishToUsers([]string{userID}, "conversation_unread", map[string]interface{}{
|
||||
"conversation_id": conversationID,
|
||||
"total_unread": totalUnread,
|
||||
})
|
||||
@@ -579,7 +579,7 @@ func (s *chatServiceImpl) RecallMessage(ctx context.Context, messageID string, u
|
||||
for _, p := range participants {
|
||||
targetIDs = append(targetIDs, p.UserID)
|
||||
}
|
||||
s.publishSSEToUsers(targetIDs, "message_recall", map[string]interface{}{
|
||||
s.publishToUsers(targetIDs, "message_recall", map[string]interface{}{
|
||||
"detail_type": detailType,
|
||||
"conversation_id": message.ConversationID,
|
||||
"group_id": groupID,
|
||||
@@ -627,7 +627,7 @@ func (s *chatServiceImpl) DeleteMessage(ctx context.Context, messageID string, u
|
||||
|
||||
// SendTyping 发送正在输入状态
|
||||
func (s *chatServiceImpl) SendTyping(ctx context.Context, senderID string, conversationID string) {
|
||||
if s.sseHub == nil {
|
||||
if s.wsHub == nil {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -651,8 +651,8 @@ func (s *chatServiceImpl) SendTyping(ctx context.Context, senderID string, conve
|
||||
if p.UserID == senderID {
|
||||
continue
|
||||
}
|
||||
if s.sseHub != nil {
|
||||
s.sseHub.PublishToUser(p.UserID, "typing", map[string]interface{}{
|
||||
if s.wsHub != nil {
|
||||
s.wsHub.PublishToUser(p.UserID, "typing", map[string]interface{}{
|
||||
"detail_type": detailType,
|
||||
"conversation_id": conversationID,
|
||||
"user_id": senderID,
|
||||
@@ -664,8 +664,8 @@ func (s *chatServiceImpl) SendTyping(ctx context.Context, senderID string, conve
|
||||
|
||||
// IsUserOnline 检查用户是否在线
|
||||
func (s *chatServiceImpl) IsUserOnline(userID string) bool {
|
||||
if s.sseHub != nil {
|
||||
return s.sseHub.HasSubscribers(userID)
|
||||
if s.wsHub != nil {
|
||||
return s.wsHub.HasClients(userID)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
apperrors "carrot_bbs/internal/errors"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/cursor"
|
||||
"carrot_bbs/internal/pkg/sse"
|
||||
"carrot_bbs/internal/pkg/utils"
|
||||
"carrot_bbs/internal/pkg/ws"
|
||||
"carrot_bbs/internal/repository"
|
||||
|
||||
"go.uber.org/zap"
|
||||
@@ -111,19 +111,19 @@ type groupService struct {
|
||||
messageRepo repository.MessageRepository
|
||||
requestRepo repository.GroupJoinRequestRepository
|
||||
notifyRepo repository.SystemNotificationRepository
|
||||
sseHub *sse.Hub
|
||||
wsHub *ws.Hub
|
||||
cache cache.Cache
|
||||
}
|
||||
|
||||
// NewGroupService 创建群组服务
|
||||
func NewGroupService(groupRepo repository.GroupRepository, userRepo repository.UserRepository, messageRepo repository.MessageRepository, requestRepo repository.GroupJoinRequestRepository, notifyRepo repository.SystemNotificationRepository, sseHub *sse.Hub, cacheBackend cache.Cache) GroupService {
|
||||
func NewGroupService(groupRepo repository.GroupRepository, userRepo repository.UserRepository, messageRepo repository.MessageRepository, requestRepo repository.GroupJoinRequestRepository, notifyRepo repository.SystemNotificationRepository, wsHub *ws.Hub, cacheBackend cache.Cache) GroupService {
|
||||
return &groupService{
|
||||
groupRepo: groupRepo,
|
||||
userRepo: userRepo,
|
||||
messageRepo: messageRepo,
|
||||
requestRepo: requestRepo,
|
||||
notifyRepo: notifyRepo,
|
||||
sseHub: sseHub,
|
||||
wsHub: wsHub,
|
||||
cache: cacheBackend,
|
||||
}
|
||||
}
|
||||
@@ -152,9 +152,9 @@ func (s *groupService) publishGroupNotice(groupID string, notice groupNoticeMess
|
||||
)
|
||||
return
|
||||
}
|
||||
if s.sseHub != nil {
|
||||
if s.wsHub != nil {
|
||||
for _, m := range members {
|
||||
s.sseHub.PublishToUser(m.UserID, "group_notice", notice)
|
||||
s.wsHub.PublishToUser(m.UserID, "group_notice", notice)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"carrot_bbs/internal/dto"
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/sse"
|
||||
"carrot_bbs/internal/pkg/ws"
|
||||
"carrot_bbs/internal/repository"
|
||||
)
|
||||
|
||||
@@ -65,7 +65,7 @@ type pushServiceImpl struct {
|
||||
pushRepo repository.PushRecordRepository
|
||||
deviceRepo repository.DeviceTokenRepository
|
||||
messageRepo repository.MessageRepository
|
||||
sseHub *sse.Hub
|
||||
wsHub *ws.Hub
|
||||
|
||||
// 推送队列
|
||||
pushQueue chan *pushTask
|
||||
@@ -84,13 +84,13 @@ func NewPushService(
|
||||
pushRepo repository.PushRecordRepository,
|
||||
deviceRepo repository.DeviceTokenRepository,
|
||||
messageRepo repository.MessageRepository,
|
||||
sseHub *sse.Hub,
|
||||
wsHub *ws.Hub,
|
||||
) PushService {
|
||||
return &pushServiceImpl{
|
||||
pushRepo: pushRepo,
|
||||
deviceRepo: deviceRepo,
|
||||
messageRepo: messageRepo,
|
||||
sseHub: sseHub,
|
||||
wsHub: wsHub,
|
||||
pushQueue: make(chan *pushTask, PushQueueSize),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
@@ -138,7 +138,7 @@ func (s *pushServiceImpl) PushToUser(ctx context.Context, userID string, message
|
||||
// pushViaWebSocket 通过WebSocket推送消息
|
||||
// 返回true表示推送成功,false表示用户不在线
|
||||
func (s *pushServiceImpl) pushViaWebSocket(ctx context.Context, userID string, message *model.Message) bool {
|
||||
if s.sseHub == nil || !s.sseHub.HasSubscribers(userID) {
|
||||
if s.wsHub == nil || !s.wsHub.HasClients(userID) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ func (s *pushServiceImpl) pushViaWebSocket(ctx context.Context, userID string, m
|
||||
}
|
||||
}
|
||||
}
|
||||
s.sseHub.PublishToUser(userID, "system_notification", notification)
|
||||
s.wsHub.PublishToUser(userID, "system_notification", notification)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ func (s *pushServiceImpl) pushViaWebSocket(ctx context.Context, userID string, m
|
||||
SenderID: message.SenderID,
|
||||
}
|
||||
|
||||
s.sseHub.PublishToUser(userID, "chat_message", map[string]interface{}{
|
||||
s.wsHub.PublishToUser(userID, "chat_message", map[string]interface{}{
|
||||
"detail_type": detailType,
|
||||
"message": event,
|
||||
})
|
||||
@@ -444,7 +444,7 @@ func (s *pushServiceImpl) PushSystemMessage(ctx context.Context, userID string,
|
||||
|
||||
// pushSystemViaWebSocket 通过WebSocket推送系统消息
|
||||
func (s *pushServiceImpl) pushSystemViaWebSocket(ctx context.Context, userID string, msgType, title, content string, data map[string]interface{}) bool {
|
||||
if s.sseHub == nil || !s.sseHub.HasSubscribers(userID) {
|
||||
if s.wsHub == nil || !s.wsHub.HasClients(userID) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -455,7 +455,7 @@ func (s *pushServiceImpl) pushSystemViaWebSocket(ctx context.Context, userID str
|
||||
"data": data,
|
||||
"created_at": time.Now().UnixMilli(),
|
||||
}
|
||||
s.sseHub.PublishToUser(userID, "system_notification", sysMsg)
|
||||
s.wsHub.PublishToUser(userID, "system_notification", sysMsg)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -472,11 +472,11 @@ func (s *pushServiceImpl) PushSystemNotification(ctx context.Context, userID str
|
||||
|
||||
// pushSystemNotificationViaWebSocket 通过WebSocket推送系统通知
|
||||
func (s *pushServiceImpl) pushSystemNotificationViaWebSocket(ctx context.Context, userID string, notification *model.SystemNotification) bool {
|
||||
if s.sseHub == nil || !s.sseHub.HasSubscribers(userID) {
|
||||
if s.wsHub == nil || !s.wsHub.HasClients(userID) {
|
||||
return false
|
||||
}
|
||||
|
||||
sseNotification := map[string]interface{}{
|
||||
wsNotification := map[string]interface{}{
|
||||
"id": fmt.Sprintf("%d", notification.ID),
|
||||
"type": string(notification.Type),
|
||||
"title": notification.Title,
|
||||
@@ -487,7 +487,7 @@ func (s *pushServiceImpl) pushSystemNotificationViaWebSocket(ctx context.Context
|
||||
|
||||
// 填充额外数据
|
||||
if notification.ExtraData != nil {
|
||||
extra := sseNotification["extra"].(map[string]interface{})
|
||||
extra := wsNotification["extra"].(map[string]interface{})
|
||||
extra["actor_id_str"] = notification.ExtraData.ActorIDStr
|
||||
extra["actor_name"] = notification.ExtraData.ActorName
|
||||
extra["avatar_url"] = notification.ExtraData.AvatarURL
|
||||
@@ -498,7 +498,7 @@ func (s *pushServiceImpl) pushSystemNotificationViaWebSocket(ctx context.Context
|
||||
|
||||
// 设置触发用户信息
|
||||
if notification.ExtraData.ActorIDStr != "" {
|
||||
sseNotification["trigger_user"] = map[string]interface{}{
|
||||
wsNotification["trigger_user"] = map[string]interface{}{
|
||||
"id": notification.ExtraData.ActorIDStr,
|
||||
"username": notification.ExtraData.ActorName,
|
||||
"avatar": notification.ExtraData.AvatarURL,
|
||||
@@ -506,6 +506,6 @@ func (s *pushServiceImpl) pushSystemNotificationViaWebSocket(ctx context.Context
|
||||
}
|
||||
}
|
||||
|
||||
s.sseHub.PublishToUser(userID, "system_notification", sseNotification)
|
||||
s.wsHub.PublishToUser(userID, "system_notification", wsNotification)
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"carrot_bbs/internal/model"
|
||||
"carrot_bbs/internal/pkg/redis"
|
||||
"carrot_bbs/internal/pkg/sse"
|
||||
"carrot_bbs/internal/pkg/ws"
|
||||
)
|
||||
|
||||
// Lua 脚本:原子性地检查状态并更新为 scanned
|
||||
@@ -54,7 +54,7 @@ type QRCodeSession struct {
|
||||
// QRCodeLoginService 二维码登录服务
|
||||
type QRCodeLoginService struct {
|
||||
redis *redis.Client
|
||||
sseHub *sse.Hub
|
||||
wsHub *ws.Hub
|
||||
jwtService *JWTService
|
||||
userService UserService
|
||||
activityService UserActivityService
|
||||
@@ -62,10 +62,10 @@ type QRCodeLoginService struct {
|
||||
}
|
||||
|
||||
// NewQRCodeLoginService 创建二维码登录服务
|
||||
func NewQRCodeLoginService(redis *redis.Client, sseHub *sse.Hub, jwtService *JWTService, userService UserService, activityService UserActivityService, logService *LogService) *QRCodeLoginService {
|
||||
func NewQRCodeLoginService(redis *redis.Client, wsHub *ws.Hub, jwtService *JWTService, userService UserService, activityService UserActivityService, logService *LogService) *QRCodeLoginService {
|
||||
return &QRCodeLoginService{
|
||||
redis: redis,
|
||||
sseHub: sseHub,
|
||||
wsHub: wsHub,
|
||||
jwtService: jwtService,
|
||||
userService: userService,
|
||||
activityService: activityService,
|
||||
@@ -170,7 +170,7 @@ func (s *QRCodeLoginService) Scan(ctx context.Context, sessionID, userID string)
|
||||
"avatar": user.Avatar,
|
||||
},
|
||||
}
|
||||
s.sseHub.PublishToUser(sessionID, "scanned", payload)
|
||||
s.wsHub.PublishToUser(sessionID, "scanned", payload)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -242,7 +242,7 @@ func (s *QRCodeLoginService) Confirm(ctx context.Context, sessionID, userID stri
|
||||
RefreshToken: refreshToken,
|
||||
User: user,
|
||||
}
|
||||
s.sseHub.PublishToUser(sessionID, "confirmed", response)
|
||||
s.wsHub.PublishToUser(sessionID, "confirmed", response)
|
||||
|
||||
return response, nil
|
||||
}
|
||||
@@ -275,14 +275,14 @@ func (s *QRCodeLoginService) Cancel(ctx context.Context, sessionID, userID strin
|
||||
}
|
||||
|
||||
// 推送取消事件
|
||||
s.sseHub.PublishToUser(sessionID, "cancelled", map[string]interface{}{})
|
||||
s.wsHub.PublishToUser(sessionID, "cancelled", map[string]interface{}{})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSSEHub 获取SSE Hub
|
||||
func (s *QRCodeLoginService) GetSSEHub() *sse.Hub {
|
||||
return s.sseHub
|
||||
// GetWSHub 获取WS Hub
|
||||
func (s *QRCodeLoginService) GetWSHub() *ws.Hub {
|
||||
return s.wsHub
|
||||
}
|
||||
|
||||
// GetUserService 获取用户服务
|
||||
|
||||
Reference in New Issue
Block a user