Spring之WebSocket

初次学习WebSocket。在本次写的WebSocket Demo里使用到了如下插件(技术):

1.百度的富文本编辑器:http://ueditor.baidu.com/website/

2.Spring对WebSocket的支持:http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#websocket

3.JQuery的EasyUI:http://www.jeasyui.com/documentation/index.php

4.Jackson技术:https://my.oschina.net/u/2608182/blog/731403

5.参考博客:http://www.xdemo.org/spring-websocket-comet/

                       http://blog.csdn.net/linlzk/article/details/51086545

                       http://www.cnblogs.com/davidwang456/p/4786636.html

**Notice:**这个Demo是在我以前做的一个关于CRUD的 Demo的基础上增加的聊天的功能。本博客将不会再针对聊天功能以外的功能做阐述,仅仅针对增加的聊天功能做阐述。关于CRUD部分感兴趣的可以参见该片博客:https://my.oschina.net/u/2608182/blog/734810

此Demo后端采用SSM框架

此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包。

1<!--Spring WebSocket--> 2 <dependency> 3 <groupId>org.springframework</groupId> 4 <artifactId>spring-websocket</artifactId> 5 <version>4.2.4.RELEASE</version> 6 </dependency> 7 <dependency> 8 <groupId>org.springframework</groupId> 9 <artifactId>spring-messaging</artifactId> 10 <version>4.2.4.RELEASE</version> 11 </dependency>

二。配置文件

SpringWebSocket的分配置文件(不要忘了在Spring总配置文件中import此分配置文件):

1<beans xmlns = "http://www.springframework.org/schema/beans" 2 xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance" 3 xmlns:websocket = "http://www.springframework.org/schema/websocket" 4 xsi:schemaLocation = " 5 http://www.springframework.org/schema/beans 6 http://www.springframework.org/schema/beans/spring-beans.xsd 7 http://www.springframework.org/schema/websocket 8 http://www.springframework.org/schema/websocket/spring-websocket.xsd"> 9 10 <websocket:handlers> 11 <!--配置匹配talkWebSoketHandler处理器的路径--> 12 <websocket:mapping path = "/easyui/talk" handler = "talkWebSoketHandler" /> 13 <!--配置HandShake拦截器--> 14 <websocket:handshake-interceptors> 15 <ref bean = "handShake" /> 16 </websocket:handshake-interceptors> 17 </websocket:handlers> 18</beans>

三。Java文件:

HandShake.java:

1package at.flying.websocket; 2 3import org.springframework.http.server.ServerHttpRequest; 4import org.springframework.http.server.ServerHttpResponse; 5import org.springframework.http.server.ServletServerHttpRequest; 6import org.springframework.stereotype.Component; 7import org.springframework.web.socket.WebSocketHandler; 8import org.springframework.web.socket.server.HandshakeInterceptor; 9 10import javax.servlet.http.HttpServletRequest; 11import javax.servlet.http.HttpSession; 12import java.util.Map; 13@Component("handShake") 14public class HandShake implements HandshakeInterceptor { 15 @Override 16 public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, 17 Map<String, Object> attributes) throws Exception { 18 /*获取从客户端传来的用户的ID值,此ID值作为存储登录用户的Key*/ 19 Long id = Long.valueOf(((ServletServerHttpRequest) request).getServletRequest().getParameter("id")); 20 /*session.getAttributes()返回的Map就是这里的attributes, 21 所以将id存入attributes里,后面再Handler里可以通过session获取到该值 22 */ 23 attributes.put("id", id); 24 /*返回true以执行下一个拦截器*/ 25 return true; 26 } 27 28 @Override 29 public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, 30 Exception exception) { 31 } 32}

TalkWebSoketHandler.java

