
前言
在上個月初,接到一個需求,要開發一個 聊天通訊 模塊 并且 集成到 專案中的多個 入口,實作業務資料的記錄追蹤.
接到需求后,還挺開心,這是我第一次 搞 通訊 類的需求,之前一直是 B 端 的業務需求,不過現在也是在做這個方向,感覺 B 端 方向 挺有意思,管理著專案的整個專案上游和下游,然后服務于 內部人員 和 外部人員 使用,感覺挺自豪的,
下面就就跟著我來看看 如何 開發一個 聊天通訊 服務吧 ! (主要站在前端的角度來講如何開發設計 )
技術堆疊
?

?
Vue 2.xWebsoketVuexElementvue-at
本專案是 以 Vue 技術堆疊生態開發的,其實不管用什么語言 , 思路是關鍵 ! 知道每一步需要干什么, 然后將每一步操作 整合起來 , 最終服務就跑起來了.
當中的每一步需要干什么 就是 編程 中的 function 功能,根據這個功能然后在細化分析需要有到哪些技術點 ,在開發的程序中,你不可能對整個鏈路的所有技術點 熟悉,這就需要遇到啥困難,臨時學習就可以了,
開始分析需求
首先,我們要等待 UI 設計師 的設計稿 畫出來, 然后根據 UI 設計師的 設計稿分析整體 聊天通訊 的結構,從view 結構 來 劃分 應該 大體 包括哪些 component , 每個component 中 又包括哪些小的 component , 這樣從 大 到 小 的方向將 設計稿 轉化為 程式員視角的 component .
確立了有哪些component , 接下來 就是 確定 每個 小的 component 又有哪些 功能了, 現在 UI 設計師們,一般畫完界面后,會通過第三方軟體 / 平臺 來將效果圖 轉化成網頁,并且可以通過 URL 可以直接訪問,當游標放到頁面中的某個元素時,可以獲取到當前元素的 css style , 不過,我建議不之 copy ,有時和自己寫的布局代碼會沖突,按需copy .
效果圖
真實效果圖,我就在這里不放出來了,為了保密性,只把整體結構,列出來,然后帶著大家分析結構和功能,如何進行編碼設計和組件設計,

功能分析圖

根據效果圖,在進行組件劃分時,我要記住這個原則:高內聚,低耦合 , 組件職責單一性
我們將組件劃分為:
聯系人組件聊天組件---- 包括了歷史記錄組件
功能根據 UI 設計師 提供的 URL 網頁來看互動效果來定,并和組長 / 產品經理 交流需求,確定需求,以及砍掉不合理需求,
需求確定后,就是梳理組件部分的功能了,
組件構成
在分析組件之前,我們需要先了解一下Vue Component ,使用Vue 的 朋友應該很熟悉了,一個組件的構成由以下組成:
-
data組件內部狀態 -
computed計算屬性,監聽data變化來實作對應的業務邏輯需求 -
watch監聽state變化 -
method組將的功能撰寫區 -
props組件接受父組件傳遞來的值,進行約束型別等 -
lifecycle組件的生命周期, 可以在組件創建到銷毀的程序中執行對應的業務邏輯
聯系人組件
這個組件主要是用來在聊天的時候,可以通過分組快速的找到某個人聯系它,功能相對簡單,
功能:
- 查找聯系人
- 有通知某人操作
功能分析
功能1: 查找聯系人
通過現有聯系人json 資料來 查找輸入的聯系人進行匹配, (簡單)
功能2: 通知某人
當用戶點擊到某個聯系人時,將點擊的人 放到輸入框里 顯示 @xxx [ 經過格式化處理 ] , 并將選中的聯系人資訊加入到發送訊息的 json 物件中,
有多種實作方案,當用戶點擊了某聯系人時,將觸發事件,攜帶值傳遞給父組件[聊天組件的入口 index.vue ] 接收,然后將值傳遞給 聊天主體組件 ,通過 在 聊天主體組件 中 通過 $refs 進行傳遞值,
下面只提供示例代碼
從聯系人串列獲取選中聯系人
//聯系人組件 concat.vue
?
?
getLogname(val){
this.$emit('toParent',{tag:'add',logname:val})
},
聊天框顯示選中的聯系人
在聊天入口組件 接收 子向父 組件傳遞 選中聯系人資料,然后給 聊天主體 組件系結 ref , 通過refs 來將聯系人資料傳遞到 聊天主體 組件顯示, [這塊 資料傳遞有多種方法,例如 Vuex]
//聊天組件入口 index.vue 它包括 聯系人組件 聊天主體組件 歷史記錄組件
?
//聯系人組件
<Concat @toParent='innerHtmlToChat'/>
?
//聊天主體組件
<ChatRoom @fullScreen="getFullStatus" @closeWindow="close" ref="chatRoom"/>
?
?
// 接受
innerHtmlToChat(data){
this.$refs.chatRoom.$refs.inputConents.innerHTML+=` @ ${data.logname}` //拼接到聊天輸入框里
},
?
效果展示

?
從聯系人串列選中人員,發送訊息
?

?
@人 接收到推送訊息
?
聊天主體組件
這個組件就負責的功能就多了,這塊我主要把關鍵的功能帶大家來分析過一遍
關鍵功能;
@好友功能,實作推送通知(在線通知 / 離線-上線通知)- 聊天工具 [
支持表情支持大檔案上傳] - 發送訊息 [
這塊就可以跟業務掛鉤了,發送資訊時,并攜帶一些符合你專案需求的資料]
功能分析
功能1 : @ 實作
vue-at檔案 : https://github.com/von7750/vue-at
它的功能和 微信 和 QQ @ 功能一樣,在聊天輸入框里,當你 輸入 @ 鍵時, 彈出好友串列,然后從中選擇聯系人進行聊天,
@ 功能必須包括以下3個關鍵功能;
- 可以彈出聯系人串列
- 可以監聽輸入字符內容進行過濾顯示對應資料
- 洗掉 @ 聯系人
- …
一開始, 我是 自己造了個 @ 功能 輪子 搞了搞,后來才發現市場上有相應的輪子,直接用第三方了,挺不錯的 vue-at,
下面來跟著我,來捋一下思路如何實作這個輪子,此處就不放實作代碼了,
先來分析一波:
當在編輯區,輸入 @ 時, 彈出框
- 我們可以在
mounted生命周期中監聽 按鍵code= 50 / 229 (中文/英文) 時,做出處理- 由于我們這塊采用的
div 可編輯屬性,那么就獲取到 可編輯屬性的游標位置- 然后通過游標位置 動態來改變 彈出框聯系人串列的樣式
topleft, 實作跟著游標的 位置顯示聯系人串列,- 然后 從串列中選擇 聯系人進行聊天,并將
聯系人串列彈框隱藏掉,上面就實作了基本的
選中聯系人功能,洗掉選中的聯系人
由于這塊是采用的可編輯屬性, 我們可以獲取選中的人,但無法直接判斷是洗掉的哪個人,這時,只能通過判斷
innerHTML中是否包含某聯系人,來進行洗掉已保存的聯系人,這時,已經基本滿足了業務需求實作了,
第三方插件已經的夠好了,我們就沒必要再造輪子,浪費時間了, 但 實作思路 必須的懂, 下面,我就來演示如何使用 第三方插件vue-at 實作 @ 功能
1. 安裝插件
npm i vue-at@2.x
2.組件 內部匯入插件組件
import At from "vue-at";
3.注冊插件組件
components: {
At
},
4. 頁面中使用
At 組件 必須包括 可編輯 輸入內容區域, 這樣,當輸入 @ 時,會彈出聯系人串列框,
members: 資料源filter-match: 過濾資料deleteMatch:洗掉的聯系人insert: 獲取聯系人
<At
:members="filtercontactListContainer"
:filter-match="filterMatch"
:deleteMatch="deleteMatch"
@insert="getValue"
>
<template slot="item" slot-scope="s">
<div v-text="s.item" style="width:100%"></div>
</template>
<div
class="inputContent"
contenteditable="true"
ref="inputConents"
></div>
</At>
// 過濾聯系人
filterMatch(name, chunk) {
return name.toLowerCase().indexOf(chunk.toLowerCase()) === 0;
},
// 洗掉聯系人
deleteMatch(name, chunk, suffix) {
this.contactList = this.contactList.filter(
item => item.logname != chunk.trim()
);
return chunk === name + suffix;
},
// 獲取聯系人
getValue(val) {
this.contactList.push({ logname: val });
},

功能2:聊天工具箱
聊天軟體除了普通文字聊天,還有一些輔助服務來增加聊天的豐富性,例如: 表情 , 檔案上傳, 截圖上傳 … 功能
我們先來看看 市場 熱門聊天軟體它們有哪些 聊天工具,
微信聊天工具箱
表情檔案上傳截屏聊天記錄視頻聊天 / 語音聊天

?
?
QQ 聊天工具箱
表情GIF 動圖截屏檔案上傳騰訊檔案圖片發送..... 騰訊業務相關功能

?
?
介紹了市場上熱門聊天的工具箱有哪些工具,回歸正題: 我們的聊天工具箱 有哪些功能呢, 其實有哪些功能根據 業務來定,后期工具箱可以不斷擴充, 我們的工具箱基本上滿足日常聊天需求
表情檔案上傳支持大檔案 ( 幾個G 都可以)截屏Ctrl + Alt + A歷史記錄
下面我就來將比較幾個重要的功能: 檔案上傳 和 截屏 , 其它功能都很簡單,
檔案上傳
上傳組件我采用的是 Element el-upload 組件,由于我業務 要求上傳檔案支持大檔案, 采用的 分片續傳 方式來實作,
分片續傳思路
-
我們上傳也是采用的
websoket上傳,首次發送時,必須發送一些必要的檔案基本資訊- 檔案名
- 檔案大小
- 發送者
- 一些跟業務相關的欄位資料
- 時間
- 檔案分片大小
- 檔案分片片數
- 上傳進度標識
-
首次發送完檔案的基本資訊后,開始發送分片檔案資訊,首先將檔案分片后,然后依次讀取片檔案流,發送時攜帶檔案流,等檔案分片回圈結束后,發送一個結束標識告訴后臺發送完畢了 [
這塊你可以和后端商量設計資料格式]
示例代碼演示
<el-upload
ref="upload"
class="upload-demo"
drag
:auto-upload="false"
:file-list="fileList"
:http-request="httpRequest"
style="width:200px"
>
<i class="el-icon-upload"></i>
<div class="el-upload__text" trigger>
<em> 將檔案拖到此處然后點擊上傳檔案</em>
</div>
</el-upload>
覆寫掉 Element 默認上傳方式,改用自定義上傳方式,
開始分片上傳
// 上傳檔案
httpRequest(options) {
let that = this;
?
//每個檔案切片大小
const bytesPerPiece = 1024 * 2048;
// 檔案必要的資訊
const { name, size } = options.file;
// 檔案分割片數
const chunkCount = Math.ceil(size / bytesPerPiece);
// 獲取到檔案后,發送檔案的基本資訊
const fileBaseInfo = {
fileName: name,
fileSize: size,
segments: "historymessage",
loginName: localStorage.getItem("usrname"),
time: new Date().toLocaleString(),
chunkSize: bytesPerPiece,
chunkCount: chunkCount,
messagetype: "bufferfile",
process: "begin",
... 一些跟業務掛鉤的 欄位
?
};
?
?
that.$websoketGlobal.ws.send(JSON.stringify(fileBaseInfo));
let start = 0;
?
// 進行分片
var blob = options.file.slice(start, start + bytesPerPiece);
//創建`FileReader`
var reader = new FileReader();
//開始讀取指定的 Blob中的內容, 一旦完成, result 屬性中保存的將是被讀取檔案的 ArrayBuffer 資料物件.
reader.readAsArrayBuffer(blob);
//讀取操作完成時自動觸發,
reader.onload = function(e) {
// 發送檔案流
that.$websoketGlobal.ws.send(reader.result);
start += bytesPerPiece;
if (start < size) {
var blob = options.file.slice(start, start + bytesPerPiece);
reader.readAsArrayBuffer(blob);
} else {
fileBaseInfo.process = "end";
// 發送上傳檔案結束 標識
that.$websoketGlobal.ws.send(JSON.stringify(fileBaseInfo));
}
that.uploadStatus = false;
that.fileList = [];
};
},
效果演示


