初次学习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包。
<!--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中浏览器与服务器交互的模型如下: