RabbitMQ优化聊天系统3|青训营笔记

101 阅读3分钟

这是我参与「第五届青训营」笔记创作活动的第9天。

今天主要是接昨天的分享进度,继续分享RabbitMQ优化大项目的聊天系统的代码实现方案。

实现思路

在发送新消息的时候,我们不仅仅调用Dao层的方法将Message信息写入到数据库中,还要将Message信息写到RabbitMQ的消息队列中去。将CurrentMessage对应的Queue的MAX-Length设置为1,并采用drop-head的过期策略。在接收到路径为/douyin/message/chat/的GET请求时,我们对RabbitMQ的Queue中的消息进行消费,然后反序列化,存到一个MessageList中去,最后将该MessageList返回给前端即可。

具体代码实现

我们将消息归类成两种:MessageList和MessageCurrent。其中MessageList是数据库的messages表中所有的message组成的List,MessageCurrent是当前用户在当前时刻发送的message。

image.png 每个userId都关联两个Queue。比如userId为1的用户,可以消费message_current1message_list1中的聊天记录。

昨天提到了MessageList的生产和消费的实现方案,今天继续分享MessageCurrent的生产和消费的实现方案:

生产端:

  1. 首先对传入的msg信息进行封装,封装到msg对象中。
  2. 通过actionType的值,判断操作类型,如果actionType为1,则将msg信息写入数据库中。
  3. 因为消费端需要的CreateTime格式为时间戳。故需要在此处进行时间格式转换,将msg.CreateTime的date格式转成时间戳。
  4. 进行序列化,将msg对象转成一个json字符串,便于后续将其Publish到Queue中。
  5. 最后,因为前端设定,发送消息的人不需要从队列里进行消费即可在它的界面展示Current聊天记录,故只需将消息写到ToId对应的消息队列中去即可。
//Model层
type RespMessage struct {
   Id         int    `json:"id"`
   ToId       int    `json:"to_id"`
   FromId     int    `json:"from_id"`
   Content    string `json:"content"`
   CreateTime string `json:"create_time"`
}

//Service层
func MessageAction(fromId int, toId int, content string, actionType int) (err error) {

    //对传入的msg信息进行封装,封装到msg对象中
   msg := model.RespMessage{
      ToId:       toId,
      FromId:     fromId,
      Content:    content,
      CreateTime: time.Now().Format("2006-01-02T15:04:05Z07:00"),
   }
   //通过actionType的值 判断操作类型 
   if actionType == g.MessageSendEvent {
       //将msg写入数据库中
      err = model.CreateMessage(&msg)
      if err != nil {
         err = errors.New("发送消息失败: " + err.Error())
      }
   }

   t := time.Time{}
   //进行时间格式转换,将msg.CreateTime的date格式转成时间戳
   t, _ = time.ParseInLocation("2006-01-02T15:04:05Z07:00", msg.CreateTime, time.Local)
   msg.CreateTime = strconv.Itoa(int(t.Unix()))

    //进行序列化,将msg对象转成一个json字符串,便于后续将其Publish到Queue中
   JsonMsg, err := json.Marshal(msg)
   strJsonMsg := string(JsonMsg)
   
   //将消息写到userId对应的消息队列中去
   //mq.PublishMessageCurrentToMQ(strJsonMsg, fromId)
   
   //将消息写到ToId对应的消息队列中去
   mq.PublishMessageCurrentToMQ(strJsonMsg, toId)

   return
}

消费端:

对Queue中的消息进行消费即可,实现流程和昨天讲解的消费端类似但是有两点需要注意

  1. Queue的Name发生了变化
  2. 由于生产端Publish到Queue中的消息为一个msg对象的json字符串,在进行反序列化后得到的是一个msg对象,而前端需要我们返回的是一个MessageList,故我们需要先创建一个MessageList对象,然后将这个msg通过append函数添加到这个MessageList中去。
func GetRabbitMQMessageCurrent(userId int) (respMessageList []model.RespMessage, err error) {
   strUserId := strconv.Itoa(userId)
   conn, _ := amqp.Dial("amqp://admin:Qd20010701.@10.211.55.4:5672/")

   ch, _ := conn.Channel()

   argumentsMap := map[string]interface{}{}
   argumentsMap["x-max-length"] = 1
   argumentsMap["x-overflow"] = "drop-head"
   q, _ := ch.QueueDeclare(
      "message_current"+strUserId, // name
      true,                        // durable
      false,                       // delete when unused
      false,                       // exclusive
      false,                       // no-wait
      argumentsMap,                // arguments
   )
   msgs, _ := ch.Consume(
      q.Name, // queue
      "",     // consumer
      true,   // auto-ack
      false,  // exclusive
      false,  // no-local
      false,  // no-wait
      nil,    // args
   )
   messageList := []model.RespMessage{}
   message := model.RespMessage{}

   go func() {
      for d := range msgs {
         json.Unmarshal(d.Body, &message)
         messageList = append(messageList, message)
      }
   }()
   err = ch.Close()
   if err != nil {
      g.Logger.Infof("ch.Close()时发生了错误!")
   }
   err = conn.Close()
   if err != nil {
      g.Logger.Infof("conn.Close()时发生了错误!")
   }
   return messageList, err
}

Controller层:

对MessageList和MessageCurrent的消费端返回的MessageList进行append,得到allMessageList,然后将allMessageList返回给前端。

recordList, err := m.GetRabbitMQMessageList(fromId)
currentList, err := m.GetRabbitMQMessageCurrent(fromId)
allMessageList := append(recordList, currentList...)

效果展示:

image.png

总结

至此我们已经将RabbitMQ引入大项目聊天系统,完成了生产端和消费端所有代码的编写。