client.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. // 客户端管理模块
  2. // 处理单个 WebSocket 客户端的连接、消息读取和发送
  3. package wsClient
  4. import (
  5. "encoding/json"
  6. "fmt"
  7. "github.com/gorilla/websocket" // WebSocket 库
  8. "github.com/rs/zerolog/log"
  9. )
  10. // Client represents connection to a single chatting user
  11. // Client 表示一个聊天用户的连接
  12. // 每个 WebSocket 连接对应一个 Client 实例
  13. type Client struct {
  14. // 客户端名称 -- 用作唯一标识,进行查找用的 (门店端: shop_{shopId}_{userId}, 买家端: customer_{customId}_{userId})
  15. // 在消息中用于标识消息发送者
  16. name string
  17. // a socket is the web socket for this uer
  18. // WebSocket 连接对象,用于与浏览器端进行双向通信
  19. socket *websocket.Conn
  20. //receive is a channel to receive messages from other clients
  21. // 用于接收来自其他客户端的消息的 channel
  22. // 房间会将广播的消息发送到这个 channel
  23. receive chan []byte
  24. //room is where the client is chatting in
  25. // 客户端所在的聊天房间引用
  26. // 用于将发送的消息转发给房间
  27. room *Room
  28. // 新增的用户参数
  29. userInfo UserInfo
  30. }
  31. type UserInfo struct {
  32. UserId int32 `json:"userId"`
  33. Username string `json:"username"`
  34. CustomId int32 `json:"customId"`
  35. ShopId int32 `json:"shopId"`
  36. Platform string `json:"platform"`
  37. Type string `json:"type,omitempty"` // 客户端类型 -- shop:门店端, customer:客户端
  38. StaffId int32 `json:"staffId"`
  39. }
  40. // read 从 WebSocket 连接读取消息并转发给房间 (原作者说: Used to send messages)
  41. // 这个方法会阻塞运行直到连接断开或出现错误
  42. func (c *Client) read() {
  43. // close the connection when we are done
  44. defer c.socket.Close()
  45. // endlessly read messages from input
  46. for {
  47. // 从 WebSocket 连接读取消息
  48. // _ 表示忽略消息类型,msg 是消息内容,err 是错误
  49. _, msg, err := c.socket.ReadMessage()
  50. // break if there is an error
  51. if err != nil {
  52. if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
  53. log.Error().Err(err).Str("client", c.name).Msg("读取WebSocket消息失败")
  54. } else {
  55. log.Info().Err(err).Str("client", c.name).Msg("WebSocket连接正常关闭")
  56. }
  57. break
  58. }
  59. // 记录原始消息内容用于调试
  60. //log.Debug().Str("client", c.name).Int("rawMessageLength", len(msg)).Str("rawMessage", string(msg)).Msg("读取到原始WebSocket消息")
  61. // 在此处处理心跳消息
  62. if string(msg) == "*hb*" {
  63. log.Debug().Str("client", c.name).Str("msg_content", string(msg)).Msg("Received heartbeat message")
  64. // 额外验证:使用 fmt.Printf 确保字段能正确显示
  65. // fmt.Printf("DEBUG: Heartbeat from %s with message: %s\n", c.name, string(msg))
  66. continue
  67. }
  68. // 构造要发送的消息对象,包含用户名和消息内容
  69. outgoing := map[string]string{
  70. "name": c.name, // 发送者用户名
  71. "message": string(msg), // 消息内容(转换为字符串)
  72. }
  73. // 将消息对象编码为 JSON 格式
  74. jsMessage, err := json.Marshal(outgoing)
  75. if err != nil {
  76. log.Error().Err(err).Msg("Enconding failed!")
  77. continue
  78. }
  79. // 将编码后的消息和发送者信息一起发送到房间的 forward channel
  80. c.room.forward <- &message{
  81. content: jsMessage, // 消息内容
  82. sender: c, // 发送者(当前客户端)
  83. } // 房间会将这条消息分发给除发送者外的所有其他客户端(在 room.go 中 run() 方法 case msg := <-r.forward 中)
  84. }
  85. }
  86. // write 从 receive channel 读取消息并发送给客户端
  87. // 在独立的 goroutine 中运行,处理向客户端发送消息的任务
  88. func (c *Client) write() {
  89. defer c.socket.Close()
  90. // 循环从 receive channel 读取消息
  91. for msg := range c.receive {
  92. err := c.socket.WriteMessage(websocket.TextMessage, msg)
  93. if err != nil {
  94. log.Error().Err(err).Str("client", c.name).Msg("写入WebSocket消息失败")
  95. return
  96. }
  97. }
  98. }
  99. // 根据房间参数生成客户端名称
  100. // 用于生成客户端的唯一标识,用作唯一标识,进行查找用的 (门店端: shop_{shopId}_{userId}, 买家端: customer_{customId}_{userId})
  101. func clientName(roomParams *RoomParams) string {
  102. // 首先根据 Type 来判断是门店端还是买家端
  103. if roomParams.Type == "shop" {
  104. return fmt.Sprintf("shop_%d_%d", roomParams.ShopId, roomParams.StaffId)
  105. } else {
  106. return fmt.Sprintf("customer_%d_%d", roomParams.CustomId, roomParams.UserId)
  107. }
  108. }