# 一、群聊相关方法汇总

控制器 app/controller/api/chat/chatgroup.js

'use strict';

const Controller = require('egg').Controller;

// 引入 uuid 库 `npm install uuid`
const { v4: uuidv4 } = require('uuid');

class ChatgroupController extends Controller {
    // 创建群聊(登录用户有这个功能,(游客)没有这个功能)
    async creategroup() {
        const { ctx, app } = this;
        // 参数验证
        ctx.validate({
            userIds: {
                type: 'array',
                required: true,
            }
        });
        // 拿参数
        let { userIds } = ctx.request.body;
        // 起初这些id是从朋友表拿的,即朋友表的id主键
        let goodfriend = await app.model.Goodfriend.findAll({
            where: {
                id: {
                    [this.app.Sequelize.Op.in]: userIds,
                },
                status: 1, // 数据状态正常
                isblack: 0, //没有加入黑名单
            },
            attributes: ['friend_id','user_id'],
        });
        if(!goodfriend || !goodfriend.length){
            return ctx.apiFail('请选择加入群聊的用户');
        }
        goodfriend = JSON.parse(JSON.stringify(goodfriend));
        // console.log('看一下goodfriend', goodfriend); return;
        // 重新组装用户id
        userIds = goodfriend.map(v=>v.friend_id);
        // console.log('看一下用户id数组userIds', userIds); return;
        
        // 我的信息
        const me = ctx.chat_user;
        const me_id = me.id;
        const me_name = me.nickname || me.username;
        // 查询一下这些id是否存在
        let users = await app.model.User.findAll({
            where: {
                id: {
                    [this.app.Sequelize.Op.in]: userIds,
                },
                status: 1,
            },
            attributes: ['id', 'username','nickname', 'avatar'],
        });
        if(!users.length){
           return ctx.apiFail('用户不存在,无法加入群聊');
        }
        // 转成普通对象
        users = JSON.parse(JSON.stringify(users));
        // console.log('看一下users', users); return;

        // 把我加在数组头部
        users.unshift(JSON.parse(JSON.stringify(me)));
        // console.log('加为群的用户信息', users); return;

        // 创建群聊
        // 群名称(最多50字符- 默认由用户昵称或者账号组成)
        let name = '';
        for(let i=0; i < users.length; i++){
            if(users[i].nickname){
                name += users[i].nickname + '、';
            }else{
                name += users[i].username + '、';
            }
        }
        // 去掉最后一个 '、'
        name = name.substring(0, name.length - 1);
        // 截取前50个字符
        if(name.length > 50){
            name = name.substring(0, 50); 
        }

        // 群头像(默认由用户头像组成,最多9个头像)
        let avatar = '';
        let arr = [];
        if(users.length > 9){
            arr = users.slice(0, 9);
        }else{
            arr = users;
        }
        for(let i = 0; i < arr.length; i++){
            avatar += arr[i].avatar + ',';
        }
        // 去掉最后一个 ','
        avatar = avatar.substring(0, avatar.length - 1);

        // 创建群组
        let group = await app.model.Group.create({
            user_id: me_id, // 创建者id
            name:name,
            avatar:avatar,
        });

        // 创建群组用户
        // 循环插入
        /*
        for(let i = 0; i < users.length; i++){
            await app.model.GroupUser.create({
                group_id:group.id,
                user_id:users[i].id,
            });
        }
        */
        // 批量插入
        let groupUsers = users.map(v => {
            return {
                group_id:group.id,
                user_id:v.id,
            }
        });
        await app.model.GroupUser.bulkCreate(groupUsers);

        // 消息推送通知加入群聊的其他人
        // 消息格式
        let message = { 
            id: uuidv4(), // 自动生成 UUID,唯一id, 聊天记录id,方便撤回消息
            from_avatar: me.avatar, // 发送者头像
            from_name: me.nickname || me.username, // 发送者名称
            from_id: me.id, // 发送者id
            to_id: group.id, // 群id
            to_name: group.name, // 群名称
            to_avatar: group.avatar, // 群头像
            chatType: 'group', // 聊天类型 群聊
            type: 'systemNotice', // 消息类型 系统通知消息
            data: {
                data: `${me.nickname || me.username}创建了一个群,大家可以开始聊天了`,
                dataType: false, 
                otherData: null,
            }, // 消息内容
            options: {}, // 其它参数
            create_time: (new Date()).getTime(), // 创建时间
            isremove: 0, // 0未撤回 1已撤回
            // 群相关信息
            group: group, 
        };
        // 循环推送给群成员
        users.forEach(v => {
            // 直接调用 `/app/extend/context.js` 封装的方法 chatWebsocketSendOrSaveMessage(sendto_id, message)
            ctx.chatWebsocketSendOrSaveMessage(v.id, message);
        });

        // 返回
        ctx.apiSuccess('ok');
    }

    // 群聊列表(登录用户和游客都有这个功能)
    async grouplist(){
        const { ctx,app } = this;
        //1.参数验证
        ctx.validate({
            page: {
                type: 'int',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '页码', //字段含义
                range:{
                    min:1,
                }
            },
            limit: {
                type: 'int',  //参数类型
                required: false, //是否必须
                defValue: 30, 
                desc: '每页多少条', //字段含义
            },
        });
        // 当前用户: 我
        const me = ctx.chat_user;
        const me_id = me.id;
        // 拿页码
        let page = ctx.params.page ? parseInt(ctx.params.page) : 1;
        // 每页多少条
        let limit = ctx.query.limit ? parseInt(ctx.query.limit) : 30;
        // 偏移量
        let offset = (page - 1) * limit; 
        // 拿数据
        let data = await app.model.Group.findAll({
            where:{
                status:1,// 状态为1的群
            },
            attributes:{
                exclude:['update_time'],
            },
            offset,
            limit,
            include:[{
                //关联群用户表
                model:app.model.GroupUser,
                attributes:['group_id','user_id','nickname','avatar'],
                // 关联查询条件
                where:{
                    user_id: me_id, // 用户id
                    status:1, // 状态
                },
            }],
            order:[
                ['id','desc'], // 按id降序排列
            ],
        });

        // 返回
        ctx.apiSuccess(data);
    }

