實時會話系統實現(1) --- http輪詢方式

最近一直在開發博客小程序,最近開發好友系統和實時會話系統。 其實衆所周知會話系統正常的業務邏輯應該是用戶A給用戶B發送一個消息,用戶A發送後用戶B馬上可以接收到並在頁面渲染出來,而且最新的消息應該是在頁面最底部。那要實現這個實時會話有什麼方法呢?我們通過本系列文章可以學到什麼知識點呢?我們可以先看看本系列文章涉及的知識點:
  • http輪詢渲染聊天信息

  • express-ws庫的基本使用

  • Nginx配置wss

  • express-ws配合輪詢實現聊天信息渲染

  • 使用socket-io實現websocket,真正實現實時會話系統


歡迎體驗小程序,如果有修改建議歡迎加我微信聊聊。



http輪詢

本篇文章將會針對http輪詢實現會話系統來講解,下一篇開始將會使用websocket改寫實現真正的實時會話系統。實際上會話系統最簡單的方式是http輪詢:用戶發送信息時實現一個http接口保存用戶聊天信息,然後在客戶端實現一個定時器,定時獲取用戶A與用戶B的聊天信息,並且重新渲染聊天界面。我們看下使用輪詢的業務邏輯:
  • 客戶端實現兩個事件:用戶信息發送時間和定時器輪詢獲取用戶聊天記錄。

  • 服務端相對應的應該有兩個API:用戶聊天信息保存API以及返回兩個用戶之間的所有聊天記錄。

  • 客戶端通過定時器定時調用查詢聊天信息API,然後每次取到數據重新渲染聊天界面。


輪詢方法實際上很簡單,但是爲什麼我們一般不會推薦使用http輪詢實現實時會話系統呢?因爲輪詢的缺點是顯而易見的,舉個很簡單的例子:我們在客戶端開了個1秒/次的定時器,每秒鐘向服務端請求聊天數據,但是這大部分的請求可能都是沒有新消息發送與接收的,所以說輪詢最大的缺點就是定時器定時請求大部分都是無用的,所以會極大的浪費和消耗帶寬和服務器資源。接下來我們可以來實戰看下效果,首先我們看下客戶端會話界面的效果:輸入框可以輸入聊天信息點擊發送可以發送文本信息,點擊+可以選擇相冊圖片發送。我們可以簡單看下客戶端界面渲染代碼,實際上就是通過for循環渲染聊天數據,將好友的信息渲染在左邊,自己發送的信息渲染在右邊:

<view class="news" bindtap='outbtn'>  <view class="historycon">    <scroll-view scroll-y="true" class="history" scroll-top="{{scrollTop}}">      <block wx:for="{{newslist}}" wx:key>        <view wx:if="{{item.flagtime}}" class="created_date">{{item.created_date}}</view>        <!--自己的消息 -->        <view class="chat-news" wx:if="{{item.username == chatInfo.userInfo.username}}">          <view style="text-align: right;padding-right: 20rpx;">            <text class="name">{{ item.username }}</text>            <image class='new_img' src="{{item.useravatar}}"></image>          </view>          <view class='my_right'>            <block wx:if="{{item.chat_type==0}}">              <view class='new_txt'>{{item.content}}</view>            </block>            <block wx:if="{{item.chat_type==1}}">              <image class="selectImg" src="{{item.content}}" data-src="{{item.content}}" lazy-load="true" bindtap="previewImg"></image>            </block>          </view>        </view>
<!-- 別人的消息 --> <view class="chat-news" wx:else> <view style="text-align: left;padding-left: 20rpx;"> <image class='new_img' src="{{item.useravatar? item.useravatar:'/images/defule.png'}}"></image> <text class="name">{{ item.username }}</text> </view> <view class='you_left'> <block wx:if="{{item.chat_type==0}}"> <view class='new_txt'>{{item.content}}</view> </block>
<block wx:if="{{item.chat_type==1}}"> <image class="selectImg" src="{{item.content}}" data-src="{{item.content}}" lazy-load="true" bindtap="previewImg"></image> </block> </view> </view> </block> </scroll-view> </view>
</view><view id="flag"></view>
<!-- 聊天輸入 --><view class="message"><view wx:if="{{loading}}"> <i-load-more tip="{{tip}}" loading="{{loading}}" /></view><i-toast id="toast" />
<form bindreset="cleanInput" class="sendMessage"> <input type="text" placeholder="請輸入聊天內容.." value="{{massage}}" bindinput='bindChange' disabled="{{loading}}"></input> <view class="add" bindtap='increase'>+</view> <button type="primary" bindtap='send' form-type="reset" size="small" button-hover="blue" disabled="{{loading}}">發送</button> </form>
<view class='increased {{aniStyle?"slideup":"slidedown"}}' wx:if="{{increase}}"> <view class="image" bindtap='chooseImage'>相冊 </view> </view></view>


