Spring之WebSocket

初次學習WebSocket。在本次寫的WebSocket Demo裏使用到了以下插件(技術):javascript

1.百度的富文本編輯器:http://ueditor.baidu.com/website/php

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

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

4.Jackson技術:http://www.javashuo.com/article/p-rsmavewr-v.html前端

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

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

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


Notice:這個Demo是在我之前作的一個關於CRUD的 Demo的基礎上增長的聊天的功能。本博客將不會再針對聊天功能之外的功能作闡述,僅僅針對增長的聊天功能作闡述。關於CRUD部分感興趣的能夠參見該片博客:http://www.javashuo.com/article/p-tozqupup-e.htmlgithub

此Demo後端採用SSM框架web

 

此Demo最終的效果圖以下(主要作後臺,UI難看, [2016-12-22]修改了一下界面可能不同),

上線的網址:http://www.flyinghe.cn/IMSystem/

此項目Github地址:https://github.com/FlyingHe/IMSystem

如下列出具體代碼和步驟(代碼中註釋詳盡):

後端代碼涉及到的domain類,DAO層代碼,Service層代碼,數據庫表的設計本博客不闡述。只給出涉及WebSocket的代碼和Action層代碼,完成代碼能夠參考以上提供的Github地址

一.導入Jar包,只列出websocket的Jar包。

<!--Spring WebSocket-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-messaging</artifactId>
            <version>4.2.4.RELEASE</version>
        </dependency>

 

二。配置文件

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

<beans xmlns = "http://www.springframework.org/schema/beans"
       xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
       xmlns:websocket = "http://www.springframework.org/schema/websocket"
       xsi:schemaLocation = "
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <!--配置匹配talkWebSoketHandler處理器的路徑-->
        <websocket:mapping path = "/easyui/talk" handler = "talkWebSoketHandler" />
        <!--配置HandShake攔截器-->
        <websocket:handshake-interceptors>
            <ref bean = "handShake" />
        </websocket:handshake-interceptors>
    </websocket:handlers>
</beans>

三。Java文件:

HandShake.java:

package at.flying.websocket;

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Map;
@Component("handShake")
public class HandShake implements HandshakeInterceptor {
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
                                   Map<String, Object> attributes) throws Exception {
        /*獲取從客戶端傳來的用戶的ID值,此ID值做爲存儲登陸用戶的Key*/
        Long id = Long.valueOf(((ServletServerHttpRequest) request).getServletRequest().getParameter("id"));
        /*session.getAttributes()返回的Map就是這裏的attributes,
          因此將id存入attributes裏,後面再Handler裏能夠經過session獲取到該值
        */
        attributes.put("id", id);
        /*返回true以執行下一個攔截器*/
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
                               Exception exception) {
    }
}

 

TalkWebSoketHandler.java

package at.flying.websocket;

import at.flying.domain.MsgType;
import at.flying.domain.TalkMsg;
import at.flying.service.TalkMsgService;
import at.flying.service.TalkerService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.*;

/**
 * Created by FlyingHe on 2016/9/16.
 */
@Component("talkWebSoketHandler")
public class TalkWebSoketHandler extends TextWebSocketHandler {
    @Autowired
    private TalkMsgService talkMsgService;
    @Autowired
    private TalkerService talkerService;
    /*存儲創建的全部鏈接*/
    private static Map<Long, WebSocketSession> sessions = new HashMap<>();
    /*用於處理Json數據*/
    private static ObjectMapper objectMapper = new ObjectMapper();

    /*獲取當前創建的全部鏈接*/
    public static Map<Long, WebSocketSession> getSessions() {
        return sessions;
    }


