人工客服聊天

人工客服聊天(WebSocket)

技术:

前端: vue、axios

后端:tomcat、servlet、redis数据库

WebSocket介绍:

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。与传统的 HTTP 请求-响应模型不同,WebSocket 允许服务器和客户端之间进行实时的、低延迟的双向数据传输。

使用场景:

  • 实时聊天应用:如客服聊天、社交聊天。
  • 实时通知和提醒:如推送通知、股票价格更新。
  • 协作工具:如在线文档编辑、多人游戏。
  • 实时数据流:如直播、在线游戏、物联网数据传输。

前端实现 WebSocket:

前端代码(用户端、客服端):
let app = new Vue({
        el:'#app',
        data:{

            content:'',//用户发的消息
            chatMessage:[], //聊天内容

            protocol:'',     //当前页面的协议
            host:'',        //服务器的主机名或 IP 地址主机和端口
            endpoint:'',    //端点
            contextPath:'', //项目名

        },
        methods:{

            //WebSocket 客服聊天  初始化参数  建立连接
            initWebSocket() {
                // 获取当前页面的协议、主机和端口
                //const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
                const protocol = this.protocol;

                //const host = window.location.host;
                const host = this.host;
                //考虑nginx反向代理没有端口的问题
                //const host = window.location.host.includes(':') ? window.location.host : window.location.host + ':8080';

                //const endpoint = '/shop_war/echo';
                /*  ******客服端连接用户的端点user    用户连接客服端的端点admin **********   */
                /*  ****  当前为客服端   用户端的话把user改为admin ***** */
                const endpoint = this.endpoint+ this.contextPath +'/user';
                //const endpoint = '/shop/echo';

                // 动态生成 WebSocket 的 URL  
                //	ws://localhost:8080/user
                const wsUrl = `${protocol}://${host}:${endpoint}`;

                // 创建 WebSocket 连接
                var ws = new WebSocket(wsUrl);

                ws.onopen = function () {
                    console.log('WebSocket 连接已经建立。');
                    // ws.send('Hello, server!');
                };
				//  ***** 接收到websocket服务器端点的通知  
                ws.onmessage = function (event) {
                    console.log('收到服务器消息:', event.data);
                    //该刷新了
                    if (event.data){
                        // alert("获取到了服务器的消息");  刷新数据 即获取客服端的消息
                        // ***** 从redis数据库中去读取聊天内容
                        app.get().then(result=>{
                            // alert("获取服务端的消息"+result.data.obj);
                            // 确保 this.chatMessage 已经是一个数组
                            if (!Array.isArray(this.chatMessage)) {
                            	app.$data.chatMessage = [];
                            }
                            let obj = result.data.obj;//获取到聊天内容
                            obj.forEach(product => {
                            	app.$data.chatMessage.push(product);
                            });
                        })
                    }
                };

                ws.onerror = function (event) {
                    console.error('WebSocket 连接出现错误:', event);
                };

                ws.onclose = function () {
                    console.log('WebSocket 连接已经关闭。');
                };

                //关闭窗口调用
                window.onbeforeunload = function() {
                    app.$data.ws.close();
                };

                this.ws = ws;
            },
            //获取 要建立连接的 初始化参数
            init(){
                //用于初始化websocket
                axios.post("websocket.action?op=getServerInfo").then(result=>{
                    if (result.data.code==0){

                    }else{
                        this.protocol = result.data.obj.protocol;
                        this.host = result.data.obj.host;
                        this.endpoint = result.data.obj.port;
                        this.contextPath = result.data.obj.contextPath;
                        //动态初始化websocket的url,并建立连接,用来提醒该刷新消息了
                        this.initWebSocket();
                    }
                })
            },
            //发送 消息 到 服务器
            send(){
                let params = new URLSearchParams(   );
                if (this.content == null || this.content == ''){
                    alert("请先输入内容");
                    return;
                }
                params.append("content",this.content);
                params.append("name","admin");
                axios.post("websocket.action?op=setMessage",params).then(result=>{
                    let jm = result.data;
                    if (jm.code == 1){
                        this.content='';
                        this.get().then(result=>{
                            app.$data.chatMessage = [];
                            if (!Array.isArray(this.chatMessage)) {
                                app.$data.chatMessage = [];
                            }
                            let obj = result.data.obj;
                            obj.forEach(product => {
                                app.$data.chatMessage.push(product);
                            });
                        })
                    }

                })
            },
            //获取  服务端的消息  后调用
            get() {
                let params = new URLSearchParams();
                return axios.post("websocket.action?op=getMessage", params)
                    .then(response => {
                        // 假设服务器返回的数据结构如下:
                        // { data: { code: 0, obj: {...} } }
                        return response;
                    })
                    .catch(error => {
                        console.error('获取数据时发生错误:', error);
                        throw error;
                    });
            },
            //初始内容并渲染  即获取聊天记录 原先的聊天内容
            initContent(){
                this.get().then(result=>{
                    app.$data.chatMessage = [];
                    if (!Array.isArray(this.chatMessage)) {
                        app.$data.chatMessage = [];
                    }
                    let obj = result.data.obj;
                    obj.forEach(product => {
                        app.$data.chatMessage.push(product);
                    });
                })
            },
        },
        //选择挂载点为mounted  vue生命周期调用
        mounted:function (){
            axios.all(  [ this.init(),this.initContent() ] );
        },
    })

