2018-05-01 BEX5內使用websocket實現前端數據實時同步

1 搭建運行websocket的環境(這裏只用tomcat說明)html

爲了能讓websocket運行起來,須要tomcat 7.0版本以上,可是目前X5使用的是tomcat6,能夠經過如下兩種方式達到條件前端

1.1 經過替換掉X5裏面的tomcat來升級,替換步驟以下:java

step1 下載解壓版的tomcat 8mysql

   https://tomcat.apache.org/download-80.cgiweb

step2 把tomcat 8拷貝到平臺版本中把名字改成平臺版本默認帶的tomcat目錄的名字sql

step3 把平臺默認帶的tomcat中的apache-tomcat\conf\context.xml文件和apache-tomcat\conf\Catalina\localhost下面的配置文件拷貝到tomcat 8中數據庫

step4 在本身的tomcat的lib中放數據庫驅動,平臺默認的tomcat的lib下帶的數據庫驅動以下: apache

   jtds-1.2.jar、mysql-connector-java-5.1.36-bin.jar、ojdbc14.jarjson

step5 若是tomcat 8端口號不是8080,須要修改model同級的conf/server.xml中配置的地址中的端口號windows

step6 部署後輸入http://IP:端口默認不會跳轉到平臺的頁面中以及地址欄中圖標是tomcat默認的,不是平臺的藍色圖標;若是須要默認跳轉到平臺頁面而且用平臺的圖標,須要把平臺默認帶的tomcat\webapps\ROOT下的index.html和favicon.ico兩個文件拷貝到本身的tomcat\webapps\ROOT下

step7 須要在apache-tomcat\bin\startup.bat中配置java的環境變量

1 set JRE_HOME=..\..\java\jre1.8
2 set JAVA_HOME=
3 set CATALINA_BASE=..\..\apache-tomcat
4 set PATH=%JRE_HOME%\bin;%PATH%

 (若是按照以上步驟有疑問能夠參考官網帖子http://docs.wex5.com/bex5-deploy-question-list-4001/)

 注意:安裝以上步驟部署後開發工具點擊運行時會出現如下報錯

Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/juli/logging/LogFactory
     at org.apache.catalina.startup.Bootstrap.<clinit>(Bootstrap.java:49)
Caused by: java.lang.ClassNotFoundException: org.apache.juli.logging.LogFactory
     at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
     at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
     at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
     at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
     ... 1 more

解決方法可參考http://docs.wex5.com/wex5-deploy-quetion-list-0006/

在Studio中,執行如下操做:

step1 在右上角選擇「Java」,切換到Java視圖

step2 選擇「運行 –> 調試配置…」,彈出調試配置對話框

step3 在「調試配置」對話框中選擇「Java應用程序 –> Tomat 5.x」, 選擇「類路徑」選項卡中,將%TOMCAT_HOME%\bin\tomcat-juli.jar添加至「用戶條目」中,以後點「應用」

 

step4 以後運行Tomcat不能經過Tomcat圖標快捷方式啓動, 必須在Java視圖中,使用下圖中的「Tomcat 5.x」啓動Tomcat

step5 啓動tomcat後, 會出現如下錯誤

[JPivot] 13 六月 2016 17:29:26,072 ERROR [Session ] com.tonbeller.tbutils.res.JNDIResourceProvider#close: error closing context
javax.naming.OperationNotSupportedException: Context is read only
    at org.apache.naming.NamingContext.checkWritable(NamingContext.java:961)
    at org.apache.naming.NamingContext.close(NamingContext.java:761)
    at com.tonbeller.tbutils.res.JNDIResourceProvider.close(JNDIResourceProvider.java:68)
    at com.tonbeller.tbutils.res.CompositeResourceProvider.close(CompositeResourceProvider.java:56)
    at com.tonbeller.tbutils.res.ResourcesFactory.initialize(ResourcesFactory.java:163)
    at com.tonbeller.tbutils.res.ResourcesFactory.<init>(ResourcesFactory.java:92)
    at com.tonbeller.tbutils.res.ResourcesFactory.<clinit>(ResourcesFactory.java:89)
    at com.tonbeller.tbutils.res.ResourcesFactoryContextListener.contextInitialized(ResourcesFactoryContextListener.java:23)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:5068)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5584)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:147)
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:899)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:875)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:652)
    at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:679)
    at org.apache.catalina.startup.HostConfig$DeployDescriptor.run(HostConfig.java:1966)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