    /*客戶端與服務器端鏈接創建以後執行的函數*/
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.out.println("afterConnectionEstablished");
        if (session.getAttributes().get("id") == null) {
            session.close();
            return;
        }
        /*獲取當前用戶的ID*/
        Long id = (Long) session.getAttributes().get("id");
        /*存儲當前用戶創建的連接,即會話session*/
        sessions.put(id, session);
        System.out.println("用戶" + id + "一上線");
        /*通知全部登陸用戶客戶端更新在線列表*/
        this.updateTalkersOnline();
    }

    /*客戶端向服務器發送信息時會調用該函數*/
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        /*獲取信息內容*/
        String jsonstr = message.getPayload();
        /*獲取該信息類型以選擇處理該信息的方式*/
        String type = objectMapper.readTree(jsonstr).get("type").asText();
        if (type.equalsIgnoreCase(MsgType.LOGOFF_TALKER)) {
            /*此類型表示該用戶要下線*/
            this.userLogoff(session, message);
        } else if (type.equalsIgnoreCase(MsgType.SEND_MESSAGE)) {
            /*此類型表示該用戶要發送信息給別人*/
            this.sendMsg(session, message);
        } else if (type.equalsIgnoreCase(MsgType.SEND_MESSAGE_TO_Robot)) {
            /*此類表示要將信息發送給機器人*/
            this.sendMsgToRobot(session, message);
        }
    }

    /*鏈接發生異常時執行的函數*/
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        this.removeAndCloseSession(null, session);
    }

    /*鏈接關閉後執行的函數*/
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        this.removeAndCloseSession(null, session);
        /*用戶下線時,通知全部登陸用戶客戶端更新在線列表*/
        this.updateTalkersOnline();
    }

    /*移除並關閉session*/
    private void removeAndCloseSession(Long id, WebSocketSession session) throws IOException {
        if (id != null && sessions.containsKey(id)) {
            WebSocketSession s = sessions.remove(id);
            if (s.isOpen()) {
                s.close();
            }
        } else {
            Long idd = null;
            if (session.getAttributes().get("id") instanceof Long) {
                idd = (Long) session.getAttributes().get("id");
            }
            if (idd != null && sessions.containsKey(idd)) {
                WebSocketSession s = sessions.remove(idd);
                if (s.isOpen()) {
                    s.close();
                }
            } else {
                if (session.isOpen()) {
                    session.close();
                }
            }
        }
    }

    /*用戶發送信息執行的函數*/
    private void sendMsg(WebSocketSession session, TextMessage message) throws IOException {
        /*將信息內容封裝到信息對象中*/
        TalkMsg talkMsg = objectMapper
                .readValue(objectMapper.readTree(message.getPayload()).get("msg").toString(), TalkMsg.class);
        talkMsg.setDate(new Date());
        /*將該信息存入數據庫*/
        this.talkMsgService.add(talkMsg);
//        System.out.println("ID:" + talkMsg.getId());
        /*獲取目標用戶的session*/
        WebSocketSession to_session = sessions.get(talkMsg.getTo());
        /*將信息轉成Json字符串*/
        String json_msg = objectMapper.writeValueAsString(talkMsg);
        /*通知當前用戶信息發送成功*/
        if (session.isOpen()) {
            session.sendMessage(
                    new TextMessage("{\"type\":\"" + MsgType.SEND_MESSAGE_SUCCESSFUL + "\",\"msg\":" + json_msg + "}"));
        }
        /*若是目標用戶在線的話就將該信息發送給目標用戶*/
        if (to_session != null && to_session.isOpen()) {
            to_session.sendMessage(
                    new TextMessage("{\"type\":\"" + MsgType.SEND_MESSAGE + "\",\"msg\":" + json_msg + "}"));
        }
    }

    /*用戶下線執行函數*/
    private void userLogoff(WebSocketSession session, TextMessage message) throws Exception {
        String jsonstr = message.getPayload();
        JsonNode root = objectMapper.readTree(jsonstr);
        if (root.get("logoffUser") != null) {
            if (root.get("logoffUser").asBoolean()) {
                Long id = root.get("id").asLong();
                this.removeAndCloseSession(id, session);
                System.out.println("用戶" + id + "已下線");
            }
        }
    }

    /*服務器提醒全部在線用戶,更新客戶端在線列表*/
    private void updateTalkersOnline() {
        /*採用多線程在在線人數比較多的狀況下提升執行效率*/
        for (Map.Entry<Long, WebSocketSession> entry : sessions.entrySet()) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    WebSocketSession session = entry.getValue();
                    if (session.isOpen()) {
                        try {
                            session.sendMessage(new TextMessage(
                                    "{\"type\":\"" + MsgType.UPDATE_TALKERS_ONLINE +
                                            "\",\"updateTalkersOnline\":true}"));
                        } catch (IOException e) {
                            try {
                                removeAndCloseSession(null, session);
                            } catch (IOException e1) {
                            }
                        }
                    }
                }
            }).start();

        }
    }

    /*將信息發送給機器人*/
    private void sendMsgToRobot(WebSocketSession session, TextMessage message) throws IOException {
        /*將信息內容封裝到信息對象中*/
        TalkMsg talkMsg_receive = objectMapper
                .readValue(objectMapper.readTree(message.getPayload()).get("msg").toString(), TalkMsg.class);
        talkMsg_receive.setDate(new Date());
        /*與機器人通訊時發送的Http請求*/
        HttpClient httpClient = new HttpClient();
        // 建立post請求,相似Post請求
        PostMethod postMethod =
                new PostMethod("http://www.tuling123.com/openapi/api");
        // 設置請求的正文內容
        String json_text = "{\"key\":\"這裏請填入你本身的圖靈請求的key\",\"info\":\"" + talkMsg_receive.getContent() +
                "\",\"userid\":\"" + talkMsg_receive.getFrom() + "\"}";
        StringRequestEntity stringRequestEntity =
                new StringRequestEntity(json_text, "application/json", "UTF-8");
        postMethod.setRequestEntity(stringRequestEntity);
        /*通知當前用戶信息發送成功*/
        if (session.isOpen()) {
            session.sendMessage(
                    new TextMessage("{\"type\":\"" + MsgType.SEND_MESSAGE_SUCCESSFUL + "\",\"msg\":" +
                            objectMapper.writeValueAsString(talkMsg_receive) + "}"));
        }
        // 發送post請求
        httpClient.executeMethod(postMethod);
        //獲取響應結果
        String result = new String(postMethod.getResponseBodyAsString().getBytes("ISO-8859-1"), "UTF-8");
         /*將機器人返回的信息封裝並轉成Json字符串*/
        TalkMsg talkMsg_send = new TalkMsg();
        talkMsg_send.setFrom(1L);
        talkMsg_send.setTo(talkMsg_receive.getFrom());
        talkMsg_send.setContent(this.parseTuringData(result));
        talkMsg_send.setDate(new Date());
        String json_msg = objectMapper.writeValueAsString(talkMsg_send);

        /*推送圖靈機器人消息給此用戶*/
        if (session.isOpen()) {
            session.sendMessage(
                    new TextMessage("{\"type\":\"" + MsgType.SEND_MESSAGE + "\",\"msg\":" + json_msg + "}"));
        }
        //釋放與圖靈的HTTP鏈接
        postMethod.releaseConnection();
    }

    /*解析從圖靈請求來的Json數據*/
    private String parseTuringData(String turingResult) throws IOException {
        int code = objectMapper.readTree(turingResult).get("code").asInt();
        String content = null;
        switch (code) {
            case 200000:
                content = this.turing_200000(turingResult);
                break;
            case 302000:
                content = this.turing_302000(turingResult);
                break;
            case 308000:
                content = this.turing_308000(turingResult);
                break;
            default:
                content = this.turing_text(turingResult);
                break;
        }

        return content;
    }

    /*圖靈請求code=308000的處理*/
    private String turing_308000(String turingResult) throws IOException {
        StringBuilder content = new StringBuilder();
        TypeFactory typeFactory = objectMapper.getTypeFactory();
        List<Map<String, String>> news = objectMapper
                .readValue(objectMapper.readTree(turingResult).get("list").toString(), typeFactory
                        .constructCollectionType(ArrayList.class, typeFactory.constructMapType(
                                HashMap.class, String.class, String.class)));
        String text = this.turing_text(turingResult);
        content.append(text).append("<br/><hr/>");
        for (Map<String, String> map : news) {
            content.append("<a target = \"_blank\" class = \"turing_link\" href = \"").append(map.get("detailurl"))
                    .append("\">").append(map.get("name")).append("<br/>材料:").append(map.get("info")).
                    append("<img src = \"").append(map.get("icon")).
                    append("\"/></a><hr/>");
        }
        content.delete(content.lastIndexOf("<hr/>"), content.length()).insert(0, "<p>")
                .insert(content.length(), "</p>");
        return content.toString();
    }

    /*圖靈請求code=302000的處理*/
    private String turing_302000(String turingResult) throws IOException {
        StringBuilder content = new StringBuilder();
        TypeFactory typeFactory = objectMapper.getTypeFactory();
        List<Map<String, String>> news = objectMapper
                .readValue(objectMapper.readTree(turingResult).get("list").toString(), typeFactory
                        .constructCollectionType(ArrayList.class, typeFactory.constructMapType(
                                HashMap.class, String.class, String.class)));
        String text = this.turing_text(turingResult);
        content.append(text).append("<br/><hr/>");
        for (Map<String, String> map : news) {
            content.append("<a target = \"_blank\" class = \"turing_link\" href = \"").append(map.get("detailurl"))
                    .append("\">").append(map.get("article")).append("----來自").append(map.get("source")).
                    append("</a><hr/>");
        }
        content.delete(content.lastIndexOf("<hr/>"), content.length()).insert(0, "<p>")
                .insert(content.length(), "</p>");
        return content.toString();
    }

    /*圖靈請求code=200000的處理*/
    private String turing_200000(String turingResult) throws IOException {
        String text = this.turing_text(turingResult);
        String url = objectMapper.readTree(turingResult).get("url").asText();
        return "<p>" + text + "<br/><a class = \"turing_link\" target = \"_blank\" href = \"" + url +
                "\">請點擊打開頁面</a></p>";
    }

    /*圖靈請求錯誤以及code=100000的處理*/
    private String turing_text(String turingResult) throws IOException {
        return objectMapper.readTree(turingResult).get("text").asText();
    }
}

MsgType.java:

package at.flying.domain;
/*
    此類用於標識客戶端和服務端相互發送信息時,指定該信息的類型,
    以使後端或者前端根據信息類型執行不一樣的操做
*/
public class MsgType {
    /*更新在線列表*/
    public static final String UPDATE_TALKERS_ONLINE = "updateTalkersOnline";
    /*用戶下線*/
    public static final String LOGOFF_TALKER = "logoffTalker";
    /*用戶發送消息*/
    public static final String SEND_MESSAGE = "sendMessage";
    /*用戶發送信息給機器人*/
    public static final String SEND_MESSAGE_TO_Robot = "sendMessageToRobot";
    /*用戶發送消息成功*/
    public static final String SEND_MESSAGE_SUCCESSFUL = "sendMessageSuccessful";
}

TalkerAction.java

package at.flying.web.action;

import at.flying.domain.Talker;
import at.flying.service.TalkerService;
import at.flying.websocket.TalkWebSoketHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.socket.WebSocketSession;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Controller
@RequestMapping("talker")
public class TalkerAction {
    @Autowired
    private TalkerService talkerService;

    @RequestMapping("findById")
    @ResponseBody
    public Map<String, Object> findById(
            @RequestParam("id")
                    Long id) {
        Talker talker = this.talkerService.findById(id);
        Map<String, Object> map = new HashMap<>();
        map.put("status", 0);
        if (talker != null) {
            map.put("status", 1);
            map.put("talker", talker);
        }
        return map;
    }