我們可以看看界面的一般效果:


剛纔已經說過了這個界面同時有一個我輪詢事件,我這裏5秒進行一次輪詢,獲取用戶最新聊天記錄然後重新渲染頁面,我們看下代碼:

getIntervalChat: function (that) {    interval = setInterval(function () {      wx.request({        url: utils.basePath + '/article/v1/getOnlineInfo',        method: "post",        data: {          chatInfo: that.data.chatInfo        },        header: {          'content-type': 'application/json'        },        success(res) {          if (res.data.status == 200) {            var data = res.data.payload.data;            data[0].flagtime = true;
for (var i = 1; i < data.length; i++) { var currenttime = new Date(data[i].created_date).getTime(); var begintime = new Date(data[i - 1].created_date).getTime();              currenttime - begintime > 1000 * 60 ? data[i].flagtime = true : data[i].flagtime = false } that.setData({              onlineInfo: data }); } } }); }, 5000); },


實際上邏輯很簡單,就是調取API獲取聊天數據,然後渲染界面。爲了佈局的美觀性,我加了個判斷,判斷本條聊天記錄與上一條信息是否發送時間超過1分鐘,如果在1分鐘以內,則時間不會重複渲染。類似QQ和微信那樣的聊天方式。那接下來我們需要實現三個API:用戶聊天數據保存API、用戶聊天數據獲取API、圖片上傳API。圖片上傳其實之前已經專門寫過一篇文章介紹過了,可以自行去查看:Node上傳文件(1)。接下來我們先看下聊天數據保存API,其實就是將發送信息的用戶與接受信息的用戶以及聊天信息進行存取,然後查詢新的聊天數據返回,這裏貼下關鍵代碼:
async.waterfall([            function (callback) {                connection.beginTransaction(function (err) {                    return callback(err);                });            },            function (callback) {                var sql = 'insert into online_chat set ?';                var value = {                    friendphone: data.friendInfo.account,                    friendname: data.friendInfo.username,                    friendavatar: data.friendInfo.avatar,                    app_sid: data.friendInfo.app_sid,                    userphone: data.userInfo.account,                    username: data.userInfo.username,                    useravatar: data.userInfo.avatar,                    created_date: new Date(),                    status: 1,                    content: data.chat_content,                    chat_type: data.chat_type                };
connection.query(sql, value, function (err, result) { if (err) { return callback(err); }
if (result.affectedRows == 0) { return callback('聊天出現故障!'); }
return callback(null, '保存聊天記錄成功!'); }); }, function (release_info, callback) { var sql = 'select id, friendphone, friendname, friendavatar, app_sid, DATE_FORMAT(created_date, "%Y-%m-%d %H:%i:%s") as created_date, userphone, username, useravatar, content, chat_type from online_chat ' + 'where (friendphone = ? and userphone = ?) or (friendphone = ? and userphone = ?)'; var value = [data.friendInfo.account, data.userInfo.account, data.userInfo.account, data.friendInfo.account];
connection.query(sql, value, function (err, result) { if (err) { return callback(err); }
var del_info = result && result.length > 0 ? result : null;
if (!del_info) { return callback(null, true, []); }
return callback(null, true, del_info); }); } ], function (DbErr, isSuccess, uidOrInfo) { if (DbErr || !isSuccess) { connection.rollback(function () { connection.release(); });
return cb(DbErr); }
connection.commit(function (e) { if (e) { connection.rollback(function () { connection.release(); });
return cb(e); }
connection.release(); cb(null, uidOrInfo); }); });

接下來看下聊天數據獲取API,這個API實際上就是查詢兩個好友間的聊天記錄,然後通過兩個賬號分別查詢用戶的基本信息如頭像暱稱等,一樣貼下關鍵代碼:

