这是我参与「第五届青训营」笔记创作活动的第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。
每个userId都关联两个Queue。比如userId为1的用户,可以消费message_current1和message_list1中的聊天记录。
昨天提到了MessageList的生产和消费的实现方案,今天继续分享MessageCurrent的生产和消费的实现方案:
生产端:
- 首先对传入的msg信息进行封装,封装到msg对象中。
- 通过actionType的值,判断操作类型,如果actionType为1,则将msg信息写入数据库中。
- 因为消费端需要的CreateTime格式为时间戳。故需要在此处进行时间格式转换,将msg.CreateTime的date格式转成时间戳。
- 进行序列化,将msg对象转成一个json字符串,便于后续将其Publish到Queue中。
- 最后,因为前端设定,发送消息的人不需要从队列里进行消费即可在它的界面展示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中的消息进行消费即可,实现流程和昨天讲解的消费端类似但是有两点需要注意:
- Queue的Name发生了变化
- 由于生产端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...)
效果展示:
总结
至此我们已经将RabbitMQ引入大项目聊天系统,完成了生产端和消费端所有代码的编写。