后端

WebSocket 服务器端点(客服端、用户端连接类):

基本的 WebSocket 生命周期事件处理(连接、消息接收、关闭、错误)以及一个方法来向客户端发送消息。

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;

// ***** 客服端 的 端点   用户端的话 把 admin改成user
@ServerEndpoint("/admin")
public class adminEchoServer {
    private static Session session;

    @OnOpen
    public void onOpen(Session session) {
        System.out.println("WebSocket 连接已经建立。");
        adminEchoServer.session = session;
    }

    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        System.out.println("收到客户端消息:" + message);
        //session.getBasicRemote().sendText("服务器收到消息:" + message);
    }

    @OnClose
    public void onClose() {
        System.out.println("WebSocket 连接已经关闭。");
    }

    @OnError
    public void onError(Throwable t) {
        System.out.println("WebSocket 连接出现错误:" + t.getMessage());
    }
	// ******  发送 通知  告诉前端需要刷新了 即前端需要去redis数据库中读取消息了
    public void send(String message) throws IOException {
        session.getBasicRemote().sendText(message);
    }
}
聊天内容具体操作类:
  • 获取初始化信息 建立websocket连接
  • 发送消息存到redis,通知user或者admin
  • 从redis中获取消息
import com.yc.bean.WebSocket;
import com.yc.commons.RedisHelper;
import com.yc.utils.JsonModel;
import com.yc.utils.adminEchoServer;
import com.yc.utils.userEchoServer;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Tuple;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;

@WebServlet(value = "/websocket.action")
public class WebSocketServlet extends BaseServlet{
    获取初始化信息  建立连接
    protected void getServerInfo(HttpServletRequest request, HttpServletResponse response) throws IOException {
        Map<String, String> serverInfo = new HashMap<>();
        String protocol = request.isSecure() ? "wss" : "ws";
        String host = request.getServerName();
        int port = request.getServerPort();
        String contextPath = request.getContextPath();

        serverInfo.put("protocol", protocol);
        serverInfo.put("host", host);
        serverInfo.put("port", String.valueOf(port));
        serverInfo.put("contextPath", contextPath);

        JsonModel jm = new JsonModel();
        jm.setCode(1);
        jm.setObj(serverInfo);
        writeJson(jm,response);
    }
    //  发送消息 存到  redis数据库中
    public void setMessage(HttpServletRequest request, HttpServletResponse response) throws IOException {
        JsonModel jm = new JsonModel();
        // name 表示 用户/客服  user/admin
        String name = request.getParameter("name");
        String content = request.getParameter("content");
        //连接redis
        Jedis jedis = new Jedis("localhost", 6379);
        // 当前时间戳
        long currentTimestamp = System.currentTimeMillis() / 1000;
        // **** 存  redis  数据库
        // ****添加新成员到有序集合Sorted Set  格式: content_websocket   时间戳   用户姓名:内容
        jedis.zadd("content_websocket", currentTimestamp, name+":"+content);
        jm.setCode(1);
        jm.setObj("发送成功");
        super.writeJson(jm,response);
        //**** 通知 用户端  或者  客服端  刷新消息了
        if ("user".equals(name)){
            userEchoServer userEchoServer = new userEchoServer();
            userEchoServer.send("我是用户端,你好");
        }else {
            adminEchoServer adminEchoServer = new adminEchoServer();
            adminEchoServer.send("我是客服端,你好");
        }
    }
    //从redis数据库 中  获取聊天内容
    protected void getMessage(HttpServletRequest request, HttpServletResponse response) throws IOException {
        JsonModel jm = new JsonModel();
        //连接redis
        Jedis jedis = new Jedis("localhost", 6379);
        // 获取有序集合的全部成员
        Set<Tuple> sortedSetMembers  =  jedis.zrangeWithScores("content_websocket",0,-1);
        // 打印输出每个成员及其分数
        List<WebSocket> list = new ArrayList<>();
        for (Tuple member : sortedSetMembers) {
            String memberValue = member.getElement(); // 内容
            Long score = (long) member.getScore();    // 时间戳
            String[] parts = memberValue.split(":");
            WebSocket webSocket = new WebSocket();
            if (parts[0]==null || parts[1]==null){
                continue;
            }
            webSocket.setName(parts[0]);//姓名
            webSocket.setContent(parts[1]);//内容
            Date date = new Date(score*1000);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String timestamp = sdf.format(date);
            webSocket.setTimestamp(timestamp);//时间
            list.add(webSocket);
        }
        jm.setCode(1);
        jm.setObj(list);
        super.writeJson(jm,response);
    }
}
前后端交互工具类:
import com.google.gson.Gson;
import com.yc.utils.JsonModel;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;

public abstract class BaseServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String op = req.getParameter("op"); //op=regFile
        JsonModel jm = new JsonModel(); //用来保存要运行后的信息  并  返回到前端
        try {
            if (op == null || "".equals(op)) {
                // out.println( "{code:0,error:'没有op参数'}"  );
                jm.setCode(0);
                jm.setError("op参数不能为空..");
                writeJson(jm,resp);
                return;
            }
            ///        反 射
            Method[] methods  = this.getClass().getDeclaredMethods();//取子类中的方法
            for (Method m:methods){
                if (  m.getName().equals(  op  )  ) {  // 判断有没有 regFile方法
                    m.invoke(this, req,  resp);//激活对应函数  regFile
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            jm.setCode(0);
            jm.setError(  e.getMessage()  );
            writeJson(jm,resp);
        }
    }
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doGet(req, resp);
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf-8");
        resp.setCharacterEncoding("utf-8");  // 响应流的编码
        resp.setContentType("text/html;charset=utf-8");

        super.service(req, resp);
    }

    //*** 后端传数据到前端  关键地方   ***    以json格式传数据到前端
    protected void writeJson(  JsonModel jm , HttpServletResponse resp) throws IOException {
        resp.setContentType("text/json;charset=utf-8");
        PrintWriter out = resp.getWriter();
        Gson g = new Gson();
        out.println(  g.toJson(  jm  )); ///后端 把 运行情况 以json类型传出到前端
        out.flush();
        out.close();
    }
    protected void writeObj(Object obj, HttpServletResponse resp) throws IOException {
        resp.setContentType("text/json;charset=utf-8");
        PrintWriter out =resp.getWriter();
        //创建一个Gson对象 g,
        Gson g = new Gson();
//      用于将Java对象转换为JSON格式的字符串
        out.print(g.toJson(     obj      ));
        out.flush();
        out.close();
    }

}
聊天内容封装类:
import lombok.Data;
@Data
public class WebSocket {
    private String name;   //判断是管理员还是用户 admin/user
    private String timestamp;//时间戳
    private String content;//聊天内容
}
前后端交互封装类:
import lombok.Data;
import java.io.Serializable;
@Data
public class JsonModel implements Serializable {
    private Integer code;  //响应码  :  0:表示失败  1:表示成功
    private Object obj;
    private String error;
}

源码链接:https://gitee.com/xinqiuuu/websocket.git

在这里插入图片描述

在这里插入图片描述

  • 13
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值