博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
聊天室应用开发实践(二):实现基于 Web 的聊天室
阅读量:5941 次
发布时间:2019-06-19

本文共 9672 字,大约阅读时间需要 32 分钟。

在中,作者 monkeyHi 已经分享了声网Agora 信令 SDk 的基本使用,可实现的场景,并分析了服务器端 Demo 的接口原理。这一篇,作者将带大家进行简单的实践,实现一个基于 Web 的简单的聊天室雏形。

本文首发于RTC 开发者社区,与作者交流。

同时,欢迎对实时音视频技术感兴趣的小伙伴参加

基于Agora已经发布的 Web 版 Demo,只要简单配置就已经具备最基本的实时聊天室功能。

获取web 版 demo

我们基于 来修改出一个聊天室。下载好 Web Demo 后解压,目录如下:

|---Agora_Signaling_Web    |---libs  // sdk 在这里    |---samples // demo在这里        |---Agora-Signaling-Tutorial-Web // 聊天室demo复制代码

用Visual Studio Code 打开 samples 目录下的 Agora-Signaling-Tutorial-Web

|——Agora-Signaling-Tutorial-Web    |—src    |  ├─assets    |  │  ├─images    |  │  └─stylesheets    |  ├─pages    |  │  ├─index    |  │  └─meeting    |  └─utils    |—static        |--agora.config.js  // 这里配置AppId        |--AgoraSig.js  // 复制它到 assets 目录复制代码

安装依赖包和运行demo

先简单说一下web端的sdk,这个sdk包装程度很高,甚至不需要开发者懂websockt和webrtc。只要调用对应的功能接口即可。

接下来,我们一起跑起demo,这个demo是一个webpack项目,专业的web开发工程师一定不会觉着陌生。

  • 首先,复制 AgoraSig.js到asstes目录。
  • 其次,npm install
  • 接下来,配置 appid 打开 src\static\agora.config.js ,修改AGORA_APP_ID 为我们自己的AppId
  • 最后,npm start

这时,浏览器应该已经弹出一个页面(如下图)。

随便输入一个用户名,我们就可以进入聊天室了,当然大家可以添加自己的用户鉴权业务。

我们打开两个标签页,分别用accontA 和accontB的身份加入同一个p2p频道,尝试互发消息。

笔者发现,我们这个聊天室并不需要另外运行过server端。而前面介绍的server端,从接口上看,其功能更倾向于做系统广播,系统消息通知。因此,如果你想实现具备云消息备份的功能的聊天软件,必须要在端上实现存储和上传备份。

毫不夸张的说,这个聊天室,只要一个 page 服务就可以跑起来了。

Web 版 Demo代码讲解

拿到一个web端项目,我们首先要看的就是package.json

我们可以从中看到项目依赖哪些Package以及项目的启动脚本。

"scripts": {    "test": "jest ./test", // 测试    "lint": "eslint .", // eslint格式化当前目录    "format": "eslint . --fix", // eslint fix当前目录下的代码格式    "dev": "cross-env NODE_ENV=development webpack-dev-server --open", // 这个会启动 webpack-dev-server 并用浏览器打开页面    "start": "npm run dev", // 功能同上一条    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules" // 编译  },复制代码

配置文件 static/agora.config.js

只需要在以下两行代码中进行配置。

const AGORA_APP_ID = '6cb4f3s4c67404d4ba0ec0b'  // appidconst AGORA_CERTIFICATE_ID = '' // 如果开启了token模式,这里要配置certificate_id复制代码

sdk文件 static/AgoraSig.js

这里和lib目录中的sdk文件是一样的,可以用新的sdk来替换。

新下载下来的demo ,还应该复制该文件到 src/assets目录下。

utils 目录

utils目录中封装了一些工具类。

signalingClient.js

这个文件中,主要对信令SDK做了进一步封装,将一些Action 转换为promise, 同时用 Event 替代了Callback。

/** * Wrapper for Agora Signaling SDK * Transfer some action to Promise and use Event instead of Callback */import EventEmitter from 'events';// 信令客户端类export default class SignalingClient {  constructor(appId, appcertificate) {    this._appId = appId;    this._appcert = appcertificate;    // Init signal using signal sdk    this.signal = Signal(appId) // eslint-disable-line     // init event emitter for channel/session/call    this.channelEmitter = new EventEmitter();    this.sessionEmitter = new EventEmitter();  }  /**   * @description login agora signaling server and init 'session'   * 登录 信令服务,并初始化会话   * @description use sessionEmitter to resolve session's callback   * @param {String} account   * @param {*} token default to be omitted   * @returns {Promise}   */  login(account, token = '_no_need_token') {    this.account = account;    return new Promise((resolve, reject) => {      this.session = this.signal.login(account, token);      // Proxy callback on session to sessionEmitter      [        'onLoginSuccess',        'onError',        'onLoginFailed',        'onLogout',        'onMessageInstantReceive',        'onInviteReceived'      ].map(event => {        return (this.session[event] = (...args) => {          this.sessionEmitter.emit(event, ...args);        });      });      // Promise.then      this.sessionEmitter.once('onLoginSuccess', uid => {        this._uid = uid;        resolve(uid);      });      // Promise.catch      this.sessionEmitter.once('onLoginFailed', (...args) => {        reject(...args);      });    });  }  /**   * @description logout agora signaling server   * 退出信令服务   * @returns {Promise}   */  logout() {    return new Promise((resolve, reject) => {      this.session.logout();      this.sessionEmitter.once('onLogout', (...args) => {        resolve(...args);      });    });  }  /**   * @description join channel   * 加入某个频道   * @description use channelEmitter to resolve channel's callback   * @param {String} channel   * @returns {Promise}   */  join(channel) {    this._channel = channel;    return new Promise((resolve, reject) => {      if (!this.session) {        throw {          Message: '"session" must be initialized before joining channel'        };      }      this.channel = this.session.channelJoin(channel);      // Proxy callback on channel to channelEmitter      // 将回调 都代理到 对应channelEmitter      [        'onChannelJoined',        'onChannelJoinFailed',        'onChannelLeaved',        'onChannelUserJoined',        'onChannelUserLeaved',        'onChannelUserList',        'onChannelAttrUpdated',        'onMessageChannelReceive'      ].map(event => {        return (this.channel[event] = (...args) => {          this.channelEmitter.emit(event, ...args);        });      });      // Promise.then      this.channelEmitter.once('onChannelJoined', (...args) => {        resolve(...args);      });      // Promise.catch      this.channelEmitter.once('onChannelJoinFailed', (...args) => {        this.channelEmitter.removeAllListeners()        reject(...args);      });    });  }  /**   * @description leave channel   * 离开当前频道   * @returns {Promise}   */  leave() {    return new Promise((resolve, reject) => {      if (this.channel) {        this.channel.channelLeave();        this.channelEmitter.once('onChannelLeaved', (...args) => {          this.channelEmitter.removeAllListeners()          resolve(...args);        });      } else {        resolve();      }    });  }  /**   * @description send p2p message   * 发送点对点消息   * @description if you want to send an object, use JSON.stringify   * @param {String} peerAccount   * @param {String} text   */  sendMessage(peerAccount, text) {    this.session && this.session.messageInstantSend(peerAccount, text);  }  /**   * @description broadcast message in the channel    * 发送频道消息   * @description if you want to send an object, use JSON.stringify   * 可以通过JSON.Stringify的方式发送object   * @param {String} text   */  broadcastMessage(text) {    this.channel && this.channel.messageChannelSend(text);  }}复制代码

pages

这里面分别是两个页面的实现,默认的index页面和聊天页面。大家可以在对webpack有一定了解的情况下,修改这两个页面。

pages/index.js

大家注意这里, 点击Join-meeting,我们获取id为account-name的DOM值,然后将account的值放到的url中。

$('#join-meeting').click(function(e) {  // Join btn clicked  e.preventDefault();  var account = $('#account-name').val() || '';  if (checkAccount(account)) {    // Account has to be a non empty numeric value    window.location.href = `meeting.html?account=${account}`;  } else {    $('#account-name')      .removeClass('is-invalid')      .addClass('is-invalid');  }});复制代码

后续聊天页面的account值通过url中的account参数来传值。

pages/meeting.js

metting.js中主要定义了client类和聊天相关的类方法。

首先,我们来看看文件尾部:

// 检测获取appid 并检测是否为空const appid = AGORA_APP_ID || '',  appcert = AGORA_CERTIFICATE_ID || '';if (!appid) {  alert('App ID missing!');}//从url获取account值 ,Browser 模块事先在util/index.js中定义好的。let localAccount = Browser.getParameterByName('account');let signal = new SignalingClient(appid, appcert);// Let channelName = Math.random() * 10000 + "";// by default call btn is disabled// 信令登陆signal.login(localAccount).then(() => {  // Once logged in, enable the call btn  let client = new Client(signal, localAccount);  $('#localAccount').html(localAccount);});复制代码

接下来,我们应该关注client类,这里笔者只节选部分代码。

相信很多朋友都发现了,demo中聊天头像都一样,傻傻分不清。

其实,生成渲染消息的方法里市可以自定义头像的,默认被写为固定图片,大家其实可以根据accont来拼装头像链接的,当然你得自己做个用户头像接口。

buildMsg(msg, me, ts) {    let html = '';    let timeStr = this.compareByLastMoment(ts);    if (timeStr) {      html += `
${timeStr}
`; } let className = me ? 'message right clearfix' : 'message clearfix'; html += '
  • '; // 注意看这里 html += ''; html += '
    ' + Utils.safe_tags_replace(msg) + '
    '; html += '
    ' + this.parseTwitterDate(ts) + '
  • '; return html; }复制代码

    这里大家要注意跨域的问题。需要自己对接口url做代理来解决跨域安全问题。开发状态下,直接配置devServer的proxy即可。

    又有的朋友说啦,我想保留消息记录,其实在端上保存消息记录还是比较容易的。

    注意看onReceiveMessage()

    onReceiveMessage(account, msg, type) {    let client = this;    var conversations = this.chats.filter(function(item) {      return item.account === account;    });    if (conversations.length === 0) {      // No conversation yet, create one      conversations = [{ id: new Date().getTime(), account: account, type: type }];      client.chats.splice(0, 0, conversations[0]);      client.updateLocalStorage();      client.updateChatList();    }    // 可以看到下面对消息做了简单处理,然后丢到msgs中    for (let i = 0; i < conversations.length; i++) {      let conversation = conversations[i];      let msgs = this.messages[conversation.id] || [];      let msg_item = { ts: new Date(), text: msg, account: account };      msgs.push(msg_item);      this.updateMessageMap(conversation, msgs);      let chatMsgContainer = $('.chat-messages');      if (String(conversation.id) === String(this.current_conversation.id)) {        this.showMessage(this.current_conversation.id)        chatMsgContainer.scrollTop(chatMsgContainer[0].scrollHeight);      }    }  }复制代码

    我们看一下引用它的位置。

    显然,无论p2p消息还是Chanel消息,都会调用这个onReceiveMessage()方法。因此,大家可以通过修改onReceiveMessage实现自己的聊天记录功能。具体是通过接口存储到我们自己的服务器,还是借助localStorage,都可以比较好的实现web端的聊天记录功能。

    诸如 window.localStorage.setItem('msglog',msgs)

    既然可以暂时保存在localStorage,那么,想导出聊天数据为json,csv也不会麻烦到哪里去。

    可能会遇到的问题

    1. npm install 报错

      解决方法: 更换仓库地址; 使用yarn install; 使用vpn

    2. 可以发送消息,但是收不到消息

      检查asset目录和static目录,是否存在AgoraSig.js ,如果不存在,从sdk的lib目录中复制并重命名为AgoraSig.js

    总结

    总体来说,基于Agora信令实现聊天室非常简单,基于demo,自己扩展一些用户管理业务就可以实现。大家可以集中精力优化交互体验,美化UI,专注于端上业务。但是,如果想要更多的可控权,希望在server端实现聊天记录之类的功能。基于信令当前版本做这类功能,需要自己来开发。信令的优势在于,方便实现一些消息通知的场景。而且对接非常容易,只要简单封装即可直接嵌入端上。另外,对于弹幕的实现,大家可以尝试在端实发送消息是同时推送消息到保存消息的接口。

    转载于:https://juejin.im/post/5cadade051882518b87e15e3

    你可能感兴趣的文章
    oracle sql语句实现累加、累减、累乘、累除
    查看>>
    SCNetworkReachabilityRef监测网络状态
    查看>>
    3D地图的定时高亮和点击事件(基于echarts)
    查看>>
    接口由40秒到200ms优化记录
    查看>>
    java 视频播放 多人及时弹幕技术 代码生成器 websocket springmvc mybatis SSM
    查看>>
    Activiti6.0,spring5,SSM,工作流引擎,OA
    查看>>
    第十三章:SpringCloud Config Client的配置
    查看>>
    使用 GPUImage 实现一个简单相机
    查看>>
    CoinWhiteBook:区块链在慈善事业中的应用
    查看>>
    Mac上基于Github搭建Hexo博客
    查看>>
    What does corn harvester involve?
    查看>>
    阿里云服务器ECS开放8080端口
    查看>>
    前端常用排序详解
    查看>>
    Spring中实现监听的方法
    查看>>
    使用Tooltip会出现一个问题,如果行上出现复选框
    查看>>
    11.03T1 DP
    查看>>
    P2924 [USACO08DEC]大栅栏Largest Fence
    查看>>
    jQuery操作table tr td
    查看>>
    工作总结:MFC自写排序算法(升序)
    查看>>
    螺旋队列问题之二
    查看>>