Files
EchoChat/backend/go-service/pkg/ws/hub.go
bujinyuan c23b9087b8 feat(ws): Task 1 - Hub 事件路由表机制
- Hub 新增 eventHandlers map + RegisterEvent / DispatchEvent 方法
- handler.go onMessage 优先查路由表,未命中走内置 fallback
- default 分支改为返回"未知事件"错误响应 + 警告日志

Made-with: Cursor
2026-03-03 10:43:01 +08:00

178 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package ws
import (
"sync"
"github.com/echochat/backend/pkg/logs"
"go.uber.org/zap"
)
// EventHandler WS 事件处理函数签名
// 业务模块通过 Hub.RegisterEvent 注册处理器Hub 在消息分发时自动路由
type EventHandler func(client *Client, msg *Message)
// Hub 管理所有活跃的 WebSocket 客户端连接
// 提供按 userID 注册/注销/查找连接的能力,支持优雅关闭
// 同时维护事件路由表,将 WS 消息分发给对应的业务模块处理器
type Hub struct {
clients map[int64]*Client // userID -> Client 映射
register chan *Client // 注册通道
unregister chan *Client // 注销通道
mu sync.RWMutex // 保护 clients map 的读写锁
stopCh chan struct{} // 停止信号,用于优雅关闭 Run 循环
eventHandlers map[string]EventHandler // 事件路由表event -> handler
ehMu sync.RWMutex // 保护 eventHandlers 的读写锁
}
// NewHub 创建 Hub 实例
func NewHub() *Hub {
return &Hub{
clients: make(map[int64]*Client),
register: make(chan *Client, 256),
unregister: make(chan *Client, 256),
stopCh: make(chan struct{}),
eventHandlers: make(map[string]EventHandler),
}
}
// Run 启动 Hub 主循环,处理连接注册和注销
// 应在单独的 goroutine 中运行,通过 Stop() 方法优雅关闭
func (h *Hub) Run() {
for {
select {
case <-h.stopCh:
h.mu.Lock()
for uid, client := range h.clients {
client.SetClosedByHub()
close(client.send)
delete(h.clients, uid)
}
h.mu.Unlock()
logs.Info(nil, "ws.hub.Run", "Hub 已停止,所有连接已关闭")
return
case client := <-h.register:
h.mu.Lock()
if old, ok := h.clients[client.UserID]; ok {
logs.Info(nil, "ws.hub.Run", "用户重复连接,关闭旧连接",
zap.Int64("user_id", client.UserID))
old.SetClosedByHub()
close(old.send)
}
h.clients[client.UserID] = client
h.mu.Unlock()
logs.Info(nil, "ws.hub.Run", "客户端已注册",
zap.Int64("user_id", client.UserID),
zap.Int("online_count", h.OnlineCount()))
case client := <-h.unregister:
h.mu.Lock()
if existing, ok := h.clients[client.UserID]; ok && existing == client {
delete(h.clients, client.UserID)
close(client.send)
}
h.mu.Unlock()
logs.Info(nil, "ws.hub.Run", "客户端已注销",
zap.Int64("user_id", client.UserID),
zap.Int("online_count", h.OnlineCount()))
}
}
}
// Stop 优雅关闭 Hub停止 Run 循环并断开所有客户端连接
func (h *Hub) Stop() {
close(h.stopCh)
}
// Register 注册客户端连接
func (h *Hub) Register(client *Client) {
h.register <- client
}
// Unregister 注销客户端连接
func (h *Hub) Unregister(client *Client) {
h.unregister <- client
}
// GetClient 根据 userID 获取客户端连接(线程安全)
func (h *Hub) GetClient(userID int64) (*Client, bool) {
h.mu.RLock()
defer h.mu.RUnlock()
client, ok := h.clients[userID]
return client, ok
}
// SendToUser 向指定用户发送消息(仅本地 Hub
// 如果用户不在本实例或缓冲区满,返回 false
func (h *Hub) SendToUser(userID int64, data []byte) bool {
h.mu.RLock()
client, ok := h.clients[userID]
h.mu.RUnlock()
if !ok {
return false
}
select {
case client.send <- data:
return true
default:
logs.Warn(nil, "ws.hub.SendToUser", "发送缓冲区已满,消息被丢弃",
zap.Int64("user_id", userID),
zap.Int("data_len", len(data)))
return false
}
}
// OnlineCount 返回当前在线连接数
func (h *Hub) OnlineCount() int {
h.mu.RLock()
defer h.mu.RUnlock()
return len(h.clients)
}
// OnlineUserIDs 返回所有在线用户 ID 列表
func (h *Hub) OnlineUserIDs() []int64 {
h.mu.RLock()
defer h.mu.RUnlock()
ids := make([]int64, 0, len(h.clients))
for id := range h.clients {
ids = append(ids, id)
}
return ids
}
// IsOnline 检查指定用户是否在线(本地 Hub
func (h *Hub) IsOnline(userID int64) bool {
h.mu.RLock()
defer h.mu.RUnlock()
_, ok := h.clients[userID]
return ok
}
// RegisterEvent 注册事件处理器到路由表
// 业务模块在启动时调用,将 WS 事件映射到对应的处理函数
// 例如hub.RegisterEvent("im.message.send", handler.HandleSendMessage)
func (h *Hub) RegisterEvent(event string, handler EventHandler) {
h.ehMu.Lock()
defer h.ehMu.Unlock()
h.eventHandlers[event] = handler
logs.Info(nil, "ws.hub.RegisterEvent", "注册事件处理器",
zap.String("event", event))
}
// DispatchEvent 将消息分发到注册的事件处理器
// 返回 true 表示找到匹配的处理器并已执行false 表示无匹配需走 fallback 逻辑
func (h *Hub) DispatchEvent(client *Client, msg *Message) bool {
h.ehMu.RLock()
handler, ok := h.eventHandlers[msg.Event]
h.ehMu.RUnlock()
if !ok {
return false
}
handler(client, msg)
return true
}