    // 获取群资料信息(登录用户和游客都有这个功能)
    async groupinfo(){
        const { ctx,app } = this;
        //1.参数验证
        ctx.validate({
            id: {
                type: 'int',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '群id', //字段含义
                range:{
                    min:1,
                }
            },
        });
        // 当前用户: 我
        const me = ctx.chat_user;
        const me_id = me.id;
        // 拿id
        let { id } = ctx.params;
        // 查看群是否存在并且我是否在群里
        let group = await app.model.Group.findOne({
            where:{
                id: id, // 群id
                status:1, // 状态
            },
            attributes:{
                exclude:['update_time'],
            },
            include:[{
                //关联群用户表
                model:app.model.GroupUser,
                attributes:{
                    exclude:['update_time'],
                },
                where:{
                    status:1, // 群用户状态
                },
                // 根据user_id 关联用户表,因为可能GroupUser中没有设置昵称和头像
                include:[{
                    model:app.model.User,
                    attributes:['id','username','avatar','nickname','uuid','role'],
                }],
            }],
        });

        if(!group){
            return ctx.apiFail('群不存在或被封禁');
        }

        // 查看群信息的用户是否在群里,不在群里则无权查看
        // 由于对游客开放,游客不在群里,所以下面的代码注释掉
        // let me_index = group.group_users.findIndex(v => v.user_id == me_id);
        // if(me_index == -1){
        //     return ctx.apiFail('您不在群里,无法查看群信息');
        // }

        // 返回
        ctx.apiSuccess(group);
    }

    // 修改群名称(群主才有这个功能)
    async groupUpdateName(){
        const { ctx,app } = this;
        //1.参数验证
        ctx.validate({
            id: {
                type: 'int',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '群id', //字段含义
                range:{
                    min:1,
                }
            },
            name: {
                type: 'string', 
                required: true, 
                // defValue: '', 
                desc: '群名称', 
                range:{
                    min:1,
                    max:20,
                }
            },
        });
        // 当前用户: 我
        const me = ctx.chat_user;
        const me_id = me.id;
        // 拿参数
        let { id,name } = ctx.request.body;
        // 只有群主才能修改群名称
        let group = await app.model.Group.findOne({
            where:{
                id: id, // 群id
                status:1, // 状态
                user_id: me_id, // 群主id
            },
            // 需要给群成员推送
            include:[{
                //关联群用户表
                model:app.model.GroupUser,
                attributes:['group_id','user_id','nickname','avatar'],
            }],
        });

        if(!group){
            return ctx.apiFail('只有群主才能修改群名称');
        }

        // 2. 修改群名称
        /*
        await group.update({
            name: name,
        });
        */
        group.name = name;
        await group.save();

        // 推送给群成员
        // 3. 我作为群主,查一下我在群里的昵称和头像
        let me_index = group.group_users.findIndex(item => item.user_id == me.id);
        if(me_index == -1){
            return ctx.apiFail('无法通知群成员');
        }
        // 我在群里的昵称: 优先拿我在群里设置的昵称,没有则拿我自己的昵称,在没有则拿账号名
        let me_group_nickname = group.group_users[me_index].nickname || me.nickname || me.username;
        // 我在群聊的头像:优先拿我在群里的头像,没有则拿我自己的头像
        let me_group_avatar = group.group_users[me_index].avatar || me.avatar;

        // 4. 定义消息格式
        let message = { 
            id: uuidv4(), // 自动生成 UUID,唯一id, 聊天记录id,方便撤回消息
            from_avatar: me_group_avatar || me.avatar, // 发送者头像
            from_name: me_group_nickname || me.nickname || me.username, // 发送者名称
            from_id: me.id, // 发送者id
            to_id: group.id, // 群id
            to_name: group.name, // 群名称
            to_avatar: group.avatar, // 群头像
            chatType: 'group', // 聊天类型 群聊
            type: 'systemNotice', // 消息类型 系统通知消息
            data: {
                data: `群名称修改为:${name}`,
                dataType: false, 
                otherData: null,
            }, // 消息内容
            options: {}, // 其它参数
            create_time: (new Date()).getTime(), // 创建时间
            isremove: 0, // 0未撤回 1已撤回
            // 群相关信息
            group: group, 
        };

        // 循环推送给群成员
        group.group_users.forEach(v => {
            // 直接调用 `/app/extend/context.js` 封装的方法 chatWebsocketSendOrSaveMessage(sendto_id, message)
            ctx.chatWebsocketSendOrSaveMessage(v.user_id, message);
        });

        // 返回
        ctx.apiSuccess('ok');
    }

    // 修改群公告(群主才有这个功能)
    async groupremark(){
        const { ctx,app } = this;
        //1.参数验证
        ctx.validate({
            id: {
                type: 'int',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '群id', //字段含义
                range:{
                    min:1,
                }
            },
            remark: {
                type: 'string', 
                required: true, 
                // defValue: '', 
                desc: '群公告', 
                range:{
                    min:1,
                    max:500,
                }
            },
        });
        // 当前用户: 我
        const me = ctx.chat_user;
        const me_id = me.id;
        // 拿参数
        let { id,remark } = ctx.request.body;
        // 只有群主才能修改群公告
        let group = await app.model.Group.findOne({
            where:{
                id: id, // 群id
                status:1, // 状态
                user_id: me_id, // 群主id
            },
            // 需要给群成员推送
            include:[{
                //关联群用户表
                model:app.model.GroupUser,
                attributes:['group_id','user_id','nickname','avatar'],
            }],
        });

        if(!group){
            return ctx.apiFail('只有群主才能修改群公告');
        }

        // 2. 修改群公告
        /*
        await group.update({
            remark: remark,
        });
        */
        group.remark = remark;
        await group.save();

        // 推送给群成员
        // 3. 我作为群主,查一下我在群里的昵称和头像
        let me_index = group.group_users.findIndex(item => item.user_id == me.id);
        if(me_index == -1){
            return ctx.apiFail('无法通知群成员');
        }
        // 我在群里的昵称: 优先拿我在群里设置的昵称,没有则拿我自己的昵称,在没有则拿账号名
        let me_group_nickname = group.group_users[me_index].nickname || me.nickname || me.username;
        // 我在群聊的头像:优先拿我在群里的头像,没有则拿我自己的头像
        let me_group_avatar = group.group_users[me_index].avatar || me.avatar;

        // 4. 定义消息格式
        let message = { 
            id: uuidv4(), // 自动生成 UUID,唯一id, 聊天记录id,方便撤回消息
            from_avatar: me_group_avatar || me.avatar, // 发送者头像
            from_name: me_group_nickname || me.nickname || me.username, // 发送者名称
            from_id: me.id, // 发送者id
            to_id: group.id, // 群id
            to_name: group.name, // 群名称
            to_avatar: group.avatar, // 群头像
            chatType: 'group', // 聊天类型 群聊
            type: 'systemNotice', // 消息类型 系统通知消息
            data: {
                data: `[公告] \n\n ${remark}`,
                dataType: false, 
                otherData: null,
            }, // 消息内容
            options: {}, // 其它参数
            create_time: (new Date()).getTime(), // 创建时间
            isremove: 0, // 0未撤回 1已撤回
            // 群相关信息
            group: group, 
        };

        // 循环推送给群成员
        group.group_users.forEach(v => {
            // 直接调用 `/app/extend/context.js` 封装的方法 chatWebsocketSendOrSaveMessage(sendto_id, message)
            ctx.chatWebsocketSendOrSaveMessage(v.user_id, message);
        });

        // 返回
        ctx.apiSuccess('ok');
    }


    // 删除群成员(群主才有这个功能)
    async groupDeleteUser(){
        const { ctx,app } = this;
        //1.参数验证
        ctx.validate({
            group_id: {
                type: 'int',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '群id', //字段含义
                range:{
                    min:1,
                }
            },
            user_id: {
                type: 'int', 
                required: true, 
                // defValue: '', 
                desc: '群成员id', 
                range:{
                    min:1,
                }
            },
        });
        // 当前用户: 我
        const me = ctx.chat_user;
        const me_id = me.id;
        // 拿参数
        let { group_id,user_id } = ctx.request.body;
        // 只有群主才能删除群成员
        let group = await app.model.Group.findOne({
            where:{
                id: group_id, // 群id
                // status:1, // 状态
                user_id: me_id, // 群主id
            },
            attributes:['user_id','id'],
        });

        if(!group){
            return ctx.apiFail('只有群主才能操作');
        }

        if(user_id == me_id){
            return ctx.apiFail('群主不能删除自己');
        }

        // 2. 删除群成员
        await app.model.GroupUser.destroy({
            where:{
                user_id: user_id, // 用户id
                group_id: group_id, // 群id
                // status:1, // 状态
            },
        });

        // 3. 更新一下群头像
        let group_avatar = ''; // 群头像
        // 查一下头像,优先取用户在群里面设置的头像,没有则取用户自己的头像
        let group_users = await app.model.GroupUser.findAll({
            where:{
                group_id: group_id, // 群id
                status:1, // 状态
            },
            attributes:['avatar'],
            // 查一下用户表的头像
            include:[
                {
                    model:app.model.User,
                    attributes:['avatar'],
                }
            ],
        });
        if(group_users.length > 0){
            group_users = JSON.parse(JSON.stringify(group_users));
            // 如果群成员多于9个则取前9个用户
            if(group_users.length > 9){
                group_users = group_users.slice(0,9);
            }
            // 取前9个用户的头像, 优先取用户在群里面设置的头像,没有则取用户自己的头像
            group_avatar = group_users.map(v => v.avatar || v.user.avatar).join(',');
            // 更新群头像
            // 使用 update 方法直接更新群头像,避免 save() 问题
            await app.model.Group.update({
                avatar: group_avatar
            }, {
                where: { id: group_id } // 明确指定更新条件
            });
        }
        

        ctx.apiSuccess('删除群成员成功');
    }

    // 进群设置(群主才有这个功能)
    async groupAddUserSet(){
        const { ctx,app } = this;
        //1.参数验证
        ctx.validate({
            group_id: {
                type: 'int',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '群id', //字段含义
                range:{
                    min:1,
                }
            },
            addGroupSet: {
                type: 'int', 
                required: true, 
                // defValue: '', 
                desc: '进群设置属性值', 
                range:{
                    min:0,
                    max:10,
                }
            },
        });
        // 当前用户: 我
        const me = ctx.chat_user;
        const me_id = me.id; 
        // 拿参数
        let { group_id,addGroupSet } = ctx.request.body;
        // 群是否存在
        let group = await app.model.Group.findOne({
            where:{
                id: group_id, // 群id
                status:1, // 状态
                user_id: me_id, // 群主id
            },
            attributes:{
                exclude:['order','update_time'],
            },
        });
        if(!group){
            return ctx.apiFail('群不存在或者被封禁或者你无权操作');
        }
        // 修改进群设置
        group.invite_confirm = addGroupSet;
        await group.save();
        ctx.apiSuccess('设置成功');
    }

