前端:
<html> <head> <title>WebChat | 聊天</title> <!--<script src="/plugins/sockjs/sockjs.js"></script>--> <link rel="stylesheet" href="plugins/amaze/css/amazeui.min.css"> <link rel="stylesheet" href="/plugins/amaze/css/admin.css"> <link rel="stylesheet" href="/plugins/contextjs/css/context.standalone.css"> <script src="/plugins/jquery/jquery-2.1.4.min.js"></script> <script src="/plugins/amaze/js/amazeui.min.js"></script> <script src="/plugins/amaze/js/app.js"></script> <script src="/plugins/layer/layer.js"></script> <script src="/plugins/laypage/laypage.js"></script> <script src="/plugins/contextjs/js/context.js"></script> </head> <body> <div id="header"></div> <div class="am-cf admin-main"> <div id="sidebar"></div> <!-- content start --> <div class="admin-content"> <div class="" style="width: 80%;float:left;"> <!-- 聊天区 --> <div class="am-scrollable-vertical" id="chat-view" style="height: 510px;"> <ul class="am-comments-list am-comments-list-flip" id="chat"> </ul> </div> <!-- 输入区 --> <div class="am-form-group am-form"> <textarea class="" id="message" name="message" rows="5" placeholder="这里输入你想发送的信息..."></textarea> </div> <!-- 接收者 --> <div class="" style="float: left"> <p class="am-kai">发送给 : <span id="sendto">全体成员</span><button class="am-btn am-btn-xs am-btn-danger" onclick="$('#sendto').text('全体成员')">复位</button></p> </div> <!-- 按钮区 --> <div class="am-btn-group am-btn-group-xs" style="float:right;"> <button class="am-btn am-btn-default" type="button" onclick="getConnection()"><span class="am-icon-plug"></span> 连接</button> <button class="am-btn am-btn-default" type="button" onclick="closeConnection()"><span class="am-icon-remove"></span> 断开</button> <button class="am-btn am-btn-default" type="button" onclick="checkConnection()"><span class="am-icon-bug"></span> 检查</button> <button class="am-btn am-btn-default" type="button" onclick="clearConsole()"><span class="am-icon-trash-o"></span> 清屏</button> <button class="am-btn am-btn-default" type="button" onclick="sendMessage()"><span class="am-icon-commenting"></span> 发送</button> </div> </div> <!-- 列表区 --> <div class="am-panel am-panel-default" style="float:right;width: 20%;"> <div class="am-panel-hd"> <h3 class="am-panel-title">在线列表 [<span id="onlinenum"></span>]</h3> </div> <ul class="am-list am-list-static am-list-striped" > <li>图灵机器人 <button class="am-btn am-btn-xs am-btn-danger" id="tuling" data-am-button>未上线</button></li> </ul> <ul class="am-list am-list-static am-list-striped" id="list"> </ul> </div> </div> <!-- content end --> </div> <a href="#" class="am-show-sm-only admin-menu" data-am-offcanvas="{target: '#admin-offcanvas'}"> <span class="am-icon-btn am-icon-th-list"></span> </a> <div id="footer"> <footer style="text-align: center"> <hr> <p class="am-padding-left">© 2016 <a href="http://www.amayadream.com">Amayadream</a>. </p> </footer> </div> <script> var userId=""; $(function () { context.init({preventDoubleContext: false}); context.settings({compress: true}); context.attach('#chat-view', [ {header: '操作菜单',}, {text: '清理', action: clearConsole}, {divider: true}, { text: '选项', subMenu: [ {header: '连接选项'}, {text: '检查', action: checkConnection}, {text: '连接', action: getConnection}, {text: '断开', action: closeConnection} ] }, { text: '销毁菜单', action: function (e) { e.preventDefault(); context.destroy('#chat-view'); } } ]); }); if("${message}"){ layer.msg('${message}', { offset: 0 }); } if("${error}"){ layer.msg('${error}', { offset: 0, shift: 6 }); } $.ajax({ url: "/sys/user/", type: 'GET', cache: false, data: "", processData: false, contentType: false, success: function (data) { console.log(data) userId=data.toString(); }, error: function (data) { alert("Connection error:" + data); } }) $("#tuling").click(function(){ var onlinenum = $("#onlinenum").text(); if($(this).text() == "未上线"){ $(this).text("已上线").removeClass("am-btn-danger").addClass("am-btn-success"); showNotice("图灵机器人加入聊天室"); $("#onlinenum").text(parseInt(onlinenum) + 1); } else{ $(this).text("未上线").removeClass("am-btn-success").addClass("am-btn-danger"); showNotice("图灵机器人离开聊天室"); $("#onlinenum").text(parseInt(onlinenum) - 1) } }); var wsServer = null; wsServer = "ws://localhost:8080/chatServer"; var ws = new WebSocket(wsServer); //创建WebSocket对象 ws.onopen = function (evt) { console.log(evt) layer.msg("已经建立连接", { offset: 0}); }; ws.onmessage = function (evt) { analysisMessage(evt.data); //解析后台传回的消息,并予以展示 }; ws.onerror = function (evt) { layer.msg("产生异常", { offset: 0}); }; ws.onclose = function (evt) { layer.msg("已经关闭连接", { offset: 0}); }; /** * 连接 */ function getConnection(){ if(ws == null){ ws = new WebSocket(wsServer); //创建WebSocket对象 ws.onopen = function (evt) { layer.msg("成功建立连接!", { offset: 0}); }; ws.onmessage = function (evt) { analysisMessage(evt.data); //解析后台传回的消息,并予以展示 }; ws.onerror = function (evt) { layer.msg("产生异常", { offset: 0}); }; ws.onclose = function (evt) { layer.msg("已经关闭连接", { offset: 0}); }; }else{ layer.msg("连接已存在!", { offset: 0, shift: 6 }); } } /** * 关闭连接 */ function closeConnection(){ if(ws != null){ ws.close(); ws = null; $("#list").html(""); //清空在线列表 layer.msg("已经关闭连接", { offset: 0}); }else{ layer.msg("未开启连接", { offset: 0, shift: 6 }); } } /** * 检查连接 */ function checkConnection(){ if(ws != null){ layer.msg(ws.readyState == 0? "连接异常":"连接正常", { offset: 0}); }else{ layer.msg("连接未开启!", { offset: 0, shift: 6 }); } } /** * 发送信息给后台 */ function sendMessage(){ console.log("sendMessage()"+userId) if(ws == null){ layer.msg("连接未开启!", { offset: 0, shift: 6 }); return; } var message = $("#message").val(); var to = $("#sendto").text() == "全体成员"? "": $("#sendto").text(); if(message == null || message == ""){ layer.msg("请不要惜字如金!", { offset: 0, shift: 6 }); return; } $("#tuling").text() == "已上线"? tuling(message):console.log("图灵机器人未开启"); //检测是否加入图灵机器人 ws.send(JSON.stringify({ message : { content : message, //from : '${userid}', from :userId, to : to, //接收人,如果没有则置空,如果有多个接收人则用,分隔 time : getDateFull() }, type : "message" })); } /** * 解析后台传来的消息 * "massage" : { * "from" : "xxx", * "to" : "xxx", * "content" : "xxx", * "time" : "xxxx.xx.xx" * }, * "type" : {notice|message}, * "list" : {[xx],[xx],[xx]} */ function analysisMessage(message){ message = JSON.parse(message); if(message.type == "message"){ //会话消息 showChat(message.message); } if(message.type == "notice"){ //提示消息 showNotice(message.message); } if(message.list != null && message.list != undefined){ //在线列表 showOnline(message.list); } } /** * 展示提示信息 */ function showNotice(notice){ $("#chat").append("<div><p class=\"am-text-success\" style=\"text-align:center\"><span class=\"am-icon-bell\"></span> "+notice+"</p></div>"); var chat = $("#chat-view"); chat.scrollTop(chat[0].scrollHeight); //让聊天区始终滚动到最下面 } /** * 展示会话信息 */ function showChat(message){ var to = message.to == null || message.to == ""? "全体成员" : message.to; //获取接收人 // var isSef = '${userid}' == message.from ? "am-comment-flip" : ""; //如果是自己则显示在右边,他人信息显示在左边 var isSef = userId == message.from ? "am-comment-flip" : ""; //如果是自己则显示在右边,他人信息显示在左边 var html = "<li class=\"am-comment "+isSef+" am-comment-primary\"><a href=\"#link-to-user-home\"><img width=\"48\" height=\"48\" class=\"am-comment-avatar\" alt=\"\" src=\"${ctx}/"+message.from+"/head\"></a><div class=\"am-comment-main\">\n" + "<header class=\"am-comment-hd\"><div class=\"am-comment-meta\"> <a class=\"am-comment-author\" href=\"#link-to-user\">"+message.from+"</a> 发表于<time> "+message.time+"</time> 发送给: "+to+" </div></header><div class=\"am-comment-bd\"> <p>"+message.content+"</p></div></div></li>"; $("#chat").append(html); $("#message").val(""); //清空输入区 var chat = $("#chat-view"); chat.scrollTop(chat[0].scrollHeight); //让聊天区始终滚动到最下面 } /** * 展示在线列表 */ function showOnline(list){ $("#list").html(""); //清空在线列表 $.each(list, function(index, item){ //添加私聊按钮 var li = "<li>"+item+"</li>"; if(userId != item){ //排除自己 li = "<li>"+item+" <button type=\"button\" class=\"am-btn am-btn-xs am-btn-primary am-round\" onclick=\"addChat('"+item+"');\"><span class=\"am-icon-phone\"><span> 私聊</button></li>"; } $("#list").append(li); }); $("#onlinenum").text($("#list li").length); //获取在线人数 } /** * 图灵机器人 * @param message */ function tuling(message){ var userid=this.userId; var html; $.getJSON("http://www.tuling123.com/openapi/api?key=6ad8b4d96861f17d68270216c880d5e3&info=" + message,function(data){ if(data.code == 100000){ html = "<li class=\"am-comment am-comment-primary\"><a href=\"#link-to-user-home\"><img width=\"48\" height=\"48\" class=\"am-comment-avatar\" alt=\"\" src=\"${ctx}/static/img/robot.jpg\"></a><div class=\"am-comment-main\">\n" + "<header class=\"am-comment-hd\"><div class=\"am-comment-meta\"> <a class=\"am-comment-author\" href=\"#link-to-user\">Robot</a> 发表于<time> "+getDateFull()+"</time> 发送给: "+userId+"</div></header><div class=\"am-comment-bd\"> <p>"+data.text+"</p></div></div></li>"; } if(data.code == 200000){ html = "<li class=\"am-comment am-comment-primary\"><a href=\"#link-to-user-home\"><img width=\"48\" height=\"48\" class=\"am-comment-avatar\" alt=\"\" src=\"${ctx}/static/img/robot.jpg\"></a><div class=\"am-comment-main\">\n" + "<header class=\"am-comment-hd\"><div class=\"am-comment-meta\"> <a class=\"am-comment-author\" href=\"#link-to-user\">Robot</a> 发表于<time> "+getDateFull()+"</time> 发送给: "+userId+"</div></header><div class=\"am-comment-bd\"> <p>"+data.text+"</p><a href=\""+data.url+"\" target=\"_blank\">"+data.url+"</a></div></div></li>"; } $("#chat").append(html); var chat = $("#chat-view"); chat.scrollTop(chat[0].scrollHeight); $("#message").val(""); //清空输入区 }); } /** * 添加接收人 */ function addChat(user){ var sendto = $("#sendto"); var receive = sendto.text() == "全体成员" ? "" : sendto.text() + ","; if(receive.indexOf(user) == -1){ //排除重复 sendto.text(receive + user); } } /** * 清空聊天区 */ function clearConsole(){ $("#chat").html(""); } function appendZero(s){return ("00"+ s).substr((s+"").length);} //补0函数 function getDateFull(){ var date = new Date(); var currentdate = date.getFullYear() + "-" + appendZero(date.getMonth() + 1) + "-" + appendZero(date.getDate()) + " " + appendZero(date.getHours()) + ":" + appendZero(date.getMinutes()) + ":" + appendZero(date.getSeconds()); return currentdate; } </script> </body> </html>
后端:
@ServerEndpoint(value = "/chatServer",configurator = HttpSessionConfigurator.class) @Component public class ChatServer { private static int onlineCount = 0; //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 private static CopyOnWriteArraySet<ChatServer> webSocketSet = new CopyOnWriteArraySet<ChatServer>(); private Session session; //与某个客户端的连接会话,需要通过它来给客户端发送数据 private String userId; //用户名 public HttpSession httpSession; //request的session public HttpServletRequest requestSession; //request的session private static List list = new ArrayList<>(); //在线列表,记录用户名称 private static Map routetab = new HashMap<>(); //用户名和websocket的session绑定的路由表 /** * 连接建立成功调用的方法 * @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据 */ @OnOpen public void onOpen(Session session, EndpointConfig config){ this.session = session; webSocketSet.add(this); //加入set中 addOnlineCount(); //在线数加1; this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName()); this.userId=(String) httpSession.getAttribute("userId"); //获取当前用户 list.add(userId); //将用户名加入在线列表 routetab.put(userId, session); //将用户名和session绑定到路由表 String message = getMessage("[" + userId + "]加入聊天室,当前在线人数为"+getOnlineCount()+"位", "notice", list); broadcast(message); //广播 } /** * 连接关闭调用的方法 */ @OnClose public void onClose(){ webSocketSet.remove(this); //从set中删除 subOnlineCount(); //在线数减1 list.remove(userId); //从在线列表移除这个用户 routetab.remove(userId); String message = getMessage("[" + userId +"]离开了聊天室,当前在线人数为"+getOnlineCount()+"位", "notice", list); broadcast(message); //广播 } /** * 接收客户端的message,判断是否有接收人而选择进行广播还是指定发送 * @param _message 客户端发送过来的消息 */ @OnMessage public void onMessage(String _message) { JSONObject chat = JSON.parseObject(_message); JSONObject message = JSON.parseObject(chat.get("message").toString()); if(message.get("to") == null || message.get("to").equals("")){ //如果to为空,则广播;如果不为空,则对指定的用户发送消息 broadcast(_message); }else{ String [] userlist = message.get("to").toString().split(","); singleSend(_message, (Session) routetab.get(message.get("from"))); //发送给自己,这个别忘了 for(String user : userlist){ if(!user.equals(message.get("from"))){ singleSend(_message, (Session) routetab.get(user)); //分别发送给每个指定用户 } } } } /** * 发生错误时调用 * @param error */ @OnError public void onError(Throwable error){ error.printStackTrace(); } /** * 广播消息 * @param message */ public void broadcast(String message){ for(ChatServer chat: webSocketSet){ try { chat.session.getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); continue; } } } /** * 对特定用户发送消息 * @param message * @param session */ public void singleSend(String message, Session session){ try { session.getBasicRemote().sendText(message); } catch (IOException e) { e.printStackTrace(); } } /** * 组装返回给前台的消息 * @param message 交互信息 * @param type 信息类型 * @param list 在线列表 * @return */ public String getMessage(String message, String type, List list){ JSONObject member = new JSONObject(); member.put("message", message); member.put("type", type); member.put("list", list); return member.toString(); } public int getOnlineCount() { return onlineCount; } public void addOnlineCount() { ChatServer.onlineCount++; } public void subOnlineCount() { ChatServer.onlineCount--; } }
HttpSessionConfigurator:
public class HttpSessionConfigurator extends ServerEndpointConfig.Configurator { @Override public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response){ HttpSession httpSession = (HttpSession)request.getHttpSession(); config.getUserProperties().put(HttpSession.class.getName(),httpSession); } }
WebSocketConfig:
@Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter(){ return new ServerEndpointExporter(); } }