async.waterfall([            function (callback) {                connection.beginTransaction(function (err) {                    return callback(err);                });            },            //通過friendphone查詢好友信息            function (callback) {                var sql = 'select username, avatar from users where account = ? and app_sid = ?';                var value = [data.friendphone, data.app_sid];
connection.query(sql, value, function (err, result) { if (err) { return callback(err); }
if (!result[0]) { return callback('用戶不存在!'); } data.friendname = result[0].username; data.friendavatar = result[0].avatar;
return callback(null, 200); }); }, //通過userphone查詢好友信息 function (info, callback) { var sql = 'select username, avatar from users where account = ? and app_sid = ?'; var value = [data.userphone, data.app_sid];
connection.query(sql, value, function (err, result) { if (err) { return callback(err); }
if (!result[0]) { return callback('用戶不存在!'); } data.username = result[0].username; data.useravatar = result[0].avatar;
return callback(null, 200); }); }, function (release_info, callback) { var sql = 'select id, friendphone, friendname, friendavatar, app_sid, DATE_FORMAT(created_date, "%Y-%m-%d %H:%i:%s") as created_date, userphone, username, useravatar, content, chat_type from online_chat ' + 'where (friendphone = ? and userphone = ?) or (friendphone = ? and userphone = ?)'; var value = [data.friendphone, data.userphone, data.userphone, data.friendphone];
connection.query(sql, value, function (err, result) { if (err) { return callback(err); }
var del_info = result && result.length > 0 ? result : null;
if (!del_info) { return callback(null, true, []); }
return callback(null, true, del_info); }); } ], function (DbErr, isSuccess, uidOrInfo) { if (DbErr || !isSuccess) { connection.rollback(function () { connection.release(); });
return cb(DbErr); }
connection.commit(function (e) { if (e) { connection.rollback(function () { connection.release(); });
return cb(e); }
connection.release(); cb(null, uidOrInfo); }); });


圖片上傳我這裏在上傳圖片的同時加了一個鑑黃操作,判斷圖片是否合規。這裏直接接入百度API,但是百度接口響應速度有點慢,導致整個圖片上傳時間得4秒鐘左右。接下來我們看下關鍵代碼:
async.waterfall([            function (callback) {                connection.beginTransaction(function (err) {                    return callback(err);                });            },            //獲取access_token            function (callback) {                postHelper.baseRequest(CONFIG.USERDEFINEPATH, {                    grant_type: CONFIG.USERDEFINEGRANTTYPE,                    client_id: CONFIG.USERDEFINEKEY,                    client_secret: CONFIG.USERDEFINESECRET                }, function(err, result) {                    if(err) {                        return callback(err);                    }
return callback(null, JSON.parse(result).access_token); }); },
//圖像審覈 function (access_token, callback) { postHelper.baseRequestbdai(CONFIG.CENSORINGPATH + access_token, { imgUrl: data, imgType: CONFIG.CENSORINGIMGTYPE }, function(err, result) { if(err) { return callback(err); }
if(result.error_code) { return callback(result.error_msg); }
if(result.conclusionType != 1) { return callback('圖片內容不合法,請重新上傳吧!'); }
return callback(null, true, result); }); } ], function (DbErr, isSuccess, uidOrInfo) { if (DbErr || !isSuccess) { connection.rollback(function () { connection.release(); });
return cb(DbErr); }
connection.commit(function (e) { if (e) { connection.rollback(function () { connection.release(); });
return cb(e); }
connection.release(); return cb(null, uidOrInfo); }); });

到這裏我們前後端都成功實現,我們就可以來測試下會話系統。我在模擬器發送測試實時聊天系統,然後在手機真機測試看看能不能通過輪詢獲取:


然後測試發送暴恐圖片看看會不會檢測出圖片不合法:


到這裏通過http輪詢的方式我們就已經成功實現實時會話系統,但是也正如我們剛纔所說的http輪詢的缺點,我們一直停留在聊天界面,但是並沒有一直處於聊天界面,這樣實際上每一次輪詢的數據都是舊數據,但是輪詢不會停止所以會消耗帶寬和服務器資源。所以很明顯使用http輪詢實現實時會話系統不是不行,但是肯定不是合理的方案,只適用於業務場景較小的應用。下一篇我們開始入門websocket,使用express-ws庫改寫http輪詢實現實時會話系統。
目前整個項目前後端已開源於碼雲,歡迎來一個star。 源碼地址:
   
     
   
   
   
https://gitee.com/mqzuimeng_admin/wx_blog.git


歡迎體驗小程序,如果有修改建議歡迎加我微信聊聊。

歡迎關注公衆號:程序猿周先森。查看更多精彩文章。


本文分享自微信公衆號 - 程序猿周先森(zhanyue_org)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章