设备心跳调整 app新增增设备时绑定联网设备
This commit is contained in:
@@ -70,7 +70,7 @@ public class ThreadPoolConfig {
|
||||
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
|
||||
pool.shutdownNow();
|
||||
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
|
||||
log.info("Pool did not terminate");
|
||||
log.info("线程池未在指定时间内终止");
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
@@ -87,9 +87,8 @@ public class ThreadPoolConfig {
|
||||
* 打印线程异常信息
|
||||
*/
|
||||
public static void printException(Runnable r, Throwable t) {
|
||||
if (t == null && r instanceof Future<?>) {
|
||||
if (t == null && r instanceof Future<?> future) {
|
||||
try {
|
||||
Future<?> future = (Future<?>) r;
|
||||
if (future.isDone()) {
|
||||
future.get();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package org.dromara.common.core.domain;
|
||||
|
||||
import org.dromara.common.core.constant.HttpStatus;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.core.constant.HttpStatus;
|
||||
import org.dromara.common.core.utils.MessageUtils;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
@@ -36,11 +37,11 @@ public class R<T> implements Serializable {
|
||||
private T data;
|
||||
|
||||
public static <T> R<T> ok() {
|
||||
return restResult(null, SUCCESS, "操作成功");
|
||||
return restResult(null, SUCCESS, message("operation.success", "操作成功"));
|
||||
}
|
||||
|
||||
public static <T> R<T> ok(T data) {
|
||||
return restResult(data, SUCCESS, "操作成功");
|
||||
return restResult(data, SUCCESS, message("operation.success", "操作成功"));
|
||||
}
|
||||
|
||||
public static <T> R<T> ok(String msg) {
|
||||
@@ -52,7 +53,7 @@ public class R<T> implements Serializable {
|
||||
}
|
||||
|
||||
public static <T> R<T> fail() {
|
||||
return restResult(null, FAIL, "操作失败");
|
||||
return restResult(null, FAIL, message("operation.fail", "操作失败"));
|
||||
}
|
||||
|
||||
public static <T> R<T> fail(String msg) {
|
||||
@@ -100,6 +101,15 @@ public class R<T> implements Serializable {
|
||||
return r;
|
||||
}
|
||||
|
||||
private static String message(String code, String defaultMessage) {
|
||||
try {
|
||||
String message = MessageUtils.message(code);
|
||||
return code.equals(message) ? defaultMessage : message;
|
||||
} catch (Exception e) {
|
||||
return defaultMessage;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Boolean isError(R<T> ret) {
|
||||
return !isSuccess(ret);
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class RegionUtils {
|
||||
Config v6Config = null;
|
||||
InputStream v6XdbInputStream = ResourceUtil.getStreamSafe(DEFAULT_IPV6_XDB_PATH);
|
||||
if (v6XdbInputStream == null) {
|
||||
log.warn("未加载 IPv6 地址库:未在类路径下找到文件 {}。当前仅启用 IPv4 查询。如需启用 IPv6,请将 ip2region_v6.xdb 放置到 resources 目录", DEFAULT_IPV6_XDB_PATH);
|
||||
log.warn("未加载 IPv6 地址库:未在类路径下找到文件 {}。当前仅启用 IPv4 查询。如需启用 IPv6,请将 ip2region_v6.xdb 放置到资源目录", DEFAULT_IPV6_XDB_PATH);
|
||||
} else {
|
||||
v6Config = Config.custom()
|
||||
.setCachePolicy(Config.BufferCache)
|
||||
@@ -128,7 +128,7 @@ public class RegionUtils {
|
||||
try {
|
||||
ip2Region.close(10000);
|
||||
} catch (Exception e) {
|
||||
log.error("Ip2Region服务关闭异常", e);
|
||||
log.error("IP地址库服务关闭异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ public class RegionUtils {
|
||||
try {
|
||||
ip2Region.close(timeout.toMillis());
|
||||
} catch (Exception e) {
|
||||
log.error("Ip2Region服务关闭异常", e);
|
||||
log.error("IP地址库服务关闭异常", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ public class SaTokenAnnotationMetadataJavadocResolver extends AbstractMetadataJa
|
||||
SA_IGNORE_CLASS = (Class<? extends Annotation>) ClassLoaderUtil.loadClass(SA_IGNORE_CLASS_NAME, false);
|
||||
SA_CHECK_LOGIN_CLASS = (Class<? extends Annotation>) ClassLoaderUtil.loadClass(SA_CHECK_LOGIN_NAME, false);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("SaTokenAnnotationJavadocResolver init success, load annotation class: {}", List.of(SA_CHECK_ROLE_CLASS, SA_CHECK_PERMISSION_CLASS, SA_IGNORE_CLASS, SA_CHECK_LOGIN_CLASS));
|
||||
log.debug("SaToken 注解 Javadoc 解析器初始化成功,已加载注解类:{}", List.of(SA_CHECK_ROLE_CLASS, SA_CHECK_PERMISSION_CLASS, SA_IGNORE_CLASS, SA_CHECK_LOGIN_CLASS));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ public class JacksonConfig {
|
||||
public Jackson2ObjectMapperBuilderCustomizer customizer() {
|
||||
return builder -> {
|
||||
builder.timeZone(TimeZone.getDefault());
|
||||
log.info("初始化 jackson 配置");
|
||||
log.info("初始化JSON序列化配置");
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,10 @@
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>water-common-json</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>water-common-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>water-app</artifactId>
|
||||
|
||||
@@ -34,7 +34,12 @@ public class DeviceMqttCommandPublisher implements IDeviceCommandPublisher {
|
||||
command.getPayload().put("commandType", command.getCommandType());
|
||||
|
||||
ackService.savePending(command);
|
||||
mqttClientManager.publish(command.getTopic(), JsonUtils.toJsonString(command.getPayload()));
|
||||
try {
|
||||
mqttClientManager.publish(command.getTopic(), JsonUtils.toJsonString(command.getPayload()));
|
||||
} catch (RuntimeException e) {
|
||||
ackService.removePending(command.getCommandId());
|
||||
throw e;
|
||||
}
|
||||
return command.getCommandId();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,10 @@ package org.dromara.mqtt;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.app.mqtt.MqttMessageDispatcher;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.mqtt.config.properties.MqttProperties;
|
||||
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
|
||||
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
|
||||
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
|
||||
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
|
||||
import org.eclipse.paho.client.mqttv3.MqttException;
|
||||
import org.eclipse.paho.client.mqttv3.MqttMessage;
|
||||
import org.eclipse.paho.client.mqttv3.*;
|
||||
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
@@ -26,11 +23,7 @@ import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@Slf4j
|
||||
@@ -50,6 +43,12 @@ public class MqttClientManager implements DisposableBean {
|
||||
@Bean
|
||||
@ConditionalOnProperty(prefix = "mqtt", name = "enabled", havingValue = "true")
|
||||
public MqttAsyncClient mqttConnect() throws MqttException {
|
||||
if (StringUtils.isBlank(props.getBrokerUrl())) {
|
||||
throw new ServiceException("启用 MQTT 时必须配置 broker-url");
|
||||
}
|
||||
if (StringUtils.isBlank(props.getClientId())) {
|
||||
throw new ServiceException("启用 MQTT 时必须配置 client-id");
|
||||
}
|
||||
messageQueue = new ArrayBlockingQueue<>(Math.max(1, props.getAsync().getQueueCapacity()));
|
||||
consumerExecutor = createConsumerExecutor();
|
||||
running = true;
|
||||
@@ -71,13 +70,13 @@ public class MqttClientManager implements DisposableBean {
|
||||
client.setCallback(new MqttCallbackExtended() {
|
||||
@Override
|
||||
public void connectionLost(Throwable cause) {
|
||||
log.warn("[MQTT] connection lost: {}", cause == null ? "" : cause.getMessage());
|
||||
log.warn("[MQTT] 连接已断开:{}", cause == null ? "" : cause.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectComplete(boolean reconnect, String serverURI) {
|
||||
if (reconnect) {
|
||||
log.info("[MQTT] reconnected: {}", serverURI);
|
||||
log.info("[MQTT] 已重新连接:{}", serverURI);
|
||||
subscribeTopics();
|
||||
}
|
||||
}
|
||||
@@ -94,7 +93,7 @@ public class MqttClientManager implements DisposableBean {
|
||||
});
|
||||
|
||||
client.connect(options).waitForCompletion();
|
||||
log.info("[MQTT] connected: {}", props.getBrokerUrl());
|
||||
log.info("[MQTT] 已连接:{}", props.getBrokerUrl());
|
||||
subscribeTopics();
|
||||
return client;
|
||||
}
|
||||
@@ -183,11 +182,11 @@ public class MqttClientManager implements DisposableBean {
|
||||
InboundMessage inboundMessage = new InboundMessage(topic, payload);
|
||||
try {
|
||||
if (!messageQueue.offer(inboundMessage, props.getAsync().getOfferTimeoutMs(), TimeUnit.MILLISECONDS)) {
|
||||
log.warn("[MQTT] inbound queue full, dropped topic={}", topic);
|
||||
log.warn("[MQTT] 上行消息队列已满,丢弃 主题={}", topic);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.warn("[MQTT] enqueue interrupted topic={}", topic);
|
||||
log.warn("[MQTT] 上行消息入队被中断 主题={}", topic);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -221,7 +220,7 @@ public class MqttClientManager implements DisposableBean {
|
||||
try {
|
||||
dispatcher.dispatch(message.topic(), message.payload());
|
||||
} catch (Exception e) {
|
||||
log.error("[MQTT] dispatch failed topic={}", message.topic(), e);
|
||||
log.error("[MQTT] 消息分发失败 主题={}", message.topic(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,30 +231,31 @@ public class MqttClientManager implements DisposableBean {
|
||||
}
|
||||
List<String> topics = props.getTopics() == null ? List.of() : props.getTopics().getSubscribe();
|
||||
if (topics == null || topics.isEmpty()) {
|
||||
log.warn("[MQTT] no subscribe topics configured");
|
||||
log.warn("[MQTT] 未配置订阅主题");
|
||||
return;
|
||||
}
|
||||
int[] qos = topics.stream().mapToInt(topic -> props.getQos()).toArray();
|
||||
client.subscribe(topics.toArray(new String[0]), qos).waitForCompletion();
|
||||
topics.forEach(topic -> log.info("[MQTT] subscribed: {} qos={}", topic, props.getQos()));
|
||||
topics.forEach(topic -> log.info("[MQTT] 已订阅 主题={} 服务质量等级={}", topic, props.getQos()));
|
||||
} catch (MqttException e) {
|
||||
log.error("[MQTT] subscribe failed", e);
|
||||
log.error("[MQTT] 订阅失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void publish(String topic, String payload) {
|
||||
try {
|
||||
if (client == null || !client.isConnected()) {
|
||||
log.warn("[MQTT] publish skipped, client not connected topic={}", topic);
|
||||
return;
|
||||
throw new ServiceException("MQTT客户端未连接");
|
||||
}
|
||||
MqttMessage message = new MqttMessage(payload.getBytes(StandardCharsets.UTF_8));
|
||||
message.setQos(props.getQos());
|
||||
message.setRetained(false);
|
||||
client.publish(topic, message);
|
||||
log.info("[MQTT] published topic={} payload={}", topic, payload);
|
||||
IMqttDeliveryToken token = client.publish(topic, message);
|
||||
token.waitForCompletion(Math.max(1000L, props.getConnectionTimeout() * 1000L));
|
||||
log.info("[MQTT] 消息已发布 主题={} 消息体={}", topic, payload);
|
||||
} catch (MqttException e) {
|
||||
log.error("[MQTT] publish failed topic={}", topic, e);
|
||||
log.error("[MQTT] 消息发布失败 主题={}", topic, e);
|
||||
throw new ServiceException("MQTT 消息发布失败:{}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package org.dromara.mqtt;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.app.domain.AppDevice;
|
||||
import org.dromara.app.domain.mqtt.DeviceCommand;
|
||||
import org.dromara.app.domain.mqtt.DeviceCommandAck;
|
||||
import org.dromara.app.mapper.AppDeviceMapper;
|
||||
import org.dromara.app.service.IDeviceCommandAckHandler;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
@@ -17,11 +20,7 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@@ -36,16 +35,24 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler {
|
||||
@Autowired
|
||||
private MqttClientManager mqttClientManager;
|
||||
private final MqttProperties mqttProperties;
|
||||
private final AppDeviceMapper appDeviceMapper;
|
||||
|
||||
public void savePending(DeviceCommand command) {
|
||||
RedisUtils.setCacheObject(pendingKey(command.getCommandId()), command, Duration.ofSeconds(mqttProperties.getCommandAck().getPendingTtlSeconds()));
|
||||
pendingIds().add(command.getCommandId());
|
||||
}
|
||||
|
||||
public void removePending(String commandId) {
|
||||
if (StringUtils.isBlank(commandId)) {
|
||||
return;
|
||||
}
|
||||
deletePending(commandId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleAck(String deviceNo, String payload) {
|
||||
if (StringUtils.isBlank(payload)) {
|
||||
log.warn("[MQTT] 收到空 ACK deviceNo={}", deviceNo);
|
||||
log.warn("[MQTT] 收到空 ACK 设备编号={}", deviceNo);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -59,26 +66,25 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler {
|
||||
try {
|
||||
ack = JsonUtils.parseObject(trimmedPayload, DeviceCommandAck.class);
|
||||
} catch (RuntimeException e) {
|
||||
log.warn("[MQTT] ACK JSON 格式错误 deviceNo={} payload={}", deviceNo, payload, e);
|
||||
log.warn("[MQTT] ACK JSON 格式错误 设备编号={} 消息体={}", deviceNo, payload, e);
|
||||
return;
|
||||
}
|
||||
if (ack == null || ack.getCommandId() == null) {
|
||||
log.warn("[MQTT] ACK 缺少 commandId deviceNo={} payload={}", deviceNo, payload);
|
||||
log.warn("[MQTT] ACK 缺少命令编号 设备编号={} 消息体={}", deviceNo, payload);
|
||||
return;
|
||||
}
|
||||
ack.setDeviceNo(deviceNo);
|
||||
RedisUtils.deleteObject(pendingKey(ack.getCommandId()));
|
||||
pendingIds().remove(ack.getCommandId());
|
||||
deletePending(ack.getCommandId());
|
||||
RedisUtils.setCacheObject(ackKey(ack.getCommandId()), ack, Duration.ofSeconds(mqttProperties.getCommandAck().getAckTtlSeconds()));
|
||||
refreshDeviceOnline(deviceNo);
|
||||
log.info("[MQTT] command ack received deviceNo={} commandId={} status={}", deviceNo, ack.getCommandId(), ack.getStatus());
|
||||
log.info("[MQTT] 收到命令确认 设备编号={} 命令编号={} 状态={}", deviceNo, ack.getCommandId(), ack.getStatus());
|
||||
}
|
||||
|
||||
private void handlePlainAck(String deviceNo, String payload) {
|
||||
List<DeviceCommand> pendingCommands = findPendingCommandsByDeviceNo(deviceNo);
|
||||
refreshDeviceOnline(deviceNo);
|
||||
if (pendingCommands.size() != 1) {
|
||||
log.warn("[MQTT] 非 JSON ACK 无法唯一匹配待确认命令 deviceNo={} pendingCount={} payload={}", deviceNo, pendingCommands.size(), payload);
|
||||
log.warn("[MQTT] 非 JSON ACK 无法唯一匹配待确认命令 设备编号={} 待确认数量={} 消息体={}", deviceNo, pendingCommands.size(), payload);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -90,10 +96,9 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler {
|
||||
ack.setStatus("1");
|
||||
ack.setMessage(payload);
|
||||
|
||||
RedisUtils.deleteObject(pendingKey(commandId));
|
||||
pendingIds().remove(commandId);
|
||||
deletePending(commandId);
|
||||
RedisUtils.setCacheObject(ackKey(commandId), ack, Duration.ofSeconds(mqttProperties.getCommandAck().getAckTtlSeconds()));
|
||||
log.info("[MQTT] plain command ack received deviceNo={} commandId={} payload={}", deviceNo, commandId, payload);
|
||||
log.info("[MQTT] 收到非 JSON 命令确认 设备编号={} 命令编号={} 消息体={}", deviceNo, commandId, payload);
|
||||
}
|
||||
|
||||
private List<DeviceCommand> findPendingCommandsByDeviceNo(String deviceNo) {
|
||||
@@ -126,7 +131,9 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler {
|
||||
retryCommand(commandId, now);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.warn("[MQTT] 命令重试锁等待被中断 commandId={}", commandId);
|
||||
log.warn("[MQTT] 命令重试锁等待被中断 命令编号={}", commandId);
|
||||
} catch (RuntimeException e) {
|
||||
log.error("[MQTT] 命令重试失败 命令编号={}", commandId, e);
|
||||
} finally {
|
||||
if (locked && lock.isHeldByCurrentThread()) {
|
||||
lock.unlock();
|
||||
@@ -144,15 +151,20 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler {
|
||||
return;
|
||||
}
|
||||
if (command.getRetryCount() >= mqttProperties.getCommandAck().getMaxRetryCount()) {
|
||||
RedisUtils.deleteObject(pendingKey(commandId));
|
||||
pendingIds().remove(commandId);
|
||||
log.warn("[MQTT] 命令重试次数已达上限 deviceNo={} commandId={}", command.getDeviceNo(), commandId);
|
||||
deletePending(commandId);
|
||||
log.warn("[MQTT] 命令重试次数已达上限 设备编号={} 命令编号={}", command.getDeviceNo(), commandId);
|
||||
return;
|
||||
}
|
||||
if (!isDeviceOnline(command.getDeviceNo())) {
|
||||
command.setRetryCount(command.getRetryCount() + 1);
|
||||
if (command.getRetryCount() >= mqttProperties.getCommandAck().getMaxRetryCount()) {
|
||||
deletePending(commandId);
|
||||
log.warn("[MQTT] 设备离线,命令达到最大等待次数,已删除待确认命令 设备编号={} 命令编号={}", command.getDeviceNo(), commandId);
|
||||
return;
|
||||
}
|
||||
command.setNextRetryAt(now + mqttProperties.getCommandAck().getRetryIntervalMs());
|
||||
savePending(command);
|
||||
log.debug("[MQTT] command retry delayed, device offline deviceNo={} commandId={}", command.getDeviceNo(), commandId);
|
||||
log.debug("[MQTT] 设备离线,命令延后重试 设备编号={} 命令编号={} 等待次数={}", command.getDeviceNo(), commandId, command.getRetryCount());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -161,7 +173,7 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler {
|
||||
command.setNextRetryAt(now + mqttProperties.getCommandAck().getRetryIntervalMs());
|
||||
mqttClientManager.publish(command.getTopic(), JsonUtils.toJsonString(command.getPayload()));
|
||||
savePending(command);
|
||||
log.info("[MQTT] command resent deviceNo={} commandId={} retry={}", command.getDeviceNo(), commandId, command.getRetryCount());
|
||||
log.info("[MQTT] 命令已重新下发 设备编号={} 命令编号={} 重试次数={}", command.getDeviceNo(), commandId, command.getRetryCount());
|
||||
}
|
||||
|
||||
private boolean isDeviceOnline(String deviceNo) {
|
||||
@@ -183,12 +195,22 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler {
|
||||
statusCache,
|
||||
Duration.ofSeconds(mqttProperties.getCommandAck().getDeviceStatusCacheTtlSeconds())
|
||||
);
|
||||
appDeviceMapper.update(null,
|
||||
new LambdaUpdateWrapper<AppDevice>()
|
||||
.set(AppDevice::getStatus, "1")
|
||||
.eq(AppDevice::getDeviceNo, deviceNo)
|
||||
);
|
||||
}
|
||||
|
||||
private RSet<String> pendingIds() {
|
||||
return RedisUtils.getClient().getSet(mqttProperties.getCommandAck().getPendingSetKey());
|
||||
}
|
||||
|
||||
private void deletePending(String commandId) {
|
||||
RedisUtils.deleteObject(pendingKey(commandId));
|
||||
pendingIds().remove(commandId);
|
||||
}
|
||||
|
||||
private String pendingKey(String commandId) {
|
||||
return mqttProperties.getCommandAck().getPendingKeyPrefix() + commandId;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package org.dromara.mqtt;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.mqtt.config.properties.MqttProperties;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MqttCommandRetryTask {
|
||||
@@ -15,6 +17,7 @@ public class MqttCommandRetryTask {
|
||||
@Scheduled(fixedDelayString = "${mqtt.command-ack.scan-interval-ms:5000}")
|
||||
public void retryExpiredCommands() {
|
||||
if (mqttProperties.getCommandAck().isEnabled()) {
|
||||
// log.info("[设备命令重新下发] 定时器调用成功 ");
|
||||
ackService.retryExpiredCommands();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import cn.hutool.http.HttpStatus;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.dromara.common.core.utils.MessageUtils;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
@@ -52,7 +53,7 @@ public class TableDataInfo<T> implements Serializable {
|
||||
this.rows = list;
|
||||
this.total = total;
|
||||
this.code = HttpStatus.HTTP_OK;
|
||||
this.msg = "查询成功";
|
||||
this.msg = message("query.success", "查询成功");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,7 +62,7 @@ public class TableDataInfo<T> implements Serializable {
|
||||
public static <T> TableDataInfo<T> build(IPage<T> page) {
|
||||
TableDataInfo<T> rspData = new TableDataInfo<>();
|
||||
rspData.setCode(HttpStatus.HTTP_OK);
|
||||
rspData.setMsg("查询成功");
|
||||
rspData.setMsg(message("query.success", "查询成功"));
|
||||
rspData.setRows(page.getRecords());
|
||||
rspData.setTotal(page.getTotal());
|
||||
return rspData;
|
||||
@@ -73,7 +74,7 @@ public class TableDataInfo<T> implements Serializable {
|
||||
public static <T> TableDataInfo<T> build(List<T> list) {
|
||||
TableDataInfo<T> rspData = new TableDataInfo<>();
|
||||
rspData.setCode(HttpStatus.HTTP_OK);
|
||||
rspData.setMsg("查询成功");
|
||||
rspData.setMsg(message("query.success", "查询成功"));
|
||||
rspData.setRows(list);
|
||||
rspData.setTotal(list.size());
|
||||
return rspData;
|
||||
@@ -85,7 +86,7 @@ public class TableDataInfo<T> implements Serializable {
|
||||
public static <T> TableDataInfo<T> build() {
|
||||
TableDataInfo<T> rspData = new TableDataInfo<>();
|
||||
rspData.setCode(HttpStatus.HTTP_OK);
|
||||
rspData.setMsg("查询成功");
|
||||
rspData.setMsg(message("query.success", "查询成功"));
|
||||
return rspData;
|
||||
}
|
||||
|
||||
@@ -104,4 +105,13 @@ public class TableDataInfo<T> implements Serializable {
|
||||
return new TableDataInfo<>(pageList, list.size());
|
||||
}
|
||||
|
||||
private static String message(String code, String defaultMessage) {
|
||||
try {
|
||||
String message = MessageUtils.message(code);
|
||||
return code.equals(message) ? defaultMessage : message;
|
||||
} catch (Exception e) {
|
||||
return defaultMessage;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public class MybatisExceptionHandler {
|
||||
log.error("请求地址'{}', 未找到数据源", requestURI);
|
||||
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "未找到数据源,请联系管理员确认");
|
||||
}
|
||||
log.error("请求地址'{}', Mybatis系统异常", requestURI, e);
|
||||
log.error("请求地址'{}', MyBatis系统异常", requestURI, e);
|
||||
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ public class OssFactory {
|
||||
client = CLIENT_CACHE.get(key);
|
||||
if (client == null || !client.checkPropertiesSame(properties)) {
|
||||
CLIENT_CACHE.put(key, new OssClient(configKey, properties));
|
||||
log.info("创建OSS实例 key => {}", configKey);
|
||||
log.info("创建OSS实例 配置键 => {}", configKey);
|
||||
return CLIENT_CACHE.get(key);
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -69,7 +69,7 @@ public class RateLimiterAspect {
|
||||
}
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存key => '{}'", count, number, combineKey);
|
||||
log.info("限制令牌 => {}, 剩余令牌 => {}, 缓存键 => '{}'", count, number, combineKey);
|
||||
} catch (Exception e) {
|
||||
if (e instanceof ServiceException) {
|
||||
throw e;
|
||||
|
||||
@@ -98,7 +98,7 @@ public class RedisConfig {
|
||||
.setReadMode(clusterServersConfig.getReadMode())
|
||||
.setSubscriptionMode(clusterServersConfig.getSubscriptionMode());
|
||||
}
|
||||
log.info("初始化 redis 配置");
|
||||
log.info("初始化 Redis 配置");
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ public class SmsExceptionHandler {
|
||||
@ExceptionHandler(SmsBlendException.class)
|
||||
public R<Void> handleSmsBlendException(SmsBlendException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("请求地址'{}',发生sms短信异常.", requestURI, e);
|
||||
log.error("请求地址'{}',发生短信异常.", requestURI, e);
|
||||
return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "短信发送失败,请稍后再试...");
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
@@ -210,7 +209,7 @@ public class SseEmitterManager {
|
||||
broadcastMessage.setMessage(sseMessageDto.getMessage());
|
||||
broadcastMessage.setUserIds(sseMessageDto.getUserIds());
|
||||
RedisUtils.publish(SSE_TOPIC, broadcastMessage, consumer -> {
|
||||
log.info("SSE发送主题订阅消息topic:{} session keys:{} message:{}",
|
||||
log.info("SSE发送主题订阅消息 主题={} 用户ID={} 消息={}",
|
||||
SSE_TOPIC, sseMessageDto.getUserIds(), sseMessageDto.getMessage());
|
||||
});
|
||||
}
|
||||
@@ -224,7 +223,7 @@ public class SseEmitterManager {
|
||||
SseMessageDto broadcastMessage = new SseMessageDto();
|
||||
broadcastMessage.setMessage(message);
|
||||
RedisUtils.publish(SSE_TOPIC, broadcastMessage, consumer -> {
|
||||
log.info("SSE发送主题订阅消息topic:{} message:{}", SSE_TOPIC, message);
|
||||
log.info("SSE发送主题订阅消息 主题={} 消息={}", SSE_TOPIC, message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public class SseTopicListener implements ApplicationRunner, Ordered {
|
||||
@Override
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
sseEmitterManager.subscribeMessage((message) -> {
|
||||
log.info("SSE主题订阅收到消息session keys={} message={}", message.getUserIds(), message.getMessage());
|
||||
log.info("SSE主题订阅收到消息 用户ID={} 消息={}", message.getUserIds(), message.getMessage());
|
||||
// 如果key不为空就按照key发消息 如果为空就群发
|
||||
if (CollUtil.isNotEmpty(message.getUserIds())) {
|
||||
message.getUserIds().forEach(key -> {
|
||||
|
||||
@@ -28,7 +28,7 @@ public class PlusTenantLineHandler implements TenantLineHandler {
|
||||
public Expression getTenantId() {
|
||||
String tenantId = TenantHelper.getTenantId();
|
||||
if (StringUtils.isBlank(tenantId)) {
|
||||
log.error("无法获取有效的租户id -> Null");
|
||||
log.error("无法获取有效的租户ID -> 空");
|
||||
return new NullValue();
|
||||
}
|
||||
// 返回固定租户
|
||||
|
||||
@@ -39,10 +39,10 @@ public class TenantKeyPrefixHandler extends KeyPrefixHandler {
|
||||
}
|
||||
String tenantId = TenantHelper.getTenantId();
|
||||
if (StringUtils.isBlank(tenantId)) {
|
||||
log.debug("无法获取有效的租户id -> Null");
|
||||
log.debug("无法获取有效的租户ID -> 空");
|
||||
return super.map(name);
|
||||
}
|
||||
if (StringUtils.startsWith(name, tenantId + "")) {
|
||||
if (StringUtils.startsWith(name, tenantId)) {
|
||||
// 如果存在则直接返回
|
||||
return super.map(name);
|
||||
}
|
||||
@@ -70,10 +70,10 @@ public class TenantKeyPrefixHandler extends KeyPrefixHandler {
|
||||
}
|
||||
String tenantId = TenantHelper.getTenantId();
|
||||
if (StringUtils.isBlank(tenantId)) {
|
||||
log.debug("无法获取有效的租户id -> Null");
|
||||
log.debug("无法获取有效的租户ID -> 空");
|
||||
return unmap;
|
||||
}
|
||||
if (StringUtils.startsWith(unmap, tenantId + "")) {
|
||||
if (StringUtils.startsWith(unmap, tenantId)) {
|
||||
// 如果存在则删除
|
||||
return unmap.substring((tenantId + ":").length());
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public class TenantSpringCacheManager extends PlusSpringCacheManager {
|
||||
}
|
||||
String tenantId = TenantHelper.getTenantId();
|
||||
if (StringUtils.isBlank(tenantId)) {
|
||||
log.error("无法获取有效的租户id -> Null");
|
||||
log.error("无法获取有效的租户ID -> 空");
|
||||
}
|
||||
if (StringUtils.startsWith(name, tenantId)) {
|
||||
// 如果存在则直接返回
|
||||
|
||||
@@ -7,11 +7,11 @@ import com.fasterxml.jackson.databind.JsonMappingException;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||
import org.dromara.common.translation.annotation.Translation;
|
||||
import org.dromara.common.translation.core.TranslationInterface;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
@@ -61,7 +61,7 @@ public class TranslationHandler extends JsonSerializer<Object> implements Contex
|
||||
Object result = trans.translation(value, translation.other());
|
||||
gen.writeObject(result);
|
||||
} catch (Exception e) {
|
||||
log.error("翻译处理异常,type: {}, value: {}", translation.type(), value, e);
|
||||
log.error("翻译处理异常,类型:{},值:{}", translation.type(), value, e);
|
||||
// 出现异常时输出原始值而不是中断序列化
|
||||
gen.writeObject(value);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package org.dromara.common.web.core;
|
||||
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.springframework.web.servlet.LocaleResolver;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
@@ -16,12 +17,24 @@ public class I18nLocaleResolver implements LocaleResolver {
|
||||
@Override
|
||||
public Locale resolveLocale(HttpServletRequest httpServletRequest) {
|
||||
String language = httpServletRequest.getHeader("content-language");
|
||||
Locale locale = Locale.getDefault();
|
||||
if (language != null && language.length() > 0) {
|
||||
String[] split = language.split("_");
|
||||
locale = new Locale(split[0], split[1]);
|
||||
if (StringUtils.isBlank(language)) {
|
||||
language = httpServletRequest.getParameter("lang");
|
||||
}
|
||||
return locale;
|
||||
if (StringUtils.isBlank(language)) {
|
||||
language = httpServletRequest.getHeader("Accept-Language");
|
||||
}
|
||||
if (StringUtils.isBlank(language)) {
|
||||
return Locale.SIMPLIFIED_CHINESE;
|
||||
}
|
||||
String languageTag = language.split(",")[0].trim().replace('_', '-');
|
||||
if ("zh".equalsIgnoreCase(languageTag)) {
|
||||
return Locale.SIMPLIFIED_CHINESE;
|
||||
}
|
||||
if ("en".equalsIgnoreCase(languageTag)) {
|
||||
return Locale.US;
|
||||
}
|
||||
Locale locale = Locale.forLanguageTag(languageTag);
|
||||
return StringUtils.isBlank(locale.getLanguage()) ? Locale.SIMPLIFIED_CHINESE : locale;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -51,16 +51,16 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
|
||||
jsonParam = rootNode.toString();
|
||||
}
|
||||
}
|
||||
log.info("[PLUS]开始请求 => URL[{}],参数类型[json],参数:[{}]", url, jsonParam);
|
||||
log.info("[请求]开始请求 => URL[{}],参数类型[JSON],参数:[{}]", url, jsonParam);
|
||||
} else {
|
||||
Map<String, String[]> parameterMap = request.getParameterMap();
|
||||
if (MapUtil.isNotEmpty(parameterMap)) {
|
||||
Map<String, String[]> map = new LinkedHashMap<>(parameterMap);
|
||||
MapUtil.removeAny(map, SystemConstants.EXCLUDE_PROPERTIES);
|
||||
String parameters = JsonUtils.toJsonString(map);
|
||||
log.info("[PLUS]开始请求 => URL[{}],参数类型[param],参数:[{}]", url, parameters);
|
||||
log.info("[请求]开始请求 => URL[{}],参数类型[表单参数],参数:[{}]", url, parameters);
|
||||
} else {
|
||||
log.info("[PLUS]开始请求 => URL[{}],无参数", url);
|
||||
log.info("[请求]开始请求 => URL[{}],无参数", url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
|
||||
StopWatch stopWatch = KEY_CACHE.get();
|
||||
if (ObjectUtil.isNotNull(stopWatch)) {
|
||||
stopWatch.stop();
|
||||
log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getDuration().toMillis());
|
||||
log.info("[请求]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getDuration().toMillis());
|
||||
KEY_CACHE.remove();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,11 +31,11 @@ public class PlusWebSocketHandler extends AbstractWebSocketHandler {
|
||||
LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
|
||||
if (ObjectUtil.isNull(loginUser)) {
|
||||
session.close(CloseStatus.BAD_DATA);
|
||||
log.info("[connect] invalid token received. sessionId: {}", session.getId());
|
||||
log.info("[连接] 收到无效令牌,会话ID={}", session.getId());
|
||||
return;
|
||||
}
|
||||
WebSocketSessionHolder.addSession(loginUser.getUserId(), new ConcurrentWebSocketSessionDecorator(session, 10 * 1000, 64000));
|
||||
log.info("[connect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
|
||||
log.info("[连接] 会话ID={},用户ID={},用户类型={}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +90,7 @@ public class PlusWebSocketHandler extends AbstractWebSocketHandler {
|
||||
*/
|
||||
@Override
|
||||
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
|
||||
log.error("[transport error] sessionId: {} , exception:{}", session.getId(), exception.getMessage());
|
||||
log.error("[传输错误] 会话ID={},异常={}", session.getId(), exception.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,11 +103,11 @@ public class PlusWebSocketHandler extends AbstractWebSocketHandler {
|
||||
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
|
||||
LoginUser loginUser = (LoginUser) session.getAttributes().get(LOGIN_USER_KEY);
|
||||
if (ObjectUtil.isNull(loginUser)) {
|
||||
log.info("[disconnect] invalid token received. sessionId: {}", session.getId());
|
||||
log.info("[断开连接] 收到无效令牌,会话ID={}", session.getId());
|
||||
return;
|
||||
}
|
||||
WebSocketSessionHolder.removeSession(loginUser.getUserId());
|
||||
log.info("[disconnect] sessionId: {},userId:{},userType:{}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
|
||||
log.info("[断开连接] 会话ID={},用户ID={},用户类型={}", session.getId(), loginUser.getUserId(), loginUser.getUserType());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,7 +54,7 @@ public class PlusWebSocketInterceptor implements HandshakeInterceptor {
|
||||
attributes.put(LOGIN_USER_KEY, loginUser);
|
||||
return true;
|
||||
} catch (NotLoginException e) {
|
||||
log.error("WebSocket 认证失败'{}',无法访问系统资源", e.getMessage());
|
||||
log.error("网络套接字认证失败'{}',无法访问系统资源", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class WebSocketTopicListener implements ApplicationRunner, Ordered {
|
||||
public void run(ApplicationArguments args) throws Exception {
|
||||
// 订阅WebSocket消息
|
||||
WebSocketUtils.subscribeMessage((message) -> {
|
||||
log.info("WebSocket主题订阅收到消息session keys={} message={}", message.getSessionKeys(), message.getMessage());
|
||||
log.info("网络套接字主题订阅收到消息 会话标识={} 消息={}", message.getSessionKeys(), message.getMessage());
|
||||
// 如果key不为空就按照key发消息 如果为空就群发
|
||||
if (CollUtil.isNotEmpty(message.getSessionKeys())) {
|
||||
message.getSessionKeys().forEach(key -> {
|
||||
@@ -40,7 +40,7 @@ public class WebSocketTopicListener implements ApplicationRunner, Ordered {
|
||||
});
|
||||
}
|
||||
});
|
||||
log.info("初始化WebSocket主题订阅监听器成功");
|
||||
log.info("初始化网络套接字主题订阅监听器成功");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -69,7 +69,7 @@ public class WebSocketUtils {
|
||||
broadcastMessage.setMessage(webSocketMessage.getMessage());
|
||||
broadcastMessage.setSessionKeys(unsentSessionKeys);
|
||||
RedisUtils.publish(WEB_SOCKET_TOPIC, broadcastMessage, consumer -> {
|
||||
log.info(" WebSocket发送主题订阅消息topic:{} session keys:{} message:{}",
|
||||
log.info("网络套接字发送主题订阅消息 主题={} 会话标识={} 消息={}",
|
||||
WEB_SOCKET_TOPIC, unsentSessionKeys, webSocketMessage.getMessage());
|
||||
});
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public class WebSocketUtils {
|
||||
WebSocketMessageDto broadcastMessage = new WebSocketMessageDto();
|
||||
broadcastMessage.setMessage(message);
|
||||
RedisUtils.publish(WEB_SOCKET_TOPIC, broadcastMessage, consumer -> {
|
||||
log.info("WebSocket发送主题订阅消息topic:{} message:{}", WEB_SOCKET_TOPIC, message);
|
||||
log.info("网络套接字发送主题订阅消息 主题={} 消息={}", WEB_SOCKET_TOPIC, message);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -115,12 +115,12 @@ public class WebSocketUtils {
|
||||
*/
|
||||
private static void sendMessage(WebSocketSession session, WebSocketMessage<?> message) {
|
||||
if (session == null || !session.isOpen()) {
|
||||
log.warn("[send] session会话已经关闭");
|
||||
log.warn("[发送] 会话已经关闭");
|
||||
} else {
|
||||
try {
|
||||
session.sendMessage(message);
|
||||
} catch (IOException e) {
|
||||
log.error("[send] session({}) 发送消息({}) 异常", session, message, e);
|
||||
log.error("[发送] 会话({}) 发送消息({}) 异常", session, message, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user