此版本不是最終版本,剛剛開始,只分享一些思路
場景
uniapp
h5
app
小程式
需求
列表區自動推送更新,系統通知,動態通知,聊天室等..
描述
- 系統級的訊息推送,比如某個頁面有了新的訊息,在頁尾顯示小紅點;或者製造一些彈窗通知;
- 頁面級的推送,列表的自動重新整理;聊天頁面對方傳送訊息時自動更新聊天內容等。
- 斷線重連
- 網路監聽
- 資源符與使用者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 協議》,轉載必須註明作者和本文連結