GO 聊天IM系统(三)整合redis、channel完成单聊私聊以及消息回调

message结构体的定义:

type Message struct {
    gorm.Model
    UserId     int64  //发送者
    TargetId   int64  //接受者
    Type       int    //发送类型  1私聊  2群聊  3心跳
    Media      int    //消息类型  1文字 2表情包 3语音 4图片 /表情包
    Content    string //消息内容
    CreateTime uint64 //创建时间
    ReadTime   uint64 //读取时间
    Pic        string
    Url        string
    Desc       string
    Amount     int //其他数字统计
}

Type字段帮助后端对消息的分发进行判断

func recvProc(node *Node) {
    for {
        _, data, err := node.Conn.ReadMessage()
        if err != nil {
            fmt.Println(err)
            return
        }
        msg := Message{}
        err = json.Unmarshal(data, &msg)
        if err != nil {
            fmt.Println(err)
        }
        //心跳检测 msg.Media == -1 || msg.Type == 3
//心跳消息实现对连接时间的更新,并不会推送消息
        if msg.Type == 3 {
            currentTime := uint64(time.Now().Unix())
            node.Heartbeat(currentTime)
        } else {
//实现对消息的分发
            dispatch(data)
            fmt.Println("[ws] recvProc <<<<< ", string(data))
        }

    }
}
func dispatch(data []byte) {
    msg := Message{}
    msg.CreateTime = uint64(time.Now().Unix())
    err := json.Unmarshal(data, &msg)
    if err != nil {
        fmt.Println(err)
        return
    }
    switch msg.Type {
    case 1: //私信
        fmt.Println("dispatch  data :", string(data))
        sendMsg(msg.TargetId, data)
    case 2: //群发
        sendGroupMsg(msg.TargetId, data) //发送的群ID ,消息内容
    }
}

使用redis存储聊天记录

func sendMsg(userId int64, msg []byte) {

    rwLocker.RLock()
    node, ok := clientMap[userId]
    rwLocker.RUnlock()
    jsonMsg := Message{}
    json.Unmarshal(msg, &jsonMsg)
    ctx := context.Background()
    targetIdStr := strconv.Itoa(int(userId))
    userIdStr := strconv.Itoa(int(jsonMsg.UserId))
    jsonMsg.CreateTime = uint64(time.Now().Unix())
    r, err := utils.Red.Get(ctx, "online_"+userIdStr).Result()
    if err != nil {
        fmt.Println(err)
    }
    var key string

    if r != "" {
        if ok {
            fmt.Println("sendMsg >>> userID: ", userId, "  msg:", string(msg))
            //把消息写进队列
            node.DataQueue <- msg
        }
    }
    //设置聊天的key
    if jsonMsg.Type == 1 {
        if userId > jsonMsg.UserId {
            key = "msg_" + userIdStr + "_" + targetIdStr
        } else {
            key = "msg_" + targetIdStr + "_" + userIdStr
        }
    } else {
        groupId := strconv.Itoa(int(jsonMsg.TargetId))
        key = "msg_group" + groupId
    }

//******************************************************************************************
//使用zset存储聊天记录,私聊key为用户id(小)_用户id(da)保证一对私聊的消息仅保有一份
//群聊key为群id保证群聊小心保有一份
    res, err := utils.Red.ZRevRange(ctx, key, 0, -1).Result()
    if err != nil {
        fmt.Println(err)
    }
//获取消息记录总数,为新加入的消息设置排序字段
    score := float64(cap(res)) + 1
    //添加最新消息
    ress, e := utils.Red.ZAdd(ctx, key, &redis.Z{score, msg}).Result() //jsonMsg
    //res, e := utils.Red.Do(ctx, "zadd", key, 1, jsonMsg).Result() //备用 后续拓展 记录完整msg
//******************************************************************************************


    if e != nil {
        fmt.Println(e)
    }
    fmt.Println(ress)
}

func sendGroupMsg(targetId int64, msg []byte) {
    fmt.Println("开始群发消息")
    userIds := SearchUserByGroupId(uint(targetId))
    for i := 0; i < len(userIds); i++ {
        //排除给自己的
        if targetId != int64(userIds[i]) {
            sendMsg(int64(userIds[i]), msg)
        }

    }
}

聊天记录的回调

前端:在点击联系人时,前端调用service.RedisMsg读写历史聊天记录

后端:

func RedisMsg(userIdA int64, userIdB int64, start int64, end int64, isRev bool, typee int64) []string {
//userIdA 私聊代表目标用户id,群聊代表群id
//start int64, end 代表想要读取的记录的条数 默认最近10条
//isRev判断是否需要对读取的数据进行倒叙
//判断想要读取聊天记录的类型
    ctx := context.Background()
    userIdStr := strconv.Itoa(int(userIdA))
    targetIdStr := strconv.Itoa(int(userIdB))
    var key string
    if typee == 1 {
        if userIdA > userIdB {
            key = "msg_" + targetIdStr + "_" + userIdStr
        } else {
            key = "msg_" + userIdStr + "_" + targetIdStr
        }
    } else {
        key = "msg_group" + userIdStr
    }
    var rels []string
    var err error
    if isRev {
        rels, err = utils.Red.ZRange(ctx, key, start, end).Result()
    } else {
        rels, err = utils.Red.ZRevRange(ctx, key, start, end).Result()
    }
    if err != nil {
        fmt.Println(err) //没有找到
    }
    return rels
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redis 的发布/订阅(pub/sub)功能可以实现消息的广播。具体实现步骤如下: 1. 在发送消息的客户端,使用 Redis 的 publish 命令发布消息到指定的频道(channel)。 2. 在接收消息的客户端,使用 Redis 的 subscribe 命令订阅相应的频道,以接收该频道上的消息。 3. 当发送消息的客户端发布消息后,订阅该频道的所有客户端都能收到该消息。 以下是一个示例代码,展示了如何使用 Redis 的 pub/sub 实现消息的广播: ```python import redis # 创建 Redis 客户端 r = redis.Redis(host='localhost', port=6379, db=0) # 定义消息频道 channel = 'messages' # 发送消息 r.publish(channel, 'Hello, world!') # 接收消息 p = r.pubsub() p.subscribe(channel) for message in p.listen(): print(f'Received message: {message["data"].decode()}') ``` 在这个示例中,我们先创建了一个 Redis 客户端,并定义了一个名为 'messages' 的频道。然后,我们使用 publish 命令向该频道发布了一条消息。接下来,我们使用 subscribe 命令订阅了该频道,并使用 pubsub().listen() 方法监听该频道上的消息。当有消息发布到该频道时,我们会收到该消息并打印到控制台上。 需要注意的是,Redis 的 pub/sub 是一种异步的消息传递机制,消息的发送和接收是独立的进程。因此,在实际使用中,我们需要考虑消息的可靠性,以及如何处理消息传递过程中可能发生的异常情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值