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
}