uniapp websocket 雏形

此版本不是最终版本,刚刚开始,只分享一些思路

场景

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 协议》,转载必须注明作者和本文链接

简洁略带风骚

《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 2

可以,看到论坛里偶尔有人讨论uni-app。 去年刚入手的uni-app,解决了小程序 APP两头开发的痛点……真是省事多了。

5天前 评论
lianglunzhong 3天前

uni-app是真的很好用~!h5 + 微信小程序妥妥的!别的没有尝试发布

3天前 评论

请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!