    @RequestMapping("findByName")
    @ResponseBody
    public Map<String, Object> findByName(
            @RequestParam("name")
                    String name) {

        Map<String, Object> map = new HashMap<>();
        map.put("status", 0);//0表明登陸失敗

        //用戶名不能是機器人
        if (!"機器人".equalsIgnoreCase(name)) {
            Talker talker = this.talkerService.findByName(name);
            if (talker != null) {
            /*檢測當前用戶是否已登陸*/
                if (TalkWebSoketHandler.getSessions().containsKey(talker.getId())) {
                    map.put("status", -1);//-1表明當前用戶已經登陸,不能重複登陸
                } else {
                    map.put("status", 1);//1表明登陸成功
                }

                map.put("talker", talker);
            }
        }
        return map;
    }

    /*查找在線用戶*/
    @RequestMapping("findTalkersOfLogin")
    @ResponseBody
    public Map<String, Object> findTalkersOfLogin(
            @RequestParam("id")
                    Long id) {
        Map<Long, WebSocketSession> sessions = TalkWebSoketHandler.getSessions();
        List<Talker> talkers = new ArrayList<>();
        talkers.add(this.talkerService.findById(1L));
        for (Map.Entry<Long, WebSocketSession> entry :
                sessions.entrySet()) {
            WebSocketSession session = entry.getValue();
            if (session.isOpen()) {
                Talker talker = this.talkerService.findById(entry.getKey());
                if (talker != null) {
                    talkers.add(talker);
                }
            }
        }
        Map<String, Object> map = new HashMap<>();
        map.put("total", talkers.size());
        map.put("talkers", talkers);
        return map;
    }
}

TalkMsgAction.java

package at.flying.web.action;

import at.flying.domain.TalkMsg;
import at.flying.domain.Talker;
import at.flying.service.TalkMsgService;
import at.flying.service.TalkerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Controller
@RequestMapping("talkMsg")
public class TalkMsgAction {

    @Autowired
    private TalkMsgService talkMsgService;
    @Autowired
    private TalkerService talkerService;

    @RequestMapping("add")
    @ResponseBody
    public Map<String, Object> add(
            @RequestBody
                    TalkMsg talkMsg) {
        this.talkMsgService.add(talkMsg);
        Map<String, Object> map = new HashMap<>();
        map.put("status", 1);
        return map;
    }

