說明:該示例只簡單的實現了客服聊天功能。
1、聊天記錄沒有保存到數據庫中,一旦服務重啟,消息記錄將會沒有,如果需要保存到數據庫中,可以擴展
2、頁面樣式用的網上模板,樣式可以自己進行修改
3、只能由用戶主要發起會話,管理員無法主動進行對話
4、頁面之間跳轉代碼沒有包含在里面,請自己書寫,在管理員消息列表頁中,需要把該咨詢的用戶ID帶到客服回復頁面中
5、${websocket_url} 這個為項目的URL地址
效果截圖:
客服回復頁面(member_admin_chat.html) |
管理員消息列表頁(member_admin_chat_list.html) |
用戶咨詢頁面(member_chat.html) |
![]() |
![]()
|
![]()
|
代碼:
頁面所需要用到的基礎樣式、圖片,下載鏈接:https://www.lanzous.com/ias1kcb (這個只是自己網上下載的樣式demo,可以根據自己的來)
pom.xml
<dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>8.0</version> <scope>provided</scope> </dependency>
或者jar包
javax.websocket-api-1.0.jar
下載地址:https://yvioo.lanzous.com/i3AXkhl3s3c
配置類
WebSocketConfig.java
package com.config; import javax.websocket.Endpoint; import javax.websocket.server.ServerApplicationConfig; import javax.websocket.server.ServerEndpointConfig; import java.util.Set; public class WebSocketConfig implements ServerApplicationConfig { @Override public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> endpointClasses) { return null; } @Override public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> scanned) { //在這里會把含有@ServerEndpoint注解的類掃描加載進來 ,可以在這里做過濾等操作 return scanned; } }
消息DTO類(使用了lombok,這里不在多做說明)
ChatDTO.java
package com.websocket.dto; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; /** * @author 。 */ @Data @Accessors(chain = true) @NoArgsConstructor @AllArgsConstructor public class ChatDTO { /** * 用戶ID */ private String userId; /** * 用戶發送信息 */ private String message; /** * 發送日期 * 消息時間格式(yyyy-MM-dd) */ private String createDate; /** * 發送時間 * 消息時間格式(yyyy-MM-dd HH:mm:ss) */ private String createTime; }
用戶DTO類
ChatUserDTO.java
package com.websocket.dto; import com.entity.CmsUser; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; /** * @author 。 */ @Data @Accessors(chain = true) @NoArgsConstructor @AllArgsConstructor public class ChatUserDTO { /** * 用戶id */ private String userId; /** * 用戶名 */ private String userName; /** * 用戶圖片 */ private String userImg; public ChatUserDTO convertUser(CmsUser user){ ChatUserDTO chatUserDTO=new ChatUserDTO(); chatUserDTO.setUserId(user.getId()+"") .setUserName(user.getUsername()) .setUserImg(user.getUserImg()); return chatUserDTO; } }
ChatWebSocket.java
package com.websocket; import com.service.RedisService; import com.util.DateFormatUtils; import com.entity.CmsUser; import com.manager.CmsUserMng; import com.enums.ChatTypeEnum; import com.websocket.Constants; import com.websocket.dto.ChatDTO; import com.websocket.dto.ChatUserDTO; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.springframework.web.context.ContextLoader; import org.springframework.web.context.WebApplicationContext; import javax.websocket.*; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.*; /** * @author 。 */ @ServerEndpoint(value = "/chat_websocket") public class ChatWebSocket { private RedisService redisService; private CmsUserMng cmsUserMng; public ChatWebSocket() { WebApplicationContext webctx = ContextLoader.getCurrentWebApplicationContext(); this.redisService = (RedisService) webctx.getBean("redisService"); this.cmsUserMng = (CmsUserMng) webctx.getBean("cmsUserMng"); } /** * 存儲用戶id */ public static Map userMap = new HashMap(); /** * 聊天記錄 */ public static Map chatRecordMap = new HashMap(); /** * 管理員列表session */ public static Session adminSession; /** * 創建 * * @param session */ @OnOpen public void onOpen(Session session) { Map<String, List<String>> requestParameterMap = session.getRequestParameterMap(); List<String> strs = requestParameterMap.get("msg"); if (strs != null && strs.size() > 0) { String json = strs.get(0); //從聊天集中去掉該集合 JSONObject object = JSONObject.fromObject(json); String userId = object.getString("user_id"); String chatType = object.getString("chat_type"); /*--------------管理員列表-----------------------*/ if (ChatTypeEnum.adminListChatType.getKey().equalsIgnoreCase(chatType)) { adminSession = session; List list = getUserList(userMap); //遍歷所有聊天用戶集合的id chat_list_show(adminSession, list); return; } /*--------------管理員聊天框打開-----------------------*/ if (ChatTypeEnum.adminChatType.getKey().equalsIgnoreCase(chatType)) { //從集合中獲取用戶對應的數據加入信息列表中 List sessions = (List) userMap.get(userId); if (sessions == null) { sessions = new ArrayList(); } sessions.add(session); userMap.put(userId, sessions); } /*--------------用戶聊天框打開-----------------------*/ if (ChatTypeEnum.userChatType.getKey().equalsIgnoreCase(chatType)) { //判斷是否建立聊天通道 List sessions = (List) userMap.get(userId); if (sessions == null) { sessions = new ArrayList(); } sessions.add(session); userMap.put(userId, sessions); } //聊天記錄信息存放 List chatRecords = (List) chatRecordMap.get(userId); if (chatRecords != null) { chat((List<Session>) userMap.get(userId), chatRecords); } } } /** * 發送消息 * * @param json {userId:'',message:'',create_time:'',create_date:'',chat_type:'admin_list/admin_chat/user_chat'} * admin_list:表示客服列表數據請求 * admin_chat:表示客服回復頁面請求 * user_chat表示用戶消息頁面請求 * * * @throws Exception */ @OnMessage public void onMessage(Session session, String json) { JSONObject object = JSONObject.fromObject(json); //用戶ID String userId = object.getString("user_id"); //用戶發送的信息 String message = object.getString("message"); //請求類型 String chatType = object.getString("chat_type"); /*--------------管理員聊天-----------------------*/ if (ChatTypeEnum.adminChatType.getKey().equalsIgnoreCase(chatType)) { //把管理員加入用戶建立的聊天管道中 //用戶聊天 //封裝請求參數,時間為當前時間 ChatDTO chatDTO = new ChatDTO(); //userId=0表示是客服的回復 chatDTO.setUserId("0") .setMessage(message) .setCreateDate(DateFormatUtils.formatDate(new Date())) .setCreateTime(DateFormatUtils.formatDateTime(new Date())); //聊天記錄信息存放 List chatRecords = (List) chatRecordMap.get(userId); if (chatRecords == null) { chatRecords = new ArrayList(); } chatRecords.add(JSONObject.fromObject(chatDTO)); chatRecordMap.put(userId, chatRecords); chat((List<Session>) userMap.get(userId), chatRecords); } /*--------------用戶聊天-----------------------*/ if (ChatTypeEnum.userChatType.getKey().equalsIgnoreCase(chatType)) { //封裝請求參數,時間為當前時間 ChatDTO chatDTO = new ChatDTO(); chatDTO.setUserId(userId) .setMessage(message) .setCreateDate(DateFormatUtils.formatDate(new Date())) .setCreateTime(DateFormatUtils.formatDateTime(new Date())); String key = chatDTO.getUserId(); //聊天記錄信息存放 List chatRecords = (List) chatRecordMap.get(key); if (chatRecords == null) { chatRecords = new ArrayList(); } chatRecords.add(JSONObject.fromObject(chatDTO)); chatRecordMap.put(key, chatRecords); chat((List<Session>) userMap.get(key), chatRecords); if (adminSession != null) { List list = getUserList(userMap); //遍歷所有聊天用戶集合的id chat_list_show(adminSession, list); } } } /** * 關閉 */ @OnClose public void onClose(Session session) { Map<String, List<String>> requestParameterMap = session.getRequestParameterMap(); List<String> strs = requestParameterMap.get("msg"); if (strs != null && strs.size() > 0) { String json = strs.get(0); JSONObject object = JSONObject.fromObject(json); String userId = object.getString("user_id"); String chatType = object.getString("chat_type"); /*--------------管理員聊天框關閉-----------------------*/ if (ChatTypeEnum.adminChatType.getKey().equalsIgnoreCase(chatType)) { } /*--------------用戶聊天框關閉-----------------------*/ if (ChatTypeEnum.userChatType.getKey().equalsIgnoreCase(chatType)) { } } } /** * 發生錯誤 * * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { System.out.println("發生錯誤"); error.printStackTrace(); } /** * 消息廣播 * * @param sessions * @param messages */ public void chat(List<Session> sessions, List messages) { for (Iterator it = sessions.iterator(); it.hasNext(); ) { Session session = (Session) it.next(); try { if (session.isOpen()) { //當當前會話沒有被關閉 發送消息 session.getBasicRemote().sendText(JSONArray.fromObject(messages) + ""); } } catch (IOException e) { e.printStackTrace(); } } } /** * 聊天列表顯示 */ public void chat_list_show(Session session, List list) { try { if (session.isOpen()) { //當當前會話沒有被關閉 發送消息 session.getBasicRemote().sendText(JSONArray.fromObject(list) + ""); } } catch (IOException e) { e.printStackTrace(); } } /** * 通過id獲取用戶數據 * * @return */ public List getUserList(Map userMap) { List list = new ArrayList(); for (Object str : userMap.keySet()) { ChatUserDTO chatUserDTO = new ChatUserDTO(); CmsUser user = cmsUserMng.findById(Integer.valueOf(str + "")); list.add(chatUserDTO.convertUser(user)); } return list; } }
聊天枚舉類
ChatTypeEnum.java
package com.websocket; /** * @author 。 */ public enum ChatTypeEnum { adminListChatType("admin_list", "管理員列表"), adminChatType("admin_chat", "管理員聊天"), userChatType("user_chat", "用戶聊天"), chatCountType("chat_count","消息數目"); private String key; public String value; ChatTypeEnum() { } ChatTypeEnum(String key, String value) { this.key = key; this.value = value; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } }
用戶咨詢頁面
member_chat.html
1 <!doctype html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"/> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/> 6 <meta name="viewport" 7 content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/> 8 <title>客服咨詢</title> 9 <link rel="stylesheet" type="text/css" href="/${res}/chat/css/chat.css"/> 10 <script src="/${res}/js/jquery.1.9.1.js"></script> 11 <script src="/${res}/chat/js/flexible.js"></script> 12 </head> 13 <body> 14 15 16 <header class="header"> 17 <a class="back" href="javascript:history.back()"></a> 18 <h5 class="tit">客服</h5> 19 </header> 20 <div id="message"> 21 22 </div> 23 <div id="footer"> 24 <img src="/${res}/chat/images/hua.png" alt=""/> 25 <input class="my-input" type="text"/> 26 <p class="send">發送</p> 27 </div> 28 <script> 29 30 //聊天 31 var ws; 32 var obj = { 33 user_id: '${user.id}', 34 message: '', 35 chat_type: "user_chat" 36 } 37 var target = "ws:${websocket_url!}/chat_websocket?msg=" + encodeURI(JSON.stringify(obj)); 38 39 40 var canSend = false; 41 $(function () { 42 43 //處理瀏覽器兼容性 44 if ('WebSocket' in window) { 45 ws = new WebSocket(target); 46 } else if ('MozWebSocket' in window) { 47 ws = new MozWebSocket(target); 48 } else { 49 alert('WebSocket is not supported by this browser.'); 50 return; 51 } 52 53 ws.onopen = function () { 54 55 }; 56 ws.onmessage = function (event) { 57 var data = JSON.parse(event.data); 58 console.log(data) 59 $('#message').html(""); 60 for (var i = 0; i < data.length; i++) { 61 if (data[i].userId != '${user.id}') { 62 reply("/${res}/chat/images/touxiangm.png", data[i].message); 63 } else { 64 ask("/${res}/chat/images/touxiang.png", data[i].message); 65 } 66 } 67 }; 68 69 ws.onclose = function (event) { 70 alert("連接斷開,請重新刷新頁面"); 71 location.reload(); 72 1 73 } 74 $('#footer').on('keyup', 'input', function () { 75 if ($(this).val().length > 0) { 76 $(this).next().css('background', '#114F8E').prop('disabled', true); 77 canSend = true; 78 } else { 79 $(this).next().css('background', '#ddd').prop('disabled', false); 80 canSend = false; 81 } 82 }) 83 $('#footer .send').click(send) 84 $("#footer .my-input").keydown(function (e) { 85 if (e.keyCode == 13) { 86 return send(); 87 } 88 }); 89 }) 90 91 /* 對方消息div */ 92 function reply(headSrc, str) { 93 var html = "<div class='reply'><div class='msg'><img src=" + headSrc + " /><span class='name'>客服</span><p><i class='msg_input'></i>" + str + "</p></div></div>"; 94 return upView(html); 95 } 96 97 /* 自己消息div */ 98 function ask(headSrc, str) { 99 var html = "<div class='ask'><div class='msg'><img src=" + headSrc + " />" + "<p><i class='msg_input'></i>" + str + "</p></div></div>"; 100 return upView(html); 101 } 102 103 function upView(html) { 104 var message = $('#message'); 105 message.append(html); 106 var h = message.outerHeight() - window.innerHeight; 107 window.scrollTo(0, document.body.scrollHeight) 108 return; 109 } 110 111 function send() { 112 if (canSend) { 113 var input = $("#footer .my-input"); 114 var val = input.val() 115 var obj = { 116 user_id: '${user.id}', 117 message: val, 118 chat_type: "user_chat" 119 } 120 ws.send(JSON.stringify(obj)); 121 //ask("/${res}/chat/images/touxiangm.png", val); 122 input.val(''); 123 } 124 } 125 </script> 126 </body> 127 </html>
管理員消息列表頁
member_admin_chat_list.html
<!DOCTYPE html> <html > <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,minimal-ui"> <title>聊天列表 - ${site.name}</title> <script src="${resSys}/jquery.js" type="text/javascript"></script> <script src="${resSys}/front.js" type="text/javascript"></script> <link rel="stylesheet" href="/${res}/bootstrap/css/bootstrap.css"> <script src="/${res}/bootstrap/js/bootstrap.js"></script> <!--[if lt IE 9]> <script src="/${res}/js/html5shiv.min.js"></script> <script src="/${res}/js/respond.min.js"></script> <![endif]--> </head> <style> .list-group-item span{ margin-right: 10px; } </style> <body> [#include "../file/file_nav.html" /] <div> <ul class="list-group" id="userList"> <li class="list-group-item"> <img src="/${res}/chat/images/touxiang.png" alt=""/> <span class="badge">14</span> Cras justo odio </li> </ul> </div> </body> <script> var ws; var obj={ user_id:'', message:'', chat_type:"admin_list" } var target="ws:${websocket_url}/chat_websocket?msg="+encodeURI(JSON.stringify(obj)); $(function () { //處理瀏覽器兼容性 if ('WebSocket' in window) { ws = new WebSocket(target); } else if ('MozWebSocket' in window) { ws = new MozWebSocket(target); } else { alert('WebSocket is not supported by this browser.'); return; } ws.onmessage = function (event) { var data=JSON.parse(event.data); var html=""; if (data!=null&&data.length>0){ for (var i=0;i<data.length;i++){ var user=data[i]; html+="<a href='${base}/member/to_admin_chat_"+user.userId+".jspx'>" html+="<li class='list-group-item'>"; html+="<img src='/${res}/chat/images/touxiang.png' />"; if (user.countmsg!=undefined){ html+="<span class='badge'>"+user.countmsg+"</span>" } html+=user.userName; html+="</li>"; html+="</a>"; } }else { html="<li style='text-align: center'>沒有消息</li>"; } $("#userList").html(html); }; ws.onclose=function (event) { } }) </script> </html>
管理員消息回復頁面
member_admin_chat.html
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"/> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/> <title>客服咨詢</title> <link rel="stylesheet" type="text/css" href="/${res}/chat/css/chat.css"/> <script src="/${res}/js/jquery.1.9.1.js"></script> <script src="/${res}/chat/js/flexible.js"></script> </head> <body> <header class="header"> <a class="back" href="javascript:history.back()"></a> <h5 class="tit">客服</h5> </header> <div id="message"> </div> <div id="footer"> <img src="/${res}/chat/images/hua.png" alt=""/> <input class="my-input" type="text"/> <p class="send">發送</p> </div> <script> //聊天 var ws; var obj = { user_id: '${user.id}', message: '', chat_type: "user_chat" } var target = "ws:${websocket_url!}/chat_websocket?msg=" + encodeURI(JSON.stringify(obj)); var canSend = false; $(function () { //處理瀏覽器兼容性 if ('WebSocket' in window) { ws = new WebSocket(target); } else if ('MozWebSocket' in window) { ws = new MozWebSocket(target); } else { alert('WebSocket is not supported by this browser.'); return; } ws.onopen = function () { }; ws.onmessage = function (event) { var data = JSON.parse(event.data); console.log(data) $('#message').html(""); for (var i = 0; i < data.length; i++) { if (data[i].userId != '${user.id}') { reply("/${res}/chat/images/touxiangm.png", data[i].message); } else { ask("/${res}/chat/images/touxiang.png", data[i].message); } } }; ws.onclose = function (event) { alert("連接斷開,請重新刷新頁面"); location.reload(); } $('#footer').on('keyup', 'input', function () { if ($(this).val().length > 0) { $(this).next().css('background', '#114F8E').prop('disabled', true); canSend = true; } else { $(this).next().css('background', '#ddd').prop('disabled', false); canSend = false; } }) $('#footer .send').click(send) $("#footer .my-input").keydown(function (e) { if (e.keyCode == 13) { return send(); } }); }) /* 對方消息div */ function reply(headSrc, str) { var html = "<div class='reply'><div class='msg'><img src=" + headSrc + " /><span class='name'>客服</span><p><i class='msg_input'></i>" + str + "</p></div></div>"; return upView(html); } /* 自己消息div */ function ask(headSrc, str) { var html = "<div class='ask'><div class='msg'><img src=" + headSrc + " />" + "<p><i class='msg_input'></i>" + str + "</p></div></div>"; return upView(html); } function upView(html) { var message = $('#message'); message.append(html); var h = message.outerHeight() - window.innerHeight; window.scrollTo(0, document.body.scrollHeight) return; } function send() { if (canSend) { var input = $("#footer .my-input"); var val = input.val() var obj = {user_id: '${user.id}', message: val, chat_type: "user_chat"} ws.send(JSON.stringify(obj)); //ask("/${res}/chat/images/touxiangm.png", val); input.val(''); } } </script> </body> </html>