Web端集成

互动消息是用于加强直播间消息沟通、提升交互体验的服务。提供了丰富、易集成的SDK,可在用户开发的直播应用中轻松集成评论、弹幕、点赞等能力。本文介绍Web端集成互动消息的操作步骤。

前提条件

客户端集成前,请确保已经完成服务端集成,并提供客户端访问的获取鉴权Token的接口。服务端集成操作指引,请参见服务端集成

浏览器支持

Web端SDK依赖浏览器对WebRTC技术的支持,支持的浏览器版本如下:

  • Chrome 63+

  • Firefox 62+

  • Opera 50+

  • Edge 79+

  • Safari 11+

集成SDK

在script标签中引入SDK。

<script src="https://g.alicdn.com/apsara-media-box/imp-interaction/1.3.2/alivc-im.iife.js"></script>

使用以下文件为SDK增加类型定义,提升开发体验。下载地址参见alivc-im.iife.d.ts.zip

使用SDK

SDK使用需遵循如下操作顺序:

  1. 初始化

  2. 登录

  3. 相关操作

  4. 登出

  5. 反初始化

其中相关操作包含群组操作和消息操作,详细说明如下:

SDK操作

  • 群组操作

    • 创建群组(需要以管理员身份进行登录才能操作)

    • 关闭群组(仅限群主/群管理员操作)

    • 进入群组

    • 离开群组

    • 查询群组信息

    • 修改群组信息(仅限群主/群管理员操作)

    • 查询群组最近成员列表

    • 查询群组全部成员(仅限群主/群管理员操作)

    • 对群组进行禁言(仅限群主/群管理员操作)

    • 对群组取消禁言(仅限群主/群管理员操作)

    • 对群组的用户进行禁言(仅限群主/群管理员操作)

    • 对群组的用户取消禁言(仅限群主/群管理员操作)

    • 查询群组内被禁言的用户列表(仅限群主/群管理员操作)

  • 消息操作

    • 单发消息

    • 群发消息

    • 查询最近群发消息列表

    • 查询全部群发消息(仅限群主/群管理员操作)

    • 删除/撤回群消息

    • 查询历史消息

注意事项

各ID需遵循以下规则:

appid :最长64位,仅限于A~Z,a~z, 0~9及“-”, 不能包含其他字符

userid:最长64位,仅限于A~Z,a~z, 0~9及“-”, 不能包含其他字符

groupid:最长64位,仅限于A~Z,a~z, 0~9及“-”, 不能包含其他字符

初始化

使用前需要进行初始化,可以在相关业务模块的主入口设置。

示例代码

const { ImEngine, ImLogLevel } = AliVCInteraction;

// 获取引擎单例
const engine = ImEngine.createEngine();

try {
  await engine.init({
    deviceId: "xxxx",    // 设备ID,可选传入
    appId: "APP_ID",     // 开通应用后可以在控制台上拷贝
    appSign: "APP_SIGN", // 开通应用后可以在控制台上拷贝
    logLevel: ImLogLevel.ERROR,  // 日志级别,调试时使用 ImLogLevel.DBUG
  });
  // 需确认init异步操作成功后,才可以继续执行login等操作

  // 初始化成功,监听事件
  // 处理回调事件  AliVCIMEngineListenerProtocol
  engine.on("connecting", () => {
    console.log("connecting");
  });
  
  engine.on("connectfailed", (err) => {
    console.log(`connect failed: ${err.message}`);
  });
  
  engine.on("connectsuccess", () => {
    console.log("connect success");
  });
  
  engine.on("disconnect", (code: number) => {
    console.log(`disconnect: ${code}`);
  });
  
  engine.on("tokenexpired", async (cb) => {
    console.log("token expired");
    // 这里需要更新为获取新的登录信息的代码
    const auth = await getLoginAuth();
    cb(null, {
      timestamp: 22123123, // 服务端返回timestamp值
      nonce: 'nonce',      // 服务端返回nonce值
      role: 'admin',       // 是否为admin角色,如果不需要可以设置为空
      token: 'xxx'         // 服务端返回token值
    });
  });
  
} catch (error) {
  // init 错误码含义
  // 1001:重复初始化、1002:创建底层引擎失败、-1:底层重复初始化、-2:初始化配置信息有误
  console.log(`Init Fail: code:${error.code}, message: ${error.msg}`);
}