    @RequestMapping("findById")
    @ResponseBody
    public TalkMsg findById(
            @RequestParam("id")
                    Long id) {
        return this.talkMsgService.findById(id);
    }

    @RequestMapping("findByFromAndTo")
    public ModelAndView findByFromAndTo(
            @RequestParam("from")
                    Long from,
            @RequestParam("to")
                    Long to,
            ModelAndView modelAndView) {
        List<TalkMsg> talkMsgs = to == 1 ? new ArrayList<>() : this.talkMsgService.findByFromAndTo(from, to);
        Talker tot = this.talkerService.findById(to);
        Talker fromt = this.talkerService.findById(from);
        modelAndView.addObject("talkMsgs", talkMsgs);
        modelAndView.addObject("to", tot);
        modelAndView.addObject("from", fromt);
        modelAndView.setViewName("talkMsg");
        return modelAndView;
    }
}

 

前端代碼:

主頁面代碼:

<!---
  Created by FlyingHe on 2016/8/14.
--->
<!DOCTYPE html>
<html lang = "en">
<head>
    <meta charset = "UTF-8">
    <title>使用WebSocket作的一個簡單的IM系統</title>
    <link rel = "stylesheet" type = "text/css" href = "easyui/themes/default/easyui.css">
    <link rel = "stylesheet" type = "text/css" href = "easyui/themes/icon.css">

    <script type = "text/javascript" src = "js/jquery-2.1.4.min.js"></script>
    <script src = "easyui/jquery.easyui.min.js"></script>
    <script src = "easyui/easyui-lang-zh_CN.js"></script>
    <script src = "easyui/jquery.edatagrid.js"></script>
    <!--富文本編輯器的Script文件-->
    <!-- 樣式文件 -->
    <link rel = "stylesheet" href = "./umeditor/themes/default/css/umeditor.css">
    <!-- 配置文件 -->
    <script type = "text/javascript" src = "./umeditor/umeditor.config.js"></script>
    <!-- 編輯器源碼文件 -->
    <script type = "text/javascript" src = "./umeditor/umeditor.js"></script>
    <!-- 語言包文件 -->
    <script type = "text/javascript" src = "./umeditor/lang/zh-cn/zh-cn.js"></script>
</head>
<style>
    #memo_id p {
        padding-left: 10px;
    }

    #numOfTalkerOnline {
        text-align: center;
    }

    #talkersOfOnline ul li {
        text-decoration: none;
        display: block;
        font-size: 24px;
        text-align: left;
        vertical-align: text-top;
        height: 30px;
        margin-bottom: 10px;
    }

    #talkersOfOnline ul li:hover {
        background-color: grey;
        cursor: pointer;
    }

    /*圖靈連接的樣式*/
    .turing_link {
        color: blue;
        text-decoration: none;
    }

    .turing_link:hover {
        color: #95B8E7;
    }

    /************************聊天選項卡樣式表 start************************/
    ul {
        list-style: none;
    }

    .qipao {
        position: relative;
        clear: both;
        /*margin-bottom: 20px;*/
    }

    .headimg img {
        width: 50px;
        height: 50px;
        border-radius: 50px;
        vertical-align: top;
    }

    .head_img {
        width: 50px;
        height: 50px;
        border-radius: 50px;
        vertical-align: middle;
    }

    .headimg {
        display: inline-block;
    }

    .leftqipao {
        display: inline-block;
        vertical-align: top;
        position: relative;
        top: 30px;
    }

    .rightqipao {
        display: block;
        vertical-align: top;
        position: relative;
        top: 30px;
    }

    .left_con {
        background-color: orange;
        padding: 15px;
        display: inline-block;
        border-radius: 10px;
        max-width: 300px;
        float: left;
        line-height: 20px;
        margin-bottom: 10px;
    }

    .left_sj {
        margin: 0;
        width: 0;
        height: 0;
        border-left: 10px solid rgba(255, 255, 255, 0);
        border-bottom: 10px solid rgba(255, 255, 255, 0);
        border-right: 10px solid orange;
        border-top: 10px solid rgba(255, 255, 255, 0);
        float: left;
        position: relative;
        top: 10px;
    }

    .right_con {
        background-color: orange;
        padding: 15px;
        display: inline-block;
        border-radius: 10px;
        max-width: 300px;
        float: right;
        line-height: 20px;
        margin-bottom: 10px;
    }

    .right_sj {
        margin: 0;
        width: 0;
        height: 0;
        border-left: 10px solid orange;
        border-bottom: 10px solid rgba(255, 255, 255, 0);
        border-right: 10px solid rgba(255, 255, 255, 0);
        border-top: 10px solid rgba(255, 255, 255, 0);
        float: right;
        position: relative;
        top: 10px;
    }

    .fl {
        float: left;
    }

    .fr {
        float: right;
    }

    .leftqipao .show_time {
        float: left;
    }

    .rightqipao .show_time {
        float: right;
    }

    /************************聊天選項卡樣式表 end************************/
