背景:公司业务需求,在官网上面需添加客服答疑页面,一般都是使用websocket通信,主要是它是一个低延迟、全双工、跨域通信通道。
注:页面底部有效果图
一、简单介绍
sockjs-client
sockjs-client是从SockJS中分离出来的用于客户端使用的通信模块,是一个浏览器的JavaScript库。主要是为了浏览器的兼容性,有些浏览器是不支持websocket,Spring框架提供了基于SockJS协议的透明的回退选项,也就是说一旦不支持这种模式,SockJS会自动降为轮询的方式。
stomjs
STOMP(Simple Text-Orientated Messaging Protocol) 面向消息的简单文本协议;
WebSocket是一个消息架构,不强制使用任何特定的消息协议,它依赖于应用层解释消息的含义。
与HTTP不同,WebSocket是处在TCP上非常薄的一层,会将字节流转化为文本/二进制消息,因此,对于实际应用来说,WebSocket的通信形式层级过低,因此,可以在 WebSocket 之上使用STOMP协议,来为浏览器和server间的 通信增加适当的消息语义。
STOMP与WebSocket 的关系:
HTTP协议解决了web浏览器发起请求以及web服务器响应请求的细节,假设HTTP协议不存在,只能使用TCP套接字来编写web应用,你可能认为这是一件疯狂的事情;
直接使用WebSocket(SockJS)就很类似于使用TCP套接字来编写web应用,因为没有高层协议,就需要我们定义应用间发送消息的语义,还需要确保连接的两端都能遵循这些语义;
同HTTP在TCP套接字上添加请求-响应模型层一样,STOMP在WebSocket之上提供了一个基于帧的线路格式层,用来定义消息语义。
二、安装插件
npm install sockjs-client
npm install stompjs
三、页面代码
html代码
<!-- 聊天弹框 主要是客户聊天的页面代码 -->
<el-dialog
ref="chatDialog"
:visible.sync="chatDialog"
width="360px"
class="chatDialog"
:close-on-click-modal="false"
:close="chatClose"
title="国瑞在线"
>
<div ref="chatContentRef" :contenteditable="false" class="chatContent clearfix" />
<div class="chating">
<a href="javascript:void(0)" class="emtionImg" :contenteditable="false" @click="emtionShow()"><img src="@/assets/images/index/icon_index_service_emtion.png" alt=""></a>
<emotion v-if="emtionIsShow" class="emotion" :height="138" @emotion="handleEmotion" />
<el-upload
class="upload"
action="https://jsonplaceholder.typicode.com/posts/"
auto-upload
multiple
:before-upload="beforeUpload"
>
<img src="@/assets/images/index/icon_index_service_img.png" alt="">
</el-upload>
<div
ref="textarea"
class="textarea"
:contenteditable="contenteditable"
@keyup.enter="sendMessage()"
@keydown.enter.prevent
@blur="focusContent"
v-html="content"
/>
<div class="chatBtn">
<el-button class="chatBtn_1" @click="closeChatDialog()">结束会话</el-button>
<el-button class="chatBtn_2" @click="sendMessage()">发送</el-button>
</div>
</div>
<img src="@/assets/images/index/icon_index_service_logo.png" alt="">
</el-dialog>
<!-- 图片弹框 -->
<el-dialog
ref="previewDialog"
:visible.sync="previewDialog"
:width="imgWidth"
>
<img :src="imgSrc" alt="">
</el-dialog>
<!-- 结束会话弹框 -->
<el-dialog
:visible.sync="closeChatCheckDialog"
width="240px"
class="closeChatCheck"
>
<span>是否结束当前会话</span>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="closeChatCheck()">是</el-button>
<el-button @click="closeChatCheckDialog = false">否</el-button>
</span>
</el-dialog>
JS代码
import Emotion from '../../../components/Emotion/index'; //表情包的使用
import SockJS from 'sockjs-client';
import Stomp from 'stompjs';
export default {
data(){
return {
stompClient:'',
timer:'',
}
},
methods:{
init() {
if (typeof (WebSocket) === 'undefined') {
alert('您的浏览器不支持socket');
} else {
this.connection();
}
},
connection() {
// 建立连接对象
let socket = new SockJS('http://192.168.1.102:9632/web-websocket/webSocketServer');
// 获取STOMP子协议的客户端对象
this.stompClient = Stomp.over(socket);
// 向服务器发起websocket连接
const userId = Number(Math.random().toString().substr(3, 8) + Number(Date.now().toString().substr(3, 8))).toString(36);
this.user = { //随机生成用户ID
username: userId
};
this.stompClient.connect(this.user,() => {
this.stompClient.subscribe('/user/' + userId + '/queue/getResponse', (response) => { // 订阅服务端提供的某个topic
if (JSON.parse(response.body) && JSON.parse(response.body).type === 'NOTICE' && JSON.parse(response.body).status === 2) {
this.users = JSON.parse(response.body).userName;
this.contenteditable = true;
const noticeMsg = `<div style="margin-top: 20px;text-align:center;margin-bottom: 10px;">您已成功连接</div>`;
this.$refs.chatContentRef.innerHTML += noticeMsg;
} else if (JSON.parse(response.body) && JSON.parse(response.body).type === 'NOTICE' && JSON.parse(response.body).status === 1) {
const noticeMsg = `<div style="margin-top: 20px;text-align:center;margin-bottom: 10px;">当前连接人数较多,请耐心稍等</div>`;
this.$refs.chatContentRef.innerHTML += noticeMsg;
this.contenteditable = false;
}
if (JSON.parse(response.body).text) {
this.$refs.chatContentRef.innerHTML +=
`<div class="clearfix"><div style="float:left"> <span style="margin-right:8px;">卑微客服在线答疑 ${this.$verify.NowDate3()}</span></div> </div>
<div class="clearfix">
<div style="background-color:#FFFFFF;color:black;padding:8px 8px;text-align:rigth;margin-top:8px;border-radius:6px;
float:left"> ${JSON.parse(response.body).text}</div>
</div>
<br />`;
this.$refs.chatContentRef.scrollTop = this.$refs.chatContentRef.scrollHeight;
}
console.log(response, '订阅成功'); // msg.body存放的是服务端发送给我们的信息
});
}, (err) => {
// 连接发生错误时的处理函数
console.log('失败',err);
});
},
disconnect() {// 断开连接
if (this.stompClient) {
this.stompClient.disconnect();
}
},
sendMessage() { //发送消息
const text = this.$refs.textarea.innerHTML;
var msg = {
'text': this.$refs.textarea.innerHTML,
'userName': this.users
};
this.stompClient.send('/sendUser', {}, JSON.stringify(msg)); //发送消息的主要代码
if (text === '') {
return;
}
const chatContentRef = this.$refs.chatContentRef;
const time = this.$verify.NowDate3();
chatContentRef.innerHTML +=
`<div class="clearfix"><div style="float:right"> <span style="margin-right:8px;">我</span>${time}</div> </div>
<div class="clearfix">
<div style="background-color:#5595FF;color:#fff;padding:8px 8px;text-align:rigth;margin-top:8px;margin-left:52px;border-radius:6px;
float:right">${text} </div>
</div>
<br />`;
chatContentRef.scrollTop = chatContentRef.scrollHeight;
this.$refs.textarea.innerHTML = '';
this.content = '';
},
closeChatDialog() {
this.closeChatCheckDialog = true;
},
chatClose() {
this.$refs.chatContentRef.innerHTML = '';
},
emtionShow() {
const that = this;
that.emtionIsShow = !that.emtionIsShow;
this.$refs.textarea.focus();
},
handleEmotion(i) {
this.lastEditRange.deleteContents(); // 删除文档的区域
var el = document.createElement('div');
el.innerHTML = this.emotion(i);
var frag = document.createDocumentFragment(); // 创建一个虚拟的dom
var node;
var lastNode;
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node);
}
this.lastEditRange.insertNode(frag);
// Preserve the selection
if (lastNode) {
this.lastEditRange = this.lastEditRange.cloneRange();
this.lastEditRange.setStartAfter(lastNode);
this.lastEditRange.collapse(true);
this.selection.removeAllRanges();
this.selection.addRange(this.lastEditRange);
}
},
// 将匹配结果替换表情图片
emotion(res) {
const word = res.replace(/\#|\;/gi, '');
return `<img style="width:20px;" src="https://twemoji.maxcdn.com/v/12.1.4/72x72/${word}.png" align="middle">`;
},
focusContent() {
this.selection = window.getSelection();
this.lastEditRange = this.selection.getRangeAt(0);
},
beforeUpload(val) {
const file = val;
const reader = new FileReader();
reader.readAsDataURL(file);
const chatContentRef = this.$refs.chatContentRef;
const time = this.$verify.NowDate3();
const stompClient = this.stompClient;
const users = this.users;
reader.onload = function() {
chatContentRef.innerHTML +=
`<div class="clearfix"><div style="float:right"> <span style="margin-right:8px;">我</span>${time}</div> </div>
<div class="clearfix">
<div style="background-color:#5595FF;color:#fff;padding:8px 8px;text-align:rigth;margin-top:8px;margin-left:52px;border-radius:6px;
float:right"> <img src=${reader.result} style="max-width:100px;"></img> </div>
</div>
<br />`;
const innerHTML = ` <img src=${reader.result} style="max-width:100px;"></img>`;
var msg = {
'text': innerHTML,
'userName': users
};
stompClient.send('/sendUser', {}, JSON.stringify(msg));
};
},
closeChatCheck() { //断开链接
this.chatDialog = false;
this.closeChatCheckDialog = false;
this.disconnect();
}
},
mounted(){
this.init();
}
}
以上就是websocket的通信内容。。。
······························································我是一条分割线····························································
以下是页面效果图:
1.客服页面:
2.客户页面: