初次學習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中瀏覽器與服務器交互的模型以下: