client.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. package server
  2. import (
  3. "encoding/json"
  4. "regexp"
  5. "time"
  6. "github.com/gin-gonic/gin"
  7. "github.com/gorilla/websocket"
  8. "github.com/mitchellh/mapstructure"
  9. "github.com/rotisserie/eris"
  10. "gorm.io/gorm"
  11. "sikey.com/websocket/models"
  12. "sikey.com/websocket/repositories"
  13. "sikey.com/websocket/utils/gid"
  14. "sikey.com/websocket/utils/zlog"
  15. )
  16. type Client struct {
  17. ctx *gin.Context
  18. UserId string
  19. hub *Hub
  20. UnderlyingConn *websocket.Conn
  21. online *models.Online
  22. isSimpleMsg bool // isSimpleMsg 是否是简单消息
  23. localization string // localization 国际码
  24. firebaseToken string // firebaseToken FCM 推送的 token
  25. // Send message channel 发送消息
  26. // 当用户在线时会通过 Send channel 发送在线消息 但如果用户不在线,
  27. Send chan *Message
  28. readWait time.Duration // readWait 读超时
  29. writeWait time.Duration // writeWait 写超时
  30. pingWait time.Duration // pingWait 心跳超时
  31. repos *repositories.Repositories
  32. }
  33. func (c *Client) reader() {
  34. defer func() {
  35. c.hub.Disconnect <- c
  36. c.Close()
  37. zlog.Debugf("Close client %s", c.UserId)
  38. }()
  39. c.UnderlyingConn.SetReadDeadline(time.Now().Add(c.readWait))
  40. for {
  41. _, bytes, err := c.UnderlyingConn.ReadMessage()
  42. if err != nil {
  43. if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
  44. zlog.Errorf("error: %v", eris.Wrap(err, c.UserId))
  45. } else {
  46. zlog.Errorf("error: %v", eris.Wrap(err, c.UserId))
  47. }
  48. // Close connect
  49. // _ = c.repos.OnlineRepository.Offline(c.ctx, c.online)
  50. return
  51. }
  52. message := deserializeMessage(bytes)
  53. switch message.Type {
  54. case MessageTypePingPong:
  55. zlog.Debugf("receive ping message from %s", c.UserId)
  56. _ = c.repos.OnlineRepository.Heartbeat(c.ctx, c.online)
  57. case MessageTypeUpChating, MessageTypeDownChating:
  58. // Chat dialogue messages
  59. chatingContent := message.Content.(ChatingContent)
  60. // Save message to database
  61. messageId := gid.GetMessageId()
  62. chatingContent.MessageId = messageId
  63. chatingContent.SessionId = chatingContent.Receiver
  64. err = c.saveMessage(messageId, message.Type, &chatingContent)
  65. if err != nil {
  66. c.writeError(message.RequestId, err)
  67. continue
  68. }
  69. // Receiver ID format determines whether the receiver is an account or a session
  70. zlog.Debugf("message receiver: %s", chatingContent.Receiver)
  71. users := c.getReceiverUserIds(chatingContent.Receiver)
  72. zlog.Debugf("users: %s", users)
  73. for _, id := range users {
  74. var messaging = *message
  75. messaging.Receiver = id
  76. messaging.Content = chatingContent
  77. zlog.Infof("Send message %s to %s", c.UserId, id)
  78. // Check if the user is online
  79. if c.firebaseToken != "" {
  80. var online bool
  81. if online, err = c.repos.OnlineRepository.Is(c.ctx, id); err != nil {
  82. zlog.Error(eris.Wrap(err, "unable to find online user"))
  83. continue
  84. } else {
  85. if !online {
  86. // Send FCM message
  87. }
  88. }
  89. }
  90. c.hub.Message <- &messaging
  91. }
  92. // Reply message id
  93. message.Content = &ContentReply{MessageId: messageId}
  94. }
  95. // Reply message
  96. // zlog.Debugf("reply message %s to %s", message.RequestId, c.UserId)
  97. if message.IsNeedReply() {
  98. c.Send <- newReplyMessage(message)
  99. // Reset read deadline, prevent Reader from shutting down
  100. // zlog.Debugf("reset read deadline for %s", c.UserId)
  101. c.UnderlyingConn.SetReadDeadline(time.Now().Add(c.pingWait))
  102. }
  103. }
  104. }
  105. func (c *Client) writer() {
  106. ticker := time.NewTicker(c.pingWait)
  107. defer func() {
  108. ticker.Stop()
  109. c.Close()
  110. }()
  111. for {
  112. select {
  113. case message, ok := <-c.Send:
  114. c.UnderlyingConn.SetWriteDeadline(time.Now().Add(c.writeWait))
  115. if !ok {
  116. // The hub closed the channel.
  117. c.UnderlyingConn.WriteMessage(websocket.CloseMessage, []byte{})
  118. return
  119. }
  120. var err error
  121. err = c.UnderlyingConn.WriteMessage(websocket.TextMessage, serializationMessage(message))
  122. if err != nil {
  123. return
  124. }
  125. // Received modification message status
  126. switch message.Type {
  127. case MessageTypeUpChating, MessageTypeDownChating:
  128. // Chat dialogue messages
  129. if chatingContent, ok := message.Content.(ChatingContent); ok {
  130. if msg, err := c.repos.SessionRepository.FindMessageById(c.ctx, chatingContent.MessageId); err == nil {
  131. msg.Received = true
  132. c.repos.SessionRepository.UpdateMessage(c.ctx, msg)
  133. } else {
  134. if eris.Is(err, gorm.ErrRecordNotFound) {
  135. break
  136. }
  137. zlog.Error(err)
  138. }
  139. }
  140. }
  141. buf, _ := message.MarshalBinary()
  142. zlog.Debugf("send message %s to %s content: %v", message.RequestId, c.UserId, string(buf))
  143. case <-ticker.C:
  144. // 到时间发送 ping 信号
  145. c.UnderlyingConn.SetWriteDeadline(time.Now().Add(c.writeWait))
  146. if err := c.UnderlyingConn.WriteMessage(websocket.PingMessage, nil); err != nil {
  147. return
  148. }
  149. }
  150. }
  151. }
  152. func (c *Client) writeError(requestId string, err error) {
  153. c.Send <- &Message{
  154. RequestId: requestId,
  155. Type: MessageTypeError,
  156. Content: ContentError{Err: err.Error()},
  157. }
  158. }
  159. func (c *Client) saveMessage(messageId string, messageType int8, content *ChatingContent) error {
  160. // Standardized structure, This is not an unnecessary step!!!
  161. // Filter out excess fields.
  162. var err error
  163. switch content.PayloadType {
  164. case ChatingContentTypeText:
  165. var textContent ContentText
  166. err = mapstructure.Decode(content.Payload, &textContent)
  167. content.Payload = textContent
  168. case ChatingContentTypeMetadata:
  169. var contentMetadata ContentMetadata
  170. err = mapstructure.Decode(content.Payload, &contentMetadata)
  171. content.Payload = contentMetadata
  172. }
  173. if err != nil {
  174. return eris.Wrap(err, "unable to decode message content")
  175. }
  176. payload, _ := json.Marshal(content.Payload)
  177. return c.repos.SessionRepository.CreateMessage(c.ctx, &models.SessionMessage{
  178. ID: messageId,
  179. SessionId: content.Receiver,
  180. Receiver: content.Receiver,
  181. Sender: c.UserId,
  182. Type: messageType,
  183. ContentType: content.PayloadType,
  184. Content: payload,
  185. IsRead: false,
  186. })
  187. }
  188. // getReceiverUserIds 通过 receiver 获取接收者的用户ID
  189. // 使用正则表达式验证ID 是否是 account_id 或 session_id
  190. // session_id 的话需要查询 session_member 表获取 session 的成员
  191. func (c *Client) getReceiverUserIds(receiver string) []string {
  192. reg, _ := regexp.Compile(`[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}`)
  193. if reg.Match([]byte(receiver)) {
  194. return []string{receiver}
  195. }
  196. members, err := c.repos.SessionRepository.GetSessionMembersRemoveOneself(
  197. c.ctx, receiver, c.UserId)
  198. if err != nil {
  199. return []string{}
  200. }
  201. var ms = make([]string, len(members))
  202. for i, memb := range members {
  203. ms[i] = memb.RefId
  204. }
  205. return ms
  206. }
  207. // Close websocket connection
  208. func (c *Client) Close() {
  209. c.UnderlyingConn.Close()
  210. online := &models.Online{UserId: c.UserId, ServerId: c.hub.serverId}
  211. c.repos.OnlineRepository.Offline(c.ctx, online)
  212. }