    // 同意或者拒绝用户进群(群主才有这个功能)
    async groupAgreeOrNo(){
        const { ctx,app } = this;
        //1.参数验证
        ctx.validate({
            group_id: {
                type: 'int',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '群id', //字段含义
                range:{
                    min:1,
                }
            },
            user_id: {
                type: 'int', 
                required: true, 
                // defValue: '', 
                desc: '进群的用户id', 
                range:{
                    min:1,
                }
            },
            type: {
                type: 'string', 
                required: true, 
                // defValue: '', 
                desc: '同意或者拒绝', 
                range:{
                    in:['agree','no'],
                }
            },
        });
        // (传token的用户)当前用户: 我
        const me = ctx.chat_user;
        const me_id = me.id; 
        // 2.拿参数
        let { group_id,user_id,type } = ctx.request.body;
        // 3.群是否存在,并且操作者(传token的用户)是群主
        let group = await app.model.Group.findOne({
            where:{
                id: group_id, // 群id
                status:1, // 状态
                user_id: me_id, // 群主id
            },
            attributes:{
                exclude:['order','update_time'],
            },
            // 获取群成员信息,后面要推送消息
            include:[
                {
                    model:app.model.GroupUser,
                    attributes:{
                        exclude:['order','update_time'],
                    },
                    where:{
                        status:1, // 状态
                    }
                }
            ],
        });
        if(!group){
            return ctx.apiFail('群不存在或者被封禁或者你无权操作');
        }

        // 4.查一下这个用户是否在群里面
        let group_user = await app.model.GroupUser.findOne({
            where:{
                group_id: group_id, // 群id
                user_id: user_id, // 用户id
                // status:1, // 状态--此处不用判断状态
            },
            attributes:{
                exclude:['order','update_time'],
            },
        });
        // 查不到则是非法操作 
        // 因为先有邀请或者加入群,写入了用户的数据,
        // 只不过它的状态是2,表示正在等待(或者锁定), 需要群主同意或者拒绝
        if(!group_user || group_user.status != 2){
            return ctx.apiFail('非法操作');
        }
        // 5.查到了,则判断是同意还是拒绝
        if(type == 'agree'){
            // 1.同意 - 则直接修改状态为1
            group_user.status = 1;
            // 2.执行修改
            await group_user.save();
        }else if(type == 'no'){
            // 拒绝 - 则直接删除这条记录
            await app.model.GroupUser.destroy({
                where:{
                    id: group_user.id, // 记录id
                },
            });
        }
        // 6.返回响应
        ctx.apiSuccess('处理成功');

        // 7.申请入群的用户信息 -- 要发消息通知
        let user = await app.model.User.findOne({
            where:{
                id: user_id, // 用户id
                status:1, // 状态
            },
            attributes:{
                exclude:['order','update_time'],
            },
        });
        if(!user){
            // 可能在申请入群后,用户违法被管理删除或者禁用了
            return ctx.apiFail('用户不存在或者被封禁');
        }
        

        // 8.如果同意入群,则通知群里面所有成员,有新成员加入
        if(type == 'agree'){
            // 1.定义消息格式
            // from_id 初始化自定义
            let _from_id = `redirect-applyAddGroup${group.id}-${user_id}-${(new Date()).getTime()}`;
            let message = { 
                id: uuidv4(), // 自动生成 UUID,唯一id, 聊天记录id,方便撤回消息
                from_avatar: 'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/group.png', // 发送者头像
                from_name: '加群申请提醒', // 发送者名称
                from_id: _from_id, // 发送者id 系统id或者类型
                to_id: group.id, // 群id
                to_name: group.name, // 群名称
                to_avatar: group.avatar, // 群头像
                chatType: 'group', // 聊天类型 群聊
                type: 'systemNotice', // 消息类型 系统通知消息
                data: {
                    data: `[${user.nickname || user.username}] 加入了群聊` ,
                    dataType: false, 
                    otherData: null,
                }, // 消息内容
                // 新增处理链接
                // redirect: {
                //     url:'/pages/applyMyfriend/applyMyfriend', // 处理链接地址
                //     type: 'navigateTo', // 处理链接类型
                // }, // 处理链接
                options: {}, // 其它参数
                create_time: (new Date()).getTime(), // 创建时间
                isremove: 0, // 0未撤回 1已撤回
                // 群相关信息
                group: group, 
            };
            // 2.给所有群成员发消息
            let allGroupUser = await app.model.GroupUser.findAll({
                where:{
                    group_id: group_id, // 群id
                    status:1, // 状态
                },
                attributes:['user_id','avatar'],
                // 查一下用户表的头像
                include:[
                    {
                        model:app.model.User,
                        attributes:['avatar'],
                    }
                ],
            });
            allGroupUser = JSON.parse(JSON.stringify(allGroupUser));
            // 发送给所有群成员
            allGroupUser.forEach(v => {
                // 直接调用 `/app/extend/context.js` 封装的方法 chatWebsocketSendOrSaveMessage(sendto_id, message)
                ctx.chatWebsocketSendOrSaveMessage(v.user_id, message);
            });


            // 3. 更新一下群头像
            let group_avatar = ''; // 群头像
            // 查一下头像,优先取用户在群里面设置的头像,没有则取用户自己的头像
            let group_users = allGroupUser;
            if(group_users.length > 0){
                group_users = JSON.parse(JSON.stringify(group_users));
                // 如果群成员多于9个则取前9个用户
                if(group_users.length > 9){
                    group_users = group_users.slice(0,9);
                }
                // 取前9个用户的头像, 优先取用户在群里面设置的头像,没有则取用户自己的头像
                group_avatar = group_users.map(v => v.avatar || v.user.avatar).join(',');
                // 更新群头像
                // 使用 update 方法直接更新群头像,避免 save() 问题
                await app.model.Group.update({
                    avatar: group_avatar
                }, {
                    where: { id: group_id } // 明确指定更新条件
                });
            }

        }
        
    }

    // 邀请人进群(群主直接邀请,群成员邀请、游客自己进群根据群设置来处理)
    async groupInviteUser(){
        const { ctx,app } = this;
        //1.参数验证
        ctx.validate({
            group_id: {
                type: 'int',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '群id', //字段含义
                range:{
                    min:1,
                }
            },
            user_id: {
                type: 'int', 
                required: true, 
                // defValue: '', 
                desc: '用户id', 
                range:{
                    min:1,
                }
            },
            inviteuser_id:{
                type: 'int', 
                required: false, 
                defValue: 0, 
                desc: '邀请人的用户id', 
            },
            addGroupDesc:{
                type: 'string', 
                required: false, 
                defValue: '', 
                desc: '加群说明或者邀请说明', 
                range:{
                    max:500,
                }
            },
        });
        // 当前用户: 我
        const me = ctx.chat_user;
        const me_id = me.id; 
        // 拿参数
        let { group_id,user_id,inviteuser_id,addGroupDesc } = ctx.request.body;
        // 2.群是否存在
        let group = await app.model.Group.findOne({
            where:{
                id: group_id, // 群id
                status:1, // 状态
            },
            attributes:{
                exclude:['order','update_time'],
            },
            include:[
                // 获取群主信息
                {
                    model: app.model.User,
                    attributes: ['username','nickname','avatar'],
                },
                // 获取群成员信息
                {
                    model: app.model.GroupUser,
                    attributes: ['nickname','avatar','user_id','status'],
                    // 群成员用户信息
                    include:[
                        {
                            model: app.model.User,
                            attributes: ['username','nickname','avatar'],
                        },
                    ],
                },
            ],
        });
        if(!group){
            return ctx.apiFail('群不存在或者被封禁');
        }

        // console.log('群信息',JSON.parse(JSON.stringify(group))); return;

        let result_group = JSON.parse(JSON.stringify(group)); // 群信息简化
        // 3.加群的人是否在群里
        /*
        let group_user = await app.model.GroupUser.findOne({
            where:{
                group_id: group_id, // 群id
                user_id: user_id, // 用户id
                // status:1, // 状态
            },
        });

        if(group_user){
            return ctx.apiFail('用户已经在群里');
        }
        */
        let index = result_group.group_users.findIndex(v=> v.user_id == user_id);
        if(index > -1){
            let adduserInfo = result_group.group_users[index];
            if(adduserInfo.status == 2){
                return ctx.apiFail('已申请加入群聊,正在等待群主同意');
            }else{
                return ctx.apiFail('用户已经在群里');
            }
        }

        // 4.加群者是否合法(是否存在这个用户)
        let user = await app.model.User.findOne({
            where:{
                id: user_id, // 用户id
                status:1, // 状态
            },
        });
        if(!user){
            return ctx.apiFail('加群的用户不存在或已被封禁,无法加入群聊');
        }
        // 加群者头像
        let AdduserAvatar = user.avatar;
        // 加群者称呼
        let AdduserName = user.nickname || user.username;
        // 加群者uuid
        let Adduseruuid = user.uuid;

        // 5.如果有邀请者查一下邀请者信息
        // console.log('邀请者id',inviteuser_id);
        // 主要处理邀请者的称呼
        let inviteusername = '';
        if(inviteuser_id && inviteuser_id > 0){
            // 查邀请者在群里的信息
            let inviteuserGroup = await app.model.GroupUser.findOne({
                where:{
                    user_id: inviteuser_id, // 邀请者用户id
                    group_id: group_id, // 群id
                },
                attributes:['nickname'],
            });
            if(inviteuserGroup){
                inviteuserGroup = JSON.parse(JSON.stringify(inviteuserGroup));
                // console.log('邀请者在群中的用户信息',inviteuserGroup);
                inviteusername = inviteuserGroup.nickname;
            }
            // 查用户表
            let inviteuser = await app.model.User.findOne({
                where:{
                    id: inviteuser_id, // 邀请者用户id
                    status:1, // 状态
                },
                attributes:['nickname','username','role','id'],
            });
            if(inviteuser){
                inviteuser = JSON.parse(JSON.stringify(inviteuser));
                // console.log('邀请者用户信息',inviteuser);
                if(inviteuser.role == 'user'){
                    inviteusername = inviteusername || inviteuser.nickname || inviteuser.username;
                    inviteusername = `[${inviteusername}]邀请 `;
                }
            }
        }

        // 6. 获取群主相关信息
        // 获取群主id
        let group_user_id = group.user_id;
        // 群主称呼和头像
        let group_user_name = '';
        let group_user_avatar = '';
        // 先查一下群主在群里面的称呼和头像
        let group_user_index = result_group.group_users.findIndex(v=> v.user_id == group_user_id);
        if(group_user_index > -1){
            group_user_name = result_group.group_users[group_user_index].nickname;
            group_user_avatar = result_group.group_users[group_user_index].avatar;
        }
        // 群主称呼
        group_user_name = group_user_name || result_group.user.nickname || result_group.user.username;
        // 群主头像
        group_user_avatar = group_user_avatar || result_group.user.avatar;


        // 根据群设置group.invite_confirm字段进行处理
        // invite_confirm: 0  默认值,任何人都可以进群
        // invite_confirm: 1  群主同意了才能进群
        // invite_confirm: 2  需先登录在申请进群
        // invite_confirm: 3  其他情况,根据业务需求操作

        // 7. invite_confirm: 2  游客需先登录在申请进群的情况
        if(group.invite_confirm == 2){
            if(group_user_id !== inviteuser_id){
                // 邀请人不是群主,且如果加群人是游客需要先登录
                if(user.role == 'visitor'){
                    return ctx.apiFail('请先登录再申请进群');
                }
            }
        }

        // 8. 添加群成员
        let addGroupUser = await app.model.GroupUser.create({
            group_id: group_id, // 群id
            user_id: user_id, // 用户id
            status:1, // 状态
            nickname: '', // 群昵称
        });
        
        // 9.invite_confirm: 1  需要群主同意的情况
        if(group.invite_confirm == 1 && group_user_id !== inviteuser_id){
            // 如果不是群主邀请的,则需要群主同意了才能进群, 将刚加群的用户状态改成2
            addGroupUser.status = 2; // 锁定状态
            await addGroupUser.save();
            // 返回
            ctx.apiSuccess('等待群主同意');
        }else{
            // 返回
            ctx.apiSuccess('加群成功');
            // 更新群头像
            let group_avatar = ''; // 群头像
            // 查一下头像,优先取用户在群里面设置的头像,没有则取用户自己的头像
            let group_users = await app.model.GroupUser.findAll({
                where:{
                    group_id: group_id, // 群id
                    status:1, // 状态
                },
                attributes:['avatar'],
                // 查一下用户表的头像
                include:[
                    {
                        model:app.model.User,
                        attributes:['avatar'],
                    }
                ],
            });
            if(group_users.length > 0){
                group_users = JSON.parse(JSON.stringify(group_users));
                // 如果群成员多于9个则取前9个用户
                if(group_users.length > 9){
                    group_users = group_users.slice(0,9);
                }
                // 取前9个用户的头像, 优先取用户在群里面设置的头像,没有则取用户自己的头像
                group_avatar = group_users.map(v => v.avatar || v.user.avatar).join(',');
                // 更新群头像
                // 使用 update 方法直接更新群头像,避免 save() 问题
                await app.model.Group.update({
                    avatar: group_avatar
                }, {
                    where: { id: group_id } // 明确指定更新条件
                });
            }
        }


        // 发送websocket消息
        // 4. 定义消息格式
        // from_id 初始化自定义
        let _from_id = `redirect-applyAddGroup${group.id}-${user_id}-${(new Date()).getTime()}`;
        let message = { 
            id: uuidv4(), // 自动生成 UUID,唯一id, 聊天记录id,方便撤回消息
            from_avatar: 'https://docs-51yrc-com.oss-cn-hangzhou.aliyuncs.com/chat/group.png', // 发送者头像
            from_name: '加群申请提醒', // 发送者名称
            from_id: _from_id, // 发送者id 系统id或者类型
            to_id: group.id, // 群id
            to_name: group.name, // 群名称
            to_avatar: group.avatar, // 群头像
            chatType: 'group', // 聊天类型 群聊
            type: 'systemNotice', // 消息类型 系统通知消息
            data: {
                data: `${inviteusername}[${user.nickname || user.username}] 加入了群聊` ,
                dataType: false, 
                otherData: null,
            }, // 消息内容
            // 新增处理链接
            // redirect: {
            //     url:'/pages/applyMyfriend/applyMyfriend', // 处理链接地址
            //     type: 'navigateTo', // 处理链接类型
            // }, // 处理链接
            options: {}, // 其它参数
            create_time: (new Date()).getTime(), // 创建时间
            isremove: 0, // 0未撤回 1已撤回
            // 群相关信息
            group: group, 
        };

        // 根据进群设置group.invite_confirm发送消息
        if(group.invite_confirm == 0){
            // 任何人都可以进群,则消息由系统发送,发送给所有群成员
            let allGroupUser = await app.model.GroupUser.findAll({
                where:{
                    group_id: group_id, // 群id
                    status:1, // 状态
                },
                attributes:['user_id'],
            });
            allGroupUser = JSON.parse(JSON.stringify(allGroupUser));
            // 发送给所有群成员
            allGroupUser.forEach(v => {
                // 直接调用 `/app/extend/context.js` 封装的方法 chatWebsocketSendOrSaveMessage(sendto_id, message)
                ctx.chatWebsocketSendOrSaveMessage(v.user_id, message);
            });

        }else if(group.invite_confirm == 1 || group.invite_confirm == 2){
            // 需要群主确认才能进群:但如果是群主邀请的,则不需要群主确认,直接进群,通知给所有人
            if(group_user_id == inviteuser_id){
                // 是群主邀请的,直接进群,发送给所有群成员
                let allGroupUser = await app.model.GroupUser.findAll({
                    where:{
                        group_id: group_id, // 群id
                        status:1, // 状态
                    },
                    attributes:['user_id'],
                });
                allGroupUser = JSON.parse(JSON.stringify(allGroupUser));
                // 发送给所有群成员
                allGroupUser.forEach(v => {
                    // 直接调用 `/app/extend/context.js` 封装的方法 chatWebsocketSendOrSaveMessage(sendto_id, message)
                    ctx.chatWebsocketSendOrSaveMessage(v.user_id, message);
                });
            }else{
                // 如果不是群主邀请的,发送给群主一个人进行审批
                // 附加信息
                let _desc = {
                    // 进群方式: 邀请进群还是自主进群
                    addGroupUserType: inviteuser_id && inviteuser_id > 0 ? '邀请进群' : '用户自主进群', 
                    // 邀请者
                    inviteUser: inviteuser_id && inviteuser_id > 0 ? 
                               (inviteusername && inviteusername.replace('[','').replace(']','').replace('邀请','')) : '',
                    // 时间
                    addGroupTime: (new Date()).getTime(),
                    // 加群说明
                    addGroupDesc: addGroupDesc, 
                    // 加群用户角色
                    addGroupUserRole: user.role,
                    // 新增一个消息id的key
                    msgidKey: _from_id, // 消息id(便于前端在消息页查找记录)
                };
                // 处理链接: 点击跳转到群成员进群审批页面
                message.redirect = {
                    url:`/pages/setpageInfo/setpageInfo?action=applyAddGroup&title=${encodeURIComponent('加群申请')}&id=${group_id}&name=${encodeURIComponent(group.name)}&avatar=${encodeURIComponent(group.avatar)}&chatType=group&applyUserid=${user_id}&applyUsername=${AdduserName}&applyUseruuid=${Adduseruuid}&applyUseravatar=${encodeURIComponent(AdduserAvatar)}&applydesc=${encodeURIComponent(JSON.stringify(_desc))}`, // 处理链接地址
                    type: 'navigateTo', // 处理链接类型
                    // 附加信息
                    desc:_desc,
                };
                // 在消息页作为新的通知消息处理,不要和群通知放一起
                // 这个消息仅发给群主即可
                message.to_id = group_user_id; // 群主id
                message.to_name = group_user_name; // 群主名称
                message.to_avatar = group_user_avatar; // 群主头像
                message.chatType = 'single'; // 聊天类型 
                message.type = 'text'; // 消息类型 系统通知消息
                message.from_name = '加群处理';
                message.data.data = `请处理申请 ${inviteusername}[${user.nickname || user.username}] 加入了群[${group.name}]` ;
                // 新增一个消息id的key
                message.msgidKey = _from_id; // 消息id(便于前端在消息页查找记录)

                // 推送
                /*
                // 单进程
                // 拿到群主的socket
                let you_socket = ctx.app.ws.chatuser[group_user_id];
                if (!you_socket) {
                    // 放到reids,设置消息列表中:key值是:'chat_getmessage_' + group_user_id(用户id)
                    this.service.cache.setList('chat_getmessage_' + group_user_id, message);
                } else {
                    // 如果群主在线,则直接推送给对方
                    you_socket.send(JSON.stringify({
                        type: 'singleChat',
                        data: message,
                        timestamp: Date.now(),
                    }));
                    // 存储到对方redis历史记录中【无需存储因为是系统发的】
                    // key: `chatlog_对方id_[single|group]_我的id`
                    // this.service.cache.setList(`chatlog_${group_user_id}_${message.chatType}_${me.id}`, message);
                }
                */
                // 多进程
                // 直接调用 `/app/extend/context.js` 封装的方法 chatWebsocketSendOrSaveMessage(sendto_id, message)
                ctx.chatWebsocketSendOrSaveMessage(group_user_id, message, true, false);
            }
        
        }else{
           // 其他情况,根据业务需求操作
        }

    }


    // 修改我在群里面的昵称(登录用户和游客都有这个功能)
    async groupnickname(){
        const { ctx,app } = this;
        //1.参数验证
        ctx.validate({
            id: {
                type: 'int',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '群id', //字段含义
                range:{
                    min:1,
                }
            },
            nickname: {
                type: 'string', 
                required: true, 
                // defValue: '', 
                desc: '群昵称', 
                range:{
                    min:1,
                    max:20,
                }
            },
        });
        // 当前用户: 我
        const me = ctx.chat_user;
        const me_id = me.id;
        // 拿参数
        let { id,nickname } = ctx.request.body;
        // 查我是否在这个群里面,并拿到我的信息
        let group_user = await app.model.GroupUser.findOne({
            where:{
                group_id: id, // 群id
                user_id: me_id, // 用户id
                status:1, // 状态
            },
        });

        if(!group_user){
            return ctx.apiFail('你不在该群聊中,无法修改昵称');
        }

        // 修改我的昵称
        group_user.nickname = nickname;
        await group_user.save();

        // 返回
        ctx.apiSuccess('ok');
    }

    // 删除群(群主可操作)或退出群(群成员可操作)
    async groupDeleteOrQuit(){ 
        const { ctx,app } = this;
        //1.参数验证
        ctx.validate({
            id: {
                type: 'int',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '群id', //字段含义
                range:{
                    min:1,
                }
            },
        });
        // 当前用户: 我
        const me = ctx.chat_user;
        const me_id = me.id;
        // 拿参数
        let { id } = ctx.request.body;
        // 1. 看一下群是否存在
        let group = await app.model.Group.findOne({
            where:{
                id: id, // 群id
                // status:1, // 状态
            },
            attributes:{
                exclude:['update_time'],
            },
            include:[{
                //关联群用户表
                model:app.model.GroupUser,
                attributes:{
                    exclude:['update_time'],
                },
                // 根据user_id 关联用户表,因为可能GroupUser中没有设置昵称和头像
                include:[{
                    model:app.model.User,
                    attributes:['id','username','avatar','nickname'],
                }],
            }],
        });
        if(!group){
            return ctx.apiFail('群不存在');
        }

        // 2. 我是否在群里
        let me_index = group.group_users.findIndex(v => v.user_id == me_id);
        if(me_index == -1){
            return ctx.apiFail('你不在该群聊中,操作失败');
        }

        // 3. 我是不是群主
        let is_group_owner = false;
        if(group.user_id == me_id){
            is_group_owner = true;
        }

        // 4. 处理及设置处理类型
        let dotype = '';
        if(is_group_owner){
            // 我是群主,删除群
            dotype = 'deleteGroup';
            await group.destroy();
        }else{
            // 我不是群主, 退出群
            dotype = 'quitGroup';
            let group_user = await app.model.GroupUser.findOne({
                where:{
                    group_id: id, // 群id
                    user_id: me_id, // 用户id
                    // status:1, // 状态
                },
            });
            // if(!group_user){
            //     return ctx.apiFail('你不在该群聊中,操作失败');
            // }
            await group_user.destroy();
        }

        // 5. 推送给群成员(删除群(解散群)推送给所有人,某个成员退群可通知群主)
        // 拿一下我的昵称和头像
        // 我在群里的昵称: 优先拿我在群里设置的昵称,没有则拿我自己的昵称,在没有则拿账号名
        let me_group_nickname = group.group_users[me_index].nickname || me.nickname || me.username;
        // 我在群聊的头像:优先拿我在群里的头像,没有则拿我自己的头像
        let me_group_avatar = group.group_users[me_index].avatar || me.avatar;

        // 6. 定义推送信息格式
        let message = { 
            id: uuidv4(), // 自动生成 UUID,唯一id, 聊天记录id,方便撤回消息
            from_avatar: me_group_avatar || me.avatar, // 发送者头像
            from_name: me_group_nickname || me.nickname || me.username, // 发送者名称
            from_id: me.id, // 发送者id
            to_id: group.id, // 群id
            to_name: group.name, // 群名称
            to_avatar: group.avatar, // 群头像
            chatType: 'group', // 聊天类型 群聊
            type: 'systemNotice', // 消息类型 系统通知消息
            data: {
                data: ``, // 消息内容--根据情况自定义
                dataType: false, 
                otherData: null,
            }, // 消息内容
            options: {}, // 其它参数
            create_time: (new Date()).getTime(), // 创建时间
            isremove: 0, // 0未撤回 1已撤回
            // 群相关信息
            group: group, 
        };

        // 7. 根据处理情况自定义消息内容
        switch (dotype) {
            case 'deleteGroup':
                message.data.data = `群主于 ${(new Date()).toLocaleString()} 解散了该群`;
                break;
            case 'quitGroup':
                message.data.data = `${me_group_nickname}${(new Date()).toLocaleString()} 已退出群聊`;
                break;
        }

        // 8. 根据处理情况自定义推送对象
        if(dotype == 'deleteGroup'){
            // 解散群,推送给群里面所有成员
            group.group_users.forEach(v => {
                // 直接调用 `/app/extend/context.js` 封装的方法 chatWebsocketSendOrSaveMessage(sendto_id, message)
                ctx.chatWebsocketSendOrSaveMessage(v.user_id, message);
            });
        }else if(dotype == 'quitGroup'){
            // 退群, 只推送给群主
            // 拿到群主id
            let group_owner_id = group.user_id;
            // 直接调用 `/app/extend/context.js` 封装的方法 chatWebsocketSendOrSaveMessage(sendto_id, message)
            ctx.chatWebsocketSendOrSaveMessage(group_owner_id, message);
        }

        // 9. 返回
        return ctx.apiSuccess('ok');
    }

    // 生成群二维码(登录用户和游客都有这个功能)(个人二维码也是用这个方法)
    async groupQrcode(){
        const { ctx,app } = this;
        //1.参数验证
        ctx.validate({
            id: {
                type: 'int',  //参数类型
                required: true, //是否必须
                // defValue: '', 
                desc: '群id或者个人id', //字段含义
                range:{
                    min:1,
                }
            },
            type:{
                type: 'string',
                required: false,
                defValue: '',
                desc: '平台类型', // 针对h5端使用
            },
            http:{
                type: 'string',
                required: false,
                defValue: '',
                desc: '网址域名', // 针对h5端使用
            },
            chatType:{
                type: 'string',
                required: false,
                defValue: '',
                desc: '聊天类型', // 针对h5端使用
            }
        });
        // 当前用户: 我
        const me = ctx.chat_user;
        const me_id = me.id;
        // 拿id
        let { id } = ctx.params;
        let type = ctx.query.type ? ctx.query.type : '';
        let http = ctx.query.http ? ctx.query.http : '';
        let chatType = ctx.query.chatType ? ctx.query.chatType : 'group';

        // 判断群和个人是否存在
        let group = null;
        let user = null;
        if(chatType == 'group'){
            // 查看群是否存在并且我是否在群里
            group = await app.model.Group.findOne({
                where:{
                    id: id, // 群id
                    status:1, // 状态
                },
                attributes:{
                    exclude:['update_time'],
                },
                include:[{
                    //关联群用户表
                    model:app.model.GroupUser,
                    attributes:{
                        exclude:['update_time'],
                    },
                    where:{
                        user_id: me_id, // 用户id
                        group_id: id, // 群id
                        status:1, // 状态
                    },
                    // 根据user_id 关联用户表,因为可能GroupUser中没有设置昵称和头像
                    include:[{
                        model:app.model.User,
                        attributes:['id','username','avatar','nickname'],
                    }],
                }],
            });

            if(!group){
                return ctx.apiFail('群不存在或被封禁或者您不在该群聊中');
            }
        }else{
            // 个人二维码
            user = await app.model.User.findOne({
                where:{
                    id: id, // 用户id
                    status:1, // 状态
                },
                attributes:{
                    exclude:['update_time'],
                },
            });

            if(!user){
                return ctx.apiFail('用户不存在或被封禁');
            }
        }
        


        // 返回二维码
        if(type && type == 'H5' && http && chatType){
            // 生成H5端的二维码,即完整的网页地址
            if(chatType == 'group'){
               // 生成添加群的二维码地址
               let url = `${http}#/pages/setpageInfo/setpageInfo?action=autoAddGroup&title=${encodeURIComponent('群介绍')}&id=${group.id}&chatType=${chatType}&name=${group.name}`;
               console.log('生成添加群的二维码地址',url);
               ctx.createQrcode(url);
            }else if(chatType == 'single'){
               // 添加添加个人的二维码地址
               let name = user.nickname || user.username;
               let url = `${http}#/pages/setpageInfo/setpageInfo?action=autoAddGroup&title=${encodeURIComponent(`加用户[${name}]为好友`)}&id=${user.id}&chatType=${chatType}&name=${name}&avatar=${user.avatar}`;
               console.log('生成个人二维码地址',url);
               ctx.createQrcode(url);
            }
        }else{
            // 生成app和小程序端的二维码
            ctx.createQrcode(JSON.stringify({
                id: group.id,
                name: group.name,
            }));
        }
    }


}

module.exports = ChatgroupController;

# 二、群聊相关方法接口说明

群相关方法接口说明:群相关方法接口说明

更新时间: 2025年9月19日星期五晚上7点44分