</style>
<script>
    /*全局Websocket*/
    var ws_websocket = null;
    /*使div滾動條滾到最低端*/
    function scrollToBottom() {
        $(".msg_container").each(function () {
            $(this).scrollTop(this.scrollHeight);
        });

    }
    /*成功接受到信息或者信息發送成功時會建立信息條*/
    function createMsg(msg) {
        var isMine = msg.from == JSON.parse($("#talkerid").val()).id ? true : false;
        var div_headimg_fr_fl = "<div class = \"headimg " + (isMine ? "fr" : "fl") + "\"><img src = \"/IMSystem" + JSON.parse($("#talkerid").val()).headImg + "\" /></div>";
        var div_left_right_sj = "<div class = \"" + (isMine ? "right" : "left") + "_sj\"></div>";
        var div_left_right_con = "<div class = \"" + (isMine ? "right" : "left") + "_con\"><span class = \"show_time\">" + msg.date + "</span><br/><hr/>" + msg.content + "</div>";
        var div_left_right_qipao = "<div class = \"" + (isMine ? "right" : "left") + "qipao\">" + div_left_right_sj + div_left_right_con + "</div>";
        var li = "<li class = \"qipao\">" + div_headimg_fr_fl + div_left_right_qipao + "</li>";
        $("#talker" + (isMine ? msg.to : msg.from)).append(li);
        scrollToBottom();

    }
    /*接收別人發來的消息*/
    function acceptMsg(data) {
        var msg = data.msg;
        createMsg(msg);
    }
    /*本身成功發送給別人消息時調用的函數*/
    function sendMsgSuccess(data) {
        createMsg(data.msg);
        //清空消息框
        UM.getEditor("talker_msg" + data.msg.to).ready(function () {
            this.setContent("", false);
        })
    }
    /*本身給別人發送消息*/
    function sendMsg() {
        var from = JSON.parse($("#talkerid").val()).id;
        var to = $("#tabs_id").tabs("getSelected").panel("options").id;
        var content = "";
        var txt = "";
        UM.getEditor("talker_msg" + to).ready(function () {
            content = to == 1 ? this.getContentTxt() : this.getContent();
            txt = this.getPlainTxt();
        });
        /*判斷內容是否爲空*/
        if (txt.trim() == "") {
            $.messager.show({
                title: '提醒',
                msg: '大哥,您啥內容都不發麼,黃段子也OK啊',
                timeout: 2000,
                showType: 'slide'
            });
            return;
        }

        /*封裝信息*/
        var message = {
            "type": to == 1 ? "sendMessageToRobot" : "sendMessage",
            "msg": {
                "from": from,
                "to": to,
                "content": content
            }
        };
        console.log("Msg:" + JSON.stringify(message));
        /*向服務器發送信息*/
        ws_websocket.send(JSON.stringify(message));

    }
    /*建立通訊選項卡*/
    function createTalkTab(from_id, to_id, to_name) {
        /*雙擊本身頭像將不會建立*/
        if (from_id == to_id) {
            $.messager.show({
                title: '提醒',
                msg: '大哥,您自言自語還須要網絡麼',
                timeout: 2000,
                showType: 'slide'
            });
            return;
        }
        /*若是該選項卡存在的話就切換到該選項卡,若不存在就建立選項卡*/
        if ($("#tabs_id").tabs("exists", to_name)) {
            $("#tabs_id").tabs("select", to_name);
            scrollToBottom();
        } else {
            $("#tabs_id").tabs("add", {
                id: to_id,
                title: to_name,
                selected: true,
                closable: true,
                href: "/IMSystem/talkMsg/findByFromAndTo?from=" + from_id + "&to=" + to_id
            })
        }
    }
    /*向服務器獲取在線列表並更新瀏覽器在線列表*/
    function updateTalkerOnline(id) {
        $.ajax({
            type: "post",
            url: "/IMSystem/talker/findTalkersOfLogin",
            data: {"id": id},
            dataType: "json",
            success: function (data) {
                var total = data.total;
                /*設置當前在線用戶數量*/
                $("#numOfTalkerOnline").text("當前在線人數:" + total);
                console.log(data);
                if (total != 0) {
                    /*清空當前在線列表*/
                    $("#talkersOfOnline ul").empty();
                    /*從新建立在線列表*/
                    for (var i = 0; i < total; i++) {
                        var username = data.talkers[i].username;
                        console.log("headImg:" + data.talkers[i].headImg);
                        var event = "ondblclick=\"createTalkTab(" + id + "," + data.talkers[i].id + ",'" + username + "')\"";
                        var img = "<img src='/IMSystem" + data.talkers[i].headImg + "' class='head_img'/>";
                        var li = "<li " + event + ">" + img + username + "</li><br/>";
                        console.log(li);
                        $("#talkersOfOnline ul").append(li);
                    }
                }
            }
        })
    }
    /*建立會話*/
    function createWebSocket(talker) {
        /*請求WebSocket的地址*/
        var url = "ws://" + window.location.host + "/IMSystem/easyui/talk?id=" + talker.id;
        /*開始創建鏈接*/
        var websocket = new WebSocket(url);
        console.log(talker.id);
        /*WebSocket創建成功執行的函數*/
        websocket.onopen = function (event) {
            /*關閉登陸對話框*/
            $("#dlg_login").dialog("close");
            /*禁用上線按鈕*/
            $("#memo_id h1 a:first").linkbutton("disable");
            /*啓用上線按鈕*/
            $("#memo_id h1 a:last").linkbutton("enable");
            console.log("IMG:" + talker.headImg);
            /*經過一個Hidden標籤的值(以Json字符串格式)存儲當前登陸用戶的信息*/
            $("#talkerid").val(JSON.stringify(talker));
            console.log("JsonImg:" + JSON.parse($("#talkerid").val()).headImg);
            console.log("userid" + $("#talkerid").val());
            /*用一個全局變量指向當前建立的WebSocket對象以供其餘函數使用該WebSocket對象*/
            ws_websocket = websocket;
            /*用戶上線提醒*/
            $.messager.show({
                title: '上線提醒',
                msg: '您已上線',
                timeout: 2000,
                showType: 'slide'
            });
        };
        /*服務端向客戶端發送信息時執行的函數*/
        websocket.onmessage = function (event) {
            console.log("EVEN:" + event.data);
            /*將服務器傳遞過來的Json字符串轉化爲JS的json對象*/
            var data = JSON.parse(event.data);
            /*根據該信息的類型執行不一樣的操做*/
            if (data.type.toLowerCase() == "updateTalkersOnline".toLowerCase()) {
                if (data.updateTalkersOnline) {
                    /*發送更新在線列表的請求*/
                    updateTalkerOnline(JSON.parse($("#talkerid").val()).id);
                }
            } else if (data.type.toLowerCase() == "sendMessage".toLowerCase()) {
                /*這個在客戶端表示別人向本身發送信息*/
                acceptMsg(data);
            } else if (data.type.toLowerCase() == "sendMessageSuccessful".toLowerCase()) {
                /*這個在客戶端表示本身向別人發送信息成功*/
                sendMsgSuccess(data);
            }
        };
        /*與服務器端鏈接關閉時執行的函數*/
        websocket.onclose = function (event) {
            /*下線後啓用上線按鈕*/
            $("#memo_id h1 a:first").linkbutton("enable");
            /*下線後禁用下線按鈕*/
            $("#memo_id h1 a:last").linkbutton("disable");
            /*清空在線列表*/
            $("#talkersOfOnline ul").empty();
            /*清空在線用戶數量內容*/
            $("#numOfTalkerOnline").text("");
            /*重置顯示當前在線用戶位置內容*/
            $("#currentTalkerName").text("當前可登錄用戶:鮮橙多,蠢比,傻逼,筆記本,好麗友派(無密碼)");
            /*下線後關閉聊天選項卡*/
            var tabs = $("#tabs_id").tabs("tabs");

            var length = tabs.length;
            for (var i = 0; i < length; i++) {
                console.log(tabs);
                $("#tabs_id").tabs("close", $("#tabs_id").tabs("getTabIndex", tabs[0]));
            }
            /*用戶下線成功提醒*/
            $.messager.show({
                title: '下線提醒',
                msg: '您已下線',
                timeout: 2000,
                showType: 'slide'
            });
        }
    }

    /*給選項卡添加屬性和事件*/
    $(function () {

        $("#tabs_id").tabs({
            border: false,
            onLoad: function (panel) {
                var d = panel.panel("options");
                if (d.id != null) {
                    /*加載成功後初始化富文本編輯器*/
                    var um = UM.getEditor("talker_msg" + d.id);
                    /*
                     只有當通訊對象是智能機器人的時候,才爲百度富文本編輯器添加鍵盤事件,
                     按Enter鍵便可發送信息
                     */
                    if (d.id == 1) {
                        $(um.body).keypress(function (event) {
                            /*keyCode是13的話表示按下的是Enter鍵*/
                            if (event.keyCode == 13) {
                                /*發送信息*/
                                sendMsg();
                            }
                        });
                    }
                    scrollToBottom();
                }
            },
            onBeforeClose: function (title, index) {
                var d = $("#tabs_id").tabs("getTab", index).panel("options");
                /*關閉選項卡以前要先銷燬富文本編輯器對象,
                 不然再次打開該選項卡時初始化富文本編輯器會出問題
                 */
                UM.getEditor("talker_msg" + d.id).destroy();
            }
        });
    });


    $(function () {
        /*初始化登陸談話框*/
        $("#dlg_login").dialog({
            title: "登陸",
            closed: true,
            modal: true,
            width: 300,
            height: 170,
            buttons: [{
                //iconCls: "icon-search",
                text: "登陸",
                handler: function () {
                    var name = $("#talker_name").val();
                    //var password = $("#talker_pwd").val();
                    $.ajax({
                        type: "post",
                        url: "/IMSystem/talker/findByName",
                        data: {"name": name},
                        dataType: "json",
                        success: function (data) {
                            if (data.status == 0) {
                                /*登陸失敗*/
                                $.messager.alert("提示", "登陸失敗,請從新登陸", 'info');
                            } else if (data.status == -1) {
                                /*該用戶已經登陸,不能重複登陸*/
                                $.messager.alert("提示", "該用戶已登陸,您不能重複登陸", 'info');
                            } else {
                                /*登陸成功*/
                                /*顯示當前登陸用戶*/
                                $("#currentTalkerName").text("當前登陸用戶:" + data.talker.username);
                                //建立會話,與服務器創建WebSocket長鏈接
                                createWebSocket(data.talker);
                            }
                        },
                        error: function () {
                            $.messager.alert("錯誤", "登陸失敗,請從新登陸", 'error');
                        }
                    });
                }
            }, {
                iconCls: "icon-cancel",
                text: "取消",
                handler: function () {
                    $("#dlg_login").dialog("close");
                }
            }]
        });

        /*爲上線按鈕添加事件*/
        $("#memo_id h1 a:first").linkbutton({
            onClick: function () {
                $("#login_form").form("clear");
                $("#dlg_login").dialog("open")
            }
        });
        /*爲下線按鈕添加事件*/
        $("#memo_id h1 a:last").linkbutton({
            onClick: function () {
                /*向服務器發送下線信息表示本身要下線了*/
                ws_websocket.send(JSON.stringify({
                    "type": "logoffTalker",
                    "logoffUser": true,
                    "id": JSON.parse($("#talkerid").val()).id
                }));
            }
        });
    })
</script>
<body class = "easyui-layout">
    <!--此hidden存儲當前登陸用戶的信息,以Json字符串的形式存儲用戶信息-->
    <input type = "hidden" id = "talkerid" />
    <!--登陸框容器-->
    <div id = "dlg_login" style = "text-align: center">
        <form id = "login_form">
            <div>
                <p><input id = "talker_name" class = "easyui-textbox" data-options = "required:true,prompt:'輸入您的姓名'" />
                </p>
                <p><input id = "talker_pwd" class = "easyui-passwordbox"
                          data-options = "required:false,prompt:'輸入您的密碼'" /></p>
            </div>
        </form>
    </div>
    <!--<div data-options = "region:'north',split:false,border:false" style = "height:5%;text-align: center;">-->
    <!--<center>使用WebSocket作的一個簡單的IM系統</center>-->
    <!--</div>-->
    <div data-options = "region:'south',split:false,border:true" style = "height:5%;text-align: center">&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中瀏覽器與服務器交互的模型以下:

 

轉載請註明出處:http://www.javashuo.com/article/p-cmompxbi-dg.html

相關文章
相關標籤/搜索