uniapp websocket 雛形

php_yt發表於2020-05-23

此版本不是最終版本,剛剛開始,只分享一些思路

場景

uniapp h5 app 小程式

需求

列表區自動推送更新,系統通知,動態通知,聊天室等..

描述

  1. 系統級的訊息推送,比如某個頁面有了新的訊息,在頁尾顯示小紅點;或者製造一些彈窗通知;
  2. 頁面級的推送,列表的自動重新整理;聊天頁面對方傳送訊息時自動更新聊天內容等。
  3. 斷線重連
  4. 網路監聽
  5. 資源符與使用者ID繫結

簡單的 socket.js Demo

考慮過socket.io,是否能用在小程式沒做測試,估計是不可行。用小程式提供的webSocket Api 寫一個 socket.js,比較簡單,大小也只有幾 kb,畢竟小程式檔案不能太大。

var Pool = {};   // Promise池
var IsOpen = !1;     // 是否開啟;socket只會open一次
var IsClose = !1;    // 是否是主動關閉
var heartTimer = null;      // 心跳控制程式碼
var reConnectTimer = null;   // 重連控制程式碼
var IsConnected = !1;    // 是否連結成功;
var IsNetWork = !0;   // 是否有網路
var isLogin = !1; // 是否登陸到socket,即繫結了 資源fd 和使用者id
export default class PromiseWebSocket
{
    config = {
        url: '',
        debug: false,
        header: {'content-type': 'application/json'},
        reConnectTime: 5000, // 斷線重連檢測時間間隔
        isHeartData: true, // 是否保持心跳
        heartTime: 1 * 60 * 1000 //心跳間隔
    };
    constructor(config){
        this.config = {
            url: config.url,
            debug: config.debug || this.config.debug,
            header: {
              ...this.config.header,
              ...config.header
            },
            method: config.method || 'GET',
            protocols : config.protocols || null,
            reConnectTime: config.reConnectTime || this.config.reConnectTime,
            isHeartData: true,
            heartTime: config.heartTime || this.config.heartTime
        };
        // 監聽網路狀態
        uni.onNetworkStatusChange((res) => {
            if (res.isConnected) {
                IsNetWork = true;
            } else {
                IsConnected = false;
                IsNetWork = false;
                IsLogin = false;
                this.config.isHeartData && this._clearHeart();
                this.config.debug && console.error('監聽到網路錯誤');
                uni.showToast({icon:'none',title: '網路錯誤,請檢查網路連結',duration:2000});
            }
        })

        // 監聽收到資訊
        uni.onSocketMessage((e) => {
            var msg = isJson(JSON.parse(e.data));
            if(!msg){
                this.config.debug && console.log('不是json物件'); return;
            }else{
                this.config.debug && console.log('收到訊息:', msg)
            }

            var type = msg['type'];
            this.config.debug && console.log(type,'訊息型別');
            // 自定義事件
            if( callback.hasOwnProperty(type) ){
                this.config.debug && console.log( callback[type],'具體型別的自定義callback' )
                callback[type](msg);
            }else if( type == 'respon' ){
                // 預設應答式
                var uuid = msg['event'];
                if(!uuid || !Pool[uuid]){ console.log('響應缺少event引數,或者非應答型別'); return;}
                let data = msg['data'] || null
                if (data.error === 0) {
                    Pool[uuid].resolve(data);
                } else {
                    Pool[uuid].reject(data);
                }
                delete Pool[uuid]
            }else{
                this.config.debug && console.log('缺少type引數');
            }
        })

        // 監聽socket是否開啟成功
        uni.onSocketOpen((header) => {
            IsOpen = true;
            IsConnected = true;
            this.config.debug && console.log('socket開啟成功');
            // 判斷是否需要傳送心跳包
            if (this.config.isHeartData) {
                this.config.debug && console.log('開始心跳')
                this._clearHeart()
                this._startHeart()
            }
        })

        // 監聽socket被關閉
        uni.onSocketClose((res) => {
            IsConnected = false;
            isLogin = false;

            if (this.config.isHeartData) {
                this.config.debug && console.log('關閉心跳')
                this._clearHeart()
            }
            this.config.debug && console.error('連線被關閉', res)
        })

        // 監聽socket錯誤
        uni.onSocketError((res) => {
            IsConnected = false
            isLogin = false
            if (this.config.isHeartData) {
                this.config.debug && console.log('關閉心跳')
                this._clearHeart()
            }
            // IsOpen = false;
            this.config.debug && console.error('socket出錯', res)
        })
    }

    // 啟動 socket 每隔N秒檢查狀態
    _connectionLoop () {
        var that = this;
        reConnectTimer = setInterval(
            () => {
                if (!IsConnected || !isLogin) {
                    if( getApp().globalData.isLogin !== true ){
                        this.config.debug && console.warn('未登入!');
                        return;
                    }
                    this.config.debug && console.log('連線中..')
                    this.initSocket(
                        function (e) {
                            // 登入
                            let u_id = jwt.getUser().u_id;
                            that._send('login','user:'+uid).then(res=>{
                                console.log('登入socket成功');
                                isLogin = true;
                            }).catch(err=>{
                                console.log('登入socket失敗');
                                isLogin = false;
                            });
                            //console.log(isLogin,'isLogin')
                            if (e.config.isSendHeart) {
                                e.config.debug && console.log('開始心跳...')
                                e._clearHeart()
                                e._startHeart()
                            }
                        },
                        function (err, e) {
                            e.config.debug && console.warn('連線失敗!',err)
                        }
                    )
                }else{
                    this.config.debug && console.log('連線狀態正常')
                }
            },
            this.config.reConnectTime // 定時檢測頻率
        )
    }

    // ----------初始化websocket連線----------
    initSocket (success, fail) {
        //-----判斷websocket是否連線-----
        if (IsConnected) {
            this.config.debug && console.log('已經連線了')
            typeof success === 'function' && success(this)
            return
        }
        uni.getNetworkType({
            fail: (err) => {
                this.config.debug && console.warn('檢查網路狀態失敗..', err);
                typeof fail === 'function' && fail(err, this);
            },
            success: (res) => {
                if (res.networkType === 'none') {
                    IsNetWork = false;// 無網路
                    IsConnected = false;
                    uni.showToast({icon:'none',title: '網路錯誤,請開啟網路服務',duration:1000});
                    typeof fail === 'function' && fail(res, this)
                } else {
                    IsNetWork = true;//有網路
                    this.config.debug && console.log('網路正常,開始建立連線...');
                    //-----建立連線-----
                    uni.connectSocket({
                        url: this.config.url,
                        method: this.config.method,
                        header: this.config.header,
                        protocols: this.config.protocols,
                        success: () => {
                            IsConnected = true;
                            this.config.debug && console.log('連線成功')
                            typeof success === 'function' && success(this)
                        },
                        fail: (err) => {
                            this.config.debug && console.log('連線失敗');
                            typeof fail === 'function' && fail(err, this)
                        }
                    })
                }
            }
        })
    }

    // 開始心跳
    _startHeart () {
      heartTimer = setInterval(() => {
        uni.sendSocketMessage({
          data: 'ping'
        })
      }, this.config.heartTime)
    }

    // 清理心跳
    _clearHeart () {
      clearInterval(heartTimer)
      heartTimer = null
    }

    /**
     * 傳送socket訊息
     * @param string event 事件名稱 ask 響應式問答 | ping
     * @param object data  請求資料 必須json物件或者空物件{}或者不傳值
     */
    _send (event, data) {
        let message = { event, data };
        const uuid = (new Date()).getTime();
        return new Promise((resolve, reject) => {
            if (IsOpen && IsConnected && IsNetWork) {
                if (!Pool[uuid]) {
                    Pool[uuid] = { message, resolve, reject }
                }
                this.config.debug && console.log('傳送訊息:',  message);
                message.uuid = uuid;
                this._sendSocketMessage(message)
            } else {
                uni.showToast({icon:'none',title: '傳送失敗,請檢查網路連結',duration:2000});
            }
      })
    }
    // 傳送socket訊息
    _sendSocketMessage (data) {
        return new Promise((resolve, reject) => {
            uni.sendSocketMessage({
                data: JSON.stringify(data),
                success: (res) => {
                    console.log(res,'傳送請求成功..')
                    resolve(res)
                },
                fail: (fail) => {
                    console.log(fail,'傳送請求失敗..')
                    reject(fail)
                }
            })
        })
    }

    // 主動關閉
    _close (option) {
        // IsOpen = false;
        IsConnected = false; // 未連結狀態
        IsClose = true; // 已關閉
        this._clearHeart(); // 關閉心跳
        uni.closeSocket(option);// 關閉socket

        this.config.debug && console.log('關閉心跳');
        this.config.debug && console.log('主動退出');
    }
    // 註冊一些自定義的事件
    on (event,func){
        if(typeof func === 'function'){
            callback[event] = func;
        }
        console.log(callback,'自定義callback列表')
    }
}

app.vue 中在應用啟動時建立 socket 連線

<script>
    import UniSocketPromise from "@/utils/socket/socket.js"
    export default {
        onLaunch: function() {
            console.log('App Launch');
            this.globalData.socket = new UniSocketPromise({
                url: "ws://xxxxxxxx:9502",
                debug: true,
                heartTime: 1 * 60 * 1000
            });
            // 連線
            this.globalData.socket._connectionLoop();
            // 註冊應用級的訊息事件
            this.globalData.socket.on('notice',function(msg){
                switch (msg.event){
                    case 'my_new':
                        // 比如給某個tobar加個小紅點
                        uni.showTabBarRedDot({
                            index: 3
                        })
                    break;
                    default:
                    break;
                }
            })
        },
        onShow: function() {
            console.log('App Show');
        },
        onHide: function() {
            console.log('App Hide')
        },
        globalData:{
            isLogin : false
        }
    }
</script>

<style>
    /*每個頁面公共css */
</style>

太晚了,有空再補充頁面的事件是如何處理的,和laravel + swoole + redis 後端..

本作品採用《CC 協議》,轉載必須註明作者和本文連結

簡潔略帶風騷

相關文章