1package at.flying.websocket; 2 3import at.flying.domain.MsgType; 4import at.flying.domain.TalkMsg; 5import at.flying.service.TalkMsgService; 6import at.flying.service.TalkerService; 7import com.fasterxml.jackson.databind.JsonNode; 8import com.fasterxml.jackson.databind.ObjectMapper; 9import com.fasterxml.jackson.databind.type.TypeFactory; 10import org.apache.commons.httpclient.HttpClient; 11import org.apache.commons.httpclient.methods.PostMethod; 12import org.apache.commons.httpclient.methods.StringRequestEntity; 13import org.springframework.beans.factory.annotation.Autowired; 14import org.springframework.stereotype.Component; 15import org.springframework.web.socket.CloseStatus; 16import org.springframework.web.socket.TextMessage; 17import org.springframework.web.socket.WebSocketSession; 18import org.springframework.web.socket.handler.TextWebSocketHandler; 19 20import java.io.IOException; 21import java.util.*; 22 23/** 24 * Created by FlyingHe on 2016/9/16. 25 */ 26@Component("talkWebSoketHandler") 27public class TalkWebSoketHandler extends TextWebSocketHandler { 28 @Autowired 29 private TalkMsgService talkMsgService; 30 @Autowired 31 private TalkerService talkerService; 32 /*存储建立的所有连接*/ 33 private static Map<Long, WebSocketSession> sessions = new HashMap<>(); 34 /*用于处理Json数据*/ 35 private static ObjectMapper objectMapper = new ObjectMapper(); 36 37 /*获取当前建立的所有连接*/ 38 public static Map<Long, WebSocketSession> getSessions() { 39 return sessions; 40 } 41 42 43 /*客户端与服务器端连接建立之后执行的函数*/ 44 @Override 45 public void afterConnectionEstablished(WebSocketSession session) throws Exception { 46 System.out.println("afterConnectionEstablished"); 47 if (session.getAttributes().get("id") == null) { 48 session.close(); 49 return; 50 } 51 /*获取当前用户的ID*/ 52 Long id = (Long) session.getAttributes().get("id"); 53 /*存储当前用户建立的链接,即会话session*/ 54 sessions.put(id, session); 55 System.out.println("用户" + id + "一上线"); 56 /*通知所有登录用户客户端更新在线列表*/ 57 this.updateTalkersOnline(); 58 } 59 60 /*客户端向服务器发送信息时会调用该函数*/ 61 @Override 62 protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { 63 /*获取信息内容*/ 64 String jsonstr = message.getPayload(); 65 /*获取该信息类型以选择处理该信息的方式*/ 66 String type = objectMapper.readTree(jsonstr).get("type").asText(); 67 if (type.equalsIgnoreCase(MsgType.LOGOFF_TALKER)) { 68 /*此类型表示该用户要下线*/ 69 this.userLogoff(session, message); 70 } else if (type.equalsIgnoreCase(MsgType.SEND_MESSAGE)) { 71 /*此类型表示该用户要发送信息给别人*/ 72 this.sendMsg(session, message); 73 } else if (type.equalsIgnoreCase(MsgType.SEND_MESSAGE_TO_Robot)) { 74 /*此类表示要将信息发送给机器人*/ 75 this.sendMsgToRobot(session, message); 76 } 77 } 78 79 /*连接发生异常时执行的函数*/ 80 @Override 81 public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { 82 this.removeAndCloseSession(null, session); 83 } 84 85 /*连接关闭后执行的函数*/ 86 @Override 87 public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { 88 this.removeAndCloseSession(null, session); 89 /*用户下线时,通知所有登录用户客户端更新在线列表*/ 90 this.updateTalkersOnline(); 91 } 92 93 /*移除并关闭session*/ 94 private void removeAndCloseSession(Long id, WebSocketSession session) throws IOException { 95 if (id != null && sessions.containsKey(id)) { 96 WebSocketSession s = sessions.remove(id); 97 if (s.isOpen()) { 98 s.close(); 99 } 100 } else { 101 Long idd = null; 102 if (session.getAttributes().get("id") instanceof Long) { 103 idd = (Long) session.getAttributes().get("id"); 104 } 105 if (idd != null && sessions.containsKey(idd)) { 106 WebSocketSession s = sessions.remove(idd); 107 if (s.isOpen()) { 108 s.close(); 109 } 110 } else { 111 if (session.isOpen()) { 112 session.close(); 113 } 114 } 115 } 116 } 117 118 /*用户发送信息执行的函数*/ 119 private void sendMsg(WebSocketSession session, TextMessage message) throws IOException { 120 /*将信息内容封装到信息对象中*/ 121 TalkMsg talkMsg = objectMapper 122 .readValue(objectMapper.readTree(message.getPayload()).get("msg").toString(), TalkMsg.class); 123 talkMsg.setDate(new Date()); 124 /*将该信息存入数据库*/ 125 this.talkMsgService.add(talkMsg); 126// System.out.println("ID:" + talkMsg.getId()); 127 /*获取目标用户的session*/ 128 WebSocketSession to_session = sessions.get(talkMsg.getTo()); 129 /*将信息转成Json字符串*/ 130 String json_msg = objectMapper.writeValueAsString(talkMsg); 131 /*通知当前用户信息发送成功*/ 132 if (session.isOpen()) { 133 session.sendMessage( 134 new TextMessage("{\"type\":\"" + MsgType.SEND_MESSAGE_SUCCESSFUL + "\",\"msg\":" + json_msg + "}")); 135 } 136 /*如果目标用户在线的话就将该信息发送给目标用户*/ 137 if (to_session != null && to_session.isOpen()) { 138 to_session.sendMessage( 139 new TextMessage("{\"type\":\"" + MsgType.SEND_MESSAGE + "\",\"msg\":" + json_msg + "}")); 140 } 141 } 142 143 /*用户下线执行函数*/ 144 private void userLogoff(WebSocketSession session, TextMessage message) throws Exception { 145 String jsonstr = message.getPayload(); 146 JsonNode root = objectMapper.readTree(jsonstr); 147 if (root.get("logoffUser") != null) { 148 if (root.get("logoffUser").asBoolean()) { 149 Long id = root.get("id").asLong(); 150 this.removeAndCloseSession(id, session); 151 System.out.println("用户" + id + "已下线"); 152 } 153 } 154 } 155 156 /*服务器提醒所有在线用户,更新客户端在线列表*/ 157 private void updateTalkersOnline() { 158 /*采用多线程在在线人数比较多的情况下提高执行效率*/ 159 for (Map.Entry<Long, WebSocketSession> entry : sessions.entrySet()) { 160 new Thread(new Runnable() { 161 @Override 162 public void run() { 163 WebSocketSession session = entry.getValue(); 164 if (session.isOpen()) { 165 try { 166 session.sendMessage(new TextMessage( 167 "{\"type\":\"" + MsgType.UPDATE_TALKERS_ONLINE + 168 "\",\"updateTalkersOnline\":true}")); 169 } catch (IOException e) { 170 try { 171 removeAndCloseSession(null, session); 172 } catch (IOException e1) { 173 } 174 } 175 } 176 } 177 }).start(); 178 179 } 180 } 181 182 /*将信息发送给机器人*/ 183 private void sendMsgToRobot(WebSocketSession session, TextMessage message) throws IOException { 184 /*将信息内容封装到信息对象中*/ 185 TalkMsg talkMsg_receive = objectMapper 186 .readValue(objectMapper.readTree(message.getPayload()).get("msg").toString(), TalkMsg.class); 187 talkMsg_receive.setDate(new Date()); 188 /*与机器人通信时发送的Http请求*/ 189 HttpClient httpClient = new HttpClient(); 190 // 创建post请求,类似Post请求 191 PostMethod postMethod = 192 new PostMethod("http://www.tuling123.com/openapi/api"); 193 // 设置请求的正文内容 194 String json_text = "{\"key\":\"这里请填入你自己的图灵请求的key\",\"info\":\"" + talkMsg_receive.getContent() + 195 "\",\"userid\":\"" + talkMsg_receive.getFrom() + "\"}"; 196 StringRequestEntity stringRequestEntity = 197 new StringRequestEntity(json_text, "application/json", "UTF-8"); 198 postMethod.setRequestEntity(stringRequestEntity); 199 /*通知当前用户信息发送成功*/ 200 if (session.isOpen()) { 201 session.sendMessage( 202 new TextMessage("{\"type\":\"" + MsgType.SEND_MESSAGE_SUCCESSFUL + "\",\"msg\":" + 203 objectMapper.writeValueAsString(talkMsg_receive) + "}")); 204 } 205 // 发送post请求 206 httpClient.executeMethod(postMethod); 207 //获取响应结果 208 String result = new String(postMethod.getResponseBodyAsString().getBytes("ISO-8859-1"), "UTF-8"); 209 /*将机器人返回的信息封装并转成Json字符串*/ 210 TalkMsg talkMsg_send = new TalkMsg(); 211 talkMsg_send.setFrom(1L); 212 talkMsg_send.setTo(talkMsg_receive.getFrom()); 213 talkMsg_send.setContent(this.parseTuringData(result)); 214 talkMsg_send.setDate(new Date()); 215 String json_msg = objectMapper.writeValueAsString(talkMsg_send); 216 217 /*推送图灵机器人消息给此用户*/ 218 if (session.isOpen()) { 219 session.sendMessage( 220 new TextMessage("{\"type\":\"" + MsgType.SEND_MESSAGE + "\",\"msg\":" + json_msg + "}")); 221 } 222 //释放与图灵的HTTP连接 223 postMethod.releaseConnection(); 224 } 225 226 /*解析从图灵请求来的Json数据*/ 227 private String parseTuringData(String turingResult) throws IOException { 228 int code = objectMapper.readTree(turingResult).get("code").asInt(); 229 String content = null; 230 switch (code) { 231 case 200000: 232 content = this.turing_200000(turingResult); 233 break; 234 case 302000: 235 content = this.turing_302000(turingResult); 236 break; 237 case 308000: 238 content = this.turing_308000(turingResult); 239 break; 240 default: 241 content = this.turing_text(turingResult); 242 break; 243 } 244 245 return content; 246 } 247 248 /*图灵请求code=308000的处理*/ 249 private String turing_308000(String turingResult) throws IOException { 250 StringBuilder content = new StringBuilder(); 251 TypeFactory typeFactory = objectMapper.getTypeFactory(); 252 List<Map<String, String>> news = objectMapper 253 .readValue(objectMapper.readTree(turingResult).get("list").toString(), typeFactory 254 .constructCollectionType(ArrayList.class, typeFactory.constructMapType( 255 HashMap.class, String.class, String.class))); 256 String text = this.turing_text(turingResult); 257 content.append(text).append("<br/><hr/>"); 258 for (Map<String, String> map : news) { 259 content.append("<a target = \"_blank\" class = \"turing_link\" href = \"").append(map.get("detailurl")) 260 .append("\">").append(map.get("name")).append("<br/>材料:").append(map.get("info")). 261 append("<img src = \"").append(map.get("icon")). 262 append("\"/></a><hr/>"); 263 } 264 content.delete(content.lastIndexOf("<hr/>"), content.length()).insert(0, "<p>") 265 .insert(content.length(), "</p>"); 266 return content.toString(); 267 } 268 269 /*图灵请求code=302000的处理*/ 270 private String turing_302000(String turingResult) throws IOException { 271 StringBuilder content = new StringBuilder(); 272 TypeFactory typeFactory = objectMapper.getTypeFactory(); 273 List<Map<String, String>> news = objectMapper 274 .readValue(objectMapper.readTree(turingResult).get("list").toString(), typeFactory 275 .constructCollectionType(ArrayList.class, typeFactory.constructMapType( 276 HashMap.class, String.class, String.class))); 277 String text = this.turing_text(turingResult); 278 content.append(text).append("<br/><hr/>"); 279 for (Map<String, String> map : news) { 280 content.append("<a target = \"_blank\" class = \"turing_link\" href = \"").append(map.get("detailurl")) 281 .append("\">").append(map.get("article")).append("----来自").append(map.get("source")). 282 append("</a><hr/>"); 283 } 284 content.delete(content.lastIndexOf("<hr/>"), content.length()).insert(0, "<p>") 285 .insert(content.length(), "</p>"); 286 return content.toString(); 287 } 288 289 /*图灵请求code=200000的处理*/ 290 private String turing_200000(String turingResult) throws IOException { 291 String text = this.turing_text(turingResult); 292 String url = objectMapper.readTree(turingResult).get("url").asText(); 293 return "<p>" + text + "<br/><a class = \"turing_link\" target = \"_blank\" href = \"" + url + 294 "\">请点击打开页面</a></p>"; 295 } 296 297 /*图灵请求错误以及code=100000的处理*/ 298 private String turing_text(String turingResult) throws IOException { 299 return objectMapper.readTree(turingResult).get("text").asText(); 300 } 301}

MsgType.java:

1package at.flying.domain; 2/* 3 此类用于标识客户端和服务端相互发送信息时,指定该信息的类型, 4 以使后端或者前端根据信息类型执行不同的操作 5*/ 6public class MsgType { 7 /*更新在线列表*/ 8 public static final String UPDATE_TALKERS_ONLINE = "updateTalkersOnline"; 9 /*用户下线*/ 10 public static final String LOGOFF_TALKER = "logoffTalker"; 11 /*用户发送消息*/ 12 public static final String SEND_MESSAGE = "sendMessage"; 13 /*用户发送信息给机器人*/ 14 public static final String SEND_MESSAGE_TO_Robot = "sendMessageToRobot"; 15 /*用户发送消息成功*/ 16 public static final String SEND_MESSAGE_SUCCESSFUL = "sendMessageSuccessful"; 17}

TalkerAction.java

1package at.flying.web.action; 2 3import at.flying.domain.Talker; 4import at.flying.service.TalkerService; 5import at.flying.websocket.TalkWebSoketHandler; 6import org.springframework.beans.factory.annotation.Autowired; 7import org.springframework.stereotype.Controller; 8import org.springframework.web.bind.annotation.RequestMapping; 9import org.springframework.web.bind.annotation.RequestParam; 10import org.springframework.web.bind.annotation.ResponseBody; 11import org.springframework.web.socket.WebSocketSession; 12 13import java.util.ArrayList; 14import java.util.HashMap; 15import java.util.List; 16import java.util.Map; 17 18@Controller 19@RequestMapping("talker") 20public class TalkerAction { 21 @Autowired 22 private TalkerService talkerService; 23 24 @RequestMapping("findById") 25 @ResponseBody 26 public Map<String, Object> findById( 27 @RequestParam("id") 28 Long id) { 29 Talker talker = this.talkerService.findById(id); 30 Map<String, Object> map = new HashMap<>(); 31 map.put("status", 0); 32 if (talker != null) { 33 map.put("status", 1); 34 map.put("talker", talker); 35 } 36 return map; 37 } 38 39 @RequestMapping("findByName") 40 @ResponseBody 41 public Map<String, Object> findByName( 42 @RequestParam("name") 43 String name) { 44 45 Map<String, Object> map = new HashMap<>(); 46 map.put("status", 0);//0代表登录失败 47 48 //用户名不能是机器人 49 if (!"机器人".equalsIgnoreCase(name)) { 50 Talker talker = this.talkerService.findByName(name); 51 if (talker != null) { 52 /*检测当前用户是否已登录*/ 53 if (TalkWebSoketHandler.getSessions().containsKey(talker.getId())) { 54 map.put("status", -1);//-1代表当前用户已经登录,不能重复登录 55 } else { 56 map.put("status", 1);//1代表登录成功 57 } 58 59 map.put("talker", talker); 60 } 61 } 62 return map; 63 } 64 65 /*查找在线用户*/ 66 @RequestMapping("findTalkersOfLogin") 67 @ResponseBody 68 public Map<String, Object> findTalkersOfLogin( 69 @RequestParam("id") 70 Long id) { 71 Map<Long, WebSocketSession> sessions = TalkWebSoketHandler.getSessions(); 72 List<Talker> talkers = new ArrayList<>(); 73 talkers.add(this.talkerService.findById(1L)); 74 for (Map.Entry<Long, WebSocketSession> entry : 75 sessions.entrySet()) { 76 WebSocketSession session = entry.getValue(); 77 if (session.isOpen()) { 78 Talker talker = this.talkerService.findById(entry.getKey()); 79 if (talker != null) { 80 talkers.add(talker); 81 } 82 } 83 } 84 Map<String, Object> map = new HashMap<>(); 85 map.put("total", talkers.size()); 86 map.put("talkers", talkers); 87 return map; 88 } 89}

TalkMsgAction.java

1package at.flying.web.action; 2 3import at.flying.domain.TalkMsg; 4import at.flying.domain.Talker; 5import at.flying.service.TalkMsgService; 6import at.flying.service.TalkerService; 7import org.springframework.beans.factory.annotation.Autowired; 8import org.springframework.stereotype.Controller; 9import org.springframework.web.bind.annotation.RequestBody; 10import org.springframework.web.bind.annotation.RequestMapping; 11import org.springframework.web.bind.annotation.RequestParam; 12import org.springframework.web.bind.annotation.ResponseBody; 13import org.springframework.web.servlet.ModelAndView; 14 15import java.util.ArrayList; 16import java.util.HashMap; 17import java.util.List; 18import java.util.Map; 19 20@Controller 21@RequestMapping("talkMsg") 22public class TalkMsgAction { 23 24 @Autowired 25 private TalkMsgService talkMsgService; 26 @Autowired 27 private TalkerService talkerService; 28 29 @RequestMapping("add") 30 @ResponseBody 31 public Map<String, Object> add( 32 @RequestBody 33 TalkMsg talkMsg) { 34 this.talkMsgService.add(talkMsg); 35 Map<String, Object> map = new HashMap<>(); 36 map.put("status", 1); 37 return map; 38 } 39 40 @RequestMapping("findById") 41 @ResponseBody 42 public TalkMsg findById( 43 @RequestParam("id") 44 Long id) { 45 return this.talkMsgService.findById(id); 46 } 47 48 @RequestMapping("findByFromAndTo") 49 public ModelAndView findByFromAndTo( 50 @RequestParam("from") 51 Long from, 52 @RequestParam("to") 53 Long to, 54 ModelAndView modelAndView) { 55 List<TalkMsg> talkMsgs = to == 1 ? new ArrayList<>() : this.talkMsgService.findByFromAndTo(from, to); 56 Talker tot = this.talkerService.findById(to); 57 Talker fromt = this.talkerService.findById(from); 58 modelAndView.addObject("talkMsgs", talkMsgs); 59 modelAndView.addObject("to", tot); 60 modelAndView.addObject("from", fromt); 61 modelAndView.setViewName("talkMsg"); 62 return modelAndView; 63 } 64}

前端代码:

主页面代码:

1<!--- 2 Created by FlyingHe on 2016/8/14. 3---> 4<!DOCTYPE html> 5<html lang = "en"> 6<head> 7 <meta charset = "UTF-8"> 8 <title>使用WebSocket做的一个简单的IM系统</title> 9 <link rel = "stylesheet" type = "text/css" href = "easyui/themes/default/easyui.css"> 10 <link rel = "stylesheet" type = "text/css" href = "easyui/themes/icon.css"> 11 12 <script type = "text/javascript" src = "js/jquery-2.1.4.min.js"></script> 13 <script src = "easyui/jquery.easyui.min.js"></script> 14 <script src = "easyui/easyui-lang-zh_CN.js"></script> 15 <script src = "easyui/jquery.edatagrid.js"></script> 16 <!--富文本编辑器的Script文件--> 17 <!-- 样式文件 --> 18 <link rel = "stylesheet" href = "./umeditor/themes/default/css/umeditor.css"> 19 <!-- 配置文件 --> 20 <script type = "text/javascript" src = "./umeditor/umeditor.config.js"></script> 21 <!-- 编辑器源码文件 --> 22 <script type = "text/javascript" src = "./umeditor/umeditor.js"></script> 23 <!-- 语言包文件 --> 24 <script type = "text/javascript" src = "./umeditor/lang/zh-cn/zh-cn.js"></script> 25</head> 26<style> 27 #memo_id p { 28 padding-left: 10px; 29 } 30 31 #numOfTalkerOnline { 32 text-align: center; 33 } 34 35 #talkersOfOnline ul li { 36 text-decoration: none; 37 display: block; 38 font-size: 24px; 39 text-align: left; 40 vertical-align: text-top; 41 height: 30px; 42 margin-bottom: 10px; 43 } 44 45 #talkersOfOnline ul li:hover { 46 background-color: grey; 47 cursor: pointer; 48 } 49 50 /*图灵链接的样式*/ 51 .turing_link { 52 color: blue; 53 text-decoration: none; 54 } 55 56 .turing_link:hover { 57 color: #95B8E7; 58 } 59 60 /************************聊天选项卡样式表 start************************/ 61 ul { 62 list-style: none; 63 } 64 65 .qipao { 66 position: relative; 67 clear: both; 68 /*margin-bottom: 20px;*/ 69 } 70 71 .headimg img { 72 width: 50px; 73 height: 50px; 74 border-radius: 50px; 75 vertical-align: top; 76 } 77 78 .head_img { 79 width: 50px; 80 height: 50px; 81 border-radius: 50px; 82 vertical-align: middle; 83 } 84 85 .headimg { 86 display: inline-block; 87 } 88 89 .leftqipao { 90 display: inline-block; 91 vertical-align: top; 92 position: relative; 93 top: 30px; 94 } 95 96 .rightqipao { 97 display: block; 98 vertical-align: top; 99 position: relative; 100 top: 30px; 101 } 102 103 .left_con { 104 background-color: orange; 105 padding: 15px; 106 display: inline-block; 107 border-radius: 10px; 108 max-width: 300px; 109 float: left; 110 line-height: 20px; 111 margin-bottom: 10px; 112 } 113 114 .left_sj { 115 margin: 0; 116 width: 0; 117 height: 0; 118 border-left: 10px solid rgba(255, 255, 255, 0); 119 border-bottom: 10px solid rgba(255, 255, 255, 0); 120 border-right: 10px solid orange; 121 border-top: 10px solid rgba(255, 255, 255, 0); 122 float: left; 123 position: relative; 124 top: 10px; 125 } 126 127 .right_con { 128 background-color: orange; 129 padding: 15px; 130 display: inline-block; 131 border-radius: 10px; 132 max-width: 300px; 133 float: right; 134 line-height: 20px; 135 margin-bottom: 10px; 136 } 137 138 .right_sj { 139 margin: 0; 140 width: 0; 141 height: 0; 142 border-left: 10px solid orange; 143 border-bottom: 10px solid rgba(255, 255, 255, 0); 144 border-right: 10px solid rgba(255, 255, 255, 0); 145 border-top: 10px solid rgba(255, 255, 255, 0); 146 float: right; 147 position: relative; 148 top: 10px; 149 } 150 151 .fl { 152 float: left; 153 } 154 155 .fr { 156 float: right; 157 } 158 159 .leftqipao .show_time { 160 float: left; 161 } 162 163 .rightqipao .show_time { 164 float: right; 165 } 166 167 /************************聊天选项卡样式表 end************************/ 168</style> 169<script> 170 /*全局Websocket*/ 171 var ws_websocket = null; 172 /*使div滚动条滚到最低端*/ 173 function scrollToBottom() { 174 $(".msg_container").each(function () { 175 $(this).scrollTop(this.scrollHeight); 176 }); 177 178 } 179 /*成功接受到信息或者信息发送成功时会创建信息条*/ 180 function createMsg(msg) { 181 var isMine = msg.from == JSON.parse($("#talkerid").val()).id ? true : false; 182 var div_headimg_fr_fl = "<div class = \"headimg " + (isMine ? "fr" : "fl") + "\"><img src = \"/IMSystem" + JSON.parse($("#talkerid").val()).headImg + "\" /></div>"; 183 var div_left_right_sj = "<div class = \"" + (isMine ? "right" : "left") + "_sj\"></div>"; 184 var div_left_right_con = "<div class = \"" + (isMine ? "right" : "left") + "_con\"><span class = \"show_time\">" + msg.date + "</span><br/><hr/>" + msg.content + "</div>"; 185 var div_left_right_qipao = "<div class = \"" + (isMine ? "right" : "left") + "qipao\">" + div_left_right_sj + div_left_right_con + "</div>"; 186 var li = "<li class = \"qipao\">" + div_headimg_fr_fl + div_left_right_qipao + "</li>"; 187 $("#talker" + (isMine ? msg.to : msg.from)).append(li); 188 scrollToBottom(); 189 190 } 191 /*接收别人发来的消息*/ 192 function acceptMsg(data) { 193 var msg = data.msg; 194 createMsg(msg); 195 } 196 /*自己成功发送给别人消息时调用的函数*/ 197 function sendMsgSuccess(data) { 198 createMsg(data.msg); 199 //清空消息框 200 UM.getEditor("talker_msg" + data.msg.to).ready(function () { 201 this.setContent("", false); 202 }) 203 } 204 /*自己给别人发送消息*/ 205 function sendMsg() { 206 var from = JSON.parse($("#talkerid").val()).id; 207 var to = $("#tabs_id").tabs("getSelected").panel("options").id; 208 var content = ""; 209 var txt = ""; 210 UM.getEditor("talker_msg" + to).ready(function () { 211 content = to == 1 ? this.getContentTxt() : this.getContent(); 212 txt = this.getPlainTxt(); 213 }); 214 /*判断内容是否为空*/ 215 if (txt.trim() == "") { 216 $.messager.show({ 217 title: '提醒', 218 msg: '大哥,您啥内容都不发么,黄段子也OK啊', 219 timeout: 2000, 220 showType: 'slide' 221 }); 222 return; 223 } 224 225 /*封装信息*/ 226 var message = { 227 "type": to == 1 ? "sendMessageToRobot" : "sendMessage", 228 "msg": { 229 "from": from, 230 "to": to, 231 "content": content 232 } 233 }; 234 console.log("Msg:" + JSON.stringify(message)); 235 /*向服务器发送信息*/ 236 ws_websocket.send(JSON.stringify(message)); 237 238 } 239 /*创建通信选项卡*/ 240 function createTalkTab(from_id, to_id, to_name) { 241 /*双击自己头像将不会创建*/ 242 if (from_id == to_id) { 243 $.messager.show({ 244 title: '提醒', 245 msg: '大哥,您自言自语还需要网络么', 246 timeout: 2000, 247 showType: 'slide' 248 }); 249 return; 250 } 251 /*如果该选项卡存在的话就切换到该选项卡,若不存在就创建选项卡*/ 252 if ($("#tabs_id").tabs("exists", to_name)) { 253 $("#tabs_id").tabs("select", to_name); 254 scrollToBottom(); 255 } else { 256 $("#tabs_id").tabs("add", { 257 id: to_id, 258 title: to_name, 259 selected: true, 260 closable: true, 261 href: "/IMSystem/talkMsg/findByFromAndTo?from=" + from_id + "&to=" + to_id 262 }) 263 } 264 } 265 /*向服务器获取在线列表并更新浏览器在线列表*/ 266 function updateTalkerOnline(id) { 267 $.ajax({ 268 type: "post", 269 url: "/IMSystem/talker/findTalkersOfLogin", 270 data: {"id": id}, 271 dataType: "json", 272 success: function (data) { 273 var total = data.total; 274 /*设置当前在线用户数量*/ 275 $("#numOfTalkerOnline").text("当前在线人数:" + total); 276 console.log(data); 277 if (total != 0) { 278 /*清空当前在线列表*/ 279 $("#talkersOfOnline ul").empty(); 280 /*重新创建在线列表*/ 281 for (var i = 0; i < total; i++) { 282 var username = data.talkers[i].username; 283 console.log("headImg:" + data.talkers[i].headImg); 284 var event = "ondblclick=\"createTalkTab(" + id + "," + data.talkers[i].id + ",'" + username + "')\""; 285 var img = "<img src='/IMSystem" + data.talkers[i].headImg + "' class='head_img'/>"; 286 var li = "<li " + event + ">" + img + username + "</li><br/>"; 287 console.log(li); 288 $("#talkersOfOnline ul").append(li); 289 } 290 } 291 } 292 }) 293 } 294 /*创建会话*/ 295 function createWebSocket(talker) { 296 /*请求WebSocket的地址*/ 297 var url = "ws://" + window.location.host + "/IMSystem/easyui/talk?id=" + talker.id; 298 /*开始建立连接*/ 299 var websocket = new WebSocket(url); 300 console.log(talker.id); 301 /*WebSocket建立成功执行的函数*/ 302 websocket.onopen = function (event) { 303 /*关闭登录对话框*/ 304 $("#dlg_login").dialog("close"); 305 /*禁用上线按钮*/ 306 $("#memo_id h1 a:first").linkbutton("disable"); 307 /*启用上线按钮*/ 308 $("#memo_id h1 a:last").linkbutton("enable"); 309 console.log("IMG:" + talker.headImg); 310 /*通过一个Hidden标签的值(以Json字符串格式)存储当前登录用户的信息*/ 311 $("#talkerid").val(JSON.stringify(talker)); 312 console.log("JsonImg:" + JSON.parse($("#talkerid").val()).headImg); 313 console.log("userid" + $("#talkerid").val()); 314 /*用一个全局变量指向当前创建的WebSocket对象以供其他函数使用该WebSocket对象*/ 315 ws_websocket = websocket; 316 /*用户上线提醒*/ 317 $.messager.show({ 318 title: '上线提醒', 319 msg: '您已上线', 320 timeout: 2000, 321 showType: 'slide' 322 }); 323 }; 324 /*服务端向客户端发送信息时执行的函数*/ 325 websocket.onmessage = function (event) { 326 console.log("EVEN:" + event.data); 327 /*将服务器传递过来的Json字符串转化为JS的json对象*/ 328 var data = JSON.parse(event.data); 329 /*根据该信息的类型执行不同的操作*/ 330 if (data.type.toLowerCase() == "updateTalkersOnline".toLowerCase()) { 331 if (data.updateTalkersOnline) { 332 /*发送更新在线列表的请求*/ 333 updateTalkerOnline(JSON.parse($("#talkerid").val()).id); 334 } 335 } else if (data.type.toLowerCase() == "sendMessage".toLowerCase()) { 336 /*这个在客户端表示别人向自己发送信息*/ 337 acceptMsg(data); 338 } else if (data.type.toLowerCase() == "sendMessageSuccessful".toLowerCase()) { 339 /*这个在客户端表示自己向别人发送信息成功*/ 340 sendMsgSuccess(data); 341 } 342 }; 343 /*与服务器端连接关闭时执行的函数*/ 344 websocket.onclose = function (event) { 345 /*下线后启用上线按钮*/ 346 $("#memo_id h1 a:first").linkbutton("enable"); 347 /*下线后禁用下线按钮*/ 348 $("#memo_id h1 a:last").linkbutton("disable"); 349 /*清空在线列表*/ 350 $("#talkersOfOnline ul").empty(); 351 /*清空在线用户数量内容*/ 352 $("#numOfTalkerOnline").text(""); 353 /*重置显示当前在线用户位置内容*/ 354 $("#currentTalkerName").text("当前可登陆用户:鲜橙多,蠢比,傻逼,笔记本,好丽友派(无密码)"); 355 /*下线后关闭聊天选项卡*/ 356 var tabs = $("#tabs_id").tabs("tabs"); 357 358 var length = tabs.length; 359 for (var i = 0; i < length; i++) { 360 console.log(tabs); 361 $("#tabs_id").tabs("close", $("#tabs_id").tabs("getTabIndex", tabs[0])); 362 } 363 /*用户下线成功提醒*/ 364 $.messager.show({ 365 title: '下线提醒', 366 msg: '您已下线', 367 timeout: 2000, 368 showType: 'slide' 369 }); 370 } 371 } 372 373 /*给选项卡添加属性和事件*/ 374 $(function () { 375 376 $("#tabs_id").tabs({ 377 border: false, 378 onLoad: function (panel) { 379 var d = panel.panel("options"); 380 if (d.id != null) { 381 /*加载成功后初始化富文本编辑器*/ 382 var um = UM.getEditor("talker_msg" + d.id); 383 /* 384 只有当通信对象是智能机器人的时候,才为百度富文本编辑器添加键盘事件, 385 按Enter键即可发送信息 386 */ 387 if (d.id == 1) { 388 $(um.body).keypress(function (event) { 389 /*keyCode是13的话表示按下的是Enter键*/ 390 if (event.keyCode == 13) { 391 /*发送信息*/ 392 sendMsg(); 393 } 394 }); 395 } 396 scrollToBottom(); 397 } 398 }, 399 onBeforeClose: function (title, index) { 400 var d = $("#tabs_id").tabs("getTab", index).panel("options"); 401 /*关闭选项卡之前要先销毁富文本编辑器对象, 402 否则再次打开该选项卡时初始化富文本编辑器会出问题 403 */ 404 UM.getEditor("talker_msg" + d.id).destroy(); 405 } 406 }); 407 }); 408 409 410 $(function () { 411 /*初始化登录谈话框*/ 412 $("#dlg_login").dialog({ 413 title: "登录", 414 closed: true, 415 modal: true, 416 width: 300, 417 height: 170, 418 buttons: [{ 419 //iconCls: "icon-search", 420 text: "登录", 421 handler: function () { 422 var name = $("#talker_name").val(); 423 //var password = $("#talker_pwd").val(); 424 $.ajax({ 425 type: "post", 426 url: "/IMSystem/talker/findByName", 427 data: {"name": name}, 428 dataType: "json", 429 success: function (data) { 430 if (data.status == 0) { 431 /*登录失败*/ 432 $.messager.alert("提示", "登录失败,请重新登录", 'info'); 433 } else if (data.status == -1) { 434 /*该用户已经登录,不能重复登录*/ 435 $.messager.alert("提示", "该用户已登录,您不能重复登录", 'info'); 436 } else { 437 /*登录成功*/ 438 /*显示当前登录用户*/ 439 $("#currentTalkerName").text("当前登录用户:" + data.talker.username); 440 //创建会话,与服务器建立WebSocket长连接 441 createWebSocket(data.talker); 442 } 443 }, 444 error: function () { 445 $.messager.alert("错误", "登录失败,请重新登录", 'error'); 446 } 447 }); 448 } 449 }, { 450 iconCls: "icon-cancel", 451 text: "取消", 452 handler: function () { 453 $("#dlg_login").dialog("close"); 454 } 455 }] 456 }); 457 458 /*为上线按钮添加事件*/ 459 $("#memo_id h1 a:first").linkbutton({ 460 onClick: function () { 461 $("#login_form").form("clear"); 462 $("#dlg_login").dialog("open") 463 } 464 }); 465 /*为下线按钮添加事件*/ 466 $("#memo_id h1 a:last").linkbutton({ 467 onClick: function () { 468 /*向服务器发送下线信息表示自己要下线了*/ 469 ws_websocket.send(JSON.stringify({ 470 "type": "logoffTalker", 471 "logoffUser": true, 472 "id": JSON.parse($("#talkerid").val()).id 473 })); 474 } 475 }); 476 }) 477</script> 478<body class = "easyui-layout"> 479 <!--此hidden存储当前登录用户的信息,以Json字符串的形式存储用户信息--> 480 <input type = "hidden" id = "talkerid" /> 481 <!--登录框容器--> 482 <div id = "dlg_login" style = "text-align: center"> 483 <form id = "login_form"> 484 <div> 485 <p><input id = "talker_name" class = "easyui-textbox" data-options = "required:true,prompt:'输入您的姓名'" /> 486 </p> 487 <p><input id = "talker_pwd" class = "easyui-passwordbox" 488 data-options = "required:false,prompt:'输入您的密码'" /></p> 489 </div> 490 </form> 491 </div> 492 <!--<div data-options = "region:'north',split:false,border:false" style = "height:5%;text-align: center;">--> 493 <!--<center>使用WebSocket做的一个简单的IM系统</center>--> 494 <!--</div>--> 495 <div data-options = "region:'south',split:false,border:true" style = "height:5%;text-align: center">&copy;Flying版权所有 496 </div> 497 <div id = "memo_id" data-options = "region:'east',title:'备注',split:false,collapsible:false,border:false" 498 style = "width:20%"> 499 <h1 style = "text-align: center"> 500 <a class = "easyui-linkbutton" data-options = "disabled:false">上线</a> 501 <a class = "easyui-linkbutton" data-options = "disabled:true">下线</a> 502 </h1> 503 <hr /> 504 <!--显示当前用户名字--> 505 <p id = "currentTalkerName" style = "text-align: center;"> 506 当前可登陆用户:鲜橙多,蠢比,傻逼,笔记本,好丽友派(无密码) 507 </p> 508 <hr /> 509 <!--显示当前在线用户数量--> 510 <h1 id = "numOfTalkerOnline"></h1> 511 <!--在线用户列表--> 512 <div id = "talkersOfOnline"> 513 <ul></ul> 514 </div> 515 </div> 516 <div id = "tabs_id" class = "easyui-tabs" data-options = "region:'center',border:false" 517 style = "padding:5px;background:#eee;"> 518 </div> 519</body> 520</html>

双击在线用户的头像时弹出的聊天选项卡里加载的Jsp文件:

1<%@ page language = "java" pageEncoding = "UTF-8" %> 2<%@taglib prefix = "c" uri = "http://java.sun.com/jsp/jstl/core" %> 3<%@ taglib prefix = "f" uri = "http://java.sun.com/jsp/jstl/fmt" %> 4 5<!DOCTYPE html> 6<html lang = "en"> 7<head> 8 <meta charset = "UTF-8"> 9 <title></title> 10</head> 11<body> 12 <center><h4 style = "padding: 0;margin: 0">您正在与 ${to.username} 通信</h4> 13 </center> 14 <hr /> 15 <div class = "msg_container" style = "height: 350px;overflow: auto"> 16 <ul id = "talker${to.id}"> 17 <c:forEach items = "${talkMsgs}" var = "talkMsg"> 18 <c:choose> 19 <c:when test = "${from.id ne talkMsg.from}"> 20 <li class = "qipao"> 21 <div class = "headimg fl"> 22 <img src = "http://flyinghe.ngrok.cc/IMSystem${from.headImg}" /> 23 </div> 24 <div class = "leftqipao"> 25 <div class = "left_sj"></div> 26 <div class = "left_con"> 27 <span class = "show_time"><f:formatDate pattern = "yyyy-MM-dd HH:mm:ss" 28 value = "${talkMsg.date}" /></span><br /> 29 <hr /> 30 ${talkMsg.content} 31 </div> 32 </div> 33 </li> 34 </c:when> 35 <c:otherwise> 36 <li class = "qipao"> 37 <div class = "headimg fr"> 38 <img src = "http://flyinghe.ngrok.cc/IMSystem${from.headImg}" /> 39 </div> 40 <div class = "rightqipao"> 41 <div class = "right_sj"></div> 42 <div class = "right_con"> 43 <span class = "show_time"><f:formatDate pattern = "yyyy-MM-dd HH:mm:ss" 44 value = "${talkMsg.date}" /></span><br /> 45 <hr /> 46 ${talkMsg.content} 47 </div> 48 </div> 49 50 </li> 51 </c:otherwise> 52 </c:choose> 53 </c:forEach> 54 </ul> 55 </div> 56 <div> 57 <textarea id = "talker_msg${to.id}" style = "width:100%;height:100px;"></textarea> 58 <center> 59 <button onclick = "sendMsg()">发送</button> 60 ${to.id eq 1 ? "(按Enter键即可发送信息)" : ""} 61 </center> 62 </div> 63</body> 64</html>

总结:

WebSocket中浏览器与服务器交互的模型如下:

转载请注明出处:https://my.oschina.net/u/2608182/blog/751338

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )