前端部分
整理好了实现聊天室的思路与要实现的功能,开始写代码。
首先用Vue-Cli脚手架搭建项目,网上教程很多,这里就不阐述了。
采用了VUE3+Ts+Element plus的技术栈。
这里为审美比较差的前端朋友们推荐两个网站(没有UI真是难受)。
渐变色:uigradients
页面主要为登录页与聊天页,主色调为黑白,这里就不放代码了,页面很简单,基本上就是以下几个部分。
登录页
借鉴了codepen的大神,原链接在此
聊天页
创建/加入房间
房间详情
实现好了页面,将api部分封装好,就可以愉快地调接口啦!使用的是老搭档axios,定义好请求拦截器、接收拦截器、处理好异常返回即可,Po出部分代码。
const service = axios.create({
baseURL:process.env.VUE_APP_BASE_URI,//请求地址区分本地与线上环境
withCredentials:false,//跨域
timeout:3*60*1000,//请求超时
});
//请求拦截器
service.interceptors.request.use((config:any) => {
//在这里对请求头添加token
return config; // 有且必须有一个config对象被返回
}, error:any => {
// 对请求错误做些什么
return Promise.reject(error);
});
//接收拦截器
service.interceptors.response.use(
response => {
//处理回调
if (response.data.state == 0) {
return Promise.resolve(response.data);
} else {
return Promise.reject(response);
}
},
error => {
console.log("服务器错误")
}
);
采用websocket实现实时通讯,需要封装websocket部分,主要分为心跳、发送、接收以及各种异常处理,做了很多console方便出错时定位错误。
心跳
let heart = {
timeOut: 5000,
timeObj: null,
serverTimeObj: null,
start: function () {
// console.log("heart start");
let self = this
self.timeObj && clearTimeout(self.timeObj);
self.serverTimeObj && clearTimeout(self.serverTimeObj);
self.timeObj = setTimeout(function () {
if (!websock) {
console.log("%c通信连接已经关闭或不可用 " + nowTime(), "color:red")
return
}
if (websock.readyState == 0) {
console.log("%c通信连接未建立,建议重新连接 " + nowTime(), "color:yellow")
}
if (websock.readyState == 1) {
console.log("%c心跳正常 " + nowTime(), "color:green")
}
if (websock.readyState == 2) {
console.log("%c通信连接正在关闭 " + nowTime(), "color:yellow")
}
if (websock.readyState == 3) {
console.log("%c通信连接已经关闭或不可用 " + nowTime(), "color:red")
}
websock.send(JSON.stringify({ text: 'websocket心跳检测' + nowTime() }));//发送消息,服务端返回信息,即表示连接良好,可以在socket的onmessage事件重置心跳机制函数
//定义一个延时器等待服务器响应,若超时,则关闭连接,重新请求server建立socket连接
self.serverTimeObj = setTimeout(function () {
reConnect();
}, self.timeOut)
}, self.timeOut)
}
}
初始化socket
const initSocket = ()=>{
if (websock) {
if (websock.readyState == 1) {
console.log("%c可正常通信,无需重连 " + nowTime(), 'color:green')
} else {
websocketclose()
}
}
//socket地址需要传入当前的房间号,方便区分推送聊天消息
websock = new WebSocket("ws://"+process.env.VUE_APP_BASE_SOCKET_URI+"/"+props.roomCode);
websock.onmessage = websocketonmessage;
websock.onerror = websocketonerror;
websock.onopen = () => {
websocketonopen()
};
}
其他
const websocketonopen = () => { //连接建立之后开始心跳
console.log("%c连接成功 " + nowTime(), 'color:green')
heart.start();
}
const websocketonerror = () => {//连接建立失败重连
console.log("%c连接异常,尝试重新连接", "color:red")
reConnect();
}
const websocketclose = ()=>{//离开房间后需要关闭socket
if (websock) {
websock.close()
websock = null
}
console.log('%c连接关闭 ' + nowTime(), "color:red");
}
const reConnect = () => {//自动重连
websocketclose()
if (reConnectCount == 3) {//可自行定义重连次数,reConnectCount需要全局定义
console.log("%c重连失败已达3次,连接关闭 " + nowTime(), "color:red")
reConnectCount = 0
return false
}
setTimeout( () =>{
reConnectCount++
initSocket();
}, 1000)
}
const websocketonmessage = (e: any) => { //数据接收
console.groupCollapsed("%cwebcosket接收数据" + nowTime(), "color:grey");
console.log(e.data);
console.groupEnd();
// 这里处理数据,使用type来区分接收到的数据是消息还是心跳返回
if(JSON.parse(e.data).type == 1){
//---------------------------------------
//处理接收的数据,渲染到页面上
//---------------------------------------
//接收到数据后,如果页面消息堆叠过多,需要自动将页面滚动至底部
proxy.$nextTick(()=>{
var ele = document.getElementById(id);
//判断元素是否出现了滚动条
if(ele.scrollHeight > ele.clientHeight) {
//设置滚动条到最底部
ele.scrollTop = ele.scrollHeight;
}
})
//---------------------------------------
}
//继续心跳
heart.start();
}
连接成功效果