Golang 17 gin web项目实战 IM系统(6) 好友功能 心跳检测
44
2025/7/25 15:00 - 2025/7/25 23:00
之前的私聊算陌生人聊天,应该采用redis什么的来保持会话状态来通知对方本人是否在线,那都是后话,现在做好友相关功能。
好友
表设计
- 好友 Friend Struct
type Friend struct {
gorm.Model
UserId uint `json:"user_id"` // 用户id
FriendId uint `json:"friend_id"` // 好友id
}
- 好友组 FriendGroup Struct
#package model
import (
"database/sql/driver"
"go-chat/internal/utils/jsonUtil"
"gorm.io/gorm"
)
type FriendGroup struct {
gorm.Model
UserId uint `json:"user_id"` // 用户id
Name *string `json:"name"` // 群组名称
FriendIdList *FriendIdList `json:"friend_id_list"`
}
// 群组成员id列表
type FriendIdList []uint
func (f *FriendIdList) Scan(value interface{}) error {
return jsonUtil.UnmarshalValue(value, f)
}
func (f *FriendIdList) Value() (driver.Value, error) {
return jsonUtil.MarshalValue(f)
}
- 好友申请 FriendRequest Struct
package model
import "gorm.io/gorm"
type FriendRequest struct {
gorm.Model
UserId uint `json:"user_id"`
FriendId uint `json:"friend_id"`
Status Status `json:"status"`
}
接口
具体服务编写还是有点复杂,但都是基础建设性代码,没有什么思路壁障。
| 接口名 | 请求路径 | 参数说明 | 响应体说明 |
|---|---|---|---|
| 添加好友 | POST /friend/add |
请求体为 []uint,元素为好友用户ID列表 |
成功无数据返回,失败返回错误信息 |
| 获取好友申请列表 | GET /friend/list_req |
无需参数,使用JWT中用户ID | 返回好友申请列表 []FriendRequestVo |
| 处理好友申请 | POST /friend/handle_req |
json<br>{ "id": int64, "status": int }status为1=同意,2=拒绝 |
成功无数据返回,失败返回错误信息 |
| 删除好友 | POST /friend/remove |
请求体为 []int64,元素为要删除的好友ID列表 |
成功无数据返回,失败返回错误信息 |
| 创建好友分组 | POST /friend/group_create |
{ "name": string,"friend_id_list":[]int }分组名称 |
成功无数据返回,失败返回错误信息 |
| 删除好友分组 | GET /friend/group_delete?id=123 |
Query参数:id为分组ID |
成功无数据返回,失败返回错误信息 |
| 修改好友分组 | POST /friend/group_update |
{ "id": int64, "name": string, "friend_id_list":[]int} |
成功无数据返回,失败返回错误信息 |
| 获取好友分组列表 | GET /friend/group_list |
无需参数,使用JWT中用户ID | 返回用户所有分组列表 []FriendGroupVo,含组名与组内好友 |
心跳检测
实现思路
- 客户端上线后每 固定间隔(如10秒) 向服务端发送心跳消息;
- 服务端收到心跳后,记录用户最后心跳时间(
heartbeat_time); - 每隔固定时间(如每30秒),后端定时任务扫描数据库,将长时间(如30秒)未发送心跳的用户设为「离线」;
- 用户当前状态为离线时,收到心跳会自动置为在线,若为「忙碌/离开」则不更改(由用户自己设置的状态);
具体实现
在MessageHandler 方法新增监听事件 heartbeat:
func (ws *WebSocketHandler) MessageHandler(id int64, messageBytes []byte) {
// 处理消息
message := &wsMessage.Message{}
err := jsonUtil.UnmarshalValue(messageBytes, message)
if err != nil {
wsClient.WebSocketClient.SendMessageToOne(id, &model.Response{
Code: http.StatusBadRequest,
Message: "数据格式错误",
Data: nil,
})
return
}
switch message.Type {
case wsMessage.Chat:
ws.ChatHandler(message.SendId, message.Data)
case wsMessage.HeartBeat:
ws.HeartBeatHandler(message.SendId, message.Data)
default:
wsClient.WebSocketClient.SendMessageToOne(id, &model.Response{
Code: http.StatusBadRequest,
Message: "数据格式错误",
Data: nil,
})
}
}
HeartBeatHandler方法实现:
func (ws *WebSocketHandler) HeartBeatHandler(sendId int64, _ interface{}) {
timestamp := time.Now().Unix()
err := ws.userService.UpdateHeartbeatTime(sendId, timestamp)
if err != nil {
wsClient.WebSocketClient.SendMessageToOne(sendId, &model.Response{
Code: http.StatusInternalServerError,
Message: err.Error(),
Data: nil,
})
return
}
// 返回心跳确认
wsClient.WebSocketClient.SendMessageToOne(sendId, &model.Response{
Code: http.StatusOK,
Message: "success",
Data: &wsMessage.Message{
SendId: sendId,
Type: wsMessage.HeartBeatAck,
Data: nil,
Time: time.Now(),
},
})
}
定时器扫描心跳停止用户并下线(注册定时器同其他中间件在bootstrap调用init):
func HeartBeatTimer() {
_, err := Timer.AddFunc("*/30 * * * * *", func() {
logrus.Infof("心跳检测任务开始执行: %v", time.Now())
err := service.UserServiceInstance.CheckOfflineUsers() // 你心跳检测的函数
if err != nil {
logrus.Errorf("心跳检测执行失败: %v", err)
}
})
if err != nil {
logrus.Error("定时任务(%v)添加失败:", "HeartBeatTimer", err)
return
}
}
Timer.go,在bootstrap.go的Start 方法调用:
package timer
import (
"github.com/robfig/cron/v3"
)
var Timer = cron.New(cron.WithSeconds())
func InitTimer() {
HeartBeatTimer()
Timer.Start()
}
仓库方法:
func (r *UserRepository) UpdateHeartbeatTime(userId int64, heartbeatTime int64, tx ...*gorm.DB) error {
// 仅当状态为离线时,才自动改为在线
return gormDB.Model(&model.User{}).
Where("id = ? AND status = ?", userId, 0).
Updates(map[string]interface{}{
"heartbeat_time": heartbeatTime,
"status": 1,
}).Error
}
下一步是群组基础功能建设(拉人、禁言等),在这之后才进入聊天消息核心功能开发
浙公网安备 33010602011771号