使用场景介绍
由于之前用表单建模搭建了一套 E-SOP文件管理系统,通过流程上传E-SOP文件附件,自动转换为 图片并保存在建模中(实现方式见泛微ECOLOGY9流程模块(建模通用)office附件上传自动转为图片并显示在流程指定附件字段中)。 然后前端用 JSP做了展示界面进行E-SOP文件图片的轮播展示。但这里有一个关键的点是如何让页面根据上线产品的变化自动更新对应的E-SOP文件图片。 一开始使用了AJAX的方式每5秒去获取信息做比对判断是否需要更新E-SOP文件图片,但随着带机量的增加,发现严重影响了OA的打开速度。所以想通过消息推送的方式来进行E-SOP文件图片的更新以减少OA服务器的压力。
实现方案
通过泛微的定时任务 触发 MQTT的 消息主题推送, 然后前端JSP中做MQTT的消息主题订阅。
环境配置
1、在\ecology\WEB-INF\prop目录下创建mqtt.properties 配置文件
mqttUrl = XXX MQTT服务器的地址
mqttPort = XXX MQTT 服务器的端口
mqttUser = XXX MQTT服务器的帐号 没有就留空
mqttPass = XXX MQTT服务器的密码 没有就留空
mqttClientId = XXX MQTT客户端的唯一ID 不指定为空就在代码中自动生成
2、在\ecology\WEB-INF\lib目录下添加org.eclipse.paho.client.mqttv3-1.2.5.jar
推送端代码及配置
MqttClientUtils.java 把 mqtt 的连接跟推送包装成工具类,方便后续调用。
import org.eclipse.paho.client.mqttv3.*;
import weaver.general.BaseBean;
import weaver.general.GCONST;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Date;
import java.util.Properties;
public class MqttClientUtils {
private static MqttClient client;
public static void setClient(MqttClient client) {
MqttClientUtils.client = client;
}
public static MqttClient getClient() {
return client;
}
public static void mqttConnect(String topic) throws IOException, MqttException {
if(topic.isEmpty()){
topic = "WFtopic"; //如果主题为空就用默认主题
}
Properties properties = new Properties();
BufferedInputStream mqtt= new BufferedInputStream(new FileInputStream(GCONST.getRootPath()+"WEB-INF"+ File.separatorChar+"prop"+File.separatorChar+"mqtt.properties")); //从配置文件中读取MQTT的相关信息
properties.load(mqtt);
//mqtt服务器地址
String mqttUrl = properties.getProperty("mqttUrl");
//mqtt服务器端口
String mqttPort = properties.getProperty("mqttPort");
//mqtt服务器帐号
String mqttUser = properties.getProperty("mqttUser");
//mqtt服务器密码
String mqttPass = properties.getProperty("mqttPass");
String clientId = properties.getProperty("mqttClientId");
if(clientId.isEmpty()){//如果客户端ID为空就指定一个
clientId = "wfClicent"+new Date().getTime();
}
String broker = "tcp://"+mqttUrl+":"+mqttPort;
MqttClient fwClient = new MqttClient(broker, clientId);
// MQTT 连接选项
MqttConnectOptions connOpts = new MqttConnectOptions();
if(!mqttUser.isEmpty()) {
connOpts.setUserName(mqttUser);
if(!mqttPass.isEmpty()) {
connOpts.setPassword(mqttPass.toCharArray());
}
}
// 保留会话
connOpts.setCleanSession(false);
new BaseBean().writeLog("Connecting to broker: " + broker);
fwClient.connect(connOpts);
MqttClientUtils.setClient(fwClient);
}
public static void publish(String topic, String pushMessage){
MqttMessage message = new MqttMessage();
message.setQos(0);
message.setRetained(false);
message.setPayload(pushMessage.getBytes());
try{
MqttClientUtils.getClient().publish(topic,message);
} catch(Exception e){
new BaseBean().writeLog("mqtt发送消息异常:" ,e);
}
}
}
定时推送代码
import com.MqttClientUtils;
import org.eclipse.paho.client.mqttv3.*;
import weaver.conn.RecordSet;
import weaver.interfaces.schedule.BaseCronJob;
import java.io.*;
public class MQTTPUSH extends BaseCronJob {
@Override
public void execute() {
RecordSet rs = new RecordSet();
String sql = "获取推送消息";
rs.execute(sql);
while (rs.next()) {
String message = rs.getString("message"); //推送内容
String center = rs.getString("center"); //工作中心
String line = rs.getString("line"); //生产线
try {//由于我这边要根据不同的工作中心和生产线推送不同的内容,所以推送主题定义为动态的,在主题前缀上加上工作中心和生产线做为一个主题。
MqttClientUtils.mqttConnect("soppush"+center+line);
MqttClientUtils.publish("soppush"+center+line,message);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (MqttException e) {
throw new RuntimeException(e);
}
}
}
}
将以上编译好的文件放到 ecology/classbean/com/下面
任务计划配置如下图
每5秒推送一次主题消息
前端MQTT订阅主题实现
<script src="../resources/jquery.js"></script>
<script src="../resources/swiper.min.js"></script>
<script src="../resources/mqttws31.min.js"></script>
<script>
function getQueryVariable(variable)
{
var query = window.location.href.split('?')[1];
var vars = query.split("&");
for (var i=0;i<vars.length;i++) {
var pair = vars[i].split("=");
if(pair[0] == variable){return pair[1];}
}
return(false);
}
var center=getQueryVariable("center");
var line=getQueryVariable("line");
var oldmessage = "";
//-------MQTT---------------
var hostname = 'MQTT地址',
port = 8083, //js中MQTT走的是ws协议,所以在EMQX中用 ws的端口
clientId = 'sop'+center+line+new Date().getTime(), //由于同条线有多台设备,防止ID冲突,所以加上了时间搓
timeout = 5,
keepAlive = 60,
cleanSession = true,
ssl = false,
topic = 'soppush'+center+line;
client = new Paho.MQTT.Client(hostname, port, clientId);
//建立客户端实例
var options = {
invocationContext: {
host: hostname,
port: port,
path: client.path,
clientId: clientId
},
timeout: timeout,
keepAliveInterval: keepAlive,
cleanSession: cleanSession,
useSSL: ssl,
// userName: userName,
// password: password,
onSuccess: onConnect,
onFailure: function (e) {
console.log(e);
}
};
client.connect(options);
//连接服务器并注册连接成功处理事件
function onConnect() {
console.log("onConnected");
client.subscribe(topic);
}
client.onConnectionLost = onConnectionLost;
//注册连接断开处理事件
client.onMessageArrived = onMessageArrived;
//注册消息接收处理事件
function onConnectionLost(responseObject) {
console.log(responseObject);
if (responseObject.errorCode !== 0) {
console.log("onConnectionLost:" + responseObject.errorMessage);
console.log("连接已断开");
}
}
function onMessageArrived(message) {
if (oldmessage == "" ){
oldmessage = message.payloadString
}else if((oldmessage != "" && oldmessage != message.payloadString) ){
window.location.reload(); //如果获取到的信息跟原来的不一致,就重新刷新页面
}
console.log("收到消息:" + message.payloadString);
}
</script>
最终效果
推送端和订阅端都连上EMQX,推送端推送信息到对应的主题中。订阅端获取对应主题中的信息。