開發(fā)者投稿|使用聲網(wǎng)的RTM SDK輕松給angular應(yīng)用加上實(shí)時(shí)聊天功能

作者:陳畏民

源起

今年寒假的前半段時(shí)間, 在家搗鼓了一個(gè)情侶類web應(yīng)用, 基于aspnetcore和angular搭建的; 寒假中實(shí)現(xiàn)了'告白', '相冊(cè)', '說說', '紀(jì)念日'這些功能, 然后前端界面上留一個(gè)功能的坑位: 聊天, 點(diǎn)擊這個(gè)聊天按鈕, 可以看到四個(gè)字, 那就是敬請(qǐng)期待; 部署上線后, 用戶當(dāng)然只有我和我的"好朋友"使用, "好朋友"先跨了我真棒, 然后問聊天功能馬上可以用了吧? 我沉默了, 心想著這個(gè)功能后面用signalr試試看吧; 現(xiàn)在已經(jīng)2020秋了, 聊天功能的界面上依舊是那四個(gè)字: 敬請(qǐng)期待

image

!

這個(gè)國(guó)慶, 我意識(shí)到不能再拖了, 自己埋的坑, 應(yīng)該趁早把它填了, 否則"好朋友"會(huì)覺得你很菜, 一個(gè)"簡(jiǎn)單的"聊天功能都做不出來;

遇見聲網(wǎng)(agora)

最開始想用signalr自己實(shí)現(xiàn)聊天功能的, 但是考慮到一方面, 自己的服務(wù)器資源有限(1核1G輕量應(yīng)用服務(wù)器); 另一方面, 自身精力能力有限, 寫出來也許不難, 但是要寫好確是不簡(jiǎn)單的; 于是尋思著找找現(xiàn)成的東西用用吧, 機(jī)緣巧合, 我聽說了聲網(wǎng)(agora), 于是去他的官網(wǎng)看了一番, 看到有詳細(xì)的文檔, 足量的免費(fèi)額度...于是決定先白嫖試用一下

關(guān)于agora

特地找了一下agora的相關(guān)資料, 看起來是挺靠譜的, 在全球都有數(shù)據(jù)中心和服務(wù)器; 小米、陌陌、新東方等知名企業(yè)都用過他們的云服務(wù);

基于agora的rtm sdk給我的應(yīng)用加上聊天功能

參考官網(wǎng)的文檔

我的環(huán)境

  • win10系統(tǒng)
  • npm包管理
  • angular8.x
  • vscode

步驟

安裝依賴

npm i agora-rtm-sdk
安裝完后需要修改下agora-rtm-sdk/index.d.ts的文件的2258
原來的內(nèi)容為:

export type { RtmChannel, RtmClient, RtmEvents, RtmMessage, RtmStatusCode };

修改為:

export { RtmChannel, RtmClient, RtmEvents, RtmMessage, RtmStatusCode };

不修改的話, 編譯會(huì)報(bào)錯(cuò)

引入依賴

因?yàn)槭窃谠赼ngular組件ChatComponent中實(shí)現(xiàn)聊天相關(guān)的功能, 所以在其中引入rtm sdk的依賴 import AgoraRTM from 'agora-rtm-sdk';

創(chuàng)建rtm客戶端并登陸到agora的rtm服務(wù)器

一行代碼創(chuàng)建rtm客戶端:

const rtmClient = AgoraRTM.createInstance('<your app id>');

<details>
<summary>登陸到rtm服務(wù)器</summary>

const rtmClient = AgoraRTM.createInstance('fd033b52ca5d40599efc96f6e2131639');

async function rtmClientLogin(user: User) {
  try {
    await rtmClient.login({ token: null, uid: user.id });
  } catch(err) {
    console.log('AgoraRTM client login failure', err);
  }
}

// 在組件的 ngOnInit 方法中調(diào)用 rtmClientLogin
async ngOnInit() {
    try {
      let user = await this.userServ.getUser().toPromise();
      if (user instanceof User) {
        this.user = user;
        rtmClientLogin(this.user);
      } else {
        throw new Error('無法獲取用戶數(shù)據(jù)');
      }
    } catch(err) {
      this.notifyServ.error('初始化聊天組件失敗', null);
      console.error('初始化聊天組件失敗', err);
    }
  }

ps: 測(cè)試階段, 所以使用的rtm的授權(quán)方式是AppID, 如果要使用這種授權(quán)方式, 在rtm控制臺(tái)創(chuàng)建項(xiàng)目的時(shí)候要注意一下, 身份認(rèn)證模式勾選 App ID, 否則在登陸到rtm服務(wù)器的時(shí)候, 會(huì)報(bào)紅

image

發(fā)送/接收消息

消息發(fā)送失敗需要通知用戶, 錯(cuò)誤通知直接使用了antdesign的NzNotificationService, 在構(gòu)造函數(shù)注入即可; 這個(gè)應(yīng)用中, 互相發(fā)消息的雙方是情侶, User表示當(dāng)前用戶, User.Spouse表示用戶的伴侶; 消息發(fā)送成功需要清空發(fā)送消息文字框并將發(fā)送的消息加入消息數(shù)組中, 讓angular更新視圖

