JAVA结合WebSocket实现简单客服聊天功能
说明:该示例只简单的实现了客服聊天功能。
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>
-----------------------有任何问题可以在评论区评论,也可以私信我,我看到的话会进行回复,欢迎大家指教------------------------
(蓝奏云官网有些地址失效了,需要把请求地址lanzous改成lanzoux才可以)