## **websocket**
> WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。
現在,很多網站為了實現推送技術,所用的技術都是 Ajax 輪詢。輪詢是在特定的的時間間隔(如每1秒),由瀏覽器對服務器發出HTTP請求,然后由服務器返回最新的數據給客戶端的瀏覽器。這種方式是讓瀏覽器不斷的向服務器發送請求,因為HTTP請求可能包含較長的頭部,其中真正有效的數據可能只是很小的一部分,顯然這樣會浪費很多的帶寬等資源。

Websocket的處理方式是讓瀏覽器通過 JavaScript 向服務器發出建立 WebSocket 連接的請求,連接建立以后,客戶端和服務器端就可以通過 TCP 連接直接交換數據。
當你獲取 Web Socket 連接后,你可以通過`send()`方法來向服務器發送數據,并通過`onmessage`事件來接收服務器返回的數據。
## **聊天功能原理**
由客戶端發送Http(`Ajax`)請求,請求后端TP框架。通過TP框架來鑒定客戶端的身份(驗證Token或數據合法性),GatewayWorker只是實現了一個單向推送功能,不會去接收用戶的任何信息(除了綁定之外),復雜的邏輯使用TP框架處理的。

> GatewayWorker是通過`sendToClient`接口發送某一條信息到客戶端
```
void Gateway::sendToClient(string $client_id, string $send_data);
```
## **前端聊天原理流程**

> 連接websocket需要socket地址,因為在服務器上配置了wss,所以此處域名后面不再接入端口號,而是已經配置好的wss
```
// websocket地址
websocketUrl:"wss://ltkf.gxyuchi.com/wss",
```
## 配置websocket的信息
```
// socket地址
url:Config.websocketUrl,
// 連接狀態
IsOpen:false,
// SocketTask
SocketTask:false,
// 是否上線(會員id綁定客戶端id,驗證用戶身份,通過則綁定)
IsOnline:false,
// 當前聊天對象(進入聊天頁面獲取)
CurrentToUser:{
userid:0, // 通過判斷userid是否為0,當前用戶處在什么場景下
username:"",
userpic:""
```
## 連接和關閉websocket
連接websocket,連接時使用`connectSocket`方法需要傳入`url`路徑和`complete`。
傳入`complete`的目的是為了返回一個[socketTask](https://uniapp.dcloud.io/api/request/socket-task)對象
```
this.SocketTask = uni.connectSocket({
url:this.url,
complete: (e)=> { },
});
```
## 監聽關閉websocket
關閉websocker連接
```
// 關閉連接
Close(){
if (this.IsOpen){
this.SocketTask.close();
return uni.removeTabBarBadge({
index:Config.TabbarIndex
});
}
},
```
設置連接狀態為false,并清空`socketTask`
```
this.IsOpen = false;
this.SocketTask = false;
```
當連接成功時,進行用戶綁定,并判斷用戶綁定結果
```
$http.post('/chat/bind',{
type:'bind',
client_id:client_id
},{
token:true
}).then(data=>{
let [err,res] = data;
console.log(res)
// 錯誤處理
if (!$http.errorCheck(err,res)) {
// 退出登錄,重新鏈接等處理
return;
}
// 成功
return this.resultUserBind(res.data.data);
});
```
當通過判斷`bind`來確認用戶綁定成功時,將狀態改為上線并初始化讀取未讀信息方法
判斷條件
```
res.status && res.type == 'bind'
```
調用方法
```
// 獲取總未讀數,并且渲染到tabbar的badge
this.initTabbarBadge();
// 獲取未讀信息
this.getChatMessages();
```
綁定失敗則斷開連接
```
uni.showToast({ title: res.msg, icon:"none" });
this.SocketTask.close();
```
## **處理未讀信息**
```
// 初始化tabbarBadge
initTabbarBadge(){
// 獲取總未讀數
let noreadnum = uni.getStorageSync('noreadnum'+User.userinfo.id);
this.__UpdateTabbarBadge(noreadnum);
},
```
## 存儲到chatdetail
存儲到chatdetail的目的是保存用戶與用戶之間的歷史聊天記錄,通過`issend `來判斷場景是否是發送信息場景,當用戶發送信息成功后,開始保存歷史記錄
```
res,issend = false
```
保存歷史記錄時,要考慮舊數據和新數據的同時保存
```
let userid = issend ? this.CurrentToUser.userid : res.from_id;
// 獲取舊數據( chatdetail_[當前用戶id]_[聊天對象id] )
let list = uni.getStorageSync('chatdetail_'+User.userinfo.id+'_'+userid);
list = list ? JSON.parse(list) : [];
// 追加
list.push(this.__format(res,{
type:"chatdetail",
isme:issend,
olddata:list,
}));
```
## **更新chatlist**
更新chatlist的目的是將當前會話置頂,修改chatlist中當前會話的data和time顯示。值得注意的是要進行一個判斷,判斷當前會話是否存在,存在:將當前會話置頂;不存在:追加至頭部
```
// 判斷是否已存在該會話,存在:將當前會話置頂;不存在:追加至頭部
let index = chatlist.findIndex((item)=>{
return item.userid == res.to_id || item.userid == res.from_id;
})
// 不存在
if (index == -1) {
let obj = this.__format(res,{ type:"chatlist" });
// 忽略本人發送
if (res.from_id !== User.userinfo.id) {
obj.noreadnum = 1;
}
chatlist.unshift(obj);
}else{
// 存在:將當前會話置頂,修改chatlist中當前會話的data和time顯示
chatlist[index].data = res.data;
chatlist[index].type = res.type;
chatlist[index].time = res.time;
// 當前聊天對象不是該id,未讀數+1(排除本人發送消息)
if (res.from_id !== User.userinfo.id && this.CurrentToUser.userid !== chatlist[index].userid) {
chatlist[index].noreadnum++;
}
// 置頂當前會話
chatlist = this.__toFirst(chatlist,index);
}
```
```
// 數組置頂
__toFirst(arr,index){
if (index != 0) {
arr.unshift(arr.splice(index,1)[0]);
}
return arr;
}
```
## **發送消息**
發送消息的格式分為不同種情況
```
// 發送的格式
let senddata = this.__format(data,{type:"send"});
```
發送時,要對消息的數據字段進行一個轉換
```
return {
to_id:this.CurrentToUser.userid,
from_id:User.userinfo.id,
from_username:User.userinfo.username,
from_userpic:User.userinfo.userpic,
type:data.type,
data:data.data,
time:new Date().getTime()
}
```
之后將已發消息進行歷史記錄保存和置頂操作
```
// 存儲到chatdetail
this.__UpdateChatdetail(senddata,true);
// 存儲到chatlist(將當前會話置頂,修改chatlist中當前會話的data和time顯示)
this.__UpdateChatlist(senddata);
// 發送到服務器(交由頁面去做)
return senddata;
```
## 讀取當前會話
讀取當前會話時,通過用戶發送消息的最后時間來拿到最新的消息
```
// 拿到當前會話的索引
let index = chatlist.findIndex((value)=>{
return value.userid == item.userid;
});
let oldnoreadnum = chatlist[index].noreadnum;
// 如果會話存在
if (index !== -1) {
chatlist[index].noreadnum = 0;
// 存儲
uni.setStorage({
key:"chatlist"+User.userinfo.id,data:JSON.stringify(chatlist)
})
// 更新tabbar的badge
this.__UpdateNoReadNum({ type:"read",num:oldnoreadnum });
}
```