發(fā)送消息
async sendMessage() {
  if (!this.newMessage) {
    return;
  }
  const spouseId = this.user.spouse.id;

  try {
    const result = await rtmClient.sendMessageToPeer({
      text: this.newMessage
    }, spouseId);
    if (!result.hasPeerReceived) {
      throw new Error('對(duì)方未接受消息');
    } else {
      this.messages.push({
        text: this.newMessage,
        sender: this.user,
        receiver: this.user.spouse,
        dateSended: new Date()
      });
      this.newMessage = undefined;
    }
  } catch (err) {
    this.notifyServ.error('發(fā)送消息失敗', null);
    console.log('發(fā)送消息失敗', err);
  }
}

ngOnInit生命周期函數(shù)中監(jiān)聽收到新消息事件, 收到新消息后, 將新消息加入消息數(shù)組中, angular會(huì)通過數(shù)據(jù)綁定更新視圖, 渲染ui

監(jiān)聽并處理收到新消息事件

async ngOnInit() {
  try {
    let user = await this.userServ.getUser().toPromise();
    if (user instanceof User) {
      this.user = user;
      rtmClientLogin(this.user);
      監(jiān)聽接收到消息事件
      rtmClient.on('MessageFromPeer', (rtmMessage, peerId) => {
        this.messages.push({
          text: rtmMessage.text,
          sender: this.user.spouse,
          receiver: this.user,
          dateSended: new Date()
        });
      });
      
    } else {
      throw new Error('無法獲取用戶數(shù)據(jù)');
    }
  } catch(err) {
    this.notifyServ.error('初始化聊天組件失敗', null);
    console.error('初始化聊天組件失敗', err);
  }
}

前端html代碼

<div id="container">
    <div class="messages">
        <div class="message-item"
             *ngFor="let msg of messages">
            <div class="sendedMessage"
                 *ngIf="msg.sender.id === user.id">
                <span class="message-text">{{msg.text}}</span>
                <span>
                    <nz-avatar nzIcon="user"
                               [nzSrc]="msg.receiver.profileImageUrl"></nz-avatar>
                </span>
            </div>

            <div class="receviedMessage"
                 *ngIf="msg.sender.id === user.spouse.id">
                <span>
                    <nz-avatar nzIcon="user"
                               [nzSrc]="msg.sender.profileImageUrl"></nz-avatar>
                </span>
                <span class="message-text">{{msg.text}}</span>
            </div>
        </div>
    </div>

    <div class="new-message">
        <div nz-row
             nzJustify="end">
            <div nz-col
                 nzSpan="18">
                <textarea nz-input
                          [(ngModel)]="newMessage"
                          [nzAutosize]="{ minRows: 1, maxRows: 6 }"></textarea>
            </div>
            <div nz-col
                 nzSpan="6">
                <button nz-button
                        nzType="primary"
                        class="mx-auto"
                        style="width: 100%;"
                        (click)="sendMessage()">發(fā)送</button>
            </div>
        </div>
    </div>
</div>

前端css

:host {
    height: 100%;
    display: block;
    position: relative;
}

#container {
    height: 100%;
    display: flex;
    flex-direction: column;
    padding-top: 4px;
}

.messages {
    flex-grow: 1;
}

nz-alert {
    display: block;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 85%;
    text-align: center;
}

.sendedMessage {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    margin-bottom: 8px;
}

.receviedMessage {
    margin-bottom: 8px;
}

.message-text {
    background: #fff;
    padding: 8px 4px;
}

.sendedMessage .message-text {
    margin-right: 4px;
}

.receviedMessage .message-text {
    margin-left: 4px;
}

效果如何?

動(dòng)圖演示:
[圖片上傳失敗...(image-4cbfbb-1614221898111)]

靜圖:


image

小結(jié)

上文基于agora的rtm sdk, 初步實(shí)現(xiàn)了簡(jiǎn)單的聊天功能; 體驗(yàn)下來感覺很方便, 不需要關(guān)注后端實(shí)現(xiàn), 只需要處理前端邏輯即可輕松構(gòu)建出實(shí)時(shí)聊天功能; 當(dāng)然, 正式在生產(chǎn)環(huán)境使用, 還是需要后端配合生成一個(gè)身份認(rèn)證令牌(token)來保證安全性的; 上文暫時(shí)只實(shí)現(xiàn)了文字的發(fā)送接收, 實(shí)際上rtm sdk還支持文件和圖片的收發(fā), 功能很強(qiáng)大, 有機(jī)會(huì)再繼續(xù)探索。


本文是參與聲網(wǎng)"內(nèi)容共建計(jì)劃"的開發(fā)者投稿作品

了解更多實(shí)時(shí)互動(dòng)相關(guān)內(nèi)容可點(diǎn)擊進(jìn)入聲網(wǎng)RTC 開發(fā)者社區(qū)進(jìn)行查看

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容