hub.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. package websocket
  2. import (
  3. "log"
  4. "sync"
  5. "github.com/2930134478/AI-CS/backend/models"
  6. )
  7. // OnClientConnectCallback 客户端连接时的回调函数。
  8. // conversationID: 对话ID
  9. // isVisitor: 是否是访客
  10. // visitorCount: 该对话当前的访客连接数
  11. // agentID: 客服ID(如果是客服连接)
  12. type OnClientConnectCallback func(conversationID uint, isVisitor bool, visitorCount int, agentID uint)
  13. // OnClientDisconnectCallback 客户端断开连接时的回调函数。
  14. // conversationID: 对话ID
  15. // isVisitor: 是否是访客
  16. // visitorCount: 该对话当前的访客连接数(断开后)
  17. type OnClientDisconnectCallback func(conversationID uint, isVisitor bool, visitorCount int)
  18. // Hub 管理所有 WebSocket 连接
  19. // 每个对话(conversation)可以有多个人连接(访客和客服)
  20. type Hub struct {
  21. // 每个对话ID对应的客户端连接列表
  22. // conversationID -> []*Client
  23. conversations map[uint]map[*Client]bool
  24. // 注册新客户端(当有人连接时)
  25. register chan *Client
  26. // 注销客户端(当有人断开连接时)
  27. unregister chan *Client
  28. // 广播消息(当有新消息时,推送给所有相关的客户端)
  29. broadcast chan *Message
  30. // 互斥锁(保护并发访问)
  31. mu sync.RWMutex
  32. // 回调函数
  33. onConnect OnClientConnectCallback
  34. onDisconnect OnClientDisconnectCallback
  35. // 分布式事件总线(可选,启用后支持多实例广播一致性)
  36. bus DistributedBus
  37. }
  38. // Message 是要广播的消息
  39. type Message struct {
  40. ConversationID uint `json:"conversation_id"`
  41. Data interface{} `json:"data"` // 消息内容(可以是 Message 对象)
  42. Type string `json:"type"` // 消息类型:new_message, conversation_update 等
  43. Scope string `json:"scope,omitempty"` // conversation | all_agents
  44. FromRemote bool `json:"-"`
  45. }
  46. // NewHub 创建一个新的 Hub
  47. func NewHub(onConnect OnClientConnectCallback, onDisconnect OnClientDisconnectCallback, bus DistributedBus) *Hub {
  48. h := &Hub{
  49. conversations: make(map[uint]map[*Client]bool),
  50. register: make(chan *Client),
  51. unregister: make(chan *Client),
  52. broadcast: make(chan *Message, 256),
  53. onConnect: onConnect,
  54. onDisconnect: onDisconnect,
  55. bus: bus,
  56. }
  57. if bus != nil {
  58. bus.Subscribe(func(msg *Message) {
  59. if msg == nil {
  60. return
  61. }
  62. msg.FromRemote = true
  63. select {
  64. case h.broadcast <- msg:
  65. default:
  66. log.Printf("⚠️ 分布式消息队列拥塞,丢弃事件: 对话ID=%d, 类型=%s", msg.ConversationID, msg.Type)
  67. }
  68. })
  69. }
  70. return h
  71. }
  72. // Run 启动 Hub,处理所有事件
  73. func (h *Hub) Run() {
  74. for {
  75. select {
  76. // 新客户端连接
  77. case client := <-h.register:
  78. h.mu.Lock()
  79. // 如果这个对话还没有客户端,创建一个新的 map
  80. if h.conversations[client.conversationID] == nil {
  81. h.conversations[client.conversationID] = make(map[*Client]bool)
  82. }
  83. // 把这个客户端加入到对话中
  84. h.conversations[client.conversationID][client] = true
  85. // 统计该对话的访客连接数
  86. visitorCount := 0
  87. for c := range h.conversations[client.conversationID] {
  88. if c.isVisitor {
  89. visitorCount++
  90. }
  91. }
  92. h.mu.Unlock()
  93. // 调用连接回调函数
  94. if h.onConnect != nil {
  95. h.onConnect(client.conversationID, client.isVisitor, visitorCount, client.agentID)
  96. }
  97. // 客户端断开连接
  98. case client := <-h.unregister:
  99. h.mu.Lock()
  100. // 从对话中移除这个客户端
  101. wasVisitor := client.isVisitor
  102. if clients, ok := h.conversations[client.conversationID]; ok {
  103. if _, ok := clients[client]; ok {
  104. delete(clients, client)
  105. // 关闭发送通道(避免重复关闭导致 panic)
  106. select {
  107. case _, ok := <-client.send:
  108. if !ok {
  109. // 通道已经关闭,不需要再次关闭
  110. }
  111. default:
  112. // 通道未关闭,关闭它
  113. close(client.send)
  114. }
  115. // 统计该对话的访客连接数(断开后)
  116. visitorCount := 0
  117. for c := range clients {
  118. if c.isVisitor {
  119. visitorCount++
  120. }
  121. }
  122. // 如果这个对话没有客户端了,删除对话
  123. if len(clients) == 0 {
  124. delete(h.conversations, client.conversationID)
  125. }
  126. h.mu.Unlock()
  127. // 调用断开回调函数
  128. if h.onDisconnect != nil {
  129. h.onDisconnect(client.conversationID, wasVisitor, visitorCount)
  130. }
  131. } else {
  132. h.mu.Unlock()
  133. log.Printf("⚠️ 客户端断开时未找到: 对话ID=%d", client.conversationID)
  134. }
  135. } else {
  136. h.mu.Unlock()
  137. log.Printf("⚠️ 客户端断开时对话不存在: 对话ID=%d", client.conversationID)
  138. }
  139. // 广播消息
  140. case message := <-h.broadcast:
  141. if message == nil {
  142. continue
  143. }
  144. if message.Scope == "all_agents" {
  145. clients := h.snapshotAllAgents()
  146. h.sendToClients(clients, message)
  147. } else {
  148. clients := h.snapshotConversationClients(message.ConversationID)
  149. if len(clients) == 0 {
  150. log.Printf("⚠️ 广播消息失败: 对话ID=%d 没有客户端连接", message.ConversationID)
  151. } else {
  152. h.sendToClients(clients, message)
  153. }
  154. }
  155. // 仅本地源事件向分布式总线发布,远端同步过来的事件不再二次发布(避免回环)。
  156. if h.bus != nil && !message.FromRemote {
  157. if err := h.bus.Publish(message); err != nil {
  158. log.Printf("⚠️ 分布式广播失败: 对话ID=%d, 类型=%s, 错误=%v", message.ConversationID, message.Type, err)
  159. }
  160. }
  161. }
  162. }
  163. }
  164. // BroadcastMessage 广播消息到指定对话的所有客户端
  165. func (h *Hub) BroadcastMessage(conversationID uint, messageType string, data interface{}) {
  166. h.broadcast <- &Message{
  167. ConversationID: conversationID,
  168. Type: messageType,
  169. Data: data,
  170. Scope: "conversation",
  171. }
  172. }
  173. // BroadcastToAllAgents 广播消息到所有客服客户端(不管连接到哪个对话)
  174. // 用于 visitor_status_update 等需要所有客服都收到的事件
  175. func (h *Hub) BroadcastToAllAgents(messageType string, data interface{}) {
  176. h.broadcast <- &Message{
  177. ConversationID: conversationIDFromData(data, 0),
  178. Type: messageType,
  179. Data: data,
  180. Scope: "all_agents",
  181. }
  182. }
  183. // VisitorConnectionCount 返回指定对话当前访客 WebSocket 连接数
  184. func (h *Hub) VisitorConnectionCount(conversationID uint) int {
  185. h.mu.RLock()
  186. defer h.mu.RUnlock()
  187. clients, ok := h.conversations[conversationID]
  188. if !ok {
  189. return 0
  190. }
  191. count := 0
  192. for client := range clients {
  193. if client.isVisitor {
  194. count++
  195. }
  196. }
  197. return count
  198. }
  199. // GetOnlineAgentIDs 获取所有在线客服的用户ID列表(去重)
  200. // 返回一个 map,key 是 agentID,value 是 true(用于快速查找)
  201. func (h *Hub) GetOnlineAgentIDs() map[uint]bool {
  202. h.mu.RLock()
  203. defer h.mu.RUnlock()
  204. agentIDs := make(map[uint]bool)
  205. for _, clients := range h.conversations {
  206. for client := range clients {
  207. if !client.isVisitor && client.agentID > 0 {
  208. agentIDs[client.agentID] = true
  209. }
  210. }
  211. }
  212. return agentIDs
  213. }
  214. func (h *Hub) snapshotConversationClients(conversationID uint) []*Client {
  215. h.mu.RLock()
  216. defer h.mu.RUnlock()
  217. clients := h.conversations[conversationID]
  218. out := make([]*Client, 0, len(clients))
  219. for c := range clients {
  220. out = append(out, c)
  221. }
  222. return out
  223. }
  224. func (h *Hub) snapshotAllAgents() []*Client {
  225. h.mu.RLock()
  226. defer h.mu.RUnlock()
  227. out := make([]*Client, 0)
  228. for _, clients := range h.conversations {
  229. for c := range clients {
  230. if !c.isVisitor {
  231. out = append(out, c)
  232. }
  233. }
  234. }
  235. return out
  236. }
  237. func (h *Hub) sendToClients(clients []*Client, message *Message) {
  238. for _, client := range clients {
  239. select {
  240. case client.send <- message:
  241. default:
  242. log.Printf("⚠️ 发送消息失败: 对话ID=%d, 客户端断开", client.conversationID)
  243. h.mu.Lock()
  244. if cc, ok := h.conversations[client.conversationID]; ok {
  245. delete(cc, client)
  246. if len(cc) == 0 {
  247. delete(h.conversations, client.conversationID)
  248. }
  249. }
  250. h.mu.Unlock()
  251. safeClose(client.send)
  252. }
  253. }
  254. }
  255. func safeClose(ch chan *Message) {
  256. defer func() {
  257. _ = recover()
  258. }()
  259. close(ch)
  260. }
  261. func conversationIDFromData(data interface{}, fallback uint) uint {
  262. if msg, ok := data.(*models.Message); ok {
  263. return msg.ConversationID
  264. }
  265. if m, ok := data.(map[string]interface{}); ok {
  266. if convID, ok2 := m["conversation_id"]; ok2 {
  267. switch v := convID.(type) {
  268. case uint:
  269. return v
  270. case float64:
  271. return uint(v)
  272. }
  273. }
  274. }
  275. return fallback
  276. }