这是我参与「第五届青训营 」伴学笔记创作活动的第 15 天
本文将介绍聊天功能的相关处理
方案
对于后端聊天接口
- 使用 WebSockets 或 Server-Sent Events (SSE) 来实现即时通讯。
- 使用第三方通讯工具,如 Pusher,Ably 等。
- 使用 XMPP (Extensible Messaging and Presence Protocol) 协议,这是一种开放的、可扩展的即时通讯协议。
- 使用 MQTT (Message Queuing Telemetry Transport) 协议,这是一种专门针对 IoT (Internet of Things) 设备的即时通讯协议。
思路
在这里,我采用了消息队列的方法
与评论的流程一致,
发送私信的话——就是用消息队列异步生产者把对应的消息发到指定主题中
main函数会协程(多线程)监听这个主题
如果监听到了会进行存储操作——存储到数据库(可以加个更新到缓存,过期时间可以设置短点)
获取聊天记录——直接数据库或者缓存读取
详情可看代码
消息队列知识点
1、Kafka 本质上是⼀个消息队列,一个高吞吐量、持久性、分布式的消息系统。
2、包含生产者(producer)和消费者(consumer),每个consumer属于一个特定的消费者组(Consumer Group)。
3、生产者生产消息(message)写入到kafka服务器(broker,kafka集群的节点),消费者从kafka服务器(broker)读取消息。
4、消息可分为不同的类型即不同的主题(topic)。
5、同一主题(topic)的消息可以分散存储到不同的服务器节点(partition)上,一个分区(partition)只能由一个消费者组内的一个消费者消费。
6、每个partition可以有多个副本,一个Leader和若干个Follower,Leader发生故障时,会选取某个Follower成为新的Leader。
教程
www.cnblogs.com/YLTFY1998/p…
Kafka可视化工具: blog.csdn.net/qq_45956730…
代码
main.go
// 监听私信聊天的消息队列
go dao.ListenChat()
service/chat/chatService.go
package chat
import (
"encoding/json"
"fmt"
"go_douyin/global/variable"
"go_douyin/model"
"go_douyin/utils/kafka_client"
)
type ChatService struct {
//chatMapper *dao.ChatMapper
kafkaClients *kafka_client.KafkaClient
}
func NewChatService() *ChatService {
return &ChatService{
kafkaClients: &kafka_client.KafkaClient{},
}
}
// AddChat 发送消息
func (h *ChatService) AddChat(chat model.Chat) error {
// 序列化评论数据
chatJSON, err := json.Marshal(chat)
if err != nil {
return err
}
// 将私信放到消息队列
err = variable.Kafka_chat.ProduceMessage(string(chatJSON))
return err
}
// GetMessages 获取聊天记录
func (h *ChatService) GetMessages(senderID string, recipientID string) ([]string, error) {
fmt.Println("此处直接获取数据库或者缓存的数据即可")
return nil, nil
}
global/variable/variable.go
// 私信聊天的队列
Kafka_chat *kafka_client.KafkaClient
Kafka_chat = kafka_client.NewKafkaClient([]string{"43.139.72.246:9092"}, "chat-topic")
chatMapper.go
package dao
import (
"encoding/json"
"fmt"
"go_douyin/global/variable"
"go_douyin/model"
)
type ChatMapper struct{}
func NewChatMapper() *ChatMapper {
return &ChatMapper{}
}
// 监听私信消息队列
func ListenChat() {
for {
// 获取消息
message, err := variable.Kafka_chat.ConsumeMessage()
if err != nil {
fmt.Printf("获取消息失败:%v\n", err)
continue
}
// 反序列化私信数据
var chat model.Chat
err = json.Unmarshal([]byte(message), &chat)
if err != nil {
fmt.Printf("反序列化私信数据失败:%v\n", err)
continue
}
// 存储私信数据
err = SaveChat(chat)
if err != nil {
fmt.Printf("存储私信数据失败:%v\n", err)
continue
}
fmt.Printf("成功存储私信数据:%+v\n", chat)
}
}
// 监听预加载私信消息队列
func ListenPreloadChatList() {
for {
fmt.Printf("正在预加载……")
// 获取消息
message, err := variable.Kafka_preload.ConsumeMessage()
if err != nil {
fmt.Printf("获取消息失败:%v\n", err)
continue
}
// 此次应写存储到缓存的函数
fmt.Printf("正在缓存" + message + "视频id的私信")
}
}
func SaveChat(chat model.Chat) error {
// 这里是将私信数据存储到数据库的代码,具体实现方式取决于你使用的数据库类型
// 这里还可以补个缓存
fmt.Println("正在存储数据库,同时更新进缓存", chat)
return nil
}
chatController.go
package controller
import (
"fmt"
"github.com/gin-gonic/gin" "go_douyin/model" "go_douyin/service/chat" "go_douyin/utils/response" "time")
type ChatController struct {
chatService *chat.ChatService
}
func NewChatController() *ChatController {
return &ChatController{
chatService: chat.NewChatService(),
}
}
// 发送信息
func (h *ChatController) AddChat(c *gin.Context) {
// 获取请求参数
var requestBody map[string]interface{}
requestBody = make(map[string]interface{})
// 解析请求体
c.ShouldBindJSON(&requestBody)
// 获取请求参数
//在 HTTP POST 请求中,请求体中的数据通常是以字符串形式发送的。JSON 格式中的数字默认都是浮点型,默认都是 float64 类型
user_id := requestBody["user_id"].(float64)
to_user_id := requestBody["to_user_id"].(float64)
content, _ := requestBody["content"].(string)
var cc model.Chat
cc.RecipientID = uint64(to_user_id)
cc.Message = content
cc.SenderID = uint64(user_id)
cc.SendTime = time.Now()
err := h.chatService.AddChat(cc)
if err != nil {
response.Success(c, "私信失败", gin.H{})
fmt.Println(err)
return
}
response.Success(c, "私信成功", gin.H{})
}
// 聊天记录
func (h *ChatController) ListChat(c *gin.Context) {
// 获取请求参数
ToUserId := c.Query("to_user_id")
userId := c.Query("user_id")
data, err := h.chatService.GetMessages(userId, ToUserId)
if err != nil {
response.Success(c, "获取失败", gin.H{})
return
}
response.Success(c, "获取成功", gin.H{
"message_list": data,
})
}
router.go
chatController := controller.NewChatController()
// 社交组:私信聊天
v5 := router.Group("/douyin/message")
{
v5.POST("action", chatController.AddChat)
v5.GET("chat", chatController.ListChat)
}
chat.go
package model
import "time"
// 这里后面的记得改和以前的一样
type Chat struct {
ChatID uint64 `gorm:"column:chat_id;primaryKey;autoIncrement"`
SenderID uint64 `gorm:"column:sender_id;not null"`
RecipientID uint64 `gorm:"column:recipient_id;not null"`
Message string `gorm:"column:message;not null"`
SendTime time.Time `gorm:"column:send_time;not null;default:CURRENT_TIMESTAMP"`
}
func (Chat) TableName() string {
return "tb_chat"
}