功能3: 截屏功能
在 PC 中,這是一個很重要的業務,通過這種技術可以從網上截取下自己感興趣的文章圖片供自己使用觀看,可以幫助人們更好的去理解使用知識,
由于我們的輸入內容區域采用的 可編輯 區域,此處可以插入任意內容,也可以使用外部 的截圖功能,粘貼到輸入框區域,這塊就沒必要的造輪子了,
1. 可編輯區域
我們給 div 加上 該屬性 contenteditable 就可以控制 div 中可輸入哪些內容,外部復制過來內容也可以直接顯示,還可以顯示其帶的css 效果,我們先來看看 contenteditable 有哪些屬性吧 !

| 值 | 描述 |
|---|---|
inherit | 默認值繼承自父元素 |
true | 或空字串,表示元素是可編輯的; |
false | 表示元素不是可編輯的, |
plaintext-only | 純文本 |
caret | 符號 |
events |
注意
不允許簡寫為 <label contenteditable>Example Label</label>
正確的用法是 <label contenteditable="true">Example Label</label>,
瀏覽器支持情況


使用
<div
class="inputContent"
contenteditable="true"
ref="inputConents">
</div>
效果展示

2. 截屏
由于采用的是 可編輯 ,那么就可以隨意從外部 copy , 哈哈,有意思的來了,支持 Windows 自帶的截屏 + PC 第三方 截屏…
💥快捷操作方法:
-
windows自帶的的截屏快捷鍵截取整個螢屏
Print Screen截取當前活動螢屏
Alt+Print Screen -
QQ截屏功能,支持個性化操作截圖Ctrl + Alt + A -
微信截屏功能, 支持個性化操作截圖Alt + A -
專門的截屏工具…
站在巨人的肩膀上, 直接起飛,😄 , 不過確實站在用戶角度想,這點確實有點不好😘,
實際效果演示
2.1 微信截屏 show time

2.2 QQ 截屏
功能4: 發送功能
這個功能貫穿這個聊天專案,專案采用的是 websoket 實作的通信服務,全雙工通信 , 發送聊天內容時,需要攜帶一些很業務相關的資料,來實作業務跟蹤分析,下面,來簡單復習過一下 websoket , 對沒有使用過websoket 同學也時學習,

WebSoket
WebSocket是一種在單個TCP連接上進行全雙工通信的協議, WebSocket使得客戶端和服務器之間的資料交換變得更加簡單,允許服務端主動向客戶端推送資料,在WebSocket API中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,并進行雙向資料傳輸,
WebSoket 特點
- 服務器可以主動向客戶端推送資訊,客戶端也可以主動向服務器發送資訊,是真正的雙向平等對話,
- 屬于服務器推送技術的一種,
- 與 HTTP 協議有著良好的兼容性,默認埠也是80和443,并且握手階段采用 HTTP 協議.
- 資料格式比較輕量,性能開銷小,通信高效,
- 可以發送文本,也可以發送二進制資料,
- 沒有同源限制,客戶端可以與任意服務器通信,
- 協議識別符號是
ws(如果加密,則為wss),服務器網址就是 URL,
WebSoket 操作 API
創建Websoket連接🔗
let socket = new WebSocket("ws://域名/服務路徑")
連接 Websoket 成功觸發
open() 方法在連接成功時,觸發
socket.onopen = function() {
console.log("websocket連接成功");
};
發送訊息
send()方法并傳入一個字串、ArrayBuffer 或 Blob .
socket.send("公眾號: 前端自學社區")
接收服務端回傳的資料
message 事件會在 WebSocket 接收到新訊息時被觸發,
socket.onmessage = function(res) {
console.log(res.data)
}
關閉 WebSoket 連接
WebSocket.close() 方法關閉 WebSocke連接或連接嘗試(如果有的話), 如果連接已經關閉,則此方法不執行任何操作,
socket.onclose = function() {
// 關閉 websocket
console.log("連接已關閉...");
//斷線重新連接
setTimeout(() => {
that.initWebsoket();
}, 2000);
};
WebSoket 錯誤處理
當websocket的連接由于一些錯誤事件的發生 (例如無法發送一些資料)而被關閉時,一個error事件將被引發.
// 監聽可能發生的錯誤
socket.addEventListener('error', function (event) {
console.log('WebSocket error: ', event);
});
通過上面我們了解了 Websoket 如何使用,接下來就是 實操了,下面走起!
專案采用的是 Vue 技術堆疊,更多寫法偏向于 Vue , 由于 WebSoket 貫穿整個專案,而且需要實時推送 @ , 我們將 Websoket 盡量放在全域入口,接收資訊onmessage 事件也放在 入口檔案中,這樣全域都能接收到資料,接收到的資料 利用 Vuex 進行管理聊天的資料 [ 歷史資料 推送資料 發送資料 ] ,
1. 新建 一個 websoket檔案,用于全域使用
export default {
ws: {},
setWs: function(wsUrl) {
this.ws = wsUrl
}
}
2. 在Vue入口檔案index.js中 全域注冊
import Vue from 'vue'
import websoketGlobal from './utils/websoket'
Vue.prototype.$websoketGlobal = websoketGlobal
3. 在 App.vue 中 接收 Websoket 推送的訊息
這塊的設計很關鍵,決定了聊天資料的存盤和設計,過多細節代碼就不放了,
大體思路我說說一下:
-
傳輸格式上定了,那么接收的資料結構也就定了,更多的就是在資料結構背景關系章了, 前后端需要約束好欄位屬性,
從聊天頁面顯示狀態來看:
- 區分資料型別的
欄位,這樣前端在接收到推送的訊息時,知道在頁面中該如何顯示,例如(該顯示圖片樣式還是文本樣式) - 區分發送訊息顯示左右的欄位, 前端通過接收到推送的訊息時, 會首先判斷是否為自己,不是的話顯示在左邊樣式
- 區分 系統的推送欄位, 根據這個欄位顯示對應的樣式,
- … 更多欄位屬性 需要根據你實際業務而來定
是
是
從資訊推送狀態來看:
-
@推送全域Notification通知 和 聊天內部推送 設計@推送 根據指定欄位型別判斷 ,然后實作全域 推送聊天內容推送: 由于它和具體某個聊天有關系,它也屬于歷史聊天資料,在聊天中根據內容資料型別來確定如何顯示
- 區分資料型別的
mounted(){
this.$websoketGlobal.ws.onmessage = res => {
const result = JSON.parse(res.data);
?
// 推送資料
?
//聊天歷史資料 新增加發送的資料
?
?
// 獲取聊天歷史資料
?
//聊天歷史資料 新增加發送的資料
?
};
}
4. 在聊天組件中使用 Websoket
在聊天組件中,其實使用的就是 發送功能 和 獲取 歷史記錄 功能,還有就是根據 推送的訊息內容欄位來決定頁面中資料如何顯示,下面聊天的樣式代碼就不放了,主要放一下 發送訊息的 示例代碼 ,
send() {
let that = this;
?
// 定義資料結構: 傳遞什么內容是 前提 前端和后端商量好的
const obj = {
messageId: Number(
Math.random()
.toString()
.substr(3, length) + Date.now()
).toString(36),
//檔案型別
messagetype: "textmessage",
//@ 聯系熱
call: that.contactList,
//聊天輸入內容
inputConent: that.$refs.inputConents.innerHTML ,
// 當前時間
time: currentDate,
?
..... 再定義一些符合你業務的欄位
};
// 發送訊息
that.$websoketGlobal.ws.send(JSON.stringify(obj));
that.$refs.inputConents.innerHTML = "";
that.contactList = []
}
},
在每次進入聊天組件時,需要首先獲取聊天的歷史記錄,聊天入口根據你的業務來定,傳遞必須引數.
mounted(){
this.$websoketGlobal.ws.send(
JSON.stringify({
id: 1
messagetype: "historymessage"
})
);
}
功能5: 離線 / 在線推送
這個相當于 微信 / QQ 在線 和 上線 收到的訊息, 當 A 用戶 @ 了 B 用戶 (此時 B 用戶 不在線),當 B 用戶 上線時,它會收到 一條資訊,這個是怎么實作呢?
我就結合專案來大體說一下思路,具體實作就不說了,實作主要在后端, 當時,向后端大佬同時還特意請教了一下,
當 A用戶 登錄了 系統,此時就會和Websoket建立連接,后端會記錄起來,該用戶的標識,狀態為登錄,當 A 用戶 @ 了 B 用戶 ,正常邏輯會推送給B用戶一條資訊,B 不在線,就不推給他?
怎么知道B 用戶是否在線呢?
前面也說到了,登錄系統就會建立連接,后端會暫時存盤起來在線的用戶,當A 用戶 向 B 用戶發送的訊息后,后端看
在線用戶串列里沒有B 用戶,那么他就不會推送,當B用戶上線了,會自動推送,前端接收,直接提醒用戶,

