BEX5内使用websocket实现前端数据实时同步
Posted wonghugh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BEX5内使用websocket实现前端数据实时同步相关的知识,希望对你有一定的参考价值。
1 搭建运行websocket的环境(这里只用tomcat说明)
为了能让websocket运行起来,需要tomcat 7.0版本以上,但是目前X5使用的是tomcat6,可以通过以下两种方式达到条件
1.1 通过替换掉X5里面的tomcat来升级,替换步骤如下:
step1 下载解压版的tomcat 8
https://tomcat.apache.org/download-80.cgi
step2 把tomcat 8拷贝到平台版本中把名字改为平台版本默认带的tomcat目录的名字
step3 把平台默认带的tomcat中的apache-tomcat\\conf\\context.xml文件和apache-tomcat\\conf\\Catalina\\localhost下面的配置文件拷贝到tomcat 8中
step4 在自己的tomcat的lib中放数据库驱动,平台默认的tomcat的lib下带的数据库驱动如下:
jtds-1.2.jar、mysql-connector-java-5.1.36-bin.jar、ojdbc14.jar
step5 如果tomcat 8端口号不是8080,需要修改model同级的conf/server.xml中配置的地址中的端口号
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之外部署新的tomcat7、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工具用于通知
以上是关于BEX5内使用websocket实现前端数据实时同步的主要内容,如果未能解决你的问题,请参考以下文章
如何使用Django 结合WebSocket 进行实时目标检测呢?以yolov5 为例,实现:FPS 25+ (0: 系统简介与架构)
24数据可视化:基于 Echarts + Python 动态实时大屏范例 - HTTP & WebSocket 网络传输协议