登录

登录需要鉴权信息,请确保已完成前提条件设置,并已通过服务端获取到了鉴权信息,包括timestamp、nonce、token等值。

示例代码

// 请确保init异步操作成功后,再执行此操作
await engine.login({
  user: {
    userId: 'abc',       // 当前app登录的用户id
    userExtension: '{}', // 用户扩展信息,可以是头像、昵称等封装为json字符串
  },
  userAuth: {
    timestamp: 22123123, // 服务端返回timestamp值
    nonce: 'nonce',      // 服务端返回nonce值
    role: 'admin',       // 是否为admin角色,如果不需要可以设置为空
    token: 'xxx'         // 服务端返回token值
  },
});

群组操作

以下操作需要确保login异步操作成功后,方可执行。

获取群组管理器

// 必须确保已经初始化,否则会返回空值
const groupManager = engine.getGroupManager();

添加和移除群组操作监听器

// 在适当的时机(例如进入房间后,且完成登录后)添加群组操作事件监听器
groupManager.on('exit', (groupId: string, reason: number) => {
  // 退出群组
  console.log(`group ${groupId} close, reason: ${reason}`);
})
groupManager.on('memberchange', (groupId: string, memberCount: number, joinUsers: ImUser[], leaveUsers: ImUser[]) => {
  // 有人进入或离开群组
  console.log(`group ${groupId} member change, memberCount: ${memberCount}, joinUsers: ${joinUsers.map(u => u.userId).join(',')}, leaveUsers: ${leaveUsers.map(u => u.userId).join('')}`);
})
groupManager.on('mutechange', (groupId: string, status) => {
  // 群组的禁言状态发生了变化
  console.log(`group ${groupId} mute change`);
})
groupManager.on('infochange', (groupId: string, info) => {
  // 有人离开了群组
  console.log(`group ${groupId} info change`);
})

// 如需移除监听某个事件,请使用 off 方法取消对应的事件
groupManager.off('infochange');

// 如需移除监听所有事件,则使用 removeAllListeners 方法
groupManager.removeAllListeners();

创建群组

需要以管理员身份进行登录才能调用成功。

await groupManager.createGroup({
  groupId: '',       // 群组ID,为空时,系统创建新群组后会返回唯一ID
  groupName: 'xxx',  // 群组昵称,一定要设置,否则会失败
  groupMeta: 'xxx'   // 群组扩展信息,如果有多个字段可以考虑封装为JSON字符串
});

关闭群组

仅限群主/群管理员调用,否则会调用失败。

// 参数为 groupId
await groupManager.closeGroup('xxx');

进入群组

// 参数为 groupId
await groupManager.joinGroup('xxx');

离开群组

// 参数为 groupId
await groupManager.leaveGroup('xxx');

查询群组信息

// 参数为 groupId
const groupInfo = await groupManager.queryGroup('xxx');

修改群组信息

支持修改群扩展信息和设置管理员,仅限群主/群管理员调用,否则会调用失败。

await groupManager.modifyGroup({
  groupId: 'xxx',  // 群组ID
  forceUpdateAdmins: true, // 是否强制修改群管理员
  admins: ['xxx'], // 群管理员ID 列表,需要清空时传空数组且 forceUpdateAdmins 传 true
  forceUpdateGroupMeta: true, // 是否强制修改群扩展信息
  groupMeta: 'xxx' // 群信息扩展信息Meta,需要清空时传空字符串且 forceUpdateGroupMeta 传 true
});

查询群组最近成员列表

// 参数为 groupId
const recentGroupUserInfo = groupManager.listRecentGroupUser(groupId);

查询群组全部成员

仅限群主/群管理员调用,否则会调用失败。

const recentGroupUserInfo = groupManager.listGroupUser({
  groupId: 'xxx',    // 群组ID
  nextpagetoken: 12, // 默认表示第一页,遍历时服务端会返回,客户端获取下一页时,应带上
  pageSize: 10,      // 最大不超过50
  sortType: ImSortType.ASC // 排序方式,ASC-先加入优先,DESC-后加入优先
});

对群组进行禁言

仅限群主/群管理员调用,否则会调用失败。

// 参数为 groupId
await groupManager.muteAll('xxx');

对群组取消禁言

