objectObjectHashMap = new HashMap<>();
objectObjectHashMap.put("bindStatus", true);
- objectObjectHashMap.put("deviceNo", "01");
+ objectObjectHashMap.put("deviceNo", deviceNo);
objectObjectHashMap.put("cmd", -1);
command.getPayload().putAll(objectObjectHashMap);
return sendCommand(command);
diff --git a/water-modules/water-app/src/main/java/org/dromara/app/task/DeviceOfflineCheckTask.java b/water-modules/water-app/src/main/java/org/dromara/app/task/DeviceOfflineCheckTask.java
new file mode 100644
index 0000000..e0908f3
--- /dev/null
+++ b/water-modules/water-app/src/main/java/org/dromara/app/task/DeviceOfflineCheckTask.java
@@ -0,0 +1,86 @@
+package org.dromara.app.task;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.mapper.AppDeviceMapper;
+import org.dromara.common.redis.utils.RedisUtils;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 设备离线检测定时任务
+ *
+ * 逻辑:Redis 中 mqtt:device:status:{deviceNo} Key 的 TTL 由 device-status-cache-ttl-seconds 控制。
+ * 设备断电/断网后不再上报数据,TTL 到期 Key 自动消失。
+ * 本任务定期扫描数据库中非离线状态的设备,如果对应 Redis Key 已不存在,
+ * 则将数据库 status 更新为 0(离线)。
+ *
+ *
+ * 扫描间隔通过 {@code mqtt.offline-check.interval-ms} 配置,建议小于设备在线 Key 的 TTL。
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class DeviceOfflineCheckTask {
+
+ private final AppDeviceMapper appDeviceMapper;
+
+ @Value("${mqtt.command-ack.device-status-cache-prefix}")
+ private String deviceStatusCachePrefix;
+
+ @Value("${mqtt.offline-check.enabled:true}")
+ private boolean enabled;
+
+ /**
+ * 每 90 秒检查一次(Redis TTL 默认 300s,90s 间隔确保 1.5 个周期内同步)
+ * 可通过 mqtt.offline-check.interval-ms 覆盖(单位 ms)
+ */
+ @Scheduled(fixedDelayString = "${mqtt.offline-check.interval-ms:90000}")
+ @Transactional(rollbackFor = Exception.class)
+ public void checkOfflineDevices() {
+ if (!enabled) {
+ return;
+ }
+
+ // 查询数据库中所有非离线状态的设备,避免状态不是 1 但仍需离线修正的设备被漏掉
+ List onlineDevices = appDeviceMapper.selectList(
+ new LambdaQueryWrapper()
+ .ne(AppDevice::getStatus, "0")
+ .select(AppDevice::getDeviceNo)
+ );
+
+ if (onlineDevices.isEmpty()) {
+ return;
+ }
+
+ // 过滤出 Redis Key 已过期(不存在)的设备
+ List offlineDeviceNos = onlineDevices.stream()
+ .map(AppDevice::getDeviceNo)
+ .filter(deviceNo -> !RedisUtils.hasKey(deviceStatusCachePrefix + deviceNo))
+ .collect(Collectors.toList());
+
+ if (offlineDeviceNos.isEmpty()) {
+ log.debug("[设备离线] 本次检测未发现离线设备,扫描设备数:{}", onlineDevices.size());
+ return;
+ }
+
+ // 批量更新为离线
+ int updated = appDeviceMapper.update(null,
+ new LambdaUpdateWrapper()
+ .set(AppDevice::getStatus, "0")
+ .ne(AppDevice::getStatus, "0")
+ .in(AppDevice::getDeviceNo, offlineDeviceNos)
+ );
+
+ log.info("[设备离线] 检测到 {} 台设备离线,已更新数据库状态,设备编号:{}",
+ updated, offlineDeviceNos);
+ }
+}
diff --git a/water-modules/water-demo/src/main/java/org/dromara/demo/controller/SaTokenTestController.java b/water-modules/water-demo/src/main/java/org/dromara/demo/controller/SaTokenTestController.java
index 66fd3cd..ade71c3 100644
--- a/water-modules/water-demo/src/main/java/org/dromara/demo/controller/SaTokenTestController.java
+++ b/water-modules/water-demo/src/main/java/org/dromara/demo/controller/SaTokenTestController.java
@@ -56,7 +56,7 @@ public class SaTokenTestController {
@SaCheckRole("none_exist") // 该注解会被忽略
@GetMapping("/basic/ignoreAll")
public R ignoreAll() {
- log.info("【场景4】SaIgnore忽略所有权限校验");
+ log.info("【场景4】忽略注解跳过所有权限校验");
return R.ok("SaIgnore生效,所有权限校验被忽略");
}
@@ -68,7 +68,7 @@ public class SaTokenTestController {
@SaCheckRole(value = {"admin", "operator"}, mode = SaMode.AND)
@GetMapping("/advance/multiRoleAnd")
public R multiRoleAnd() {
- log.info("【场景5】多角色AND模式(admin+operator)校验通过");
+ log.info("【场景5】多角色且模式(admin+operator)校验通过");
return R.ok("同时拥有admin和operator角色,校验通过");
}
@@ -78,7 +78,7 @@ public class SaTokenTestController {
@SaCheckRole(value = {"admin", "test"}, mode = SaMode.OR)
@GetMapping("/advance/multiRoleOr")
public R multiRoleOr() {
- log.info("【场景6】多角色OR模式(admin|test)校验通过");
+ log.info("【场景6】多角色或模式(admin|test)校验通过");
return R.ok("拥有admin或test角色,校验通过");
}
@@ -88,7 +88,7 @@ public class SaTokenTestController {
@SaCheckPermission(value = {"system:user:edit", "system:log:view"}, mode = SaMode.AND)
@GetMapping("/advance/multiPermAnd")
public R multiPermAnd() {
- log.info("【场景7】多权限AND模式(system:user:edit+system:log:view)校验通过");
+ log.info("【场景7】多权限且模式(system:user:edit+system:log:view)校验通过");
return R.ok("同时拥有system:user:edit和system:log:view权限,校验通过");
}
@@ -98,7 +98,7 @@ public class SaTokenTestController {
@SaCheckPermission(value = {"system:user:add", "system:user:delete"}, mode = SaMode.OR)
@GetMapping("/advance/multiPermOr")
public R multiPermOr() {
- log.info("【场景8】多权限OR模式(system:user:add|system:user:delete)校验通过");
+ log.info("【场景8】多权限或模式(system:user:add|system:user:delete)校验通过");
return R.ok("拥有system:user:add或system:user:delete权限,校验通过");
}
@@ -134,7 +134,7 @@ public class SaTokenTestController {
@SaCheckPermission("system:user:all")
@GetMapping("/advanced/mixRolePermAnd")
public R mixRolePermAnd() {
- log.info("【场景11】角色+权限混合AND(admin+system:user:all)校验通过");
+ log.info("【场景11】角色+权限混合且模式(admin+system:user:all)校验通过");
return R.ok("拥有admin角色且拥有system:user:all权限,校验通过");
}
@@ -146,7 +146,7 @@ public class SaTokenTestController {
@SaCheckPermission(value = {"system:manage"}, mode = SaMode.OR)
@GetMapping("/advanced/mixRolePermOr")
public R mixRolePermOr() {
- log.info("【场景12】角色+权限混合OR(super_admin|system:manage)校验通过");
+ log.info("【场景12】角色+权限混合或模式(super_admin|system:manage)校验通过");
return R.ok("拥有super_admin角色或system:manage权限,校验通过");
}
@@ -157,7 +157,7 @@ public class SaTokenTestController {
@SaCheckPermission(value = "system:user:export", orRole = {"admin", "operator"})
@GetMapping("/advanced/permWithOrRole")
public R permWithOrRole() {
- log.info("【场景13】权限+orRole兜底校验通过");
+ log.info("【场景13】权限+角色兜底校验通过");
return R.ok("拥有system:user:export权限,或拥有admin/operator角色,校验通过");
}
@@ -170,7 +170,7 @@ public class SaTokenTestController {
@SaIgnore
@GetMapping("/special/ignoreOverride")
public R ignoreOverride() {
- log.info("【场景14】SaIgnore覆盖类级别权限注解");
+ log.info("【场景14】忽略注解覆盖类级别权限注解");
return R.ok("方法级SaIgnore覆盖类级别权限校验");
}
diff --git a/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java b/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java
index 77389bc..8aadd3e 100644
--- a/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java
+++ b/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwCommonServiceImpl.java
@@ -107,7 +107,7 @@ public class FlwCommonServiceImpl implements IFlwCommonService {
// // 指定获取一个短信服务商 configKey
// SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
// SmsResponse smsResponse = smsBlend.sendMessage(phones, templateId, map);
- log.info("【短信发送 - TODO】用户数量={} 内容={}", userList.size(), message);
+ log.info("【短信发送 - 待实现】用户数量={} 内容={}", userList.size(), message);
}
default -> log.warn("【消息发送】未处理的消息类型:{}", messageTypeEnum);
}
diff --git a/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java b/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java
index 1cc61fe..a34bea0 100644
--- a/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java
+++ b/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwDefinitionServiceImpl.java
@@ -194,8 +194,8 @@ public class FlwDefinitionServiceImpl implements IFlwDefinitionService {
try {
defService.removeDef(ids);
} catch (Exception e) {
- log.error("Error removing flow definitions: {}", e.getMessage(), e);
- throw new RuntimeException("Failed to remove flow definitions", e);
+ log.error("删除流程定义失败:{}", e.getMessage(), e);
+ throw new RuntimeException("删除流程定义失败", e);
}
return true;
}
diff --git a/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java b/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java
index 4814736..ba55a67 100644
--- a/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java
+++ b/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwInstanceServiceImpl.java
@@ -266,7 +266,7 @@ public class FlwInstanceServiceImpl implements IFlwInstanceService {
flowInstances.forEach(instance -> {
Definition definition = definitionMap.get(instance.getDefinitionId());
if (ObjectUtil.isNull(definition)) {
- log.warn("实例 ID: {} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
+ log.warn("实例ID:{} 对应的流程定义信息未找到,跳过删除事件触发。", instance.getId());
return;
}
flowProcessEventHandler.processDeleteHandler(definition.getFlowCode(), instance.getBusinessId());
diff --git a/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java b/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java
index 8e39869..767a61c 100644
--- a/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java
+++ b/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwNodeExtServiceImpl.java
@@ -267,7 +267,7 @@ public class FlwNodeExtServiceImpl implements NodeExtService, IFlwNodeExtService
nodeExtVo.setVariables(variables);
} else {
// 未知扩展类型,记录日志
- log.warn("未知扩展类型:code={}, value={}", code, value);
+ log.warn("未知扩展类型:编码={},值={}", code, value);
}
}
return nodeExtVo;
diff --git a/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java b/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java
index 8edccd0..d8557f8 100644
--- a/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java
+++ b/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskAssigneeServiceImpl.java
@@ -283,7 +283,7 @@ public class FlwTaskAssigneeServiceImpl implements IFlwTaskAssigneeService, Hand
return Pair.of(type, parts[1]);
}
} catch (Exception e) {
- log.warn("解析 storageId 失败,格式非法:{},错误信息:{}", storageId, e.getMessage());
+ log.warn("解析存储ID失败,格式非法:{},错误信息:{}", storageId, e.getMessage());
return null;
}
}
diff --git a/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java b/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java
index af693e6..dbbf42a 100644
--- a/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java
+++ b/water-modules/water-workflow/src/main/java/org/dromara/workflow/service/impl/FlwTaskServiceImpl.java
@@ -754,8 +754,8 @@ public class FlwTaskServiceImpl implements IFlwTaskService {
public boolean taskOperation(TaskOperationBo bo, String taskOperation) {
TaskOperationEnum op = TaskOperationEnum.getByCode(taskOperation);
if (op == null) {
- log.error("Invalid operation type:{} ", taskOperation);
- throw new ServiceException("Invalid operation type " + taskOperation);
+ log.error("无效的操作类型:{}", taskOperation);
+ throw new ServiceException("无效的操作类型 " + taskOperation);
}
FlowParams flowParams = FlowParams.build().message(bo.getMessage());