群聊接口
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
"xk-websocket-v2/internal/model"
|
||||
@@ -114,6 +116,63 @@ func CreateChatGroupHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 为所有成员创建会话记录
|
||||
if service.ConversationSvc != nil {
|
||||
if err := service.ConversationSvc.EnsureGroupConversations(room.RoomID); err != nil {
|
||||
log.Printf("⚠️ 创建群聊会话失败 (roomID: %s): %v", room.RoomID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 发送系统消息通知所有成员
|
||||
if service.ChatSvc != nil && service.UserSvc != nil {
|
||||
// 获取创建者信息
|
||||
creator, err := service.UserSvc.GetUserByID(creatorID)
|
||||
if err == nil {
|
||||
// 构建系统消息内容
|
||||
content := fmt.Sprintf("%s 邀请你加入了群聊", creator.Name)
|
||||
extraData := map[string]interface{}{
|
||||
"type": "group_created",
|
||||
"room_id": room.RoomID,
|
||||
"creator": creatorID,
|
||||
}
|
||||
extraJSON, _ := json.Marshal(extraData)
|
||||
|
||||
// 为每个成员发送系统消息
|
||||
for _, memberID := range members {
|
||||
if memberID != creatorID {
|
||||
// 创建系统消息
|
||||
systemMsg := model.ChatMessage{
|
||||
RoomID: room.RoomID,
|
||||
SenderUserID: creatorID,
|
||||
ReceiverUserID: room.RoomID, // 群聊时使用 roomID
|
||||
MessageType: model.MessageTypeSystem,
|
||||
Content: content,
|
||||
Extra: string(extraJSON),
|
||||
}
|
||||
|
||||
// 持久化消息
|
||||
if err := service.ChatSvc.DB.Create(&systemMsg).Error; err != nil {
|
||||
log.Printf("❌ 发送群聊系统消息失败 (memberID: %s): %v", memberID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 更新会话
|
||||
if service.ConversationSvc != nil {
|
||||
_ = service.ConversationSvc.UpsertConversationOnMessage(memberID, room.RoomID, room.RoomID, systemMsg, false)
|
||||
}
|
||||
|
||||
// 通过 WebSocket 推送消息
|
||||
pushMsg := model.WsPayload{
|
||||
RequestType: "receive_message",
|
||||
Data: systemMsg,
|
||||
}
|
||||
msgBytes, _ := json.Marshal(pushMsg)
|
||||
service.ChatSvc.DispatchMessage(memberID, msgBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
utils.SuccessWithData(c, room, "创建群聊成功")
|
||||
}
|
||||
|
||||
@@ -249,8 +308,19 @@ func GetGroupInfoHandler(c *gin.Context) {
|
||||
*/
|
||||
func ListGroupMembersHandler(c *gin.Context) {
|
||||
roomID := c.Param("room_id")
|
||||
keyword := c.Query("keyword")
|
||||
|
||||
var members []model.RoomMember
|
||||
var err error
|
||||
|
||||
if keyword != "" {
|
||||
// 如果有搜索关键词,使用搜索方法
|
||||
members, err = service.RoomSvc.SearchGroupMembers(roomID, keyword)
|
||||
} else {
|
||||
// 否则返回所有成员
|
||||
members, err = service.RoomSvc.ListGroupMembers(roomID)
|
||||
}
|
||||
|
||||
members, err := service.RoomSvc.ListGroupMembers(roomID)
|
||||
if err != nil {
|
||||
utils.InternalError(c, "查询失败")
|
||||
return
|
||||
@@ -310,6 +380,97 @@ func RemoveGroupMemberHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 发送群通知和系统消息
|
||||
if service.ChatSvc != nil && service.UserSvc != nil {
|
||||
// 获取操作者和被移除成员信息
|
||||
operator, opErr := service.UserSvc.GetUserByID(operatorID.(string))
|
||||
member, memErr := service.UserSvc.GetUserByID(memberID)
|
||||
|
||||
if opErr == nil && memErr == nil {
|
||||
// 获取群成员列表
|
||||
memberIDs, mErr := service.RoomSvc.GetRoomMembers(roomID)
|
||||
if mErr == nil {
|
||||
// 构建群通知消息
|
||||
notifContent := fmt.Sprintf("%s 将 %s 移出了群聊", operator.Name, member.Name)
|
||||
notifExtra := map[string]interface{}{
|
||||
"type": "member_remove",
|
||||
"room_id": roomID,
|
||||
"operator": operatorID.(string),
|
||||
"target": memberID,
|
||||
"operator_name": operator.Name,
|
||||
"target_name": member.Name,
|
||||
}
|
||||
notifExtraJSON, _ := json.Marshal(notifExtra)
|
||||
|
||||
// 构建系统消息
|
||||
systemContent := fmt.Sprintf("你被 %s 移出了群聊", operator.Name)
|
||||
systemExtra := map[string]interface{}{
|
||||
"type": "member_removed",
|
||||
"room_id": roomID,
|
||||
"operator": operatorID.(string),
|
||||
}
|
||||
systemExtraJSON, _ := json.Marshal(systemExtra)
|
||||
|
||||
// 为所有成员发送群通知
|
||||
for _, uid := range memberIDs {
|
||||
if uid == memberID {
|
||||
continue // 被移除的成员不接收群通知
|
||||
}
|
||||
|
||||
// 群通知消息
|
||||
notifMsg := model.ChatMessage{
|
||||
RoomID: roomID,
|
||||
SenderUserID: operatorID.(string),
|
||||
ReceiverUserID: roomID,
|
||||
MessageType: model.MessageTypeGroupNotif,
|
||||
Content: notifContent,
|
||||
Extra: string(notifExtraJSON),
|
||||
}
|
||||
|
||||
if err := service.ChatSvc.DB.Create(¬ifMsg).Error; err == nil {
|
||||
// 更新会话
|
||||
if service.ConversationSvc != nil {
|
||||
_ = service.ConversationSvc.UpsertConversationOnMessage(uid, roomID, roomID, notifMsg, false)
|
||||
}
|
||||
|
||||
// 推送消息
|
||||
pushMsg := model.WsPayload{
|
||||
RequestType: "receive_message",
|
||||
Data: notifMsg,
|
||||
}
|
||||
msgBytes, _ := json.Marshal(pushMsg)
|
||||
service.ChatSvc.DispatchMessage(uid, msgBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// 为被移除的成员发送系统消息
|
||||
systemMsg := model.ChatMessage{
|
||||
RoomID: roomID,
|
||||
SenderUserID: operatorID.(string),
|
||||
ReceiverUserID: roomID,
|
||||
MessageType: model.MessageTypeSystem,
|
||||
Content: systemContent,
|
||||
Extra: string(systemExtraJSON),
|
||||
}
|
||||
|
||||
if err := service.ChatSvc.DB.Create(&systemMsg).Error; err == nil {
|
||||
// 更新会话
|
||||
if service.ConversationSvc != nil {
|
||||
_ = service.ConversationSvc.UpsertConversationOnMessage(memberID, roomID, roomID, systemMsg, false)
|
||||
}
|
||||
|
||||
// 推送消息
|
||||
pushMsg := model.WsPayload{
|
||||
RequestType: "receive_message",
|
||||
Data: systemMsg,
|
||||
}
|
||||
msgBytes, _ := json.Marshal(pushMsg)
|
||||
service.ChatSvc.DispatchMessage(memberID, msgBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
utils.Success(c, "已移除成员")
|
||||
}
|
||||
|
||||
@@ -427,6 +588,55 @@ func QuitGroupHandler(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 发送群通知
|
||||
if service.ChatSvc != nil && service.UserSvc != nil {
|
||||
// 获取退出成员信息
|
||||
member, memErr := service.UserSvc.GetUserByID(userID.(string))
|
||||
|
||||
if memErr == nil {
|
||||
// 获取群成员列表(退出后剩余的成员)
|
||||
memberIDs, mErr := service.RoomSvc.GetRoomMembers(roomID)
|
||||
if mErr == nil {
|
||||
// 构建群通知消息
|
||||
notifContent := fmt.Sprintf("%s 退出了群聊", member.Name)
|
||||
notifExtra := map[string]interface{}{
|
||||
"type": "member_leave",
|
||||
"room_id": roomID,
|
||||
"target": userID.(string),
|
||||
"target_name": member.Name,
|
||||
}
|
||||
notifExtraJSON, _ := json.Marshal(notifExtra)
|
||||
|
||||
// 为所有剩余成员发送群通知
|
||||
for _, uid := range memberIDs {
|
||||
notifMsg := model.ChatMessage{
|
||||
RoomID: roomID,
|
||||
SenderUserID: userID.(string),
|
||||
ReceiverUserID: roomID,
|
||||
MessageType: model.MessageTypeGroupNotif,
|
||||
Content: notifContent,
|
||||
Extra: string(notifExtraJSON),
|
||||
}
|
||||
|
||||
if err := service.ChatSvc.DB.Create(¬ifMsg).Error; err == nil {
|
||||
// 更新会话
|
||||
if service.ConversationSvc != nil {
|
||||
_ = service.ConversationSvc.UpsertConversationOnMessage(uid, roomID, roomID, notifMsg, false)
|
||||
}
|
||||
|
||||
// 推送消息
|
||||
pushMsg := model.WsPayload{
|
||||
RequestType: "receive_message",
|
||||
Data: notifMsg,
|
||||
}
|
||||
msgBytes, _ := json.Marshal(pushMsg)
|
||||
service.ChatSvc.DispatchMessage(uid, msgBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
utils.Success(c, "已退出群聊")
|
||||
}
|
||||
|
||||
|
||||
@@ -132,6 +132,36 @@ func (s *ChatService) HandleUserMessage(senderClient *manager.Client, req *model
|
||||
room, err := RoomSvc.GetRoom(req.RoomID)
|
||||
if err == nil && room.RoomType == "group" {
|
||||
isGroupMessage = true
|
||||
|
||||
// 双重防护:验证发送者是否为群成员
|
||||
memberIDs, mErr := RoomSvc.GetRoomMembers(req.RoomID)
|
||||
if mErr != nil {
|
||||
log.Printf("❌ 获取群成员失败: %v", mErr)
|
||||
return
|
||||
}
|
||||
|
||||
isMember := false
|
||||
for _, memberID := range memberIDs {
|
||||
if memberID == senderClient.UserID {
|
||||
isMember = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isMember {
|
||||
log.Printf("❌ 用户 %s 不是群 %s 的成员,拒绝发送消息", senderClient.UserID, req.RoomID)
|
||||
// 发送错误消息给发送者
|
||||
errorMsg := model.WsPayload{
|
||||
RequestType: "error",
|
||||
Data: map[string]interface{}{
|
||||
"message": "您已被移出群聊,无法发送消息",
|
||||
"code": "FORBIDDEN",
|
||||
},
|
||||
}
|
||||
errorBytes, _ := json.Marshal(errorMsg)
|
||||
s.DispatchMessage(senderClient.UserID, errorBytes)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
"xk-websocket-v2/internal/model"
|
||||
"xk-websocket-v2/internal/utils"
|
||||
@@ -296,6 +297,38 @@ func (s *RoomService) ListGroupMembers(roomID string) ([]model.RoomMember, error
|
||||
return members, nil
|
||||
}
|
||||
|
||||
/**
|
||||
* SearchGroupMembers
|
||||
* 功能:搜索群成员(根据用户名或昵称)
|
||||
*/
|
||||
func (s *RoomService) SearchGroupMembers(roomID, keyword string) ([]model.RoomMember, error) {
|
||||
var members []model.RoomMember
|
||||
keywordPattern := "%" + keyword + "%"
|
||||
|
||||
if err := s.DB.Where("room_id = ?", roomID).
|
||||
Preload("User", "name LIKE ? OR email LIKE ? OR phone LIKE ?", keywordPattern, keywordPattern, keywordPattern).
|
||||
Order("role DESC, joined_at ASC").
|
||||
Find(&members).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 过滤掉 User 为 nil 或不符合搜索条件的成员
|
||||
filtered := make([]model.RoomMember, 0)
|
||||
for _, m := range members {
|
||||
if m.User != nil {
|
||||
// 检查用户名、邮箱、手机号是否匹配
|
||||
if strings.Contains(strings.ToLower(m.User.Name), strings.ToLower(keyword)) ||
|
||||
strings.Contains(strings.ToLower(m.User.Email), strings.ToLower(keyword)) ||
|
||||
strings.Contains(strings.ToLower(m.User.Phone), strings.ToLower(keyword)) ||
|
||||
(m.Nickname != "" && strings.Contains(strings.ToLower(m.Nickname), strings.ToLower(keyword))) {
|
||||
filtered = append(filtered, m)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
/**
|
||||
* ListUserGroups
|
||||
* 功能:查询用户所在的所有群聊房间
|
||||
|
||||
Reference in New Issue
Block a user