處理方式:在%JUSTEP_HOME%\runtime\ReportServer\WEB-INF\classes目錄下添加一個resfactory.properties文件,文件內容爲

tbeller.usejndi=false

以上的操做貌似對tomcat8並不生效,並且也麻煩,因此我的建議最好採用另外一種方法來知足運行需求

1.2 經過在X5以外部署新的tomcat七、8來運行websocket

step1 安裝JRE,若是使用X5自帶的則直接跳過這步

step2 解壓安裝tomcat8(下載地址參考上面的方法)

step3 配置環境變量(百度就知道),可是爲了避免影響同服務器的其餘tomcat,可使用上面方法的step7

step4 修改tomcat的端口,讓它不與其餘tomcat端口衝突


2 請求websocket使用ws仍是wss的選擇
當使用websocket的項目使用的是https,若是使用ws協議請求鏈接websocket會出現如下錯誤

Mixed Content: The page at 'https://xxx' was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint 'ws://xxx/websocket/?rid=18'. 
This request has been blocked; this endpoint must be available over WSS.

恰好發現有個博客遇到了一樣的問題 https://mengkang.net/774.html

爲了解決這一問題,提供瞭如下兩種方法

2.1 讓https請求中容許使用ws
在頁面頭信息增長

header('Content-Security-Policy: connect-src *;');

(這個方法我沒測試過,能夠本身根據那個博客試一下)

2.2 爲websocket所在tomcat增長ssl證書

假如websocket部署的是跟X5部署的服務器是同一個,那麼X5使用https的話,直接把證書配置進websocket的tomcat就好了;可是也有可能websocket所在的服務器和x5並不必定在同一個服務器,因此下面講一下let‘s encrypt證書的申請方法。

(let‘s encrypt是什麼本身百度就能夠了,反正就是一個免費的好用的證書)

本方法是在windows服務器下申請證書,若是是其餘系統,能夠自行百度,通常使用openssl進行申請,我這裏是採用證書官方提供的工具進行申請

step1 下載一個letsencrypt-win-simple工具,放入服務器解壓

     https://pan.baidu.com/s/1g_Enw7CHrRGxkfwYBY8F-g

   

step2 點擊letsencrypt運行

step3 填寫我的郵箱以接收證書過時等信息

step4 贊成一些啥協議(相似安裝時候選的贊成,還正只能選Y)

step5 選擇證書,選擇M

step6 填寫域名

step7 填寫域名對應服務的根目錄文件路徑,例如我用的端口80的是tomcat,那麼我就要填tomcat的webapps下的ROOT路徑

它會把一個文件放入到根路徑下,並經過域名+根路徑方式來訪問,若是訪問到了就會生成證書,證書地址它會在命令窗口顯示出來

通常會生成到C:\Users\Administrator\AppData\Roaming\letsencrypt-win-simple,這個路徑直接輸入到系統地址欄跳轉

step8 上面步驟經過後還會詢問是否須要自動續證書(證書有效期3個月),若是須要,後面會要求填寫服務器登錄的帳號密碼,這裏就直接跳過,能夠本身嘗試

step9 獲得證書後,就能夠到tomcat 8 中增長如下代碼

 <Connector port="443" protocol="org.apache.coyote.http11.Http11AprProtocol"
               maxThreads="150" SSLEnabled="true"  URIEncoding="UTF-8">
        <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
        <SSLHostConfig>
            <Certificate certificateKeyFile="C:/Users/Administrator/AppData/Roaming/letsencrypt-win-simple/httpsacme-v01.api.letsencrypt.org/zcit2018.cn-key.pem"
                         certificateFile="C:/Users/Administrator/AppData/Roaming/letsencrypt-win-simple/httpsacme-v01.api.letsencrypt.org/zcit2018.cn-crt.pem"
                         certificateChainFile="C:/Users/Administrator/AppData/Roaming/letsencrypt-win-simple/httpsacme-v01.api.letsencrypt.org/zcit2018.cn-chain.pem"
                         type="RSA" />
        </SSLHostConfig>
    </Connector>

這樣就能經過https進行訪問了

(在網上看到個差很少的證書申請博客 https://blog.csdn.net/qq_27424559/article/details/67661220)

3 websocket代碼實現

3.1 後端服務

(項目名爲ZCServer)
step1 增長一個監聽用的websocket

 1 package com.zc.websocket;
 2 
 3 import java.io.IOException;
 4 import java.util.HashMap;
 5 import java.util.Map;
 6 import java.util.concurrent.CopyOnWriteArraySet;
 7 
 8 import javax.websocket.OnClose;
 9 import javax.websocket.OnOpen;
10 import javax.websocket.Session;
11 import javax.websocket.server.ServerEndpoint;
12 
13 
14 @ServerEndpoint("/listener")
15 public class Listener {
16     
17     private static int onlineCount = 0;
18     
19     private static Map<String,CopyOnWriteArraySet<Listener>> webSocketMap = new HashMap<String,CopyOnWriteArraySet<Listener>>();
20     
21     private String fEventID = null;
22     
23     //與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據
24     private Session session;
25     
26     
27     public static CopyOnWriteArraySet<Listener> getwebSocketSet(String fEventID){
28         
29         return Listener.webSocketMap.get(fEventID);
30     }
31      
32     /**
33      * 鏈接創建成功調用的方法
34      * 
35      * @param session 可選的參數。session爲與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據
36      * 
37      */
38     @OnOpen
39     public void onOpen(Session session) {
40         this.session = session;
41         
42         this.fEventID = session.getRequestParameterMap().get("fEventID").get(0);
43         
44         CopyOnWriteArraySet<Listener> socekts = webSocketMap.get(this.fEventID);
45         if(socekts==null){ 
46             socekts = new CopyOnWriteArraySet<Listener>();
47             webSocketMap.put(this.fEventID,socekts); 
48         }
49         
50         socekts.add(this);
51         
52         addOnlineCount();   
53         System.out.println("有新監聽加入!當前監聽數爲" + getOnlineCount());
54     }
55     
56     /**
57      * 鏈接關閉調用的方法
58      */
59     @OnClose
60     public void onClose() {
61         
62         webSocketMap.get(this.fEventID).remove(this);
63         
64         
65          subOnlineCount();
66          System.out.println("有一監聽關閉!當前監聽數爲" + getOnlineCount());
67     }
68     
69     /**
70      * 這個方法與上面幾個方法不同。沒有用註解,是根據本身須要添加的方法。
71      * 
72      * @param message
73      * @throws IOException
74      */
75     public void sendMessage(String message) throws IOException {
76         this.session.getBasicRemote().sendText(message);
77     }
78     
79     
80      public static synchronized int getOnlineCount() {
81                  return onlineCount;
82             }
83         
84              public static synchronized void addOnlineCount() {
85                  Listener.onlineCount++;
86             }
87          
88             public static synchronized void subOnlineCount() {
89                 Listener.onlineCount--;
90      }
91     
92 }

代碼使用註解的方式,不須要進行配置,使用類變量記錄須要監聽的對象,實現分組管理不一樣的監聽

step2 增長一個用於通知監聽的websocket(也能夠用一個servlet)

package com.zc.websocket;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.OnMessage;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import com.alibaba.fastjson.JSONObject;


@ServerEndpoint("/notifier")
public class Notifier {
    
    
    /**
     * 收到客戶端消息後調用的方法
     * 
     * @param message
     *            客戶端發送過來的消息
     * @param session
     *            可選的參數
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        
        System.out.print("有新的通知:"+message);
        
        
        JSONObject values = JSONObject.parseObject(message);
        
        CopyOnWriteArraySet<Listener> websocketSet = Listener.getwebSocketSet(values.getString("fEventID"));
        
        if(websocketSet==null){
            System.out.println(" 監聽數量:0");
            return;
        }else{
            System.out.println(" 監聽數量:"+websocketSet.size());
        }
        
        // 羣發消息
        for (Listener item : websocketSet) {
            try {
                item.sendMessage(values.getString("message"));
            } catch (IOException e) {
                e.printStackTrace();
                continue;
            }
        }
    }
    
}

3.2 前端調用

step1 定義一個js工具

define(function(require) {
    
    var Model = function() {
        this.callParent();
    };
    
    var URI = "wss://zcit2018.cn";
//    var URI = "ws://baxd.ys100.com:8999";
    
    var err = {
        "type":"鏈接失敗",
        "URI":URI,
        "address":"/UI2/B/M000_Core/process/Utils/WebSocketUtils.js"
    };
    
    /**
     * 註冊一個fEventID的消息接收監聽
     * @param fEventID 監聽標識
     * @params callback 回調函數,帶參數
     * @returns websocket對象
     */
    Model.setListener = function(fEventID,callback){
        if(!fEventID){
            throw "fEventID 不能爲空!";
        }
        
        var websocket = null;
        
        //判斷當前瀏覽器是否支持WebSocket
        if ('WebSocket' in window) {
            try{
            websocket = new WebSocket(URI+"/ZCServer/listener?fEventID="+fEventID);
            }catch(e){
                console.log(err);
                return false;
            }
        } else {
            console.log('當前瀏覽器 Not support websocket');
            return false;
        }
        //鏈接發生錯誤的回調方法
        websocket.onerror = function() {
            
            console.log(err);
            
        };
        
        //鏈接成功創建的回調方法
        websocket.onopen = function() {};
        //接收到消息的回調方法
        websocket.onmessage = function(event) {
            if(callback&& typeof callback =="function"){
                callback(event);
            }
        };
        
        //鏈接關閉的回調方法
        websocket.onclose = function() {};
        
        return websocket;
    };    
    
    /**
     * 給fEventIDs發送通知message
     * @params fEventIDs 監聽者IDs數組
     * @params message 消息內容
     * 
     */
    Model.sendNotic = function(fEventIDs,message){
        if(!Array.isArray(fEventIDs)){
            throw "fEventIDs 必須爲數組!";
        }
        
        var websocket = null;
        
        //判斷當前瀏覽器是否支持WebSocket
        if ('WebSocket' in window) {
            try{
                websocket = new WebSocket(URI+"/ZCServer/notifier");
            }catch(e){
                console.log(err);
                return false;
            }
        } else {
            console.log('當前瀏覽器 Not support websocket');
            return false;
        }
        
        

        websocket.onopen = function() {

            for ( var i in fEventIDs) {
                websocket.send(JSON.stringify({
                    "fEventID" : fEventIDs[i],
                    "message" : message?message:""
                }));
            }
            websocket.close();
        };
        
        
    };
    

    return Model;
});

增長監聽器的代碼(引入工具js就不描述了)

1 websocket.setListener("10e0495c-650c-486a-8d65-552f103aa243",function(re){
2      // 處理邏輯
3  });

增長通知的代碼

websocket.sendNotic(['10e0495c-650c-486a-8d65-552f103aa243'],message);

一個處理可能須要引發多個監聽器的響應,因此參數使用的數組

整個websocket基本就是這樣,可是有時候進行通知的未必是經過前端操做的,因此還須要提供一個java工具用於通知

相關文章
相關標籤/搜索