仅限群主/群管理员调用,否则会调用失败。

// 参数为 groupId
await groupManager.cancelMuteAll('xxx');

对群组的用户进行禁言

仅限群主/群管理员调用,否则会调用失败。

await groupManager.muteUser({
  groupId: 'xxx',    // 群组ID
  userList: ['xxx']  // 需要禁言的用户ID列表
});

对群组的用户取消禁言

仅限群主/群管理员调用,否则会调用失败。

await groupManager.cancelMuteUser({
  groupId: 'xxx',    // 群组ID
  userList: ['xxx']  // 需要取消禁言的用户ID列表
});

查询群组内被禁言的用户列表

仅限群主/群管理员调用,否则会调用失败。

// 参数为 groupId
const muteUsersInfo = await groupManager.listMuteUsers('xxx');

消息操作

获取消息管理器

// 必须确保已经初始化,否则会返回空值
const messageManager = await engine.getMessageManager();

添加和移除消息操作监听器

// 收到其他用户发来的单聊消息
messageManager.on("recvc2cmessage", (msg) => {
  console.log('recvc2cmessage', msg);
});

// 收到群聊消息
messageManager.on("recvgroupmessage", (msg, groupId) => {
  console.log('recvgroupmessage', msg, groupId);
});

// 群消息删除
messageManager.on('deletegroupmessage', (msgId, groupId) => {
  console.log(`group ${groupId} delete message ${msgId}`)
});

// 如需移除监听某个事件,请使用 off 方法取消对应的事件
messageManager.off('recvgroupmessage');

// 如需移除监听所有事件,则使用 removeAllListeners 方法
messageManager.removeAllListeners();

单发消息

try {
  const messageId = await messageManager.sendC2cMessage({
    receiverId: 'xxx',     // 接受者id
    data: 'xxx',           // 发送消息内容,如果是结构化可考虑使用JSON字符串
    type: 88888,           // 自定义消息类型,需大于10000
    skipAudit: false,      // 可选(默认 false),跳过安全审核,true:发送的消息不经过阿里云安全审核服务审核;false:发送的消息经过阿里云安全审核服务审核,审核失败则不发送。
    level: ImMessageLevel.NORMAL // 可选(默认 NORMAL), 消息分级,需要高可靠时,使用 ImMessageLevel.HIGH
  });
  console.log('send success, messageId: ', messageId);
} catch (error) {
  // error.code 为 424 时为对方未在线,建议待对方上线后再重发
  console.log('send fail', error);
}

群发消息

// 发群组消息前请确保已加入群组
try {
  const messageId = await messageManager.sendGroupMessage({
    groupId: 'xxx',       // 群组id
    data: 'xxx',          // 发送消息内容,如果是结构化可考虑使用json字符串
    type: 88888,          // 自定义消息类型,需大于10000
    skipAudit: false,     // 可选(默认 false),跳过安全审核,true:发送的消息不经过阿里云安全审核服务审核;false:发送的消息经过阿里云安全审核服务审核,审核失败则不发送。
    skipMuteCheck: false, // 可选(默认 false),跳过禁言检测,true:忽略被禁言用户,还可发消息;false:当被禁言时,消息无法发送
    level: ImMessageLevel.NORMAL, // 可选(默认 NORMAL), 消息分级,需要高可靠时,使用 ImMessageLevel.HIGH
    noStorage: true,     // 可选(默认 false)true:表示该消息不需要存储,也无法拉取查询;false:消息进行存储,可以拉取查询。如果在直播间的弹幕场景,需要设置为 false。
    repeatCount: 1        // 可选(默认 1)本消息重复数量,内容完成一样的消息可以使用该字段进行聚合,并发送一次即可。
  });
  console.log('send success, messageId: ', messageId);
} catch (error) {
  // error.code 为 425 时为未加入群组
  console.log('send fail', error);
}

删除/撤回群消息

// 删除/撤回群组消息前请确保已加入群组
await messageManager.deleteMessage({
  groupId: 'xxx',       // 群组id
  messageId: 'xxx',     // 需要删除的消息 id
});

查询最近群发消息列表

// 查询群发消息列表前请确保已加入群组
const messagesInfo = await messageManager.listRecentMessage({
  groupId: 'xxx',     // 群组id
})
说明

加入群组后,调用该接口可返回最近50条消息,所有类型的用户均可调用。

查询全部群发消息列表

仅限群主/群管理员调用,否则会调用失败。普通用户建议使用listRecentMessage接口获取最近50条消息。

// ImSortType.ASC 正序,ImSortType.DESC 倒序
// 查询全部群发消息列表前请确保已加入群组
const { ImSortType } = window.AliVCInteraction;

// 需要先调用 engine.getMessageManager 方法得到 messageManager 对象
const messageList = await messageManager.listMessage({
  groupId: 'xxx',    // 群组ID
  type: 88888,       // 自定义消息类型,需大于10000
  nextPageToken: 12, // 不传时表示第一页,遍历时服务端会返回下一页Token,客户端获取下一页时应带上
  pageSize: 10,      // 默认10条,最大30条
  sortType: ImSortType.ASC // 排序方式,默认为时间递增
});

查询历史消息

// ImSortType.ASC 正序,ImSortType.DESC 倒序
const { ImSortType } = window.AliVCInteraction;

// 需要先调用 engine.getMessageManager 方法得到 messageManager 对象
const messageList = messageManager.listHistoryMessage({
  groupId: 'xxx',    // 群组ID
  type: 88888,       // 消息类型,自定义消息类型需大于10000
  nextPageToken: 12, // 可选,不传时表示第一页,遍历时服务端会返回下一页Token,客户端获取下一页时应带上
  pageSize: 10,      // 默认10条,最大30条
  sortType: ImSortType.ASC, // 排序方式,默认为时间递增
  beginTime: 0,      // 可选(默认 0),开始时间,秒级时间戳
  endTime: 0         // 可选(默认 0),结束时间,秒级时间戳
});

登出

await engine.logout();

反初始化

登出后如无需再进行登录,可以进行反初始化操作,SDK会对底层操作实例进行释放。

engine.unInit()

快速体验

前提条件

快速体验demo需要您的开发环境启动一个 HTTP 服务,如果您没有安装http-server的npm包,可以先执行npm install --global http-server指令全局安装。

步骤一:创建目录

创建一个demo文件夹,并按照下方文件目录结构创建好quick.htmlquick.js两个文件。

- demo
  - quick.html
  - quick.js

步骤二:编辑quick.html

将下方代码复制到quick.html,并保存。

示例代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>互动消息 quick start</title>
    <link rel="stylesheet" href="https://g.alicdn.com/code/lib/bootstrap/5.3.0/css/bootstrap.min.css" />
  </head>
  <body class="container">
    <h1 class="mt-2">互动消息 quick start</h1>

    <div class="toast-container position-fixed top-0 end-0 p-3">
      <div id="loginToast" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
        <div class="toast-header">
          <strong class="me-auto">登录消息</strong>
          <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
        </div>
        <div class="toast-body" id="loginToastBody"></div>
      </div>
    </div>

    <div class="row mt-3">
      <div class="col-6">
        <form id="loginForm">
          <div class="form-group mb-2">
            <label for="userId" class="form-label">用户ID</label>
            <input class="form-control form-control-sm" id="userId" placeholder="请输入英文字母或数字" />
          </div>
          <div class="form-group mb-2">
            <label for="groupId" class="form-label">群组ID</label>
            <input class="form-control form-control-sm" id="groupId" placeholder="加入群组前请确认是否已存在,不存在先创建" />
          </div>
          <div class="mb-2">
            <button id="loginBtn" type="button" class="btn btn-primary btn-sm">登录</button>
            <button id="joinBtn" type="button" class="btn btn-primary btn-sm">加入群组</button>
            <button id="createBtn" type="button" class="btn btn-primary btn-sm">创建群组</button>
            <button id="leaveBtn" type="button" class="btn btn-secondary btn-sm" disabled>离开群组</button>
            <button id="logoutBtn" type="button" class="btn btn-secondary btn-sm" disabled>登出</button>
          </div>
          <p class="mb-2">假如群组ID已存在,可以一键登录+加入群组</p>
          <div class="mb-2">
            <button id="oneLoginBtn" type="button" class="btn btn-primary btn-sm">一键登录+加入群组</button>
            <button id="oneLogoutBtn" type="button" class="btn btn-secondary btn-sm" disabled>一键离开群组+登出</button>
          </div>
        </form>

        <form id="msgForm" action="#" class="mt-4">
          <div class="form-group mb-2">
            <label for="msgText" class="form-label">消息</label>
            <input class="form-control form-control-sm" id="msgText" />
          </div>
          <div class="mb-2">
            <button id="sendBtn" type="button" class="btn btn-primary btn-sm" disabled>发送</button>
          </div>
        </form>
      </div>
      <div class="col-6">
        <h5>
          消息展示
          <button id="clearBtn" type="button" class="btn btn-secondary btn-sm float-end">清空</button>
        </h5>
        
        <div id="msgList" class="mt-4"></div>
      </div>
    </div>

    <script src="https://g.alicdn.com/code/lib/jquery/3.7.1/jquery.min.js"></script>
    <script src="https://g.alicdn.com/code/lib/bootstrap/5.3.0/js/bootstrap.min.js"></script>
    <script crossorigin="anonymous" src="https://g.alicdn.com/apsara-media-box/imp-interaction/1.3.2/alivc-im.iife.js"></script>
    <script src="./quick.js"></script>
  </body>
</html>

步骤三:编辑quick.js

将下方代码复制到quick.js,并将AppId、AppKey、AppSign粘贴至代码指定变量中保存。

示例代码

// 请从控制台复制对应的值填入下面 AppId、 AppKey、AppSign 变量当中
// 注意:这里仅作为本地快速体验使用,实际开发请勿在前端泄露 AppKey
const AppId = '';
const AppKey = '';
const AppSign = '';

const { ImEngine, ImLogLevel, ImMessageLevel } = window.AliVCInteraction;
// 获取引擎单例
const engine = ImEngine.createEngine();

let groupManager;
let messageManager;
let joinedGroupId;

const sha256 = async (message) => {
  // Convert the message string to an ArrayBuffer
  const encoder = new TextEncoder();
  const data = encoder.encode(message);

  // Calculate the hash using the subtle crypto API
  const result = await crypto.subtle.digest('SHA-256', data).then((buffer) => {
    // Convert the ArrayBuffer to a hexadecimal string
    let hash = Array.prototype.map.call(new Uint8Array(buffer), (x) => ('00' + x.toString(16)).slice(-2)).join('');
    return hash;
  });

  return result;
};

const getLoginAuth = async (userId, role) => {
  const nonce = 'AK_4';

  const timestamp = Math.floor(Date.now() / 1000) + 3600 * 3;

  const pendingShaStr = `${AppId}${AppKey}${userId}${nonce}${timestamp}${role}`;
  const appToken = await sha256(pendingShaStr);

  return {
    nonce,
    timestamp,
    token: appToken,
    role,
  };
};

function showToast(baseId, message) {
  $(`#${baseId}Body`).text(message);
  const toast = new bootstrap.Toast($(`#${baseId}`));

  toast.show();
}

function showMessage(text) {
  $('#msgList').append(`<div class="mb-2">${text}</div>`);
}

function listenEngineEvents() {
  // 处理回调事件  AliVCIMEngineListenerProtocol
  engine.on("connecting", () => {
    console.log("connecting");
  });
  
  engine.on("connectfailed", (err) => {
    console.log(`connect failed: ${err.message}`);
  });
  
  engine.on("connectsuccess", () => {
    console.log("connect success");
  });
  
  engine.on("disconnect", (code) => {
    console.log(`disconnect: ${code}`);
  });
  
  engine.on("tokenexpired", async (cb) => {
    console.log("token expired");
    // 这里需要更新为获取新的登录信息的代码
    // const auth = await getLoginAuth(userId, role);
    // cb(null, auth);
  });
}

function listenGroupEvents() {
  if (!groupManager) {
    return;
  }
  // 在适当的时机(例如进入房间后,且完成登录后)添加群组操作事件监听器
  groupManager.on('exit', (groupId, reason) => {
    // 退出群组
    showMessage(`group ${groupId} close, reason: ${reason}`);
  })
  groupManager.on('memberchange', (groupId, memberCount, joinUsers, leaveUsers) => {
    // 有人进入或离开群组
    showMessage(`group ${groupId} member change, memberCount: ${memberCount}, joinUsers: ${joinUsers.map(u => u.userId).join(',')}, leaveUsers: ${leaveUsers.map(u => u.userId).join('')}`);
  })
  groupManager.on('mutechange', (groupId, status) => {
    // 群组的禁言状态发生了变化
    showMessage(`group ${groupId} mute change`);
  })
  groupManager.on('infochange', (groupId, info) => {
    // 有人离开了群组
    showMessage(`group ${groupId} info change`);
  })
}

function listenMessageEvents() {
  if (!messageManager) {
    return;
  }
  // 收到群聊消息
  messageManager.on("recvgroupmessage", (msgData, groupId) => {
    console.log('recvgroupmessage', msgData, groupId);
    showMessage(`receive group: ${msgData.groupId}, type: ${msgData.type}, data: ${msgData.data}`);
  });
}

async function login(userId) {
  // 先初始化,注意别忘了加 await
  await engine.init({
    deviceId: "xxxx",    // 设备ID,可选传入
    appId: AppId,     // 开通应用后可以在控制台上拷贝
    appSign: AppSign, // 开通应用后可以在控制台上拷贝
    logLevel: ImLogLevel.ERROR,  // 日志级别,调试时使用 ImLogLevel.DBUG
  });
  // 初始化成功,监听事件
  listenEngineEvents();

  const role = 'admin'; // 是否为admin角色,如果不需要可以设置为空
  // 获取登录信息
  const authData = await getLoginAuth(userId, role);
  // 初始化成功再登录,注意别忘了加 await
  await engine.login({
    user: {
      userId,       // 当前app登录的用户id
      userExtension: '{}', // 用户扩展信息,可以是头像、昵称等封装为json字符串
    },
    userAuth: {
      timestamp: authData.timestamp, // 服务端返回timestamp值
      nonce: authData.nonce,      // 服务端返回nonce值
      role: authData.role,       // 是否为admin角色,如果不需要可以设置为空
      token: authData.token,        // 服务端返回token值
    },
  });

  // 必须确保已经初始化,否则会返回空值
  groupManager = engine.getGroupManager();
  messageManager = engine.getMessageManager();
}

async function logout() {
  await engine.logout();
  engine.unInit();
  groupManager = undefined;
  messageManager = undefined;
}

async function joinGroup(groupId) {
  if (!groupManager) {
    return;
  }
  await groupManager.joinGroup(groupId);
  joinedGroupId = groupId;
  listenGroupEvents();
  listenMessageEvents();
}

async function leaveGroup() {
  if (!groupManager || !joinedGroupId) {
    return;
  }
  await groupManager.leaveGroup(joinedGroupId);
  groupManager.removeAllListeners();
  messageManager.removeAllListeners();
}

$('#loginBtn').click(() => {
  const userId = $('#userId').val();
  if (!userId) {
    return;
  }
  login(userId)
    .then(() => {
      console.log('初始化、登录成功');
      showToast('loginToast', '初始化、登录成功');
      $('#loginBtn').prop('disabled', true);
      $('#logoutBtn').prop('disabled', false);
    })
    .catch((err) => {
      console.log('初始化、登录失败', err.code, err.msg);
    });
});

$('#oneLoginBtn').click(async () => {
  const userId = $('#userId').val();
  const groupId = $('#groupId').val();
  if (!userId || !groupId) {
    return;
  }
  try {
    await login(userId);
    showToast('loginToast', '初始化、登录成功');
    $('#loginBtn').prop('disabled', true);
    $('#logoutBtn').prop('disabled', false);

    await joinGroup(groupId);
    showMessage(`加入群组 ${groupId} 成功`);
    $('#joinBtn').prop('disabled', true);
    $('#leaveBtn').prop('disabled', false);
    $('#sendBtn').prop('disabled', false);
    $('#oneLoginBtn').prop('disabled', true);
    $('#oneLogoutBtn').prop('disabled', false);
  } catch (error) {
    console.log('一键登录+加入群组', err.code, err.msg);
  }
});

$('#joinBtn').click(() => {
  const groupId = $('#groupId').val();
  if (!groupId || !groupManager) {
    return;
  }
  joinGroup(groupId)
    .then(() => {
      showMessage(`加入群组 ${groupId} 成功`);
      $('#joinBtn').prop('disabled', true);
      $('#leaveBtn').prop('disabled', false);
      $('#sendBtn').prop('disabled', false);
    })
    .catch((err) => {
      console.log('加入群组失败', err.code, err.msg);
    });
});

$('#logoutBtn').click(() => {
  logout()
    .then(() => {
      console.log('登出成功');
      showToast('loginToast', '登出成功');
      $('#loginBtn').prop('disabled', false);
      $('#logoutBtn').prop('disabled', true);
    })
    .catch((err) => {
      console.log('初始化、登录失败', err.code, err.msg);
    });
});

$('#leaveBtn').click(() => {
  leaveGroup()
    .then(() => {
      showMessage('离开群组成功');
      $('#leaveBtn').prop('disabled', true);
      $('#sendBtn').prop('disabled', true);
      $('#joinBtn').prop('disabled', false);
    })
    .catch((err) => {
      console.log('离开群组失败', err.code, err.msg);
    });
});

$('#oneLogoutBtn').click(async () => {
  try {
    // 先离开群组,无论成功与否,都继续执行 logout
    await leaveGroup();
  } catch (error) {
    console.log(error);
  }
  try {
    await logout();
    showToast('loginToast', '登出成功');
    $('#loginBtn').prop('disabled', false);
    $('#logoutBtn').prop('disabled', true);
    $('#leaveBtn').prop('disabled', true);
    $('#sendBtn').prop('disabled', true);
    $('#joinBtn').prop('disabled', false);
    $('#oneLogoutBtn').prop('disabled', true);
    $('#oneLoginBtn').prop('disabled', false);
  } catch (error) {
    console.log(error);
  }
});

$('#createBtn').click(() => {
  const groupId = $('#groupId').val();
  if (!groupManager) {
    return;
  }
  groupManager.createGroup(
    {
      groupId,       // 群组ID,为空时,系统创建新群组后会返回唯一ID
      groupName: 'xxx',  // 群组昵称,一定要设置,否则会失败
      groupMeta: 'xxx'   // 群组扩展信息,如果有多个字段可以考虑封装为JSON字符串
    }
  )
  .then((res) => {
    console.log('创建群组成功', res);
  })
  .catch((err) => {
    console.log('创建群组失败', err.code, err.msg);
  });
});

$('#sendBtn').click(() => {
  const text = $('#msgText').val();
  if (!messageManager || !joinedGroupId) {
    return;
  }
  messageManager.sendGroupMessage({
    groupId: joinedGroupId,       // 群组id
    data: text,          // 发送消息内容,如果是结构化可考虑使用json字符串
    type: 88888,          // 自定义消息类型,需大于10000
    skipAudit: false,     // 可选(默认 false),跳过安全审核,true:发送的消息不经过阿里云安全审核服务审核;false:发送的消息经过阿里云安全审核服务审核,审核失败则不发送。
    skipMuteCheck: false, // 可选(默认 false),跳过禁言检测,true:忽略被禁言用户,还可发消息;false:当被禁言时,消息无法发送
    level: ImMessageLevel.NORMAL, // 可选(默认 NORMAL), 消息分级,需要高可靠时,使用 ImMessageLevel.HIGH
    noStorage: true,     // 可选(默认 false)true:表示该消息不需要存储,也无法拉取查询;false:消息进行存储,可以拉取查询。如果在直播间的弹幕场景,需要设置为 false。
    repeatCount: 1        // 可选(默认 1)本消息重复数量,内容完成一样的消息可以使用该字段进行聚合,并发送一次即可。
  })
  .then((res) => {
    console.log('群消息发送成功', res);
    $('#msgText').val('');
  })
  .catch((err) => {
    console.log('群消息发送失败', err.code, err.msg);
  });
});

$('#clearBtn').click(() => {
  $('#msgList').empty();
});

步骤四:运行体验

  1. 在终端中进入demo文件夹,然后执行http-server -p 8080,启动一个HTTP服务。

  2. 浏览器中新建标签页,访问localhost:8080/quick.html,在界面上填入用户ID ,单击登录按钮。

  3. 在界面上填入群组ID,单击加入群组按钮。

  4. 浏览器中再新建一个标签页,访问localhost:8080/quick.html,在界面上填入与上一步相同的群组ID和另一个用户ID,单击登录加入群组按钮。

  5. 在界面上消息文本输入框输入要发送的消息,单击发送按钮,即可发送群消息。发送成功后,两个用户将收到服务端下发的对应消息。