群聊优化

This commit is contained in:
2025-12-08 14:55:47 +08:00
parent 20e8cd7d82
commit 041d22c382
4 changed files with 316 additions and 26 deletions

View File

@@ -643,14 +643,20 @@ func ChangeMemberRoleHandler(c *gin.Context) {
memberID := c.Param("user_id")
var req struct {
Role int8 `json:"role" binding:"required"`
Role *int8 `json:"role"` // 使用指针类型,允许 0 值(普通成员)
}
if err := c.ShouldBindJSON(&req); err != nil {
utils.BadRequest(c, "参数错误: "+err.Error())
return
}
if err := service.RoomSvc.ChangeMemberRole(roomID, operatorID.(string), memberID, req.Role); err != nil {
// 手动验证 role 是否提供
if req.Role == nil {
utils.BadRequest(c, "参数错误: role 字段必须提供")
return
}
if err := service.RoomSvc.ChangeMemberRole(roomID, operatorID.(string), memberID, *req.Role); err != nil {
utils.BadRequest(c, err.Error())
return
}

View File

@@ -5,6 +5,7 @@
package api
import (
"encoding/json"
"fmt"
"math/rand"
"strconv"
@@ -105,8 +106,8 @@ func CreateUserHandler(c *gin.Context) {
*/
func UpdateUserHandler(c *gin.Context) {
var req struct {
ID string `json:"id" binding:"required"`
Updates map[string]interface{} `json:"updates" binding:"required"`
ID string `json:"id" binding:"required"`
Updates map[string]interface{} `json:"updates" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
@@ -119,9 +120,70 @@ func UpdateUserHandler(c *gin.Context) {
return
}
// 如果更新了头像或名称,通知好友
if service.ContactSvc != nil && service.ChatSvc != nil {
if _, hasName := req.Updates["name"]; hasName {
go notifyFriendsOfProfileUpdate(req.ID, req.Updates)
} else if _, hasAvatar := req.Updates["avatar"]; hasAvatar {
go notifyFriendsOfProfileUpdate(req.ID, req.Updates)
}
}
utils.Success(c, "更新成功")
}
/**
* notifyFriendsOfProfileUpdate
* 功能:通知好友用户信息更新
*/
func notifyFriendsOfProfileUpdate(userID string, updates map[string]interface{}) {
if service.ContactSvc == nil || service.ChatSvc == nil || service.UserSvc == nil {
return
}
// 获取更新后的用户信息
user, err := service.UserSvc.GetUserByID(userID)
if err != nil {
return
}
// 获取好友列表
friendIDs, err := service.ContactSvc.GetFriendUserIDs(userID)
if err != nil || len(friendIDs) == 0 {
return
}
// 构建通知消息
notifExtra := map[string]interface{}{
"type": "profile_update",
"user_id": userID,
"name": user.Name,
"avatar": user.Avatar,
"updates": updates,
}
notifExtraJSON, _ := json.Marshal(notifExtra)
// 发送给所有好友
for _, friendID := range friendIDs {
notifMsg := model.ChatMessage{
RoomID: userID,
SenderUserID: userID,
ReceiverUserID: friendID,
MessageType: model.MessageTypeFriendNotif,
Content: user.Name + " 更新了个人资料",
Extra: string(notifExtraJSON),
}
// 不持久化此类通知,直接推送
pushMsg := model.WsPayload{
RequestType: "receive_message",
Data: notifMsg,
}
msgBytes, _ := json.Marshal(pushMsg)
service.ChatSvc.DispatchMessage(friendID, msgBytes)
}
}
/**
* DeleteUserHandler
* 功能:删除用户
@@ -152,4 +214,3 @@ func generateUserID() string {
random := rand.Intn(1000000)
return fmt.Sprintf("user_%d_%d", timestamp, random)
}

View File

@@ -53,7 +53,7 @@ func (s *ContactService) AddFriend(fromUserID, toUserID, message string) error {
// 检查是否已有待处理的申请
var existingRequest model.FriendRequest
result = s.DB.Where("from_user_id = ? AND to_user_id = ? AND status = ?",
result = s.DB.Where("from_user_id = ? AND to_user_id = ? AND status = ?",
fromUserID, toUserID, "pending").First(&existingRequest)
if result.Error == nil {
return errors.New("已发送过好友申请")
@@ -79,7 +79,7 @@ func (s *ContactService) AddFriend(fromUserID, toUserID, message string) error {
*/
func (s *ContactService) GetFriendRequests(userID string) ([]model.FriendRequest, error) {
var requests []model.FriendRequest
result := s.DB.Where("(to_user_id = ? AND status = ?) OR (from_user_id = ? AND status = ?)",
result := s.DB.Where("(to_user_id = ? AND status = ?) OR (from_user_id = ? AND status = ?)",
userID, "pending", userID, "rejected").
Order("created_at DESC").
Find(&requests)
@@ -127,7 +127,7 @@ func (s *ContactService) AcceptFriendRequest(requestID uint, userID string) erro
room := model.ChatRoom{
RoomID: roomID,
RoomType: "p2p",
OwnerID: "0", // 单聊群主为0
OwnerID: "0", // 单聊群主为0
CreatorID: request.ToUserID, // 接受者为创建者
}
if err := tx.Create(&room).Error; err != nil {
@@ -411,22 +411,22 @@ func (s *ContactService) GetContactsWithUserInfo(userID string) ([]map[string]in
// 组合数据,包含 user 对象
item := map[string]interface{}{
"id": contact.ContactID,
"user_id": contact.ContactID,
"id": contact.ContactID,
"user_id": contact.ContactID,
"contact_user_id": contact.ContactID,
"user": user, // 包含完整的用户信息对象
"remark_name": contact.RemarkName,
"room_id": contact.RoomID,
"group_id": contact.GroupID,
"is_top": contact.IsTop,
"is_muted": contact.IsMuted,
"user": user, // 包含完整的用户信息对象
"remark_name": contact.RemarkName,
"room_id": contact.RoomID,
"group_id": contact.GroupID,
"is_top": contact.IsTop,
"is_muted": contact.IsMuted,
"is_special_care": contact.IsSpecialCare,
"is_blocked": contact.IsBlocked,
"last_chat_time": contact.LastChatTime,
"last_message": contact.LastMessage,
"last_msg": contact.LastMessage,
"unread_count": contact.UnreadCount,
"unread": contact.UnreadCount,
"is_blocked": contact.IsBlocked,
"last_chat_time": contact.LastChatTime,
"last_message": contact.LastMessage,
"last_msg": contact.LastMessage,
"unread_count": contact.UnreadCount,
"unread": contact.UnreadCount,
}
result = append(result, item)
}
@@ -434,3 +434,20 @@ func (s *ContactService) GetContactsWithUserInfo(userID string) ([]map[string]in
return result, nil
}
/**
* GetFriendUserIDs
* 功能获取用户的所有好友ID列表
*/
func (s *ContactService) GetFriendUserIDs(userID string) ([]string, error) {
var contacts []model.UserContact
if err := s.DB.Where("user_id = ?", userID).Find(&contacts).Error; err != nil {
return nil, err
}
friendIDs := make([]string, 0, len(contacts))
for _, contact := range contacts {
friendIDs = append(friendIDs, contact.ContactID)
}
return friendIDs, nil
}

View File

@@ -5,6 +5,7 @@
package service
import (
"encoding/json"
"fmt"
"strings"
"time"
@@ -500,9 +501,33 @@ func (s *RoomService) ChangeMemberRole(roomID, operatorID, memberID string, role
return fmt.Errorf("不能修改群主自身角色")
}
return s.DB.Model(&model.RoomMember{}).
// 获取原角色
var oldMember model.RoomMember
s.DB.Where("room_id = ? AND user_id = ?", roomID, memberID).First(&oldMember)
oldRole := oldMember.Role
if err := s.DB.Model(&model.RoomMember{}).
Where("room_id = ? AND user_id = ?", roomID, memberID).
Update("role", role).Error
Update("role", role).Error; err != nil {
return err
}
// 如果是转让群主,需要把原群主降级
if role == 2 && memberID != room.OwnerID {
// 将原群主降级为管理员
s.DB.Model(&model.RoomMember{}).
Where("room_id = ? AND user_id = ?", roomID, operatorID).
Update("role", 1)
// 更新房间的 owner_id
s.DB.Model(&model.ChatRoom{}).
Where("room_id = ?", roomID).
Update("owner_id", memberID)
}
// 发送角色变更通知(异步)
go s.sendRoleChangeNotification(roomID, operatorID, memberID, oldRole, role)
return nil
}
/**
@@ -636,9 +661,16 @@ func (s *RoomService) MuteGroupMember(roomID, operatorID, memberID string, durat
}
// 更新禁言状态
return s.DB.Model(&model.RoomMember{}).
if err := s.DB.Model(&model.RoomMember{}).
Where("room_id = ? AND user_id = ?", roomID, memberID).
Update("muted_until", mutedUntil).Error
Update("muted_until", mutedUntil).Error; err != nil {
return err
}
// 发送群通知(异步)
go s.sendMuteNotification(roomID, operatorID, memberID, duration, mutedUntil)
return nil
}
/**
@@ -653,3 +685,177 @@ func (s *RoomService) IsGroupMemberMuted(roomID, userID string) (bool, *time.Tim
}
return member.IsMuted(), member.MutedUntil, nil
}
/**
* sendMuteNotification
* 功能:发送禁言/解除禁言通知给所有群成员
*/
func (s *RoomService) sendMuteNotification(roomID, operatorID, memberID string, duration int64, mutedUntil *time.Time) {
if ChatSvc == nil || UserSvc == nil {
return
}
// 获取操作者和被禁言成员信息
operator, opErr := UserSvc.GetUserByID(operatorID)
member, memErr := UserSvc.GetUserByID(memberID)
if opErr != nil || memErr != nil {
return
}
// 获取群成员列表
memberIDs, err := s.GetRoomMembers(roomID)
if err != nil {
return
}
// 构建通知内容
var notifContent string
var eventType string
if duration == -1 {
notifContent = fmt.Sprintf("%s 解除了 %s 的禁言", operator.Name, member.Name)
eventType = "member_unmute"
} else if duration == 0 {
notifContent = fmt.Sprintf("%s 将 %s 永久禁言", operator.Name, member.Name)
eventType = "member_mute"
} else {
durationText := formatDuration(duration)
notifContent = fmt.Sprintf("%s 将 %s 禁言 %s", operator.Name, member.Name, durationText)
eventType = "member_mute"
}
// 构建 extra 数据
notifExtra := map[string]interface{}{
"type": eventType,
"room_id": roomID,
"operator": operatorID,
"operator_name": operator.Name,
"target": memberID,
"target_name": member.Name,
"duration": duration,
"muted_until": mutedUntil,
}
notifExtraJSON, _ := json.Marshal(notifExtra)
// 为所有成员发送群通知
for _, uid := range memberIDs {
notifMsg := model.ChatMessage{
RoomID: roomID,
SenderUserID: operatorID,
ReceiverUserID: roomID,
MessageType: model.MessageTypeGroupNotif,
Content: notifContent,
Extra: string(notifExtraJSON),
}
if err := ChatSvc.DB.Create(&notifMsg).Error; err == nil {
// 更新会话
if ConversationSvc != nil {
_ = ConversationSvc.UpsertConversationOnMessage(uid, roomID, roomID, notifMsg, false)
}
// 推送消息
pushMsg := model.WsPayload{
RequestType: "receive_message",
Data: notifMsg,
}
msgBytes, _ := json.Marshal(pushMsg)
ChatSvc.DispatchMessage(uid, msgBytes)
}
}
}
/**
* formatDuration
* 功能:格式化时长为可读字符串
*/
func formatDuration(seconds int64) string {
if seconds < 60 {
return fmt.Sprintf("%d秒", seconds)
} else if seconds < 3600 {
return fmt.Sprintf("%d分钟", seconds/60)
} else if seconds < 86400 {
return fmt.Sprintf("%d小时", seconds/3600)
} else {
return fmt.Sprintf("%d天", seconds/86400)
}
}
/**
* sendRoleChangeNotification
* 功能:发送角色变更通知给所有群成员
*/
func (s *RoomService) sendRoleChangeNotification(roomID, operatorID, memberID string, oldRole, newRole int8) {
if ChatSvc == nil || UserSvc == nil {
return
}
// 获取操作者和目标成员信息
operator, opErr := UserSvc.GetUserByID(operatorID)
member, memErr := UserSvc.GetUserByID(memberID)
if opErr != nil || memErr != nil {
return
}
// 获取群成员列表
memberIDs, err := s.GetRoomMembers(roomID)
if err != nil {
return
}
// 构建通知内容
var notifContent string
roleNames := map[int8]string{0: "普通成员", 1: "管理员", 2: "群主"}
if newRole == 2 {
// 转让群主
notifContent = fmt.Sprintf("%s 将群主转让给了 %s", operator.Name, member.Name)
} else if newRole == 1 && oldRole == 0 {
// 设置管理员
notifContent = fmt.Sprintf("%s 被设置为管理员", member.Name)
} else if newRole == 0 && oldRole == 1 {
// 取消管理员
notifContent = fmt.Sprintf("%s 被取消管理员", member.Name)
} else {
notifContent = fmt.Sprintf("%s 的角色变更为 %s", member.Name, roleNames[newRole])
}
// 构建 extra 数据
notifExtra := map[string]interface{}{
"type": "role_change",
"room_id": roomID,
"operator": operatorID,
"operator_name": operator.Name,
"target": memberID,
"target_name": member.Name,
"old_role": oldRole,
"new_role": newRole,
}
notifExtraJSON, _ := json.Marshal(notifExtra)
// 为所有成员发送群通知
for _, uid := range memberIDs {
notifMsg := model.ChatMessage{
RoomID: roomID,
SenderUserID: operatorID,
ReceiverUserID: roomID,
MessageType: model.MessageTypeGroupNotif,
Content: notifContent,
Extra: string(notifExtraJSON),
}
if err := ChatSvc.DB.Create(&notifMsg).Error; err == nil {
// 更新会话
if ConversationSvc != nil {
_ = ConversationSvc.UpsertConversationOnMessage(uid, roomID, roomID, notifMsg, false)
}
// 推送消息
pushMsg := model.WsPayload{
RequestType: "receive_message",
Data: notifMsg,
}
msgBytes, _ := json.Marshal(pushMsg)
ChatSvc.DispatchMessage(uid, msgBytes)
}
}
}