欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

webSocket信息推送 实现客服聊天功能 群发和点对点

程序员文章站 2022-05-21 14:22:48
...

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

客服系统开发需要用到点对点聊天,如果是不断访问接口的形式获取数据,性能消耗大
用WebSocket通信技术,这样的话只需要连接一次,信息即可互相推送

webSocket信息推送 实现客服聊天功能 群发和点对点webSocket信息推送 实现客服聊天功能 群发和点对点
解决思路

1.当用户或者客服进入聊天界面时,发起ws请求
“ws://127.0.0.1:8888/websocket/”+id
id为唯一标识符,辨别连接用户

2.发送信息 根据id发送信息

3.接收信息 前端接收信息包含接收人id,如果接收人是当前聊天对话框的用户,就把数据渲染到对话框里

步骤
1.引入pox

 <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
 </dependency>

<dependency>
            <groupId>net.sf.json-lib</groupId>
            <artifactId>json-lib</artifactId>
            <version>2.4</version>
            <classifier>jdk15</classifier>
 </dependency>
@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }


}
@Component
@ServerEndpoint("/websocket/{id}")
public class WebSocket {

    private Session session;

    private static CopyOnWriteArraySet<WebSocket> webSockets = new CopyOnWriteArraySet<>();
    private static Map<String, Session> sessionPool = new HashMap<String, Session>();

    @OnOpen
    public void onOpen(Session session, @PathParam(value = "id") String id) {
        this.session = session;
        webSockets.add(this);
        sessionPool.put(id, session);
        System.out.println(id + "【websocket消息】有新的连接,总数为:" + webSockets.size());
    }

    @OnClose
    public void onClose() {
        webSockets.remove(this);
        System.out.println("【websocket消息】连接断开,总数为:" + webSockets.size());
    }

    @OnMessage
    public void onMessage(String message) {
        System.out.println("【websocket消息】收到客户端消息:" + message);
    }

    public void sendAllMessage(String message) {
        for (WebSocket webSocket : webSockets) {
            System.out.println("【websocket消息】广播消息:" + message);
            try {
                webSocket.session.getAsyncRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void sendOneMessage(String id, String message) {
        System.out.println("【websocket消息】单点消息:" + message);
        Session session = sessionPool.get(id);
        if (session != null) {
            try {
                session.getAsyncRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

发送


  String msg="";
        String todeuser=""+touser;
        System.out.println("todeuser"+todeuser);
        JSONObject json;

        //1.将Bean转换为Json格式
        json = JSONObject.fromObject(chatVo);

        //2.将Json格式转换为String类型
        msg = json.toString();
        System.out.println("Bean转换为Json:" + msg);
        webSocket.sendOneMessage(todeuser,msg);

chatVo

package com.gdkm.vo;

import lombok.Data;

@Data
public class ChatVo {
    /**
     * 发送者
     */
    private Integer userId;
    /**
     * id
     */
    private Integer cId;
    /**
     * 头像
     */
    private String userIocn;
    /**
     * 内容
     */
    private String cltext;
    /**
     * 时间
     */
    private Data createtime;
    /**
     * 0是未读
     */
    private String clstate;
    /**
     * 0是自己  1是对方
     */
    private String tpye;
}

我的前端页面

<template>

    <div class="chat">

        <el-card class="box-card box-cards">
            <el-breadcrumb separator-class="el-icon-arrow-right">
                <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
                <el-breadcrumb-item>在线客服</el-breadcrumb-item>
            </el-breadcrumb>
        </el-card>

        <div class="box-card box-cards">
            <el-row class="con">
                <el-col :span="24">
                   <div class="righthead">
                       <h4>{{userNickname}}</h4>
                   </div>
                   <div class="context" id="liao">
                       <el-row class="itemchat" v-for="(item, index) in chatlogList" :key="index" >
                       <el-avatar class="chattouxiang" :class="item.tpye==0?'right':'left'" :size="40" :src="item.userIocn"></el-avatar>
                           <div class="send" :class="item.tpye==0?'right':'left'">
                               <span>{{item.cltext}}
                               </span>
                               <div class="arrow"  :class="item.tpye==0?'sendright':'sendleft'"/>
                           </div>
<!--                           <span :class="item.tpye==0?'right':'left'" class="clstate">{{item.clstate==0?'未读':'已读'}}-->
<!--                               </span>-->
                       </el-row>

                   </div>
                        <div class="rightbottom">
                          <textarea v-model="textarea" @keydown.13="tosendOneWebSocket()" ></textarea>
                        </div>
                </el-col>
            </el-row>
        </div>

        <log ref="child"   v-show="this.userNickname!=''"/>
    </div>
</template>

<script>
    import {appuser,chatlog,sendOneWebSocket,upchatlog} from "../../network/chat";
    import {userInfo} from "../../network/me";
    import Log from "./chatComps/Log";

    export default {
        name: "Chat",
        data(){
            return {
                touser:1,
                textarea:'',
                appuser:[],
                chatlogList:[],
                userNickname:'客服',
            }
        },
        beforeCreate() {

        },
        components:{
            Log
        },
        methods:{

            tosendOneWebSocket(){
                let text=this.textarea
                let id=this.touser
                sendOneWebSocket(id,text).then(res=>{
                    console.log(res)
                })
                const log = {
                    "userIocn": this.$store.state.user.userIcon,
                    "cltext": this.textarea,
                    "clstate": "1",
                    "tpye": "0"
                }
                this.chatlogList.push(log)
                this.textarea=''
            },
            tochatlog(touser){

                chatlog(touser).then(res=>{
                    console.log(res)
                    this.chatlogList=  res.data
                })
            },
            toappuser(){
                appuser().then(res=>{
                    console.log(res)
                    this.appuser=res.data
                })
            },
            initWebSocket() {
                // 连接错误
                this.websocket.onerror = this.setErrorMessage;

                // 连接成功
                this.websocket.onopen = this.setOnopenMessage;

                // 收到消息的回调
                this.websocket.onmessage = this.setOnmessageMessage;

                // 连接关闭的回调
                this.websocket.onclose = this.setOncloseMessage;

                // 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
                window.onbeforeunload = this.closeWebSocket;

            },
            setErrorMessage() {
                // eslint-disable-next-line no-console
                console.log(
                    "WebSocket连接发生错误   状态码:" + this.websocket.readyState
                );
            },
            setOnopenMessage() {
                // eslint-disable-next-line no-console
                console.log("WebSocket连接成功    状态码:" + this.websocket.readyState);
            },
            setOnmessageMessage(event) {
                let data=eval("("+event.data+")")
                upchatlog(data.CId)
                this.chatlogList.push(data);
            },
            setOncloseMessage() {
                // eslint-disable-next-line no-console
                console.log("WebSocket连接关闭    状态码:" + this.websocket.readyState);
            },
            closeWebSocket() {
                this.websocket.close();
            },
        },
        updated(){
            let ele= document.getElementById("liao");
            ele.scrollTop = ele.scrollHeight;
        },
        mounted() {
            window.onbeforeunload = this.closeWebSocket;
            this.websocket = new WebSocket(
                "ws://127.0.0.1:8888/linux/websocket/"+this.$store.state.user.userId
            )
            this.initWebSocket();
            this.tochatlog(1);
            userInfo(1).then(res=>{
                const user=res.data
                this.userNickname=user.userNickname
            })
            this.$refs.child.parentMsg(this.touser)
        },
        beforeRouteEnter:(to,from,next)=>{
            next(vm => {
                if (vm.$store.state.user.roleId!=1){
                    vm.$message.error('权限不通过');
                    next('/home');
                }
            })
        }

    }
</script>

<style scoped>

    .chat{
    }
    .clstate{
        font-size: 11px;margin-top: 20px;margin-left: 10px;margin-right: 10px
    }
    textarea{
        width: 100%;
        height: 100%;
        resize:none;
        border: none;
        outline: none;
    }
    .items{
        margin-left:5px ;
    }
    .item{
        height: 70px;
        background-color: #2E2E2E;
        border: 0;
        margin-top: 1px;
        line-height: 70px;
        color: white;
        border-radius: 5px;

    }
    .itemtouxiang{
        float: left;
         margin:15px 10px 0px 15px;
    }
    .touxiang{
       margin-top: 15px;
       margin-left: 15px;
        margin-right: 10px;
       float: left;
    }
    .con{
        width: 100%;
    }
    .box-cards {
        width: 65%;
        margin: 20px auto;
    }
    .lefthead{
        height: 80px;
        background-color: #2E2E2E;
        border-bottom: 1px white solid;
        line-height: 80px;
        color: white;
    }
    ::-webkit-scrollbar {
        width: 0 !important;
    }
    .leftbottom{
        height: 530px;
        background-color: #3D3D3D;
        overflow: auto;

    }
    .righthead{
        height: 60px;
        border-bottom: 1px #F5F5F5  solid;
        background-color: white;
        line-height: 60px;
        padding-left: 20px;
    }
    .context{
        height: 360px;
        border-bottom: 1px #E8E8E8  solid;
        overflow: auto;
        background-color: #F5F5F5;
        padding: 20px 20px;

    }
    .rightbottom{
        height: 130px;
        padding: 10px 10px;
        background-color: #FFFFFF;
    }
    .send {
        position:relative;
        background:#3399FF;
        border-radius:5px; /* 圆角 */
        padding:10px 10px;
        line-height: 20px;
        max-width: 60%;
    }
    .send .arrow {
        position:absolute;
        top:9px;
        width:0;
        height:0;
        font-size:0;
        border:solid 8px;
    }
    .send .sendright{
        right:-15px; /* 圆角的位置需要细心调试哦 */
        border-color:#F5F5F5 #F5F5F5 #F5F5F5 #3399FF;
    }
    .send .sendleft{
        left:-15px; /* 圆角的位置需要细心调试哦 */
        border-color:#F5F5F5 #3399FF #F5F5F5 #F5F5F5;
    }

    .send span{
      font-size: 15px;
      word-break:break-word;
    }
    .chattouxiang{
        margin-left: 20px;
        margin-right: 20px;
    }
    .right{
        float: right;
    }
    .left{
        float: left;
    }

    .itemchat{
        margin-top: 10px;
    }
</style>
<template>
    <div class="chat">
        <el-card class="box-card box-cards">
            <el-breadcrumb separator-class="el-icon-arrow-right">
                <el-breadcrumb-item :to="{ path: '/home' }">首页</el-breadcrumb-item>
                <el-breadcrumb-item>客服中心</el-breadcrumb-item>
            </el-breadcrumb>
        </el-card>

        <div class="box-card box-cards">
            <el-row class="con">
                <el-col :span="7">
                    <div class="lefthead">
                        <el-avatar class="touxiang" :size="50" :src="this.$store.state.user.userIcon"></el-avatar>
                        <h3>{{this.$store.state.user.userNickname}}</h3>
                    </div>
                    <div class="leftbottom">
                        <div class="item"   v-for="(item, index) in appuser" :key="index" @click="tochatlog(item.userId,item.userNickname)"
                             v-show="item.userId != $store.state.user.userId"
                        >
<!--                            this.$router.push('/user/'+item.userId)-->
                            <div @click="touserInfo(item.userId)">
                            <el-avatar class="itemtouxiang" :size="40" :src="item.userIcon"  ></el-avatar>
                            </div>
                            <h5>{{item.userNickname}}<el-badge is-dot class="items" v-if="item.xinxi"></el-badge></h5>
                        </div>

                    </div>
                </el-col>
                <el-col :span="17" v-if="showright">
                   <div class="righthead">
                       <h4>{{userNickname}}</h4>
                   </div>
                   <div class="context"  id="liao2">
                       <el-row class="itemchat" v-for="(item, index) in chatlogList" :key="index" >
                       <el-avatar class="chattouxiang" :class="item.tpye==0?'right':'left'" :size="40" :src="item.userIocn"></el-avatar>
                           <div class="send" :class="item.tpye==0?'right':'left'">
                               <span>{{item.cltext}}
                               </span>
                               <div class="arrow"  :class="item.tpye==0?'sendright':'sendleft'"/>
                           </div>
<!--                           <span :class="item.tpye==0?'right':'left'" class="clstate" v-show="item.tpye==0" >{{item.clstate==0?'未读':'已读'}}-->
<!--                               </span>-->
                       </el-row>

                   </div>
                        <div class="rightbottom">
                          <textarea v-model="textarea" @keydown.13="tosendOneWebSocket()" ></textarea>
                        </div>
                </el-col>
            </el-row>
        </div>

        <log ref="child"   v-show="this.userNickname!=''"/>
    </div>
</template>

<script>
    import {appuser,chatlog,sendOneWebSocket,upchatlog} from "../../network/chat";
    import Log from "./chatComps/Log";
    export default {
        name: "Chat",
        data(){
            return {
                textarea:'',
                appuser:[],
                chatlogList:[],
                userNickname:'',
                showright:true,
                touser:0,

            }
        },
        updated(){
            let ele= document.getElementById("liao2");
            ele.scrollTop = ele.scrollHeight;
        },
        computed:{
            logshow(){
                return false
            }
        },
        components:{
            Log
        },
        beforeCreate() {
        },

        methods:{
            touserInfo(userId){
                this.$router.push('/user/'+userId)
            },
            tosendOneWebSocket(){
                let text=this.textarea
                let id=this.touser
                sendOneWebSocket(id,text).then(res=>{
                    console.log(res)
                })
                const log = {
                    "userIocn": this.$store.state.user.userIcon,
                    "cltext": this.textarea,
                    "clstate": "1",
                    "tpye": "0"
                }
                this.chatlogList.push(log)
                this.textarea=''
            },
            //websocket
            initWebSocket() {
                // 连接错误
                this.websocket.onerror = this.setErrorMessage;

                // 连接成功
                this.websocket.onopen = this.setOnopenMessage;

                // 收到消息的回调
                this.websocket.onmessage = this.setOnmessageMessage;

                // 连接关闭的回调
                this.websocket.onclose = this.setOncloseMessage;

                // 监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
                window.onbeforeunload = this.closeWebSocket;
            },
            setErrorMessage() {
                // eslint-disable-next-line no-console
                console.log(
                    "WebSocket连接发生错误   状态码:" + this.websocket.readyState
                );
            },
            setOnopenMessage() {
                // eslint-disable-next-line no-console
                console.log("WebSocket连接成功    状态码:" + this.websocket.readyState);
            },
            setOnmessageMessage(event) {
                let data=eval("("+event.data+")")
                console.log(data)
                if(data.userId == this.touser){
                    upchatlog(data.CId)
                    this.chatlogList.push(data);
                }
            },
            setOncloseMessage() {
                // eslint-disable-next-line no-console
                console.log("WebSocket连接关闭    状态码:" + this.websocket.readyState);
            },
            closeWebSocket() {
                this.websocket.close();
            },
            tochatlog(touser,userNickname){
                this.logshow=true
                this.touser=touser
                chatlog(touser).then(res=>{
                    this.chatlogList=  res.data
                    this.userNickname=userNickname
                    this.showright=true
                })
                // setInterval(() => {
                //     this.tochatlog();
                // }, 300);
                this.$refs.child.parentMsg(this.touser)
            },
            toappuser(){
                appuser().then(res=>{
                    this.appuser=res.data
                })
            },
            send(){
                console.log("发送")
                const log = {
                    "userIocn": "http://gdkmlzh.cn-gd.ufileos.com/img%2F5e3af915-83c8-4f23-9953-25ccb46363c1.jpg?UCloudPublicKey=TOKEN_0c9d0118-e892-4784-8702-34bf5b992604&Signature=8p462HhcXyhM%2FJmhhnygrZotLjw%3D&Expires=1891772663",
                    "cltext": this.textarea,
                    "clstate": "0",
                    "tpye": "1"
                }
                this.chatlogList.push(log)
                this.textarea=''
            },

        },
        mounted() {
            window.onbeforeunload = this.closeWebSocket;

            this.websocket = new WebSocket(
                "ws://127.0.0.1:8888/linux/websocket/"+this.$store.state.user.userId
            )
            this.initWebSocket();

            this.toappuser()
            setInterval(() => {
                this.toappuser();
            }, 3000);
        },
        beforeRouteEnter:(to,from,next)=>{
            next(vm => {
                if (vm.$store.state.user.roleId!=2){
                    vm.$message.error('权限不通过');
                    next('/home');
                }
            })
        },
    }
</script>

<style scoped>
    .page{
        margin:10px;
        text-align: center;
    }
    .chat{
    }
    .clstate{
        font-size: 11px;margin-top: 20px;margin-left: 10px;margin-right: 10px
    }
    textarea{
        width: 100%;
        height: 100%;
        resize:none;
        border: none;
        outline: none;
    }
    .items{
        margin-left:5px ;
    }
    .item{
        height: 70px;
        background-color: #2E2E2E;
        border: 0;
        margin-top: 1px;
        line-height: 70px;
        color: white;
        border-radius: 5px;

    }
    .itemtouxiang{
        float: left;
         margin:15px 10px 0px 15px;
    }
    .touxiang{
       margin-top: 15px;
       margin-left: 15px;
        margin-right: 10px;
       float: left;
    }
    .con{
        width: 100%;
    }
    .box-cards {
        width: 65%;
        margin: 20px auto;
    }
    .lefthead{
        height: 80px;
        background-color: #2E2E2E;
        border-bottom: 1px white solid;
        line-height: 80px;
        color: white;
    }
    ::-webkit-scrollbar {
        width: 0 !important;
    }
    .leftbottom{
        height: 530px;
        background-color: #3D3D3D;
        overflow: auto;

    }
    .righthead{
        height: 60px;
        border-bottom: 1px #F5F5F5  solid;
        background-color: white;
        line-height: 60px;
        padding-left: 20px;
    }
    .context{
        height: 360px;
        border-bottom: 1px #E8E8E8  solid;
        overflow: auto;
        background-color: #F5F5F5;
        padding: 20px 20px;
    }
    .rightbottom{
        height: 130px;
        padding: 10px 10px;
        background-color: #FFFFFF;
    }
    .send {
        position:relative;
        background:#3399FF;
        border-radius:5px; /* 圆角 */
        padding:10px 10px;
        line-height: 20px;
        max-width: 60%;
    }
    .send .arrow {
        position:absolute;
        top:9px;
        width:0;
        height:0;
        font-size:0;
        border:solid 8px;
    }
    .send .sendright{
        right:-16px; /* 圆角的位置需要细心调试哦 */
        border-color:#F5F5F5 #F5F5F5 #F5F5F5 #3399FF;
    }
    .send .sendleft{
        left:-16px; /* 圆角的位置需要细心调试哦 */
        border-color:#F5F5F5 #3399FF #F5F5F5 #F5F5F5;
    }

    .send span{
      font-size: 15px;
      word-break:break-word;
    }
    .chattouxiang{
        margin-left: 20px;
        margin-right: 20px;
    }
    .right{
        float: right;
    }
    .left{
        float: left;
    }

    .itemchat{
        margin-top: 10px;
    }
    .box-cards2 {
        width: 65%;
        margin: 20px auto;
    }
    .context2{
        border-bottom: 1px #E8E8E8  solid;
        background-color: #F5F5F5;
        padding: 20px 20px;
    }

</style>