初次学习WebSocket。在本次写的WebSocket Demo里使用到了以下插件(技术):javascript
1.百度的富文本编辑器:http://ueditor.baidu.com/website/php
2.Spring对WebSocket的支持:http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#websocketcss
3.JQuery的EasyUI:http://www.jeasyui.com/documentation/index.phphtml
4.Jackson技术:http://www.javashuo.com/article/p-rsmavewr-v.html前端
5.参考博客:http://www.xdemo.org/spring-websocket-comet/java
http://blog.csdn.net/linlzk/article/details/51086545jquery
http://www.cnblogs.com/davidwang456/p/4786636.htmlgit
Notice:这个Demo是在我之前作的一个关于CRUD的 Demo的基础上增长的聊天的功能。本博客将不会再针对聊天功能之外的功能作阐述,仅仅针对增长的聊天功能作阐述。关于CRUD部分感兴趣的能够参见该片博客:http://www.javashuo.com/article/p-tozqupup-e.html,github
此Demo后端采用SSM框架。web
此Demo最终的效果图以下(主要作后台,UI难看, [2016-12-22]修改了一下界面可能不同),
上线的网址:http://www.flyinghe.cn/IMSystem/
此项目Github地址:https://github.com/FlyingHe/IMSystem
如下列出具体代码和步骤(代码中注释详尽):
后端代码:涉及到的domain类,DAO层代码,Service层代码,数据库表的设计本博客不阐述。只给出涉及WebSocket的代码和Action层代码,完成代码能够参考以上提供的Github地址。
一.导入Jar包,只列出websocket的Jar包。
<!--Spring WebSocket--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>4.2.4.RELEASE</version> </dependency>
二。配置文件
SpringWebSocket的分配置文件(不要忘了在Spring总配置文件中import此分配置文件):
<beans xmlns = "http://www.springframework.org/schema/beans" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" xmlns:websocket = "http://www.springframework.org/schema/websocket" xsi:schemaLocation = " http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/websocket http://www.springframework.org/schema/websocket/spring-websocket.xsd"> <websocket:handlers> <!--配置匹配talkWebSoketHandler处理器的路径--> <websocket:mapping path = "/easyui/talk" handler = "talkWebSoketHandler" /> <!--配置HandShake拦截器--> <websocket:handshake-interceptors> <ref bean = "handShake" /> </websocket:handshake-interceptors> </websocket:handlers> </beans>
三。Java文件:
HandShake.java:
package at.flying.websocket; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.stereotype.Component; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.HandshakeInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.util.Map; @Component("handShake") public class HandShake implements HandshakeInterceptor { @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception { /*获取从客户端传来的用户的ID值,此ID值做为存储登陆用户的Key*/ Long id = Long.valueOf(((ServletServerHttpRequest) request).getServletRequest().getParameter("id")); /*session.getAttributes()返回的Map就是这里的attributes, 因此将id存入attributes里,后面再Handler里能够经过session获取到该值 */ attributes.put("id", id); /*返回true以执行下一个拦截器*/ return true; } @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { } }
TalkWebSoketHandler.java
package at.flying.websocket; import at.flying.domain.MsgType; import at.flying.domain.TalkMsg; import at.flying.service.TalkMsgService; import at.flying.service.TalkerService; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.StringRequestEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; import java.io.IOException; import java.util.*; /** * Created by FlyingHe on 2016/9/16. */ @Component("talkWebSoketHandler") public class TalkWebSoketHandler extends TextWebSocketHandler { @Autowired private TalkMsgService talkMsgService; @Autowired private TalkerService talkerService; /*存储创建的全部链接*/ private static Map<Long, WebSocketSession> sessions = new HashMap<>(); /*用于处理Json数据*/ private static ObjectMapper objectMapper = new ObjectMapper(); /*获取当前创建的全部链接*/ public static Map<Long, WebSocketSession> getSessions() { return sessions; } /*客户端与服务器端链接创建以后执行的函数*/ @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { System.out.println("afterConnectionEstablished"); if (session.getAttributes().get("id") == null) { session.close(); return; } /*获取当前用户的ID*/ Long id = (Long) session.getAttributes().get("id"); /*存储当前用户创建的连接,即会话session*/ sessions.put(id, session); System.out.println("用户" + id + "一上线"); /*通知全部登陆用户客户端更新在线列表*/ this.updateTalkersOnline(); } /*客户端向服务器发送信息时会调用该函数*/ @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { /*获取信息内容*/ String jsonstr = message.getPayload(); /*获取该信息类型以选择处理该信息的方式*/ String type = objectMapper.readTree(jsonstr).get("type").asText(); if (type.equalsIgnoreCase(MsgType.LOGOFF_TALKER)) { /*此类型表示该用户要下线*/ this.userLogoff(session, message); } else if (type.equalsIgnoreCase(MsgType.SEND_MESSAGE)) { /*此类型表示该用户要发送信息给别人*/ this.sendMsg(session, message); } else if (type.equalsIgnoreCase(MsgType.SEND_MESSAGE_TO_Robot)) { /*此类表示要将信息发送给机器人*/ this.sendMsgToRobot(session, message); } } /*链接发生异常时执行的函数*/ @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { this.removeAndCloseSession(null, session); } /*链接关闭后执行的函数*/ @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { this.removeAndCloseSession(null, session); /*用户下线时,通知全部登陆用户客户端更新在线列表*/ this.updateTalkersOnline(); } /*移除并关闭session*/ private void removeAndCloseSession(Long id, WebSocketSession session) throws IOException { if (id != null && sessions.containsKey(id)) { WebSocketSession s = sessions.remove(id); if (s.isOpen()) { s.close(); } } else { Long idd = null; if (session.getAttributes().get("id") instanceof Long) { idd = (Long) session.getAttributes().get("id"); } if (idd != null && sessions.containsKey(idd)) { WebSocketSession s = sessions.remove(idd); if (s.isOpen()) { s.close(); } } else { if (session.isOpen()) { session.close(); } } } } /*用户发送信息执行的函数*/ private void sendMsg(WebSocketSession session, TextMessage message) throws IOException { /*将信息内容封装到信息对象中*/ TalkMsg talkMsg = objectMapper .readValue(objectMapper.readTree(message.getPayload()).get("msg").toString(), TalkMsg.class); talkMsg.setDate(new Date()); /*将该信息存入数据库*/ this.talkMsgService.add(talkMsg); // System.out.println("ID:" + talkMsg.getId()); /*获取目标用户的session*/ WebSocketSession to_session = sessions.get(talkMsg.getTo()); /*将信息转成Json字符串*/ String json_msg = objectMapper.writeValueAsString(talkMsg); /*通知当前用户信息发送成功*/ if (session.isOpen()) { session.sendMessage( new TextMessage("{\"type\":\"" + MsgType.SEND_MESSAGE_SUCCESSFUL + "\",\"msg\":" + json_msg + "}")); } /*若是目标用户在线的话就将该信息发送给目标用户*/ if (to_session != null && to_session.isOpen()) { to_session.sendMessage( new TextMessage("{\"type\":\"" + MsgType.SEND_MESSAGE + "\",\"msg\":" + json_msg + "}")); } } /*用户下线执行函数*/ private void userLogoff(WebSocketSession session, TextMessage message) throws Exception { String jsonstr = message.getPayload(); JsonNode root = objectMapper.readTree(jsonstr); if (root.get("logoffUser") != null) { if (root.get("logoffUser").asBoolean()) { Long id = root.get("id").asLong(); this.removeAndCloseSession(id, session); System.out.println("用户" + id + "已下线"); } } } /*服务器提醒全部在线用户,更新客户端在线列表*/ private void updateTalkersOnline() { /*采用多线程在在线人数比较多的状况下提升执行效率*/ for (Map.Entry<Long, WebSocketSession> entry : sessions.entrySet()) { new Thread(new Runnable() { @Override public void run() { WebSocketSession session = entry.getValue(); if (session.isOpen()) { try { session.sendMessage(new TextMessage( "{\"type\":\"" + MsgType.UPDATE_TALKERS_ONLINE + "\",\"updateTalkersOnline\":true}")); } catch (IOException e) { try { removeAndCloseSession(null, session); } catch (IOException e1) { } } } } }).start(); } } /*将信息发送给机器人*/ private void sendMsgToRobot(WebSocketSession session, TextMessage message) throws IOException { /*将信息内容封装到信息对象中*/ TalkMsg talkMsg_receive = objectMapper .readValue(objectMapper.readTree(message.getPayload()).get("msg").toString(), TalkMsg.class); talkMsg_receive.setDate(new Date()); /*与机器人通讯时发送的Http请求*/ HttpClient httpClient = new HttpClient(); // 建立post请求,相似Post请求 PostMethod postMethod = new PostMethod("http://www.tuling123.com/openapi/api"); // 设置请求的正文内容 String json_text = "{\"key\":\"这里请填入你本身的图灵请求的key\",\"info\":\"" + talkMsg_receive.getContent() + "\",\"userid\":\"" + talkMsg_receive.getFrom() + "\"}"; StringRequestEntity stringRequestEntity = new StringRequestEntity(json_text, "application/json", "UTF-8"); postMethod.setRequestEntity(stringRequestEntity); /*通知当前用户信息发送成功*/ if (session.isOpen()) { session.sendMessage( new TextMessage("{\"type\":\"" + MsgType.SEND_MESSAGE_SUCCESSFUL + "\",\"msg\":" + objectMapper.writeValueAsString(talkMsg_receive) + "}")); } // 发送post请求 httpClient.executeMethod(postMethod); //获取响应结果 String result = new String(postMethod.getResponseBodyAsString().getBytes("ISO-8859-1"), "UTF-8"); /*将机器人返回的信息封装并转成Json字符串*/ TalkMsg talkMsg_send = new TalkMsg(); talkMsg_send.setFrom(1L); talkMsg_send.setTo(talkMsg_receive.getFrom()); talkMsg_send.setContent(this.parseTuringData(result)); talkMsg_send.setDate(new Date()); String json_msg = objectMapper.writeValueAsString(talkMsg_send); /*推送图灵机器人消息给此用户*/ if (session.isOpen()) { session.sendMessage( new TextMessage("{\"type\":\"" + MsgType.SEND_MESSAGE + "\",\"msg\":" + json_msg + "}")); } //释放与图灵的HTTP链接 postMethod.releaseConnection(); } /*解析从图灵请求来的Json数据*/ private String parseTuringData(String turingResult) throws IOException { int code = objectMapper.readTree(turingResult).get("code").asInt(); String content = null; switch (code) { case 200000: content = this.turing_200000(turingResult); break; case 302000: content = this.turing_302000(turingResult); break; case 308000: content = this.turing_308000(turingResult); break; default: content = this.turing_text(turingResult); break; } return content; } /*图灵请求code=308000的处理*/ private String turing_308000(String turingResult) throws IOException { StringBuilder content = new StringBuilder(); TypeFactory typeFactory = objectMapper.getTypeFactory(); List<Map<String, String>> news = objectMapper .readValue(objectMapper.readTree(turingResult).get("list").toString(), typeFactory .constructCollectionType(ArrayList.class, typeFactory.constructMapType( HashMap.class, String.class, String.class))); String text = this.turing_text(turingResult); content.append(text).append("<br/><hr/>"); for (Map<String, String> map : news) { content.append("<a target = \"_blank\" class = \"turing_link\" href = \"").append(map.get("detailurl")) .append("\">").append(map.get("name")).append("<br/>材料:").append(map.get("info")). append("<img src = \"").append(map.get("icon")). append("\"/></a><hr/>"); } content.delete(content.lastIndexOf("<hr/>"), content.length()).insert(0, "<p>") .insert(content.length(), "</p>"); return content.toString(); } /*图灵请求code=302000的处理*/ private String turing_302000(String turingResult) throws IOException { StringBuilder content = new StringBuilder(); TypeFactory typeFactory = objectMapper.getTypeFactory(); List<Map<String, String>> news = objectMapper .readValue(objectMapper.readTree(turingResult).get("list").toString(), typeFactory .constructCollectionType(ArrayList.class, typeFactory.constructMapType( HashMap.class, String.class, String.class))); String text = this.turing_text(turingResult); content.append(text).append("<br/><hr/>"); for (Map<String, String> map : news) { content.append("<a target = \"_blank\" class = \"turing_link\" href = \"").append(map.get("detailurl")) .append("\">").append(map.get("article")).append("----来自").append(map.get("source")). append("</a><hr/>"); } content.delete(content.lastIndexOf("<hr/>"), content.length()).insert(0, "<p>") .insert(content.length(), "</p>"); return content.toString(); } /*图灵请求code=200000的处理*/ private String turing_200000(String turingResult) throws IOException { String text = this.turing_text(turingResult); String url = objectMapper.readTree(turingResult).get("url").asText(); return "<p>" + text + "<br/><a class = \"turing_link\" target = \"_blank\" href = \"" + url + "\">请点击打开页面</a></p>"; } /*图灵请求错误以及code=100000的处理*/ private String turing_text(String turingResult) throws IOException { return objectMapper.readTree(turingResult).get("text").asText(); } }
MsgType.java:
package at.flying.domain; /* 此类用于标识客户端和服务端相互发送信息时,指定该信息的类型, 以使后端或者前端根据信息类型执行不一样的操做 */ public class MsgType { /*更新在线列表*/ public static final String UPDATE_TALKERS_ONLINE = "updateTalkersOnline"; /*用户下线*/ public static final String LOGOFF_TALKER = "logoffTalker"; /*用户发送消息*/ public static final String SEND_MESSAGE = "sendMessage"; /*用户发送信息给机器人*/ public static final String SEND_MESSAGE_TO_Robot = "sendMessageToRobot"; /*用户发送消息成功*/ public static final String SEND_MESSAGE_SUCCESSFUL = "sendMessageSuccessful"; }
TalkerAction.java
package at.flying.web.action; import at.flying.domain.Talker; import at.flying.service.TalkerService; import at.flying.websocket.TalkWebSoketHandler; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.socket.WebSocketSession; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Controller @RequestMapping("talker") public class TalkerAction { @Autowired private TalkerService talkerService; @RequestMapping("findById") @ResponseBody public Map<String, Object> findById( @RequestParam("id") Long id) { Talker talker = this.talkerService.findById(id); Map<String, Object> map = new HashMap<>(); map.put("status", 0); if (talker != null) { map.put("status", 1); map.put("talker", talker); } return map; } @RequestMapping("findByName") @ResponseBody public Map<String, Object> findByName( @RequestParam("name") String name) { Map<String, Object> map = new HashMap<>(); map.put("status", 0);//0表明登陆失败 //用户名不能是机器人 if (!"机器人".equalsIgnoreCase(name)) { Talker talker = this.talkerService.findByName(name); if (talker != null) { /*检测当前用户是否已登陆*/ if (TalkWebSoketHandler.getSessions().containsKey(talker.getId())) { map.put("status", -1);//-1表明当前用户已经登陆,不能重复登陆 } else { map.put("status", 1);//1表明登陆成功 } map.put("talker", talker); } } return map; } /*查找在线用户*/ @RequestMapping("findTalkersOfLogin") @ResponseBody public Map<String, Object> findTalkersOfLogin( @RequestParam("id") Long id) { Map<Long, WebSocketSession> sessions = TalkWebSoketHandler.getSessions(); List<Talker> talkers = new ArrayList<>(); talkers.add(this.talkerService.findById(1L)); for (Map.Entry<Long, WebSocketSession> entry : sessions.entrySet()) { WebSocketSession session = entry.getValue(); if (session.isOpen()) { Talker talker = this.talkerService.findById(entry.getKey()); if (talker != null) { talkers.add(talker); } } } Map<String, Object> map = new HashMap<>(); map.put("total", talkers.size()); map.put("talkers", talkers); return map; } }
TalkMsgAction.java
package at.flying.web.action; import at.flying.domain.TalkMsg; import at.flying.domain.Talker; import at.flying.service.TalkMsgService; import at.flying.service.TalkerService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.servlet.ModelAndView; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Controller @RequestMapping("talkMsg") public class TalkMsgAction { @Autowired private TalkMsgService talkMsgService; @Autowired private TalkerService talkerService; @RequestMapping("add") @ResponseBody public Map<String, Object> add( @RequestBody TalkMsg talkMsg) { this.talkMsgService.add(talkMsg); Map<String, Object> map = new HashMap<>(); map.put("status", 1); return map; } @RequestMapping("findById") @ResponseBody public TalkMsg findById( @RequestParam("id") Long id) { return this.talkMsgService.findById(id); } @RequestMapping("findByFromAndTo") public ModelAndView findByFromAndTo( @RequestParam("from") Long from, @RequestParam("to") Long to, ModelAndView modelAndView) { List<TalkMsg> talkMsgs = to == 1 ? new ArrayList<>() : this.talkMsgService.findByFromAndTo(from, to); Talker tot = this.talkerService.findById(to); Talker fromt = this.talkerService.findById(from); modelAndView.addObject("talkMsgs", talkMsgs); modelAndView.addObject("to", tot); modelAndView.addObject("from", fromt); modelAndView.setViewName("talkMsg"); return modelAndView; } }
前端代码:
主页面代码:
<!--- Created by FlyingHe on 2016/8/14. ---> <!DOCTYPE html> <html lang = "en"> <head> <meta charset = "UTF-8"> <title>使用WebSocket作的一个简单的IM系统</title> <link rel = "stylesheet" type = "text/css" href = "easyui/themes/default/easyui.css"> <link rel = "stylesheet" type = "text/css" href = "easyui/themes/icon.css"> <script type = "text/javascript" src = "js/jquery-2.1.4.min.js"></script> <script src = "easyui/jquery.easyui.min.js"></script> <script src = "easyui/easyui-lang-zh_CN.js"></script> <script src = "easyui/jquery.edatagrid.js"></script> <!--富文本编辑器的Script文件--> <!-- 样式文件 --> <link rel = "stylesheet" href = "./umeditor/themes/default/css/umeditor.css"> <!-- 配置文件 --> <script type = "text/javascript" src = "./umeditor/umeditor.config.js"></script> <!-- 编辑器源码文件 --> <script type = "text/javascript" src = "./umeditor/umeditor.js"></script> <!-- 语言包文件 --> <script type = "text/javascript" src = "./umeditor/lang/zh-cn/zh-cn.js"></script> </head> <style> #memo_id p { padding-left: 10px; } #numOfTalkerOnline { text-align: center; } #talkersOfOnline ul li { text-decoration: none; display: block; font-size: 24px; text-align: left; vertical-align: text-top; height: 30px; margin-bottom: 10px; } #talkersOfOnline ul li:hover { background-color: grey; cursor: pointer; } /*图灵连接的样式*/ .turing_link { color: blue; text-decoration: none; } .turing_link:hover { color: #95B8E7; } /************************聊天选项卡样式表 start************************/ ul { list-style: none; } .qipao { position: relative; clear: both; /*margin-bottom: 20px;*/ } .headimg img { width: 50px; height: 50px; border-radius: 50px; vertical-align: top; } .head_img { width: 50px; height: 50px; border-radius: 50px; vertical-align: middle; } .headimg { display: inline-block; } .leftqipao { display: inline-block; vertical-align: top; position: relative; top: 30px; } .rightqipao { display: block; vertical-align: top; position: relative; top: 30px; } .left_con { background-color: orange; padding: 15px; display: inline-block; border-radius: 10px; max-width: 300px; float: left; line-height: 20px; margin-bottom: 10px; } .left_sj { margin: 0; width: 0; height: 0; border-left: 10px solid rgba(255, 255, 255, 0); border-bottom: 10px solid rgba(255, 255, 255, 0); border-right: 10px solid orange; border-top: 10px solid rgba(255, 255, 255, 0); float: left; position: relative; top: 10px; } .right_con { background-color: orange; padding: 15px; display: inline-block; border-radius: 10px; max-width: 300px; float: right; line-height: 20px; margin-bottom: 10px; } .right_sj { margin: 0; width: 0; height: 0; border-left: 10px solid orange; border-bottom: 10px solid rgba(255, 255, 255, 0); border-right: 10px solid rgba(255, 255, 255, 0); border-top: 10px solid rgba(255, 255, 255, 0); float: right; position: relative; top: 10px; } .fl { float: left; } .fr { float: right; } .leftqipao .show_time { float: left; } .rightqipao .show_time { float: right; } /************************聊天选项卡样式表 end************************/ </style> <script> /*全局Websocket*/ var ws_websocket = null; /*使div滚动条滚到最低端*/ function scrollToBottom() { $(".msg_container").each(function () { $(this).scrollTop(this.scrollHeight); }); } /*成功接受到信息或者信息发送成功时会建立信息条*/ function createMsg(msg) { var isMine = msg.from == JSON.parse($("#talkerid").val()).id ? true : false; var div_headimg_fr_fl = "<div class = \"headimg " + (isMine ? "fr" : "fl") + "\"><img src = \"/IMSystem" + JSON.parse($("#talkerid").val()).headImg + "\" /></div>"; var div_left_right_sj = "<div class = \"" + (isMine ? "right" : "left") + "_sj\"></div>"; var div_left_right_con = "<div class = \"" + (isMine ? "right" : "left") + "_con\"><span class = \"show_time\">" + msg.date + "</span><br/><hr/>" + msg.content + "</div>"; var div_left_right_qipao = "<div class = \"" + (isMine ? "right" : "left") + "qipao\">" + div_left_right_sj + div_left_right_con + "</div>"; var li = "<li class = \"qipao\">" + div_headimg_fr_fl + div_left_right_qipao + "</li>"; $("#talker" + (isMine ? msg.to : msg.from)).append(li); scrollToBottom(); } /*接收别人发来的消息*/ function acceptMsg(data) { var msg = data.msg; createMsg(msg); } /*本身成功发送给别人消息时调用的函数*/ function sendMsgSuccess(data) { createMsg(data.msg); //清空消息框 UM.getEditor("talker_msg" + data.msg.to).ready(function () { this.setContent("", false); }) } /*本身给别人发送消息*/ function sendMsg() { var from = JSON.parse($("#talkerid").val()).id; var to = $("#tabs_id").tabs("getSelected").panel("options").id; var content = ""; var txt = ""; UM.getEditor("talker_msg" + to).ready(function () { content = to == 1 ? this.getContentTxt() : this.getContent(); txt = this.getPlainTxt(); }); /*判断内容是否为空*/ if (txt.trim() == "") { $.messager.show({ title: '提醒', msg: '大哥,您啥内容都不发么,黄段子也OK啊', timeout: 2000, showType: 'slide' }); return; } /*封装信息*/ var message = { "type": to == 1 ? "sendMessageToRobot" : "sendMessage", "msg": { "from": from, "to": to, "content": content } }; console.log("Msg:" + JSON.stringify(message)); /*向服务器发送信息*/ ws_websocket.send(JSON.stringify(message)); } /*建立通讯选项卡*/ function createTalkTab(from_id, to_id, to_name) { /*双击本身头像将不会建立*/ if (from_id == to_id) { $.messager.show({ title: '提醒', msg: '大哥,您自言自语还须要网络么', timeout: 2000, showType: 'slide' }); return; } /*若是该选项卡存在的话就切换到该选项卡,若不存在就建立选项卡*/ if ($("#tabs_id").tabs("exists", to_name)) { $("#tabs_id").tabs("select", to_name); scrollToBottom(); } else { $("#tabs_id").tabs("add", { id: to_id, title: to_name, selected: true, closable: true, href: "/IMSystem/talkMsg/findByFromAndTo?from=" + from_id + "&to=" + to_id }) } } /*向服务器获取在线列表并更新浏览器在线列表*/ function updateTalkerOnline(id) { $.ajax({ type: "post", url: "/IMSystem/talker/findTalkersOfLogin", data: {"id": id}, dataType: "json", success: function (data) { var total = data.total; /*设置当前在线用户数量*/ $("#numOfTalkerOnline").text("当前在线人数:" + total); console.log(data); if (total != 0) { /*清空当前在线列表*/ $("#talkersOfOnline ul").empty(); /*从新建立在线列表*/ for (var i = 0; i < total; i++) { var username = data.talkers[i].username; console.log("headImg:" + data.talkers[i].headImg); var event = "ondblclick=\"createTalkTab(" + id + "," + data.talkers[i].id + ",'" + username + "')\""; var img = "<img src='/IMSystem" + data.talkers[i].headImg + "' class='head_img'/>"; var li = "<li " + event + ">" + img + username + "</li><br/>"; console.log(li); $("#talkersOfOnline ul").append(li); } } } }) } /*建立会话*/ function createWebSocket(talker) { /*请求WebSocket的地址*/ var url = "ws://" + window.location.host + "/IMSystem/easyui/talk?id=" + talker.id; /*开始创建链接*/ var websocket = new WebSocket(url); console.log(talker.id); /*WebSocket创建成功执行的函数*/ websocket.onopen = function (event) { /*关闭登陆对话框*/ $("#dlg_login").dialog("close"); /*禁用上线按钮*/ $("#memo_id h1 a:first").linkbutton("disable"); /*启用上线按钮*/ $("#memo_id h1 a:last").linkbutton("enable"); console.log("IMG:" + talker.headImg); /*经过一个Hidden标签的值(以Json字符串格式)存储当前登陆用户的信息*/ $("#talkerid").val(JSON.stringify(talker)); console.log("JsonImg:" + JSON.parse($("#talkerid").val()).headImg); console.log("userid" + $("#talkerid").val()); /*用一个全局变量指向当前建立的WebSocket对象以供其余函数使用该WebSocket对象*/ ws_websocket = websocket; /*用户上线提醒*/ $.messager.show({ title: '上线提醒', msg: '您已上线', timeout: 2000, showType: 'slide' }); }; /*服务端向客户端发送信息时执行的函数*/ websocket.onmessage = function (event) { console.log("EVEN:" + event.data); /*将服务器传递过来的Json字符串转化为JS的json对象*/ var data = JSON.parse(event.data); /*根据该信息的类型执行不一样的操做*/ if (data.type.toLowerCase() == "updateTalkersOnline".toLowerCase()) { if (data.updateTalkersOnline) { /*发送更新在线列表的请求*/ updateTalkerOnline(JSON.parse($("#talkerid").val()).id); } } else if (data.type.toLowerCase() == "sendMessage".toLowerCase()) { /*这个在客户端表示别人向本身发送信息*/ acceptMsg(data); } else if (data.type.toLowerCase() == "sendMessageSuccessful".toLowerCase()) { /*这个在客户端表示本身向别人发送信息成功*/ sendMsgSuccess(data); } }; /*与服务器端链接关闭时执行的函数*/ websocket.onclose = function (event) { /*下线后启用上线按钮*/ $("#memo_id h1 a:first").linkbutton("enable"); /*下线后禁用下线按钮*/ $("#memo_id h1 a:last").linkbutton("disable"); /*清空在线列表*/ $("#talkersOfOnline ul").empty(); /*清空在线用户数量内容*/ $("#numOfTalkerOnline").text(""); /*重置显示当前在线用户位置内容*/ $("#currentTalkerName").text("当前可登录用户:鲜橙多,蠢比,傻逼,笔记本,好丽友派(无密码)"); /*下线后关闭聊天选项卡*/ var tabs = $("#tabs_id").tabs("tabs"); var length = tabs.length; for (var i = 0; i < length; i++) { console.log(tabs); $("#tabs_id").tabs("close", $("#tabs_id").tabs("getTabIndex", tabs[0])); } /*用户下线成功提醒*/ $.messager.show({ title: '下线提醒', msg: '您已下线', timeout: 2000, showType: 'slide' }); } } /*给选项卡添加属性和事件*/ $(function () { $("#tabs_id").tabs({ border: false, onLoad: function (panel) { var d = panel.panel("options"); if (d.id != null) { /*加载成功后初始化富文本编辑器*/ var um = UM.getEditor("talker_msg" + d.id); /* 只有当通讯对象是智能机器人的时候,才为百度富文本编辑器添加键盘事件, 按Enter键便可发送信息 */ if (d.id == 1) { $(um.body).keypress(function (event) { /*keyCode是13的话表示按下的是Enter键*/ if (event.keyCode == 13) { /*发送信息*/ sendMsg(); } }); } scrollToBottom(); } }, onBeforeClose: function (title, index) { var d = $("#tabs_id").tabs("getTab", index).panel("options"); /*关闭选项卡以前要先销毁富文本编辑器对象, 不然再次打开该选项卡时初始化富文本编辑器会出问题 */ UM.getEditor("talker_msg" + d.id).destroy(); } }); }); $(function () { /*初始化登陆谈话框*/ $("#dlg_login").dialog({ title: "登陆", closed: true, modal: true, width: 300, height: 170, buttons: [{ //iconCls: "icon-search", text: "登陆", handler: function () { var name = $("#talker_name").val(); //var password = $("#talker_pwd").val(); $.ajax({ type: "post", url: "/IMSystem/talker/findByName", data: {"name": name}, dataType: "json", success: function (data) { if (data.status == 0) { /*登陆失败*/ $.messager.alert("提示", "登陆失败,请从新登陆", 'info'); } else if (data.status == -1) { /*该用户已经登陆,不能重复登陆*/ $.messager.alert("提示", "该用户已登陆,您不能重复登陆", 'info'); } else { /*登陆成功*/ /*显示当前登陆用户*/ $("#currentTalkerName").text("当前登陆用户:" + data.talker.username); //建立会话,与服务器创建WebSocket长链接 createWebSocket(data.talker); } }, error: function () { $.messager.alert("错误", "登陆失败,请从新登陆", 'error'); } }); } }, { iconCls: "icon-cancel", text: "取消", handler: function () { $("#dlg_login").dialog("close"); } }] }); /*为上线按钮添加事件*/ $("#memo_id h1 a:first").linkbutton({ onClick: function () { $("#login_form").form("clear"); $("#dlg_login").dialog("open") } }); /*为下线按钮添加事件*/ $("#memo_id h1 a:last").linkbutton({ onClick: function () { /*向服务器发送下线信息表示本身要下线了*/ ws_websocket.send(JSON.stringify({ "type": "logoffTalker", "logoffUser": true, "id": JSON.parse($("#talkerid").val()).id })); } }); }) </script> <body class = "easyui-layout"> <!--此hidden存储当前登陆用户的信息,以Json字符串的形式存储用户信息--> <input type = "hidden" id = "talkerid" /> <!--登陆框容器--> <div id = "dlg_login" style = "text-align: center"> <form id = "login_form"> <div> <p><input id = "talker_name" class = "easyui-textbox" data-options = "required:true,prompt:'输入您的姓名'" /> </p> <p><input id = "talker_pwd" class = "easyui-passwordbox" data-options = "required:false,prompt:'输入您的密码'" /></p> </div> </form> </div> <!--<div data-options = "region:'north',split:false,border:false" style = "height:5%;text-align: center;">--> <!--<center>使用WebSocket作的一个简单的IM系统</center>--> <!--</div>--> <div data-options = "region:'south',split:false,border:true" style = "height:5%;text-align: center">©Flying版权全部 </div> <div id = "memo_id" data-options = "region:'east',title:'备注',split:false,collapsible:false,border:false" style = "width:20%"> <h1 style = "text-align: center"> <a class = "easyui-linkbutton" data-options = "disabled:false">上线</a> <a class = "easyui-linkbutton" data-options = "disabled:true">下线</a> </h1> <hr /> <!--显示当前用户名字--> <p id = "currentTalkerName" style = "text-align: center;"> 当前可登录用户:鲜橙多,蠢比,傻逼,笔记本,好丽友派(无密码) </p> <hr /> <!--显示当前在线用户数量--> <h1 id = "numOfTalkerOnline"></h1> <!--在线用户列表--> <div id = "talkersOfOnline"> <ul></ul> </div> </div> <div id = "tabs_id" class = "easyui-tabs" data-options = "region:'center',border:false" style = "padding:5px;background:#eee;"> </div> </body> </html>
双击在线用户的头像时弹出的聊天选项卡里加载的Jsp文件:
<%@ page language = "java" pageEncoding = "UTF-8" %> <%@taglib prefix = "c" uri = "http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix = "f" uri = "http://java.sun.com/jsp/jstl/fmt" %> <!DOCTYPE html> <html lang = "en"> <head> <meta charset = "UTF-8"> <title></title> </head> <body> <center><h4 style = "padding: 0;margin: 0">您正在与 ${to.username} 通讯</h4> </center> <hr /> <div class = "msg_container" style = "height: 350px;overflow: auto"> <ul id = "talker${to.id}"> <c:forEach items = "${talkMsgs}" var = "talkMsg"> <c:choose> <c:when test = "${from.id ne talkMsg.from}"> <li class = "qipao"> <div class = "headimg fl"> <img src = "http://flyinghe.ngrok.cc/IMSystem${from.headImg}" /> </div> <div class = "leftqipao"> <div class = "left_sj"></div> <div class = "left_con"> <span class = "show_time"><f:formatDate pattern = "yyyy-MM-dd HH:mm:ss" value = "${talkMsg.date}" /></span><br /> <hr /> ${talkMsg.content} </div> </div> </li> </c:when> <c:otherwise> <li class = "qipao"> <div class = "headimg fr"> <img src = "http://flyinghe.ngrok.cc/IMSystem${from.headImg}" /> </div> <div class = "rightqipao"> <div class = "right_sj"></div> <div class = "right_con"> <span class = "show_time"><f:formatDate pattern = "yyyy-MM-dd HH:mm:ss" value = "${talkMsg.date}" /></span><br /> <hr /> ${talkMsg.content} </div> </div> </li> </c:otherwise> </c:choose> </c:forEach> </ul> </div> <div> <textarea id = "talker_msg${to.id}" style = "width:100%;height:100px;"></textarea> <center> <button onclick = "sendMsg()">发送</button> ${to.id eq 1 ? "(按Enter键便可发送信息)" : ""} </center> </div> </body> </html>
总结:
WebSocket中浏览器与服务器交互的模型以下: