台湾展会用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

363 lines
11 KiB

package com.yvan.pusher;
import com.yvan.entity.AgvStatusVo;
import com.yvan.entity.BasLocationVo;
import com.yvan.entity.InvUpdateVo;
import com.yvan.entity.ServerStatusVo;
import com.yvan.logisticsEnv.EnvConfig;
import com.yvan.logisticsModel.LogisticsRuntime;
import com.yvan.workbench.autoconfigure.LccConfigProperties;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.clever.core.BannerUtils;
import org.clever.core.mapper.JacksonMapper;
import org.eclipse.paho.mqttv5.client.*;
import org.eclipse.paho.mqttv5.client.persist.MemoryPersistence;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.MqttMessage;
import org.eclipse.paho.mqttv5.common.packet.MqttProperties;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class FrontendMessagePushService implements MqttCallback {
public static final FrontendMessagePushService INSTANCE = new FrontendMessagePushService();
private volatile LccConfigProperties.FrontendMqtt mqttConfig;
private volatile String clientId;
private volatile MqttClient mqttClient;
private final MemoryPersistence persistence = new MemoryPersistence();
private final Lock connectionLock = new ReentrantLock();
// QoS 配置
private static final int QOS = 1;
// 重连参数
private static final int MAX_RETRIES = 5;
private static final int RETRY_INTERVAL = 3; // 秒
// 连接状态
@Getter
private volatile boolean connected = false;
private FrontendMessagePushService() {
}
/**
* 启动MQTT服务
*/
@SneakyThrows
public void start(LccConfigProperties.FrontendMqtt mqttConfig, String clientId) {
this.mqttConfig = mqttConfig;
this.clientId = clientId;
connectionLock.lock();
try {
if (connected) {
log.warn("FRONTEND_MQTT service is already started");
return;
}
log.info("Starting FRONTEND_MQTT service for clientId: {}", this.clientId);
// 创建MQTT客户端
mqttClient = new MqttClient(mqttConfig.getBrokerUrl(), clientId, persistence);
mqttClient.setCallback(this);
// 配置连接选项
MqttConnectionOptions options = new MqttConnectionOptions();
options.setServerURIs(new String[]{mqttConfig.getBrokerUrl()});
options.setUserName(mqttConfig.getUsername());
options.setPassword(mqttConfig.getPassword().getBytes());
options.setAutomaticReconnect(true);
options.setConnectionTimeout(10);
options.setKeepAliveInterval(60);
options.setExecutorServiceTimeout(1);
// 尝试连接
int attempts = 0;
while (attempts < MAX_RETRIES && !connected) {
attempts++;
try {
log.info("Connecting to FRONTEND_MQTT broker (attempt {}/{})", attempts, MAX_RETRIES);
mqttClient.connect(options);
connected = true;
log.info("FRONTEND_MQTT connected successfully");
} catch (MqttException e) {
log.error("FRONTEND_MQTT connection failed (attempt " + attempts + "/{" + MAX_RETRIES + "})", e);
// 重试前等待
if (attempts < MAX_RETRIES) {
try {
TimeUnit.SECONDS.sleep(RETRY_INTERVAL);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
break;
}
}
}
}
if (!connected) {
log.error("Failed to connect to FRONTEND_MQTT broker after {} attempts", MAX_RETRIES);
}
} finally {
connectionLock.unlock();
}
}
/**
* 停止MQTT服务
*/
public void stop() {
connectionLock.lock();
try {
if (mqttClient != null && mqttClient.isConnected()) {
try {
mqttClient.disconnect();
mqttClient.close();
log.info("FRONTEND_MQTT disconnected");
} catch (MqttException e) {
log.error("Error disconnecting FRONTEND_MQTT", e);
}
}
connected = false;
} finally {
connectionLock.unlock();
}
}
// ==================== 消息推送方法 ====================
/**
* 推送服务器状态
* /lcc/{proj_id}/{env_id}/server
*
* @param runtime 运行时环境
* @param statusData 状态数据
*/
public void pushServerState(LogisticsRuntime runtime, ServerStatusVo statusData) {
String topic = buildTopic(runtime, "server");
publishJson(topic, statusData);
}
/**
* 推送客户端状态
* /lcc/{proj_id}/{env_id}/client
*
* @param runtime 运行时环境
* @param clientData 客户端数据
*/
public void pushClientState(LogisticsRuntime runtime, Map<String, Object> clientData) {
String topic = buildTopic(runtime, "client");
publishJson(topic, clientData);
}
/**
* 推送任务更新
* /lcc/{proj_id}/{env_id}/task
*
* @param runtime 运行时环境
* @param taskData 任务数据
*/
public void pushTaskUpdate(LogisticsRuntime runtime, Object taskData) {
String topic = buildTopic(runtime, "task");
publishJson(topic, taskData);
}
/**
* 推送库存更新
* /lcc/{proj_id}/{env_id}/inv
*
* @param runtime 运行时环境
* @param lpn 容器号
* @param before 更新前库存
* @param after 更新后库存
* @param qty 更新数量
*/
public void pushInvUpdate(LogisticsRuntime runtime, String lpn, BasLocationVo before, BasLocationVo after, int qty) {
String topic = buildTopic(runtime, "inv");
publishJson(topic, new InvUpdateVo(
lpn,
before,
after,
qty
));
}
/**
* 推送设备状态
* /lcc/{proj_id}/{env_id}/device/{id}/status
*
* @param runtime 运行时环境
* @param deviceId 设备ID
* @param statusData 状态数据
*/
public void pushDeviceStatus(LogisticsRuntime runtime, String deviceId, AgvStatusVo statusData) {
String topic = buildTopic(runtime, "device/" + deviceId + "/status");
publishJson(topic, statusData);
}
/**
* 推送设备存活状态
*
* @param runtime 运行时环境
* @param deviceId 设备ID
* @param type 设备类型
* @param online 是否在线
*/
public void pushDeviceAlive(LogisticsRuntime runtime, String deviceId, String type, boolean online) {
String topic = buildTopic(runtime, "device/" + deviceId + "/alive");
Map<String, Object> data = new HashMap<>();
data.put("id", deviceId);
data.put("type", type);
data.put("online", online);
publishJson(topic, data);
}
/**
* 推送日志
* /lcc/{proj_id}/{env_id}/log/{type}
*
* @param runtime 运行时环境
* @param logType 日志类型
* @param log 日志数据
*/
public void pushLogs(LogisticsRuntime runtime, String logType, String log) {
String topic = buildTopic(runtime, "log/" + logType);
publish(topic, log);
}
/**
* 推送告警
* /lcc/{proj_id}/{env_id}/alarm
*
* @param runtime 运行时环境
* @param alarmData 告警数据
*/
public void pushAlarm(LogisticsRuntime runtime, Object alarmData) {
String topic = buildTopic(runtime, "alarm");
publishJson(topic, alarmData);
}
/**
* 推送脚本更新
* /lcc/{proj_id}/script
*
* @param runtime 运行时环境
* @param scriptData 脚本数据
*/
public void pushScriptUpdate(LogisticsRuntime runtime, Object scriptData) {
// 脚本系统没有环境ID
String topic = "/lcc/" + runtime.projectUuid + "/script";
publishJson(topic, scriptData);
}
// ==================== 内部工具方法 ====================
/**
* 构建主题路径
*/
private String buildTopic(LogisticsRuntime runtime, String suffix) {
return "/lcc/" + runtime.projectUuid + "/" + runtime.envId + "/" + suffix;
}
/**
* 发布JSON数据
*/
private void publishJson(String topic, Object data) {
String json = JacksonMapper.getInstance().toJson(data);
log.info("Publishing to topic: {}, data: {}", topic, json);
publish(topic, json);
}
/**
* 通用发布方法
*/
private void publish(String topic, String payload) {
if (!connected) {
log.error("Attempted to publish while disconnected: {}", topic);
return;
}
try {
MqttMessage message = new MqttMessage(payload.getBytes(StandardCharsets.UTF_8));
message.setQos(QOS);
message.setRetained(false);
mqttClient.publish(topic, message);
log.debug("Published to {}: {}", topic, payload);
} catch (MqttException e) {
log.error("Failed to publish to topic: " + topic, e);
}
}
// ==================== MQTT 回调方法 ====================
@Override
public void disconnected(MqttDisconnectResponse disconnectResponse) {
log.warn("FRONTEND_MQTT disconnected: {}", disconnectResponse);
connectionLock.lock();
try {
connected = false;
} finally {
connectionLock.unlock();
}
}
@Override
public void mqttErrorOccurred(MqttException exception) {
log.error("FRONTEND_MQTT error occurred", exception);
}
@Override
public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
log.debug("Message arrived on topic: {}", topic);
// 作为服务端,我们只发布消息,不接收消息
// 可以记录或处理意外收到的消息
}
@Override
public void deliveryComplete(IMqttToken iMqttToken) {
try {
String[] topics = iMqttToken.getTopics();
if (topics != null && topics.length > 0) {
log.info("Message delivery confirmed for topic: {}", topics[0]);
} else {
log.info("Message delivery confirmed (no topic info)");
}
} catch (Exception e) {
log.warn("Error getting delivery token info", e);
}
}
@Override
public void connectComplete(boolean reconnect, String serverURI) {
BannerUtils.printConfig(log, "FRONTEND_MQTT 开启监听成功", new String[]{
"brokerUrl: " + serverURI,
"userName: " + this.mqttConfig.getUsername(),
"clientId: " + clientId});
connectionLock.lock();
try {
connected = true;
} finally {
connectionLock.unlock();
}
}
@Override
public void authPacketArrived(int i, MqttProperties mqttProperties) {
log.info("FRONTEND_MQTT authPacketArrived({}, {})", i, mqttProperties);
}
}