Spring之WebSocket

Easter79
• 阅读 710

初次学习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

Spring之WebSocket

Spring之WebSocket

Spring之WebSocket

以下列出具体代码和步骤(代码中注释详尽):

后端代码涉及到的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">&copy;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中浏览器与服务器交互的模型如下:

Spring之WebSocket

Spring之WebSocket

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

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
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
Easter79 Easter79
2年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
Easter79
Easter79
Lv1
今生可爱与温柔,每一样都不能少。
文章
2.8k
粉丝
5
获赞
1.2k