WebSocket實現客服在線實時聊天(單聊模式)


本篇文章講述如何將WebSocket群聊模式改造為單聊模式,並詳細講解該功能的實現過程。

Demo是一個商城小項目,將單聊應用在了前台用戶與后台客服的在線聊天,此模式為單聊模式,代碼都是在我之前寫的websocket群聊代碼的基礎之上改寫的,可參考之前的文章

一,思路梳理

1,首先思考群聊的實現方式。

每當一個用戶使用websocket建立連接時,都會存放一個連接對象(在connectMap集合存放,鍵為sessionId,值為該連接對象),每次當用戶發送一條數據時,都會遍歷connectMap中所有的連接對象,然后進行廣播發送消息,接收消息也是,誰的消息都收,來之不拒,所以此為群聊。

2,再來舉一反三,思考單聊的實現方式。

單聊的話,要改變下面幾個地方。

1,存放connectMap連接對象時,值要為userId(唯一標識)。

2,使用websocket建立連接時要把自己的userId(發送人)和對方的userId(接收人)傳到后台,因為發消息時,根據userId得到連接對象,並使用連接對象發消息到對方的窗口和自己的窗口。

3,在js獲取消息的地方要判斷該消息的發送人userId是否和初始化時的接收人userId一致,如果一致的話則將消息添加到自己的窗口進行顯示,如果不一致則不顯示。

有一個地方需要提一下,比如要給A用戶發送消息,就要根據A的userId得到連接對象,然后就會把消息發送給A用戶。

二,代碼分析

1,首先是用戶聊天框的js代碼(html忽略)

此為用戶點擊在線客服按鈕進入聊天框頁面就執行的js代碼。

說明:下面接收人id寫死了為超級管理員,我這里省事情了,因為超級管理員的賬號(也就是唯一標識)就是超級管理員這五個字,當真正開發時就要動態獲取唯一標識了!

<script type="text/javascript">
	//1,獲取連接,new WebSocket()
	//獲取到路徑url的參數(用戶的手機號,這里將手機號作為了用戶的唯一標識)
    var accounta=window.location.search;
    var account=accounta.slice(9);
    //發送人id
    var sb=account;
	//將發送人id參數傳給服務端
	var wsUrl="ws://127.0.0.1:8082/charRoomServer";
	var allUrl=wsUrl+"/"+sb;
	//接收人id
	var jsrid='超級管理員';
	//客戶端與服務端建立連接,建立連接以后,它會發出一個ws.open事件
	var ws=new WebSocket(allUrl);
	//連接成功后就將接收人id發送給服務端
	ws.onopen=function(){
		ws.send(jsrid);
	}
	//客戶端收到服務端發送的消息
	ws.onmessage=function(message){
	    var shenfen=JSON.stringify(message.data).toString();
	    var shenfens=shenfen.slice(shenfen.lastIndexOf("|")+1,-1);
     //這里通過很low的方式拿到了發送人id  [shenfens](后台拼接字符串並前台截取得到)   
     //判斷發送人id是否和頁面初始化時的接收人id(jsrid)是否一致,相同則說明是對方回復的消息,並將該消息添加到自己的聊天框;如果收到的發送人id與“大白機器人”或者是和自己的id一致,也要將消息添加到自己的聊天框(自己發的消息嘛)
        if(jsrid==shenfens||shenfens=="大白機器人:"||shenfens==sb){
            //獲取以后,在客戶端顯示
            messages.innerHTML+=message.data;
        }else{
            //不做任何操作
        }
	}
	//獲取某個用戶輸入的聊天內容,並發送到服務端
	 function getMessage(){
		var inputMessage=document.getElementById("inputMessage").value;
		//alert(inputMessage);
		//獲取消息以后,要發送給服務端,然后廣播給所有用戶
		if(typeof(inputMessage)=='undefined'){
			alert("請輸入您要發送的消息!");
		}else{
			ws.send(inputMessage);
			//輸入框消息清空
			inputMessage.value="";
		}	
	} 
	//當關閉頁面或者用戶退出時,會執行一個ws.close()方法
	window.onbeforeunload=function(){
		ws.close();
	}
	//按回車發送信息
	document.onkeyup=function(e){
		if(e.keyCode==13){
			getMessage();
			inputMessage.value="";
		}
	}
</script>

2,其次為后台系統客服聊天框的js代碼

<script>
        var name="";
        $(function () {
            $.post(
                "/demo/getName",
                function (data) {
                    name=data.name;
                },
                "json"
            );
 //得到未讀消息並展示到列表中(當點擊其中一個未讀消息時會得到該消息的賬號(前台用戶的userId唯一標識))
            $.post(
                "/demo/getweidu",
                function (data) {
                    var temp='';
                    var count=0;
                    var account='';
                    for(var i=0;i<data.length;i++){
                        temp+='<div class="yonghu" onclick="liaotian('+data[i].account+')">\n' +
                            '            <span id="lalala">'+data[i].account+'</span>\n' +
                            '            <div id="'+data[i].account+'" class="weidu">'+data[i].count+'</div>\n' +
                            '        </div>';
                    }
                    $("#nav").append(temp);
                }
            );
        });

//這是點擊對應的未讀消息之后將未讀消息展示到客服的聊天框
        var sb=null;
        function liaotian(v) {  //下面的websocket內容在這個方法里面,執行這個點擊事件之后觸發websocket連接
            sb=v;
            $("#content").empty();
            $("#"+v).hide();
            $.post(
                "/demo/getxiaoxi",
                {account:v},
                function (data) {
                    // alert(JSON.stringify(data));
                    var str='';
                    for(var i=0;i<data.length;i++){
                        var time=data[i].addtime.slice(0,10);
                        str+=' <div class="message"><span>'+data[i].account+'</span>用戶:<span>'+time+'</span>'+data[i].message+'</div>';
                    }
                    $("#content").append(str);
                },
                "json"
            );
            //將點擊后的未讀消息改為已讀消息
            $.post(
                "/demo/changeYD",
                {account:v},
                function (data) {
                    console.log(data);
                }
            );
            //發送人id(超級管理員寫死的,上面說過了)
            var wsUrl="ws://127.0.0.1:8082/charRoomServer";
            var allUrl=wsUrl+"/"+"超級管理員";
            //客戶端與服務端建立連接,建立連接以后,它會發出一個ws.open事件
            var ws=new WebSocket(allUrl);
            //接收人userId
            var jsrid=sb;
            //連接成功后,提示瀏覽器客戶端輸入名稱
            ws.onopen=function(){
                ws.send(jsrid);
            }
            //客戶端收到服務端發送的消息
            ws.onmessage=function(message){
                //截取到普通用戶的手機號
                var shenfen=JSON.stringify(message.data).toString();
                var shenfens=shenfen.slice(shenfen.lastIndexOf("|")+1,-1);
                 console.log("身份:"+shenfens);
                // //判斷發送人id是否和頁面初始化時的接收人id(jsrid)是否一致,相同則說明是對方回復的消息
                if(jsrid==shenfens||"超級管理員"==shenfens){
                    //獲取以后,在客戶端顯示
                    content.innerHTML+=message.data;
                }else{
                    //不做任何操作
                }
            }
            //獲取某個用戶輸入的聊天內容,並發送到服務端
            function getMessage(){
                var inputMessage=document.getElementById("inputMessage").value;
                //alert(inputMessage);
                //獲取消息以后,要發送給服務端,然后廣播給所有用戶
                if(typeof(inputMessage)=='undefined'){
                    alert("請輸入您要發送的消息!");
                }else{
                    ws.send(inputMessage);
                    //輸入框消息清空
                    inputMessage.value="";
                }
            }
            //當關閉頁面或者用戶退出時,會執行一個ws.close()方法
             window.onbeforeunload=function(){
                 ws.close();
             }
            //按回車發送信息
            document.onkeyup=function(e){
                if(e.keyCode==13){
                    getMessage();
                    inputMessage.value="";
                }
            }
        }
    </script>

3,最后為服務端的websocket實例

package com.qianlong.controller;

import com.qianlong.service.SelectService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
/**
 * 聊天室的服務端程序
 * @author Administrator
 *
 */
//聲明websocket某個服務端的地址
@ServerEndpoint(value = "/charRoomServer/{param}")
@Component
public class ChatRoomServer {
    @Autowired
    public static SelectService selectService;
    private boolean firstFlag=true;
    private Session session;
    private String userName;

    //發送人id
    private String userId;
    //key代表此次客戶端的userId,value代表此次連接對象
    private static final HashMap<String, Object> connectMap=new HashMap<String, Object>();
    //保存所有用戶昵稱信息
    //key是session的id,value是用戶昵稱
    private static final HashMap<String, String> userMap=new HashMap<String, String>();
    //服務端收到客戶端的連接請求,連接成功后會執行此方法
    @OnOpen
    public void start(@PathParam(value = "param") String param, Session session) {
        this.session=session;
        this.userId=param; //接收參數
        connectMap.put(param,this);
    }

    //客戶端發來的信息,服務端接收
    @OnMessage              //接收人userId
    public void chat(String clientMessage,Session session) {
        //firstFlag為true是第一次進入,第二次進入之后設為false
        ChatRoomServer client=null;
        if(firstFlag) {
            this.userName=clientMessage;
            //將新進來的用戶保存到用戶map
            userMap.put(session.getId(), userName);

            try {
               if("超級管理員".equals(userId)){

               }else{
                   //構造發送給客戶端的提示信息
                   String message=htmlMessage("大白機器人:","親愛的"+userId+",您想了解點兒啥?");
                   client=(ChatRoomServer) connectMap.get(userId);
                   //給對應的web端發送一個文本信息
                   client.session.getBasicRemote().sendText(message);
               }
            } catch (IOException e) {
                e.printStackTrace();
            }
            firstFlag=false;
        }else {
            System.err.println("clientMessage:"+userName);
            //給對方發消息
            String message1=htmlMessage(userId,clientMessage);
            client  = (ChatRoomServer) connectMap.get(userName);
           if(client!=null){
               try {
                   client.session.getBasicRemote().sendText(message1);
               } catch (IOException e) {
                   e.printStackTrace();
               }
           }
            //給自己窗口發消息
            String message2=htmlMessage(userId,clientMessage);
            client  = (ChatRoomServer) connectMap.get(userId);
            try {
                client.session.getBasicRemote().sendText(message2);
            } catch (IOException e) {
                e.printStackTrace();
            }
           
            //這是將前台用戶發送的消息存數據庫並標記為未讀,和上面通信沒關系
            if("超級管理員".equals(userId)){

            }else{
                Map map=new HashMap();
                map.put("account",userId);
                map.put("message",clientMessage);
                map.put("addtime",new Date());
                int i = selectService.chatInsert(map);
                System.out.println(i);
            }
        }
        }
    
    /**
     * 前台js的ws.close事件,會觸發后台的標注onClose的方法
     */
    @OnClose
    public void close() {
        userMap.remove(session.getId());
        connectMap.remove(userId);
    }
    /**
     * 渲染頁面,把信息構造好標簽再發送
     */
    public String htmlMessage(String userName,String message) {
        StringBuffer stringBuffer=new StringBuffer();
        SimpleDateFormat sf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        stringBuffer.append("<article>");
        stringBuffer.append("<span>"+sf.format(new Date())+"</span>");
        stringBuffer.append("<div class='avatar'>");
        stringBuffer.append("<h3>"+userName+"</h3>");
        stringBuffer.append("</div>");
        stringBuffer.append("<div class='msg'>");
        stringBuffer.append("<div class='tri'></div>");
        stringBuffer.append("<div class='msg_inner'>"+message+"</div>");
        stringBuffer.append("</div>");
        stringBuffer.append("</article>");
        //這里拼接了消息發送人的userId,在前台進行截取字符串接收發送人的userId
        stringBuffer.append("|"+userName);
        return stringBuffer.toString();
    }
}

三,依賴注入

websocket中進行依賴注入service並調用service方法進行數據庫存儲,如果按常規的方式是走不通的。

解決方式:

在該springboot項目中添加一個WebsocketConfig配置類,對service進行配置。

@Configuration
public class WebSocketConfig {
    /**
     * ServerEndpointExporter 用於掃描和注冊所有攜帶 ServerEndPoint 注解的實例,
     * 若部署到外部容器 則無需提供此類。
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

    /**
     * 因 SpringBoot WebSocket 對每個客戶端連接都會創建一個 WebSocketServer(@ServerEndpoint 注解對應的對象,Bean 注入操作會被直接略過,因而手動注入一個全局變量
     */
    @Autowired
    public void setSelectService(SelectService selectService) {
        ChatRoomServer.selectService = selectService;
    }
}

然后在websocket中注入service

在這里插入圖片描述

在這里插入圖片描述

四,效果展示

在前台商城登錄用戶18838030468賬號

點擊聯系客服

在這里插入圖片描述

進到聊天框中(左下角的用戶名忽略,因為上面通過截取字符串得到的發送人userId,所以這里多了一個userId)

在這里插入圖片描述

然后發送消息給客服

在這里插入圖片描述

然后退出18838030468賬號並切換用戶13333333333賬號進行登錄,然后點擊客服,進入聊天頁面

在這里插入圖片描述

然后發送消息給客服

在這里插入圖片描述

這個13333333333賬號先不要退,直接登錄后台超級管理員賬號

登錄之后會提示未讀消息的信息。

點擊客服管理,進入客服聊天平台,並顯示消息未讀數。

在這里插入圖片描述

點擊18838030468未讀消息,查看消息並進行回復。(這是給18838030468用戶發送的消息,其他用戶是接收不到的,正規開發中,要把客服發送的消息存到緩存或數據庫中,然后再在前台用戶聊天框中進行顯示聊天記錄即可,這里忽略不做)

在這里插入圖片描述

然后再點擊13333333333未讀消息進行查看,並回復消息。

在這里插入圖片描述

這是給13333333333用戶發送的消息,其他用戶也是收不到的。

查看13333333333用戶的聊天框內容,可以收到客服的消息,然后該用戶可以和客服實現在線單聊。

在這里插入圖片描述

本次websocket講解到此結束,謝謝觀賞!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM