设备bug修改 新增设备解绑功能
This commit is contained in:
@@ -365,6 +365,11 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler {
|
||||
// 其他线程正在刷新,本次跳过;状态写入幂等,下条 ACK 仍会兜底刷新
|
||||
return;
|
||||
}
|
||||
appDeviceMapper.update(null,
|
||||
new LambdaUpdateWrapper<AppDevice>()
|
||||
.set(AppDevice::getStatus, "1")
|
||||
.eq(AppDevice::getDeviceNo, deviceNo)
|
||||
);
|
||||
Map<String, Object> statusCache = new HashMap<>();
|
||||
statusCache.put("deviceNo", deviceNo);
|
||||
statusCache.put("status", "1");
|
||||
@@ -374,11 +379,6 @@ 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)
|
||||
);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.warn("[MQTT] 刷新设备在线状态被中断 设备编号={}", deviceNo);
|
||||
|
||||
@@ -30,7 +30,6 @@ public class MqttDeviceUnbindResultPublisher implements IDeviceUnbindResultPubli
|
||||
throw new ServiceException("解绑回执缺少设备编号");
|
||||
}
|
||||
Map<String, Object> payload = new LinkedHashMap<>();
|
||||
payload.put("commandId", result.commandId());
|
||||
payload.put("bindingId", result.bindingId());
|
||||
payload.put("status", result.status());
|
||||
payload.put("message", result.message());
|
||||
|
||||
@@ -418,10 +418,23 @@ class MqttCommandAckServiceTest {
|
||||
when(redissonClient.getLock("lock:mqtt:device:status:D01")).thenReturn(lock);
|
||||
when(lock.tryLock(0, 10, TimeUnit.SECONDS)).thenReturn(true);
|
||||
when(lock.isHeldByCurrentThread()).thenReturn(true);
|
||||
AtomicBoolean databaseUpdated = new AtomicBoolean();
|
||||
doAnswer(invocation -> {
|
||||
databaseUpdated.set(true);
|
||||
return 1;
|
||||
}).when(appDeviceMapper).update(eq(null), any());
|
||||
|
||||
MqttCommandAckService service = new MqttCommandAckService(properties, appDeviceMapper);
|
||||
try (MockedStatic<RedisUtils> redis = mockStatic(RedisUtils.class)) {
|
||||
redis.when(RedisUtils::getClient).thenReturn(redissonClient);
|
||||
redis.when(() -> RedisUtils.setCacheObject(
|
||||
eq("mqtt:device:status:D01"), any(Map.class), eq(Duration.ofSeconds(900))
|
||||
)).thenAnswer(invocation -> {
|
||||
if (!databaseUpdated.get()) {
|
||||
throw new AssertionError("online cache was written before database status update");
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
ReflectionTestUtils.invokeMethod(service, "refreshDeviceOnline", "D01");
|
||||
|
||||
@@ -434,4 +447,30 @@ class MqttCommandAckServiceTest {
|
||||
verify(lock).unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void refreshDeviceOnlineDoesNotWriteCacheWhenDatabaseUpdateFails() throws Exception {
|
||||
MqttProperties properties = new MqttProperties();
|
||||
AppDeviceMapper appDeviceMapper = mock(AppDeviceMapper.class);
|
||||
RedissonClient redissonClient = mock(RedissonClient.class);
|
||||
RLock lock = mock(RLock.class);
|
||||
when(redissonClient.getLock("lock:mqtt:device:status:D01")).thenReturn(lock);
|
||||
when(lock.tryLock(0, 10, TimeUnit.SECONDS)).thenReturn(true);
|
||||
when(lock.isHeldByCurrentThread()).thenReturn(true);
|
||||
doThrow(new IllegalStateException("database failure")).when(appDeviceMapper).update(eq(null), any());
|
||||
|
||||
MqttCommandAckService service = new MqttCommandAckService(properties, appDeviceMapper);
|
||||
try (MockedStatic<RedisUtils> redis = mockStatic(RedisUtils.class)) {
|
||||
redis.when(RedisUtils::getClient).thenReturn(redissonClient);
|
||||
|
||||
assertThatThrownBy(() -> ReflectionTestUtils.invokeMethod(service, "refreshDeviceOnline", "D01"))
|
||||
.isInstanceOf(IllegalStateException.class)
|
||||
.hasMessage("database failure");
|
||||
|
||||
redis.verify(() -> RedisUtils.setCacheObject(
|
||||
eq("mqtt:device:status:D01"), any(Map.class), eq(Duration.ofSeconds(600))
|
||||
), never());
|
||||
verify(lock).unlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.dromara.mqtt;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.dromara.app.domain.mqtt.DeviceUnbindResult;
|
||||
import org.dromara.mqtt.config.properties.MqttProperties;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@Tag("dev")
|
||||
class MqttDeviceUnbindResultPublisherTest {
|
||||
|
||||
private static GenericApplicationContext applicationContext;
|
||||
|
||||
@BeforeAll
|
||||
static void initializeJsonUtils() {
|
||||
applicationContext = new GenericApplicationContext();
|
||||
applicationContext.registerBean(ObjectMapper.class, (Supplier<ObjectMapper>) ObjectMapper::new);
|
||||
applicationContext.refresh();
|
||||
new SpringUtil().setApplicationContext(applicationContext);
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void closeApplicationContext() {
|
||||
applicationContext.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void publishUsesBindingIdWithoutCommandId() throws Exception {
|
||||
MqttClientManager clientManager = mock(MqttClientManager.class);
|
||||
MqttDeviceUnbindResultPublisher publisher = new MqttDeviceUnbindResultPublisher(new MqttProperties());
|
||||
ReflectionTestUtils.setField(publisher, "mqttClientManager", clientManager);
|
||||
|
||||
publisher.publish(new DeviceUnbindResult(
|
||||
"D01", "binding-1", DeviceUnbindResult.APPLIED, "done"));
|
||||
|
||||
ArgumentCaptor<String> payloadCaptor = ArgumentCaptor.forClass(String.class);
|
||||
verify(clientManager).publish(org.mockito.ArgumentMatchers.eq("/d01/subscriber/unbind/ack"),
|
||||
payloadCaptor.capture());
|
||||
JsonNode payload = new ObjectMapper().readTree(payloadCaptor.getValue());
|
||||
assertThat(payload.has("commandId")).isFalse();
|
||||
assertThat(payload.get("bindingId").asText()).isEqualTo("binding-1");
|
||||
assertThat(payload.get("status").asText()).isEqualTo(DeviceUnbindResult.APPLIED);
|
||||
assertThat(payload.get("message").asText()).isEqualTo("done");
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,12 @@
|
||||
<groupId>p6spy</groupId>
|
||||
<artifactId>p6spy</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.dromara.common.mybatis.p6spy;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.p6spy.StdoutLogger;
|
||||
|
||||
/**
|
||||
* P6Spy SQL logger that suppresses SQL executed by scheduled tasks.
|
||||
*/
|
||||
public class ScheduledTaskAwareStdoutLogger extends StdoutLogger {
|
||||
|
||||
private static final String SCHEDULE_POOL_PREFIX = "schedule-pool-";
|
||||
private static final String VIRTUAL_SCHEDULE_POOL_PREFIX = "virtual-schedule-pool-";
|
||||
private static final String SPRING_SCHEDULING_PREFIX = "scheduling-";
|
||||
|
||||
static boolean isScheduledTaskThread(String threadName) {
|
||||
return threadName != null
|
||||
&& (threadName.startsWith(SCHEDULE_POOL_PREFIX)
|
||||
|| threadName.startsWith(VIRTUAL_SCHEDULE_POOL_PREFIX)
|
||||
|| threadName.startsWith(SPRING_SCHEDULING_PREFIX));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logText(String text) {
|
||||
if (!isScheduledTaskThread(Thread.currentThread().getName())) {
|
||||
super.logText(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,8 @@
|
||||
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
|
||||
# 自定义日志打印
|
||||
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
|
||||
#日志输出到控制台
|
||||
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
|
||||
# 日志输出到控制台,定时任务线程不打印 SQL
|
||||
appender=org.dromara.common.mybatis.p6spy.ScheduledTaskAwareStdoutLogger
|
||||
# 使用日志系统记录 sql
|
||||
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
|
||||
# 取消JDBC URL前缀
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.dromara.common.mybatis.p6spy;
|
||||
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@Tag("dev")
|
||||
public class ScheduledTaskAwareStdoutLoggerTest {
|
||||
|
||||
@Test
|
||||
public void identifiesScheduledTaskThreads() {
|
||||
assertThat(ScheduledTaskAwareStdoutLogger.isScheduledTaskThread("schedule-pool-1")).isTrue();
|
||||
assertThat(ScheduledTaskAwareStdoutLogger.isScheduledTaskThread("virtual-schedule-pool-2")).isTrue();
|
||||
assertThat(ScheduledTaskAwareStdoutLogger.isScheduledTaskThread("scheduling-3")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keepsSqlLoggingEnabledForOtherThreads() {
|
||||
assertThat(ScheduledTaskAwareStdoutLogger.isScheduledTaskThread("XNIO-1 task-2")).isFalse();
|
||||
assertThat(ScheduledTaskAwareStdoutLogger.isScheduledTaskThread("mqtt-consumer-1")).isFalse();
|
||||
assertThat(ScheduledTaskAwareStdoutLogger.isScheduledTaskThread(null)).isFalse();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user