聊天室入口組件
聊天室入口組件包括: 聯系人組件 + 聊天主體組件 , 它做的事情其實很簡單了,
- 如何打開聊天室 ?
- 如何給聊天室傳遞歷史資料?
如何打開聊天室?
外部可能通過多個入口來打開聊天室,通過一個狀態來控制顯示聊天室,傳遞型別為Boolean
如何給聊天室傳遞歷史資料?
外部通過給聊天室組件傳遞必要資料,這些必要資料然后在聯系人組件 和 聊天主體組件 內部消耗,獲取各自需要的資料,這樣聊天室入口組件的職責單一,很好進行管理,
下面來看看聊天室的入口組件:
<template>
<div>
<transition name="el-fade-in-linear" :duration="4000">
<div
class="chat-container"
>
<div
class="left-concat"
>
//聯系人組件
<Concat @toParent="innerHtmlToChat" />
</div>
<div
class="right-chatRoom"
>
// 聊天室主體組件
<ChatRoom
ref="chatRoom"
/>
</div>
</div>
</transition>
</div>
</template>
內部的通信主要是由 Vuex 來進行管理, 由于聊天室在全域都需要喚醒,可以將聊天入口組件放到全域入口檔案,這樣,不管專案需要多少個入口,只需要傳遞喚醒聊天入口組件的狀態 和 入口組件需要的必要引數 來獲取歷史聊天資料,
<Chat
// 控制是否顯示聊天室
v-if="$store.state.chatStore.roomStatus"
//聊天室需要的必要資料
:orderInfo="$store.state.chatStore"
/>
這樣,當專案其它模塊需要 聊天室 這個功能,只需要 一行代碼 即可 接入,作為插槽接入,
<template slot="note" slot-scope="props">
<i class="el-icon-chat-dot-square" @click="openChatRoome(props.data.row)"></i>
</template>
openChat(row){
this.$store.commit("Chat", { status: true, data: row });
},
總結
在開發這個 聊天服務 中也遇到了很多難點和坑,不過一個一個踩過來了,越往后做思路越開, 開發完這個 聊天服務 對技術理解又有更深的認知了,在你感覺某個功能很難困難,不知道怎么實作,你先行動起來,按照自己的思路一步一步推理,推理的程序就會思路打開了,會有多種方式來實作了,
最后
聊天服務開發了一個月,寫文章寫了一個周左右,寫作不易,如果文章學到了,點個贊👍👍👍關注,支持一下!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/301060.html
標籤:其他
下一篇:移動WEB開發之回應式布局
