群聊接口
This commit is contained in:
@@ -627,7 +627,7 @@
|
||||
"name": "房间管理",
|
||||
"item": [
|
||||
{
|
||||
"name": "创建房间",
|
||||
"name": "创建房间(单聊)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
@@ -667,6 +667,157 @@
|
||||
"path": ["api", "rooms", "10001_10002"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "创建群聊",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{token}}"
|
||||
},
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"name\": \"开发讨论群\",\n \"avatar\": \"G\",\n \"member_ids\": [\"{{user_id}}\", \"10002\", \"10003\"],\n \"admin_ids\": [\"10002\"]\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/groups",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "groups"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "获取群信息",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{token}}"
|
||||
}
|
||||
],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/groups/group_1234567890",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "groups", "group_1234567890"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "获取群成员列表",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{token}}"
|
||||
}
|
||||
],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/groups/group_1234567890/members",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "groups", "group_1234567890", "members"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "邀请成员入群",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{token}}"
|
||||
},
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"member_ids\": [\"10004\", \"10005\"]\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/groups/group_1234567890/members",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "groups", "group_1234567890", "members"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "移除群成员",
|
||||
"request": {
|
||||
"method": "DELETE",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{token}}"
|
||||
}
|
||||
],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/groups/group_1234567890/members/10004",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "groups", "group_1234567890", "members", "10004"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "修改群信息",
|
||||
"request": {
|
||||
"method": "PATCH",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{token}}"
|
||||
},
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"name\": \"新的群名称\",\n \"avatar\": \"NG\"\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/groups/group_1234567890",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "groups", "group_1234567890"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "调整成员角色",
|
||||
"request": {
|
||||
"method": "PATCH",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{token}}"
|
||||
},
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"role\": 1\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/groups/group_1234567890/members/10002/role",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "groups", "group_1234567890", "members", "10002", "role"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -674,7 +825,7 @@
|
||||
"name": "消息管理",
|
||||
"item": [
|
||||
{
|
||||
"name": "发送消息",
|
||||
"name": "发送消息(单聊)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
@@ -702,6 +853,64 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "发送消息(群聊文本)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{token}}"
|
||||
},
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "X-User-ID",
|
||||
"value": "{{user_id}}"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"room_id\": \"group_1234567890\",\n \"message_type\": 0,\n \"content\": \"群聊中的一条文本消息\",\n \"duration\": 0\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/send",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "send"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "发送系统消息(群通知示例)",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Authorization",
|
||||
"value": "Bearer {{token}}"
|
||||
},
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
},
|
||||
{
|
||||
"key": "X-User-ID",
|
||||
"value": "{{user_id}}"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"room_id\": \"group_1234567890\",\n \"message_type\": 7,\n \"content\": \"用户 10004 加入了群聊\",\n \"extra\": \"{\\\"event\\\":\\\"member_join\\\",\\\"operator_id\\\":\\\"{{user_id}}\\\",\\\"target_id\\\":\\\"10004\\\"}\",\n \"duration\": 0\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/send",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "send"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "获取历史消息",
|
||||
"request": {
|
||||
|
||||
@@ -348,10 +348,19 @@ func main() {
|
||||
authGroup.POST("/conversations/update", api.UpdateConversationHandler)
|
||||
authGroup.POST("/conversations/delete", api.DeleteConversationHandler)
|
||||
|
||||
// 房间管理
|
||||
// 房间 / 群聊管理
|
||||
authGroup.POST("/rooms", api.CreateRoomHandler)
|
||||
authGroup.GET("/rooms/:id", api.GetRoomHandler)
|
||||
|
||||
// 群聊相关接口
|
||||
authGroup.POST("/groups", api.CreateChatGroupHandler)
|
||||
authGroup.GET("/groups/:room_id", api.GetGroupInfoHandler)
|
||||
authGroup.GET("/groups/:room_id/members", api.ListGroupMembersHandler)
|
||||
authGroup.POST("/groups/:room_id/members", api.AddGroupMembersHandler)
|
||||
authGroup.DELETE("/groups/:room_id/members/:user_id", api.RemoveGroupMemberHandler)
|
||||
authGroup.PATCH("/groups/:room_id", api.UpdateGroupInfoHandler)
|
||||
authGroup.PATCH("/groups/:room_id/members/:user_id/role", api.ChangeMemberRoleHandler)
|
||||
|
||||
// 附件管理
|
||||
authGroup.POST("/attachments/upload", api.UploadAttachmentHandler)
|
||||
authGroup.GET("/attachments", api.GetAttachmentsHandler)
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"xk-websocket-v2/internal/model"
|
||||
"xk-websocket-v2/internal/service"
|
||||
"xk-websocket-v2/internal/utils"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -69,3 +71,236 @@ func GetRoomHandler(c *gin.Context) {
|
||||
utils.SuccessWithData(c, room, "获取成功")
|
||||
}
|
||||
|
||||
/**
|
||||
* CreateChatGroupHandler
|
||||
* 功能:创建群聊
|
||||
* 路径:POST /api/groups
|
||||
*/
|
||||
func CreateChatGroupHandler(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
|
||||
var req struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Avatar string `json:"avatar"`
|
||||
MemberIDs []string `json:"member_ids" binding:"required"`
|
||||
AdminIDs []string `json:"admin_ids"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
utils.BadRequest(c, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
creatorID := userID.(string)
|
||||
members := req.MemberIDs
|
||||
|
||||
// 确保创建者在成员列表中
|
||||
found := false
|
||||
for _, id := range members {
|
||||
if id == creatorID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
members = append(members, creatorID)
|
||||
}
|
||||
|
||||
room, err := service.RoomSvc.CreateGroupRoom(creatorID, members, req.Name, req.Avatar, req.AdminIDs)
|
||||
if err != nil {
|
||||
utils.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessWithData(c, room, "创建群聊成功")
|
||||
}
|
||||
|
||||
/**
|
||||
* GetGroupInfoHandler
|
||||
* 功能:获取群聊基础信息及成员数量
|
||||
* 路径:GET /api/groups/:room_id
|
||||
*/
|
||||
func GetGroupInfoHandler(c *gin.Context) {
|
||||
roomID := c.Param("room_id")
|
||||
|
||||
room, err := service.RoomSvc.GetRoom(roomID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
utils.NotFound(c, "群聊不存在")
|
||||
return
|
||||
}
|
||||
utils.InternalError(c, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
members, err := service.RoomSvc.ListGroupMembers(roomID)
|
||||
if err != nil {
|
||||
utils.InternalError(c, "查询成员失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 统计管理员ID
|
||||
adminIDs := make([]string, 0)
|
||||
for _, m := range members {
|
||||
if m.Role == 1 {
|
||||
adminIDs = append(adminIDs, m.UserID)
|
||||
}
|
||||
}
|
||||
|
||||
utils.SuccessWithData(c, gin.H{
|
||||
"room": room,
|
||||
"member_count": len(members),
|
||||
"admin_ids": adminIDs,
|
||||
"owner_id": room.OwnerID,
|
||||
}, "获取成功")
|
||||
}
|
||||
|
||||
/**
|
||||
* ListGroupMembersHandler
|
||||
* 功能:获取群成员列表
|
||||
* 路径:GET /api/groups/:room_id/members
|
||||
*/
|
||||
func ListGroupMembersHandler(c *gin.Context) {
|
||||
roomID := c.Param("room_id")
|
||||
|
||||
members, err := service.RoomSvc.ListGroupMembers(roomID)
|
||||
if err != nil {
|
||||
utils.InternalError(c, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
utils.SuccessWithData(c, members, "获取成功")
|
||||
}
|
||||
|
||||
/**
|
||||
* AddGroupMembersHandler
|
||||
* 功能:邀请/拉人入群
|
||||
* 路径:POST /api/groups/:room_id/members
|
||||
*/
|
||||
func AddGroupMembersHandler(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
roomID := c.Param("room_id")
|
||||
|
||||
var req struct {
|
||||
MemberIDs []string `json:"member_ids" binding:"required"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
utils.BadRequest(c, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := service.RoomSvc.AddGroupMembers(roomID, userID.(string), req.MemberIDs); err != nil {
|
||||
utils.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.Success(c, "添加成员成功")
|
||||
}
|
||||
|
||||
/**
|
||||
* RemoveGroupMemberHandler
|
||||
* 功能:移除群成员或退群
|
||||
* 路径:DELETE /api/groups/:room_id/members/:user_id
|
||||
*/
|
||||
func RemoveGroupMemberHandler(c *gin.Context) {
|
||||
operatorID, _ := c.Get("user_id")
|
||||
roomID := c.Param("room_id")
|
||||
memberID := c.Param("user_id")
|
||||
|
||||
// 自己移除自己 = 退群
|
||||
if operatorID.(string) == memberID {
|
||||
if err := service.RoomSvc.QuitGroup(roomID, memberID); err != nil {
|
||||
utils.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
utils.Success(c, "已退出群聊")
|
||||
return
|
||||
}
|
||||
|
||||
// 否则为管理员/群主踢人
|
||||
if err := service.RoomSvc.RemoveGroupMember(roomID, operatorID.(string), memberID); err != nil {
|
||||
utils.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.Success(c, "已移除成员")
|
||||
}
|
||||
|
||||
/**
|
||||
* UpdateGroupInfoHandler
|
||||
* 功能:修改群名称/头像
|
||||
* 路径:PATCH /api/groups/:room_id
|
||||
*/
|
||||
func UpdateGroupInfoHandler(c *gin.Context) {
|
||||
userID, _ := c.Get("user_id")
|
||||
roomID := c.Param("room_id")
|
||||
|
||||
var req struct {
|
||||
Name string `json:"name"`
|
||||
Avatar string `json:"avatar"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
utils.BadRequest(c, "参数错误: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 仅群主或管理员可修改群信息
|
||||
ok, err := service.RoomSvc.IsGroupAdminOrOwner(roomID, userID.(string))
|
||||
if err != nil {
|
||||
utils.InternalError(c, "权限校验失败")
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
utils.Forbidden(c, "无权限修改群信息")
|
||||
return
|
||||
}
|
||||
|
||||
updates := make(map[string]interface{})
|
||||
if req.Name != "" {
|
||||
updates["room_name"] = req.Name
|
||||
}
|
||||
if req.Avatar != "" {
|
||||
updates["room_avatar"] = req.Avatar
|
||||
}
|
||||
|
||||
if len(updates) == 0 {
|
||||
utils.BadRequest(c, "没有可更新的字段")
|
||||
return
|
||||
}
|
||||
|
||||
// 更新群基础信息
|
||||
if err := service.RoomSvc.DB.Model(&model.ChatRoom{}).
|
||||
Where("room_id = ?", roomID).
|
||||
Updates(updates).Error; err != nil {
|
||||
utils.InternalError(c, "更新失败")
|
||||
return
|
||||
}
|
||||
|
||||
utils.Success(c, "更新成功")
|
||||
}
|
||||
|
||||
/**
|
||||
* ChangeMemberRoleHandler
|
||||
* 功能:调整成员角色
|
||||
* 路径:PATCH /api/groups/:room_id/members/:user_id/role
|
||||
*/
|
||||
func ChangeMemberRoleHandler(c *gin.Context) {
|
||||
operatorID, _ := c.Get("user_id")
|
||||
roomID := c.Param("room_id")
|
||||
memberID := c.Param("user_id")
|
||||
|
||||
var req struct {
|
||||
Role int8 `json:"role" binding:"required"`
|
||||
}
|
||||
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 {
|
||||
utils.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
utils.Success(c, "角色已更新")
|
||||
}
|
||||
|
||||
@@ -274,6 +274,8 @@ type RoomMember struct {
|
||||
RoomID string `gorm:"type:varchar(100);primaryKey;comment:房间ID" json:"room_id"`
|
||||
// 用户ID
|
||||
UserID string `gorm:"type:varchar(100);primaryKey;comment:用户ID" json:"user_id"`
|
||||
// 成员角色:0-成员 1-管理员 2-群主
|
||||
Role int8 `gorm:"type:tinyint(1);default:0;comment:成员角色(0=成员,1=管理员,2=群主)" json:"role"`
|
||||
// 加入时间
|
||||
JoinedAt time.Time `gorm:"type:datetime;autoCreateTime;comment:加入时间" json:"joined_at"`
|
||||
}
|
||||
@@ -570,4 +572,16 @@ const (
|
||||
// 文件大小限制(字节)
|
||||
MaxImageSize = 10 * 1024 * 1024 // 10MB
|
||||
MaxVideoSize = 500 * 1024 * 1024 // 500MB
|
||||
|
||||
// 消息类型常量
|
||||
MessageTypeText = 0 // 文本
|
||||
MessageTypeImage = 1 // 图片
|
||||
MessageTypeAudio = 2 // 语音
|
||||
MessageTypeVideo = 3 // 视频
|
||||
MessageTypeSystem = 4 // 系统消息
|
||||
MessageTypeFriendNotif = 5 // 好友通知
|
||||
MessageTypeSignal = 6 // 信令消息
|
||||
MessageTypeGroupNotif = 7 // 群通知
|
||||
MessageTypeFile = 8 // 文件
|
||||
MessageTypeMoments = 9 // 朋友圈通知(预留)
|
||||
)
|
||||
|
||||
@@ -124,7 +124,7 @@ func (s *ChatService) HandleUserMessage(senderClient *manager.Client, req *model
|
||||
|
||||
// 3. 消息持久化 (MySQL)
|
||||
// WebRTC 的 Candidate/Offer/Answer 信令通常不需要持久化,只有聊天记录和通话记录需要
|
||||
if req.MessageType != 6 && req.MessageType != 7 { // 假设 6,7 是纯信令流
|
||||
if req.MessageType != model.MessageTypeSignal && req.MessageType != model.MessageTypeGroupNotif {
|
||||
msg := model.ChatMessage{
|
||||
RoomID: req.RoomID,
|
||||
SenderUserID: senderClient.UserID,
|
||||
@@ -142,12 +142,16 @@ func (s *ChatService) HandleUserMessage(senderClient *manager.Client, req *model
|
||||
}
|
||||
|
||||
// 4. 更新会话列表(发送方与接收方)
|
||||
if ConversationSvc != nil {
|
||||
// 当前实现仅支持点对点私聊
|
||||
// 目前仅对点对点私聊做会话维护,群聊和系统类消息先不更新会话,后续按需扩展
|
||||
if ConversationSvc != nil && req.MessageType == model.MessageTypeText ||
|
||||
req.MessageType == model.MessageTypeImage ||
|
||||
req.MessageType == model.MessageTypeAudio ||
|
||||
req.MessageType == model.MessageTypeVideo ||
|
||||
req.MessageType == model.MessageTypeFile {
|
||||
|
||||
senderID := senderClient.UserID
|
||||
receiverID := req.ReceiverUserID
|
||||
|
||||
// 对发送方更新会话(不增加未读)
|
||||
if senderID != "" && receiverID != "" {
|
||||
// sender 视角 target 为 receiver
|
||||
_ = ConversationSvc.UpsertConversationOnMessage(senderID, receiverID, req.RoomID, msg, true)
|
||||
@@ -164,7 +168,23 @@ func (s *ChatService) HandleUserMessage(senderClient *manager.Client, req *model
|
||||
}
|
||||
msgBytes, _ := json.Marshal(pushMsg)
|
||||
|
||||
// 分发消息 (路由到目标用户)
|
||||
// 分发消息 (路由到目标用户或群成员)
|
||||
if req.RoomID != "" {
|
||||
// 判断是否群聊房间,群聊需要按成员广播
|
||||
room, err := RoomSvc.GetRoom(req.RoomID)
|
||||
if err == nil && room.RoomType == "group" {
|
||||
memberIDs, mErr := RoomSvc.GetRoomMembers(req.RoomID)
|
||||
if mErr == nil {
|
||||
for _, uid := range memberIDs {
|
||||
// 群聊中仍然按用户路由,发送给所有成员
|
||||
s.DispatchMessage(uid, msgBytes)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 默认:点对点或无法识别房间类型时按 receiver_user_id 路由
|
||||
s.DispatchMessage(req.ReceiverUserID, msgBytes)
|
||||
} else {
|
||||
// 信令消息直接透传,不存库
|
||||
|
||||
@@ -207,3 +207,241 @@ func (s *RoomService) RemoveRoomMember(roomID, userID string) error {
|
||||
Delete(&model.RoomMember{}).Error
|
||||
}
|
||||
|
||||
/**
|
||||
* CreateGroupRoom
|
||||
* 功能:创建群聊房间,并为成员写入角色(群主/管理员/成员)
|
||||
*/
|
||||
func (s *RoomService) CreateGroupRoom(creatorID string, memberIDs []string, name string, avatar string, adminIDs []string) (*model.ChatRoom, error) {
|
||||
if len(memberIDs) == 0 {
|
||||
return nil, fmt.Errorf("成员列表不能为空")
|
||||
}
|
||||
|
||||
// 1. 先通过通用的 CreateRoom 创建群聊和基础成员关系(角色默认都是 0)
|
||||
room, err := s.CreateRoom("group", memberIDs, creatorID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2. 更新群名称和头像(如果提供)
|
||||
updates := map[string]interface{}{}
|
||||
if name != "" {
|
||||
updates["room_name"] = name
|
||||
}
|
||||
if avatar != "" {
|
||||
updates["room_avatar"] = avatar
|
||||
}
|
||||
if len(updates) > 0 {
|
||||
if err := s.DB.Model(room).Updates(updates).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 为成员设置角色:群主=2 管理员=1 成员=0
|
||||
roomID := room.RoomID
|
||||
|
||||
// 创建者作为群主
|
||||
if err := s.DB.Model(&model.RoomMember{}).
|
||||
Where("room_id = ? AND user_id = ?", roomID, creatorID).
|
||||
Update("role", 2).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 管理员列表去重并排除群主
|
||||
adminSet := map[string]struct{}{}
|
||||
for _, id := range adminIDs {
|
||||
if id == "" || id == creatorID {
|
||||
continue
|
||||
}
|
||||
adminSet[id] = struct{}{}
|
||||
}
|
||||
|
||||
if len(adminSet) > 0 {
|
||||
for adminID := range adminSet {
|
||||
if err := s.DB.Model(&model.RoomMember{}).
|
||||
Where("room_id = ? AND user_id = ?", roomID, adminID).
|
||||
Update("role", 1).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return room, nil
|
||||
}
|
||||
|
||||
/**
|
||||
* ListGroupMembers
|
||||
* 功能:返回群成员及其角色信息
|
||||
*/
|
||||
func (s *RoomService) ListGroupMembers(roomID string) ([]model.RoomMember, error) {
|
||||
var members []model.RoomMember
|
||||
if err := s.DB.Where("room_id = ?", roomID).Find(&members).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return members, nil
|
||||
}
|
||||
|
||||
/**
|
||||
* ListUserGroups
|
||||
* 功能:查询用户所在的所有群聊房间
|
||||
*/
|
||||
func (s *RoomService) ListUserGroups(userID string) ([]model.ChatRoom, error) {
|
||||
var rooms []model.ChatRoom
|
||||
err := s.DB.
|
||||
Joins("JOIN room_members ON room_members.room_id = chat_rooms.room_id").
|
||||
Where("room_members.user_id = ? AND chat_rooms.room_type = ?", userID, "group").
|
||||
Find(&rooms).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rooms, nil
|
||||
}
|
||||
|
||||
/**
|
||||
* AddGroupMembers
|
||||
* 功能:批量添加群成员(仅群主或管理员可操作)
|
||||
*/
|
||||
func (s *RoomService) AddGroupMembers(roomID, operatorID string, memberIDs []string) error {
|
||||
if len(memberIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 权限校验:必须是群主或管理员
|
||||
isAdmin, err := s.isGroupAdminOrOwner(roomID, operatorID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isAdmin {
|
||||
return fmt.Errorf("无权限添加成员")
|
||||
}
|
||||
|
||||
for _, uid := range memberIDs {
|
||||
if uid == "" {
|
||||
continue
|
||||
}
|
||||
member := model.RoomMember{
|
||||
RoomID: roomID,
|
||||
UserID: uid,
|
||||
Role: 0, // 默认成员
|
||||
}
|
||||
if err := s.DB.Create(&member).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/**
|
||||
* RemoveGroupMember
|
||||
* 功能:踢出群成员(群主或管理员),或作为封装调用 RemoveRoomMember
|
||||
*/
|
||||
func (s *RoomService) RemoveGroupMember(roomID, operatorID, memberID string) error {
|
||||
// 群主或管理员才能踢人
|
||||
isAdmin, err := s.isGroupAdminOrOwner(roomID, operatorID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isAdmin {
|
||||
return fmt.Errorf("无权限移除成员")
|
||||
}
|
||||
|
||||
// 不允许移除群主
|
||||
room, err := s.GetRoom(roomID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if memberID == room.OwnerID {
|
||||
return fmt.Errorf("不能移除群主")
|
||||
}
|
||||
|
||||
return s.RemoveRoomMember(roomID, memberID)
|
||||
}
|
||||
|
||||
/**
|
||||
* QuitGroup
|
||||
* 功能:成员主动退出群聊
|
||||
*/
|
||||
func (s *RoomService) QuitGroup(roomID, userID string) error {
|
||||
room, err := s.GetRoom(roomID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 群主不能直接退群
|
||||
if userID == room.OwnerID {
|
||||
return fmt.Errorf("群主不能直接退出群聊,请先转让群主或解散群")
|
||||
}
|
||||
|
||||
return s.RemoveRoomMember(roomID, userID)
|
||||
}
|
||||
|
||||
/**
|
||||
* ChangeMemberRole
|
||||
* 功能:修改成员角色(仅群主可设置/取消管理员)
|
||||
*/
|
||||
func (s *RoomService) ChangeMemberRole(roomID, operatorID, memberID string, role int8) error {
|
||||
// 仅允许 0/1/2
|
||||
if role < 0 || role > 2 {
|
||||
return fmt.Errorf("非法角色值")
|
||||
}
|
||||
|
||||
room, err := s.GetRoom(roomID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 只有群主可以调整角色
|
||||
if operatorID != room.OwnerID {
|
||||
return fmt.Errorf("只有群主可以调整成员角色")
|
||||
}
|
||||
|
||||
// 防止将群主本人的角色修改为其他值
|
||||
if memberID == room.OwnerID && role != 2 {
|
||||
return fmt.Errorf("不能修改群主自身角色")
|
||||
}
|
||||
|
||||
return s.DB.Model(&model.RoomMember{}).
|
||||
Where("room_id = ? AND user_id = ?", roomID, memberID).
|
||||
Update("role", role).Error
|
||||
}
|
||||
|
||||
/**
|
||||
* IsGroupAdminOrOwner
|
||||
* 功能:对外暴露的权限判断:是否为群主或管理员
|
||||
*/
|
||||
func (s *RoomService) IsGroupAdminOrOwner(roomID, userID string) (bool, error) {
|
||||
return s.isGroupAdminOrOwner(roomID, userID)
|
||||
}
|
||||
|
||||
/**
|
||||
* IsGroupOwner
|
||||
* 功能:判断用户是否为群主
|
||||
*/
|
||||
func (s *RoomService) IsGroupOwner(roomID, userID string) (bool, error) {
|
||||
room, err := s.GetRoom(roomID)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return room.OwnerID == userID, nil
|
||||
}
|
||||
|
||||
/**
|
||||
* isGroupAdminOrOwner
|
||||
* 功能:判断用户是否是群主或管理员
|
||||
*/
|
||||
func (s *RoomService) isGroupAdminOrOwner(roomID, userID string) (bool, error) {
|
||||
if userID == "" {
|
||||
return false, nil
|
||||
}
|
||||
var member model.RoomMember
|
||||
err := s.DB.Where("room_id = ? AND user_id = ?", roomID, userID).First(&member).Error
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return member.Role == 1 || member.Role == 2, nil
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@ DROP TABLE IF EXISTS `room_members`;
|
||||
CREATE TABLE `room_members` (
|
||||
`room_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '房间ID',
|
||||
`user_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户ID',
|
||||
`role` tinyint(1) NOT NULL DEFAULT 0 COMMENT '成员角色(0=成员,1=管理员,2=群主)',
|
||||
`joined_at` datetime(3) NULL DEFAULT NULL COMMENT '加入时间',
|
||||
PRIMARY KEY (`room_id`, `user_id`) USING BTREE,
|
||||
INDEX `idx_room_members_user_id`(`user_id` ASC) USING BTREE
|
||||
|
||||
Reference in New Issue
Block a user