diff --git a/pom.xml b/pom.xml index da9f20f..44c5206 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,8 @@ 1.16.7 3.3.4 + + 3.5.3 2.28.22 @@ -151,6 +153,18 @@ ${fastexcel.version} + + + com.google.zxing + core + ${zxing.version} + + + com.google.zxing + javase + ${zxing.version} + + org.apache.velocity diff --git a/water-admin/pom.xml b/water-admin/pom.xml index 446bc59..9c1f81b 100644 --- a/water-admin/pom.xml +++ b/water-admin/pom.xml @@ -96,6 +96,15 @@ org.dromara water-app + + + com.google.zxing + core + + + com.google.zxing + javase + diff --git a/water-common/water-common-mqtt/src/main/java/org/dromara/mqtt/MqttCommandAckService.java b/water-common/water-common-mqtt/src/main/java/org/dromara/mqtt/MqttCommandAckService.java index de80c2f..afa8445 100644 --- a/water-common/water-common-mqtt/src/main/java/org/dromara/mqtt/MqttCommandAckService.java +++ b/water-common/water-common-mqtt/src/main/java/org/dromara/mqtt/MqttCommandAckService.java @@ -36,6 +36,7 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler { private MqttClientManager mqttClientManager; private final MqttProperties mqttProperties; private final AppDeviceMapper appDeviceMapper; + private static final String DEVICE_STATUS_LOCK_PREFIX = "lock:mqtt:device:status:"; public void savePending(DeviceCommand command) { RedisUtils.setCacheObject(pendingKey(command.getCommandId()), command, Duration.ofSeconds(mqttProperties.getCommandAck().getPendingTtlSeconds())); @@ -46,7 +47,7 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler { if (StringUtils.isBlank(commandId)) { return; } - deletePending(commandId); + withCommandLock(commandId, () -> deletePending(commandId)); } @Override @@ -74,8 +75,10 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler { return; } ack.setDeviceNo(deviceNo); - deletePending(ack.getCommandId()); - RedisUtils.setCacheObject(ackKey(ack.getCommandId()), ack, Duration.ofSeconds(mqttProperties.getCommandAck().getAckTtlSeconds())); + withCommandLock(ack.getCommandId(), () -> { + deletePending(ack.getCommandId()); + RedisUtils.setCacheObject(ackKey(ack.getCommandId()), ack, Duration.ofSeconds(mqttProperties.getCommandAck().getAckTtlSeconds())); + }); refreshDeviceOnline(deviceNo); log.info("[MQTT] 收到命令确认 设备编号={} 命令编号={} 状态={}", deviceNo, ack.getCommandId(), ack.getStatus()); } @@ -96,8 +99,10 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler { ack.setStatus("1"); ack.setMessage(payload); - deletePending(commandId); - RedisUtils.setCacheObject(ackKey(commandId), ack, Duration.ofSeconds(mqttProperties.getCommandAck().getAckTtlSeconds())); + withCommandLock(commandId, () -> { + deletePending(commandId); + RedisUtils.setCacheObject(ackKey(commandId), ack, Duration.ofSeconds(mqttProperties.getCommandAck().getAckTtlSeconds())); + }); log.info("[MQTT] 收到非 JSON 命令确认 设备编号={} 命令编号={} 消息体={}", deviceNo, commandId, payload); } @@ -121,24 +126,7 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler { } private void retryCommandIfLocked(String commandId, long now) { - RLock lock = RedisUtils.getClient().getLock(mqttProperties.getCommandAck().getRetryLockKeyPrefix() + commandId); - boolean locked = false; - try { - locked = lock.tryLock(0, mqttProperties.getCommandAck().getRetryLockTtlMs(), TimeUnit.MILLISECONDS); - if (!locked) { - return; - } - retryCommand(commandId, now); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - log.warn("[MQTT] 命令重试锁等待被中断 命令编号={}", commandId); - } catch (RuntimeException e) { - log.error("[MQTT] 命令重试失败 命令编号={}", commandId, e); - } finally { - if (locked && lock.isHeldByCurrentThread()) { - lock.unlock(); - } - } + withCommandLock(commandId, () -> retryCommand(commandId, now)); } private void retryCommand(String commandId, long now) { @@ -186,20 +174,30 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler { } private void refreshDeviceOnline(String deviceNo) { - Map statusCache = new HashMap<>(); - statusCache.put("deviceNo", deviceNo); - statusCache.put("status", "1"); - statusCache.put("lastReportTime", Instant.now().toString()); - RedisUtils.setCacheObject( - mqttProperties.getCommandAck().getDeviceStatusCachePrefix() + deviceNo, - statusCache, - Duration.ofSeconds(mqttProperties.getCommandAck().getDeviceStatusCacheTtlSeconds()) - ); - appDeviceMapper.update(null, - new LambdaUpdateWrapper() - .set(AppDevice::getStatus, "1") - .eq(AppDevice::getDeviceNo, deviceNo) - ); + RLock lock = RedisUtils.getClient().getLock(DEVICE_STATUS_LOCK_PREFIX + deviceNo); + boolean locked = false; + try { + lock.lock(10, TimeUnit.SECONDS); + locked = true; + Map statusCache = new HashMap<>(); + statusCache.put("deviceNo", deviceNo); + statusCache.put("status", "1"); + statusCache.put("lastReportTime", Instant.now().toString()); + RedisUtils.setCacheObject( + mqttProperties.getCommandAck().getDeviceStatusCachePrefix() + deviceNo, + statusCache, + Duration.ofSeconds(mqttProperties.getCommandAck().getDeviceStatusCacheTtlSeconds()) + ); + appDeviceMapper.update(null, + new LambdaUpdateWrapper() + .set(AppDevice::getStatus, "1") + .eq(AppDevice::getDeviceNo, deviceNo) + ); + } finally { + if (locked && lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } } private RSet pendingIds() { @@ -211,6 +209,27 @@ public class MqttCommandAckService implements IDeviceCommandAckHandler { pendingIds().remove(commandId); } + private void withCommandLock(String commandId, Runnable action) { + RLock lock = RedisUtils.getClient().getLock(mqttProperties.getCommandAck().getRetryLockKeyPrefix() + commandId); + boolean locked = false; + try { + locked = lock.tryLock(0, mqttProperties.getCommandAck().getRetryLockTtlMs(), TimeUnit.MILLISECONDS); + if (!locked) { + return; + } + action.run(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn("[MQTT] 命令锁等待被中断 命令编号={}", commandId); + } catch (RuntimeException e) { + log.error("[MQTT] 命令锁内处理失败 命令编号={}", commandId, e); + } finally { + if (locked && lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } + } + private String pendingKey(String commandId) { return mqttProperties.getCommandAck().getPendingKeyPrefix() + commandId; } diff --git a/water-common/water-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java b/water-common/water-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java index 14fc4dc..18ca5b6 100644 --- a/water-common/water-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java +++ b/water-common/water-common-oss/src/main/java/org/dromara/common/oss/core/OssClient.java @@ -149,7 +149,7 @@ public class OssClient { .contentType(contentType) // 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同, // 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置) - //.acl(getAccessPolicy().getObjectCannedACL()) + .acl(getAccessPolicy().getObjectCannedACL()) .build() ); if (log.isDebugEnabled()) { @@ -203,7 +203,7 @@ public class OssClient { .contentType(contentType) // 用于设置对象的访问控制列表(ACL)。不同云厂商对ACL的支持和实现方式有所不同, // 因此根据具体的云服务提供商,你可能需要进行不同的配置(自行开启,阿里云有acl权限配置,腾讯云没有acl权限配置) - //.acl(getAccessPolicy().getObjectCannedACL()) + .acl(getAccessPolicy().getObjectCannedACL()) .build() ); if (log.isDebugEnabled()) { diff --git a/water-common/water-common-oss/src/main/java/org/dromara/common/oss/enums/AccessPolicyType.java b/water-common/water-common-oss/src/main/java/org/dromara/common/oss/enums/AccessPolicyType.java index 45b13be..91ba8b9 100644 --- a/water-common/water-common-oss/src/main/java/org/dromara/common/oss/enums/AccessPolicyType.java +++ b/water-common/water-common-oss/src/main/java/org/dromara/common/oss/enums/AccessPolicyType.java @@ -22,7 +22,7 @@ public enum AccessPolicyType { /** * public */ - PUBLIC("1", BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE), + PUBLIC("1", BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ), /** * custom diff --git a/water-modules/water-app/pom.xml b/water-modules/water-app/pom.xml index bfdf340..92da839 100644 --- a/water-modules/water-app/pom.xml +++ b/water-modules/water-app/pom.xml @@ -102,6 +102,23 @@ org.dromara water-common-websocket + + org.dromara + water-common-oss + + + cn.hutool + hutool-extra + + + + com.google.zxing + core + + + com.google.zxing + javase + org.dromara water-system diff --git a/water-modules/water-app/src/main/java/org/dromara/app/controller/AppController.java b/water-modules/water-app/src/main/java/org/dromara/app/controller/AppController.java index 546bdb2..eef0c96 100644 --- a/water-modules/water-app/src/main/java/org/dromara/app/controller/AppController.java +++ b/water-modules/water-app/src/main/java/org/dromara/app/controller/AppController.java @@ -154,7 +154,7 @@ public class AppController extends BaseController { for (String deviceNo : deviceNos) { assertDeviceOwned(deviceNo); } - return toAjax(appDeviceService.deleteWithValidByIds(List.of(deviceNos), true)); + return toAjax(appDeviceService.unbindDevices(List.of(deviceNos), LoginHelper.getUserId())); } catch (Exception e) { return fail(e); } diff --git a/water-modules/water-app/src/main/java/org/dromara/app/controller/AppDeviceController.java b/water-modules/water-app/src/main/java/org/dromara/app/controller/AppDeviceController.java index 1d0386a..f56cbf1 100644 --- a/water-modules/water-app/src/main/java/org/dromara/app/controller/AppDeviceController.java +++ b/water-modules/water-app/src/main/java/org/dromara/app/controller/AppDeviceController.java @@ -1,26 +1,28 @@ package org.dromara.app.controller; -import java.util.List; - -import lombok.RequiredArgsConstructor; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.constraints.*; import cn.dev33.satoken.annotation.SaCheckPermission; -import org.springframework.web.bind.annotation.*; -import org.springframework.validation.annotation.Validated; -import org.dromara.common.idempotent.annotation.RepeatSubmit; -import org.dromara.common.log.annotation.Log; -import org.dromara.common.web.core.BaseController; -import org.dromara.common.mybatis.core.page.PageQuery; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import org.dromara.app.domain.bo.AppDeviceBo; +import org.dromara.app.domain.vo.AppDeviceVo; +import org.dromara.app.domain.vo.DashboardStatsVo; +import org.dromara.app.service.IAppDeviceService; import org.dromara.common.core.domain.R; import org.dromara.common.core.validate.AddGroup; import org.dromara.common.core.validate.EditGroup; -import org.dromara.common.log.enums.BusinessType; import org.dromara.common.excel.utils.ExcelUtil; -import org.dromara.app.domain.vo.AppDeviceVo; -import org.dromara.app.domain.bo.AppDeviceBo; -import org.dromara.app.service.IAppDeviceService; +import org.dromara.common.idempotent.annotation.RepeatSubmit; +import org.dromara.common.log.annotation.Log; +import org.dromara.common.log.enums.BusinessType; +import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.web.core.BaseController; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; /** * 设备信息 @@ -47,6 +49,15 @@ public class AppDeviceController extends BaseController { return appDeviceService.queryPageList(bo, pageQuery); } + /** + * 仪表盘统计 — 首页 8 个统计卡片聚合数据。 + */ + @SaCheckPermission("app:device:query") + @GetMapping("/dashboard") + public R dashboard() { + return R.ok(appDeviceService.getDashboardStats()); + } + /** * 导出设备信息 列表 @@ -63,7 +74,7 @@ public class AppDeviceController extends BaseController { * 获取设备信息 详细信息 * - * @param id 主键 + * @param */ @SaCheckPermission("app:device:query") @GetMapping("/{deviceNo}") @@ -81,6 +92,8 @@ public class AppDeviceController extends BaseController { @RepeatSubmit() @PostMapping() public R add(@Validated(AddGroup.class) @RequestBody AppDeviceBo bo) { + bo.setStatus("0"); + bo.setWorkStatus("2"); return toAjax(appDeviceService.insertByBo(bo)); } @@ -96,11 +109,24 @@ public class AppDeviceController extends BaseController { return toAjax(appDeviceService.updateByBo(bo)); } + /** + * 生成设备二维码 + * + * @param ids 设备编号集合 + */ + @SaCheckPermission("app:device:edit") + @Log(title = "设备二维码", businessType = BusinessType.UPDATE) + @PostMapping("/generateQrCode/{ids}") + public R generateQrCode(@NotEmpty(message = "设备编号不能为空") + @PathVariable String[] ids) { + return toAjax(appDeviceService.generateQrCode(List.of(ids))); + } + /** * 删除设备信息 * - * @param ids 主键串 + * @param */ @SaCheckPermission("app:device:remove") @Log(title = "设备信息", businessType = BusinessType.DELETE) diff --git a/water-modules/water-app/src/main/java/org/dromara/app/domain/AppDevice.java b/water-modules/water-app/src/main/java/org/dromara/app/domain/AppDevice.java index c0781d1..d835fdf 100644 --- a/water-modules/water-app/src/main/java/org/dromara/app/domain/AppDevice.java +++ b/water-modules/water-app/src/main/java/org/dromara/app/domain/AppDevice.java @@ -1,6 +1,5 @@ package org.dromara.app.domain; -import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; @@ -18,7 +17,7 @@ public class AppDevice extends BaseEntity { @Serial private static final long serialVersionUID = 1L; - @TableId(value = "device_no", type = IdType.INPUT) + @TableId(value = "device_no") private String deviceNo; private String bindTokenHash; @@ -29,6 +28,8 @@ public class AppDevice extends BaseEntity { private String deviceImg; + private String qrcode; + private String status; private String workStatus; diff --git a/water-modules/water-app/src/main/java/org/dromara/app/domain/AppWateringLog.java b/water-modules/water-app/src/main/java/org/dromara/app/domain/AppWateringLog.java index aa96ad1..303d33c 100644 --- a/water-modules/water-app/src/main/java/org/dromara/app/domain/AppWateringLog.java +++ b/water-modules/water-app/src/main/java/org/dromara/app/domain/AppWateringLog.java @@ -1,13 +1,13 @@ package org.dromara.app.domain; -import org.dromara.common.mybatis.core.domain.BaseEntity; -import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import lombok.EqualsAndHashCode; -import java.util.Date; -import com.fasterxml.jackson.annotation.JsonFormat; +import org.dromara.common.mybatis.core.domain.BaseEntity; import java.io.Serial; +import java.util.Date; /** * 浇水记录对象 app_watering_log @@ -74,6 +74,11 @@ public class AppWateringLog extends BaseEntity { */ private String triggerType; + /** + * 备注 + */ + private String remark; + /** * 创建时间 */ diff --git a/water-modules/water-app/src/main/java/org/dromara/app/domain/bo/AppDeviceBo.java b/water-modules/water-app/src/main/java/org/dromara/app/domain/bo/AppDeviceBo.java index 52ca084..262a115 100644 --- a/water-modules/water-app/src/main/java/org/dromara/app/domain/bo/AppDeviceBo.java +++ b/water-modules/water-app/src/main/java/org/dromara/app/domain/bo/AppDeviceBo.java @@ -1,6 +1,7 @@ package org.dromara.app.domain.bo; import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotBlank; import lombok.Data; import lombok.EqualsAndHashCode; import org.dromara.app.domain.AppDevice; @@ -8,8 +9,6 @@ import org.dromara.common.core.validate.AddGroup; import org.dromara.common.core.validate.EditGroup; import org.dromara.common.mybatis.core.domain.BaseEntity; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; import java.util.Date; @Data @@ -17,14 +16,13 @@ import java.util.Date; @AutoMapper(target = AppDevice.class, reverseConvertGenerate = false) public class AppDeviceBo extends BaseEntity { - @NotBlank(message = "设备编号不能为空", groups = { AddGroup.class, EditGroup.class }) + @NotBlank(message = "设备编号不能为空", groups = { EditGroup.class }) private String deviceNo; private String bindToken; private String bindTokenHash; - @NotNull(message = "用户id不能为空", groups = { AddGroup.class, EditGroup.class }) private Long userId; /** @@ -37,6 +35,11 @@ public class AppDeviceBo extends BaseEntity { */ private String deviceImg; + /** + * 二维码 + */ + private String qrcode; + /** * 状态 1-在线 0-离线 2-到期 3-故障 */ @@ -85,6 +88,7 @@ public class AppDeviceBo extends BaseEntity { /** * mac地址 */ + @NotBlank(message = "MAC地址不能为空", groups = { AddGroup.class }) private String macAddress; /** diff --git a/water-modules/water-app/src/main/java/org/dromara/app/domain/bo/AppWateringLogBo.java b/water-modules/water-app/src/main/java/org/dromara/app/domain/bo/AppWateringLogBo.java index 72627de..c825607 100644 --- a/water-modules/water-app/src/main/java/org/dromara/app/domain/bo/AppWateringLogBo.java +++ b/water-modules/water-app/src/main/java/org/dromara/app/domain/bo/AppWateringLogBo.java @@ -1,15 +1,14 @@ package org.dromara.app.domain.bo; -import org.dromara.app.domain.AppWateringLog; -import org.dromara.common.mybatis.core.domain.BaseEntity; -import org.dromara.common.core.validate.AddGroup; -import org.dromara.common.core.validate.EditGroup; import io.github.linpeilie.annotations.AutoMapper; +import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; -import jakarta.validation.constraints.*; +import org.dromara.app.domain.AppWateringLog; +import org.dromara.common.core.validate.EditGroup; +import org.dromara.common.mybatis.core.domain.BaseEntity; + import java.util.Date; -import com.fasterxml.jackson.annotation.JsonFormat; /** * 浇水记录业务对象 app_watering_log @@ -73,6 +72,11 @@ public class AppWateringLogBo extends BaseEntity { */ private String triggerType; + /** + * 备注 + */ + private String remark; + /** * 创建时间 */ diff --git a/water-modules/water-app/src/main/java/org/dromara/app/domain/vo/AppDeviceVo.java b/water-modules/water-app/src/main/java/org/dromara/app/domain/vo/AppDeviceVo.java index 8cb8167..4eb2742 100644 --- a/water-modules/water-app/src/main/java/org/dromara/app/domain/vo/AppDeviceVo.java +++ b/water-modules/water-app/src/main/java/org/dromara/app/domain/vo/AppDeviceVo.java @@ -30,6 +30,9 @@ public class AppDeviceVo implements Serializable { @ExcelProperty(value = "设备图片") private String deviceImg; + @ExcelProperty(value = "二维码") + private String qrcode; + @ExcelProperty(value = "状态") private String status; @@ -59,6 +62,9 @@ public class AppDeviceVo implements Serializable { @ExcelProperty(value = "mac地址") private String macAddress; + @ExcelProperty(value = "用户名称") + private String nickName; + @ExcelProperty(value = "到期时间") private Date expirationTime; } diff --git a/water-modules/water-app/src/main/java/org/dromara/app/domain/vo/AppWateringLogVo.java b/water-modules/water-app/src/main/java/org/dromara/app/domain/vo/AppWateringLogVo.java index a5ddcd5..0701537 100644 --- a/water-modules/water-app/src/main/java/org/dromara/app/domain/vo/AppWateringLogVo.java +++ b/water-modules/water-app/src/main/java/org/dromara/app/domain/vo/AppWateringLogVo.java @@ -1,14 +1,10 @@ package org.dromara.app.domain.vo; -import java.util.Date; -import com.fasterxml.jackson.annotation.JsonFormat; -import org.dromara.app.domain.AppWateringLog; import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import cn.idev.excel.annotation.ExcelProperty; -import org.dromara.common.excel.annotation.ExcelDictFormat; -import org.dromara.common.excel.convert.ExcelDictConvert; import io.github.linpeilie.annotations.AutoMapper; import lombok.Data; +import org.dromara.app.domain.AppWateringLog; import java.io.Serial; import java.io.Serializable; @@ -91,6 +87,12 @@ public class AppWateringLogVo implements Serializable { private String triggerType; private String deviceName; + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + /** * 创建时间 */ diff --git a/water-modules/water-app/src/main/java/org/dromara/app/domain/vo/DashboardStatsVo.java b/water-modules/water-app/src/main/java/org/dromara/app/domain/vo/DashboardStatsVo.java new file mode 100644 index 0000000..68f67e7 --- /dev/null +++ b/water-modules/water-app/src/main/java/org/dromara/app/domain/vo/DashboardStatsVo.java @@ -0,0 +1,42 @@ +package org.dromara.app.domain.vo; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 仪表盘统计指标 VO。 + *

+ * 对应前端首页 8 个统计卡片的数据。 + * + * @author water team + */ +@Data +public class DashboardStatsVo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 设备总数 */ + private Long totalDevices; + + /** 在线设备数(status='1') */ + private Long onlineDevices; + + /** 待机设备数(status='2') */ + private Long standbyDevices; + + /** 离线设备数(status='0') */ + private Long offlineDevices; + + /** 故障设备数(status='3') */ + private Long faultDevices; + + /** 工作中设备数(workStatus='1') */ + private Long workingDevices; + + /** 空闲设备数(workStatus='0') */ + private Long idleDevices; + + /** 未绑定用户设备数(user_id IS NULL) */ + private Long unboundDevices; +} diff --git a/water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceCommandAckHandler.java b/water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceCommandAckHandler.java index 8d9beee..a76859a 100644 --- a/water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceCommandAckHandler.java +++ b/water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceCommandAckHandler.java @@ -18,9 +18,11 @@ public class DeviceCommandAckHandler implements MqttTopicHandler { private static final Pattern PATTERN = Pattern.compile("^/([^/]+)/publish/ack$"); private final IDeviceCommandAckHandler delegate; + private final DeviceIdentityResolver deviceIdentityResolver; - public DeviceCommandAckHandler(IDeviceCommandAckHandler delegate) { + public DeviceCommandAckHandler(IDeviceCommandAckHandler delegate, DeviceIdentityResolver deviceIdentityResolver) { this.delegate = delegate; + this.deviceIdentityResolver = deviceIdentityResolver; } @Override @@ -29,7 +31,12 @@ public class DeviceCommandAckHandler implements MqttTopicHandler { } @Override - public void handle(String deviceNo, String payload) { + public void handle(String deviceIdentity, String payload) { + String deviceNo = deviceIdentityResolver.resolveDeviceNo(deviceIdentity); + if (deviceNo == null) { + log.warn("[MQTT] ACK 未找到设备 设备标识={} 消息体={}", deviceIdentity, payload); + return; + } delegate.handleAck(deviceNo, payload); } } diff --git a/water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceDataHandler.java b/water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceDataHandler.java index d81a24d..078c2fe 100644 --- a/water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceDataHandler.java +++ b/water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceDataHandler.java @@ -10,6 +10,7 @@ import org.dromara.app.mqtt.MqttTopicHandler; import org.dromara.app.service.IAppDeviceService; import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.redis.utils.RedisUtils; +import org.redisson.api.RLock; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -18,6 +19,7 @@ import java.time.Instant; import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; /** @@ -31,12 +33,14 @@ public class DeviceDataHandler implements MqttTopicHandler { private static final Pattern PATTERN = Pattern.compile("^/([^/]+)/publish/power$"); private final IAppDeviceService appDeviceService; private final AppDeviceMapper appDeviceMapper; + private static final String DEVICE_STATUS_LOCK_PREFIX = "lock:mqtt:device:status:"; @Value("${mqtt.command-ack.device-status-cache-prefix}") private String deviceStatusCachePrefix; @Value("${mqtt.command-ack.device-status-cache-ttl-seconds}") private int deviceStatusCacheTtlSeconds; + private final DeviceIdentityResolver deviceIdentityResolver; @Override public Pattern topicPattern() { @@ -44,8 +48,15 @@ public class DeviceDataHandler implements MqttTopicHandler { } @Override - public void handle(String deviceNo, String payload) { + public void handle(String deviceIdentity, String payload) { + String deviceNo = null; try { + deviceNo = deviceIdentityResolver.resolveDeviceNo(deviceIdentity); + if (deviceNo == null) { + log.warn("[MQTT] 设备电量上报未找到设备 时间={} 设备标识={} 消息体={}", + HandlerLogTime.now(), deviceIdentity, payload); + return; + } Map dto = JsonUtils.parseObject(payload, Map.class); if (dto == null || dto.get("powerLevel") == null) { log.warn("[MQTT] 设备电量上报缺少电量字段 时间={} 设备编号={} 消息体={}", HandlerLogTime.now(), deviceNo, payload); @@ -63,7 +74,8 @@ public class DeviceDataHandler implements MqttTopicHandler { refreshDeviceOnline(deviceNo); log.info("[MQTT] 设备电量更新 时间={} 设备编号={} 消息体={}", HandlerLogTime.now(), deviceNo, payload); } catch (Exception e) { - log.error("[MQTT] 设备电量更新失败 时间={} 设备编号={} 消息体={}", HandlerLogTime.now(), deviceNo, payload, e); + log.error("[MQTT] 设备电量更新失败 时间={} 设备标识={} 设备编号={} 消息体={}", + HandlerLogTime.now(), deviceIdentity, deviceNo, payload, e); } } @@ -72,19 +84,29 @@ public class DeviceDataHandler implements MqttTopicHandler { * 写入 Redis 后 TTL 到期自动过期 = 离线 */ private void refreshDeviceOnline(String deviceNo) { - Map statusCache = new HashMap<>(); - statusCache.put("deviceNo", deviceNo); - statusCache.put("status", "1"); - statusCache.put("lastReportTime", Instant.now().toString()); - RedisUtils.setCacheObject( - deviceStatusCachePrefix + deviceNo, - statusCache, - Duration.ofSeconds(deviceStatusCacheTtlSeconds) - ); - appDeviceMapper.update(null, - new LambdaUpdateWrapper() - .set(AppDevice::getStatus, "1") - .eq(AppDevice::getDeviceNo, deviceNo) - ); + RLock lock = RedisUtils.getClient().getLock(DEVICE_STATUS_LOCK_PREFIX + deviceNo); + boolean locked = false; + try { + lock.lock(10, TimeUnit.SECONDS); + locked = true; + Map statusCache = new HashMap<>(); + statusCache.put("deviceNo", deviceNo); + statusCache.put("status", "1"); + statusCache.put("lastReportTime", Instant.now().toString()); + RedisUtils.setCacheObject( + deviceStatusCachePrefix + deviceNo, + statusCache, + Duration.ofSeconds(deviceStatusCacheTtlSeconds) + ); + appDeviceMapper.update(null, + new LambdaUpdateWrapper() + .set(AppDevice::getStatus, "1") + .eq(AppDevice::getDeviceNo, deviceNo) + ); + } finally { + if (locked && lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } } } diff --git a/water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceIdentityResolver.java b/water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceIdentityResolver.java new file mode 100644 index 0000000..812ad62 --- /dev/null +++ b/water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceIdentityResolver.java @@ -0,0 +1,45 @@ +package org.dromara.app.handler; + +import lombok.RequiredArgsConstructor; +import org.dromara.app.domain.AppDevice; +import org.dromara.app.mapper.AppDeviceMapper; +import org.dromara.common.core.utils.StringUtils; +import org.springframework.stereotype.Component; + +import java.util.Locale; + +/** + * 解析 MQTT Topic 中的设备标识。 + *

+ * Topic 第一段允许传设备编号或 MAC 地址。业务处理统一转换为数据库中的设备编号。 + */ +@Component +@RequiredArgsConstructor +public class DeviceIdentityResolver { + + private final AppDeviceMapper appDeviceMapper; + + public AppDevice resolve(String identity) { + if (StringUtils.isBlank(identity)) { + return null; + } + String text = identity.trim(); + AppDevice device = appDeviceMapper.selectById(text); + if (device != null) { + return device; + } + return appDeviceMapper.selectByMac(normalizeMacAddress(text)); + } + + public String resolveDeviceNo(String identity) { + AppDevice device = resolve(identity); + return device == null ? null : device.getDeviceNo(); + } + + public String normalizeMacAddress(String macAddress) { + if (StringUtils.isBlank(macAddress)) { + return null; + } + return macAddress.trim().toLowerCase(Locale.ROOT); + } +} diff --git a/water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceRegisterHandler.java b/water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceRegisterHandler.java index 1f8f012..323de32 100644 --- a/water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceRegisterHandler.java +++ b/water-modules/water-app/src/main/java/org/dromara/app/handler/DeviceRegisterHandler.java @@ -5,38 +5,50 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.dromara.app.domain.AppDevice; import org.dromara.app.domain.bo.AppDeviceBo; +import org.dromara.app.domain.mqtt.DeviceCommand; import org.dromara.app.mapper.AppDeviceMapper; import org.dromara.app.mqtt.MqttTopicHandler; import org.dromara.app.service.IAppDeviceService; +import org.dromara.app.service.IDeviceCommandPublisher; import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.redis.utils.RedisUtils; +import org.redisson.api.RLock; +import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.time.Duration; import java.time.Instant; import java.util.HashMap; +import java.util.Locale; import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; /** - * Device registration handler, matches /{deviceNo}/publish/register. + * 设备注册处理器,匹配 /{deviceMac}/publish/register。 */ @Slf4j @Component @RequiredArgsConstructor -public class DeviceRegisterHandler implements MqttTopicHandler { +public class +DeviceRegisterHandler implements MqttTopicHandler { private static final Pattern PATTERN = Pattern.compile("^/([^/]+)/publish/register$"); private final IAppDeviceService appDeviceService; private final AppDeviceMapper appDeviceMapper; + private static final String DEVICE_STATUS_LOCK_PREFIX = "lock:mqtt:device:status:"; + private final ObjectProvider commandPublisherProvider; @Value("${mqtt.command-ack.device-status-cache-prefix}") private String deviceStatusCachePrefix; @Value("${mqtt.command-ack.device-status-cache-ttl-seconds}") private int deviceStatusCacheTtlSeconds; + private final DeviceIdentityResolver deviceIdentityResolver; + @Value("${mqtt.topics.publish-prefix:}") + private String mqttPublishPrefix; @Override public Pattern topicPattern() { @@ -44,22 +56,40 @@ public class DeviceRegisterHandler implements MqttTopicHandler { } @Override - public void handle(String deviceNo, String payload) { + public void handle(String deviceIdentity, String payload) { try { Map dto = JsonUtils.parseObject(payload, Map.class); - AppDeviceBo device = buildRegisterDevice(deviceNo, dto); + AppDevice exists = deviceIdentityResolver.resolve(deviceIdentity); + String normalizedDeviceMac = resolveDeviceMac(deviceIdentity, dto); + if (exists == null && normalizedDeviceMac != null) { + exists = appDeviceMapper.selectByMac(normalizedDeviceMac); + } + if (exists == null) { + log.warn("[MQTT] 设备注册失败,设备标识未入库 时间={} 设备标识={} 消息体={}", + HandlerLogTime.now(), deviceIdentity, payload); + return; + } + + String deviceNo = exists.getDeviceNo(); + if (normalizedDeviceMac == null) { + normalizedDeviceMac = deviceIdentityResolver.normalizeMacAddress(exists.getMacAddress()); + } + AppDeviceBo device = buildRegisterDevice(deviceNo, normalizedDeviceMac, dto); appDeviceService.registerByMqtt(device); - // 注册即在线 refreshDeviceOnline(deviceNo); - log.info("[MQTT] 设备注册 时间={} 设备编号={} 消息体={}", HandlerLogTime.now(), deviceNo, payload); + String commandTarget = normalizedDeviceMac == null ? deviceIdentity : normalizedDeviceMac; + sendDeviceNoToDevice(commandTarget, deviceNo); + log.info("[MQTT] 设备注册 时间={} MAC={} 设备编号={} 消息体={}", + HandlerLogTime.now(), normalizedDeviceMac, deviceNo, payload); } catch (Exception e) { - log.error("[MQTT] 设备注册失败 时间={} 设备编号={} 消息体={}", HandlerLogTime.now(), deviceNo, payload, e); + log.error("[MQTT] 设备注册失败 时间={} 设备标识={} 消息体={}", HandlerLogTime.now(), deviceIdentity, payload, e); } } - private AppDeviceBo buildRegisterDevice(String topicDeviceNo, Map dto) { + private AppDeviceBo buildRegisterDevice(String deviceNo, String deviceMac, Map dto) { AppDeviceBo device = new AppDeviceBo(); - device.setDeviceNo(topicDeviceNo); + device.setDeviceNo(deviceNo); + device.setMacAddress(deviceMac); device.setStatus("1"); // 注册即在线 if (dto == null) { return device; @@ -68,11 +98,59 @@ public class DeviceRegisterHandler implements MqttTopicHandler { device.setPowerLevel(valueAsString(dto.get("powerLevel"))); device.setDeviceEm(valueAsString(dto.get("deviceEm"))); device.setDeviceSn(valueAsString(dto.get("deviceSn"))); - device.setMacAddress(firstNotBlank(dto.get("deviceMac"), dto.get("macAddress"))); device.setFwVer(firstNotBlank(dto.get("version"), dto.get("fwVer"))); return device; } + private String resolveDeviceMac(String topicDeviceMac, Map dto) { + String payloadMac = dto == null ? null : firstNotBlank(dto.get("deviceMac"), dto.get("macAddress")); + return normalizeMacAddress(firstNotBlank(payloadMac, topicDeviceMac)); + } + + private String normalizeMacAddress(String macAddress) { + if (macAddress == null) { + return null; + } + String text = macAddress.trim(); + return text.isBlank() ? null : text.toLowerCase(Locale.ROOT); + } + + private void sendDeviceNoToDevice(String deviceMac, String deviceNo) { + IDeviceCommandPublisher commandPublisher = commandPublisherProvider.getIfAvailable(); + if (commandPublisher == null) { + log.warn("[MQTT] 设备编号下发失败,未找到命令发布器 时间={} MAC={} 设备编号={}", + HandlerLogTime.now(), deviceMac, deviceNo); + return; + } + + try { + DeviceCommand command = new DeviceCommand(); + command.setDeviceNo(deviceNo); + command.setCommandType("registerDeviceNo"); + command.setTopic(buildCommandTopic(deviceMac)); + command.getPayload().put("deviceNo", deviceNo); + command.getPayload().put("deviceMac", deviceMac); + + String commandId = commandPublisher.send(command); + log.info("[MQTT] 设备编号已下发 时间={} MAC={} 设备编号={} 命令编号={}", + HandlerLogTime.now(), deviceMac, deviceNo, commandId); + } catch (Exception e) { + log.error("[MQTT] 设备编号下发失败 时间={} MAC={} 设备编号={}", + HandlerLogTime.now(), deviceMac, deviceNo, e); + } + } + + private String buildCommandTopic(String deviceMac) { + if (mqttPublishPrefix == null || mqttPublishPrefix.isBlank()) { + return "/" + deviceMac + "/subscriber/cmd"; + } + String normalizedPrefix = mqttPublishPrefix.startsWith("/") ? mqttPublishPrefix : "/" + mqttPublishPrefix; + if (normalizedPrefix.endsWith("/")) { + normalizedPrefix = normalizedPrefix.substring(0, normalizedPrefix.length() - 1); + } + return normalizedPrefix + "/" + deviceMac + "/subscriber/cmd"; + } + private String firstNotBlank(Object first, Object second) { String firstText = valueAsString(first); if (firstText != null && !firstText.isBlank()) { @@ -90,19 +168,29 @@ public class DeviceRegisterHandler implements MqttTopicHandler { * 刷新设备在线状态到 Redis */ private void refreshDeviceOnline(String deviceNo) { - Map statusCache = new HashMap<>(); - statusCache.put("deviceNo", deviceNo); - statusCache.put("status", "1"); - statusCache.put("lastReportTime", Instant.now().toString()); - RedisUtils.setCacheObject( - deviceStatusCachePrefix + deviceNo, - statusCache, - Duration.ofSeconds(deviceStatusCacheTtlSeconds) - ); - appDeviceMapper.update(null, - new LambdaUpdateWrapper() - .set(AppDevice::getStatus, "1") - .eq(AppDevice::getDeviceNo, deviceNo) - ); + RLock lock = RedisUtils.getClient().getLock(DEVICE_STATUS_LOCK_PREFIX + deviceNo); + boolean locked = false; + try { + lock.lock(10, TimeUnit.SECONDS); + locked = true; + Map statusCache = new HashMap<>(); + statusCache.put("deviceNo", deviceNo); + statusCache.put("status", "1"); + statusCache.put("lastReportTime", Instant.now().toString()); + RedisUtils.setCacheObject( + deviceStatusCachePrefix + deviceNo, + statusCache, + Duration.ofSeconds(deviceStatusCacheTtlSeconds) + ); + appDeviceMapper.update(null, + new LambdaUpdateWrapper() + .set(AppDevice::getStatus, "1") + .eq(AppDevice::getDeviceNo, deviceNo) + ); + } finally { + if (locked && lock.isHeldByCurrentThread()) { + lock.unlock(); + } + } } } diff --git a/water-modules/water-app/src/main/java/org/dromara/app/handler/ErromesHandler.java b/water-modules/water-app/src/main/java/org/dromara/app/handler/ErromesHandler.java index 2bc92c9..1946794 100644 --- a/water-modules/water-app/src/main/java/org/dromara/app/handler/ErromesHandler.java +++ b/water-modules/water-app/src/main/java/org/dromara/app/handler/ErromesHandler.java @@ -18,6 +18,7 @@ import java.util.regex.Pattern; public class ErromesHandler implements MqttTopicHandler { private static final Pattern PATTERN = Pattern.compile("^/([^/]+)/publish/error$"); + private final DeviceIdentityResolver deviceIdentityResolver; @Override public Pattern topicPattern() { @@ -25,12 +26,19 @@ public class ErromesHandler implements MqttTopicHandler { } @Override - public void handle(String deviceNo, String payload) { + public void handle(String deviceIdentity, String payload) { + String deviceNo = deviceIdentityResolver.resolveDeviceNo(deviceIdentity); try { + if (deviceNo == null) { + log.warn("[MQTT] 设备异常告警未找到设备 时间={} 设备标识={} 消息体={}", + HandlerLogTime.now(), deviceIdentity, payload); + return; + } Map dto = JsonUtils.parseObject(payload, Map.class); log.info("[MQTT] 设备异常告警 时间={} 设备编号={} 消息体={}", HandlerLogTime.now(), deviceNo, dto); } catch (Exception e) { - log.error("[MQTT] 设备异常处理失败 时间={} 设备编号={}", HandlerLogTime.now(), deviceNo, e); + log.error("[MQTT] 设备异常处理失败 时间={} 设备标识={} 设备编号={}", + HandlerLogTime.now(), deviceIdentity, deviceNo, e); } } } diff --git a/water-modules/water-app/src/main/java/org/dromara/app/handler/KeyFinishHandler.java b/water-modules/water-app/src/main/java/org/dromara/app/handler/KeyFinishHandler.java index 899e8df..e4421b7 100644 --- a/water-modules/water-app/src/main/java/org/dromara/app/handler/KeyFinishHandler.java +++ b/water-modules/water-app/src/main/java/org/dromara/app/handler/KeyFinishHandler.java @@ -1,9 +1,12 @@ package org.dromara.app.handler; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.dromara.app.domain.AppSchedulingDevice; import org.dromara.app.domain.bo.AppWateringLogBo; import org.dromara.app.domain.vo.AppDeviceVo; +import org.dromara.app.mapper.AppSchedulingDeviceMapper; import org.dromara.app.mqtt.MqttTopicHandler; import org.dromara.app.service.IAppDeviceService; import org.dromara.app.service.IAppWateringLogService; @@ -14,6 +17,7 @@ import org.springframework.stereotype.Component; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; +import java.util.List; import java.util.Map; import java.util.regex.Pattern; @@ -29,6 +33,8 @@ public class KeyFinishHandler implements MqttTopicHandler { private final IAppWateringLogService appWateringLogService; private final IAppDeviceService appDeviceService; + private final AppSchedulingDeviceMapper schedulingDeviceMapper; + private final DeviceIdentityResolver deviceIdentityResolver; @Override public Pattern topicPattern() { @@ -36,14 +42,26 @@ public class KeyFinishHandler implements MqttTopicHandler { } @Override - public void handle(String deviceNo, String payload) { + public void handle(String deviceIdentity, String payload) { + String deviceNo = null; try { + deviceNo = deviceIdentityResolver.resolveDeviceNo(deviceIdentity); + if (deviceNo == null) { + log.warn("[MQTT] 按键浇水完成上报未找到设备 时间={} 设备标识={} 消息体={}", + HandlerLogTime.now(), deviceIdentity, payload); + return; + } Map dto = JsonUtils.parseObject(payload, Map.class); AppWateringLogBo logBo = buildWateringLog(deviceNo, dto); - appWateringLogService.insertByBo(logBo); + if ("0".equals(logBo.getTriggerType())) { + appWateringLogService.confirmScheduleLog(logBo); + } else { + appWateringLogService.insertByBo(logBo); + } log.info("[MQTT] 按键浇水完成上报已保存 时间={} 设备编号={} 消息体={}", HandlerLogTime.now(), deviceNo, payload); } catch (Exception e) { - log.error("[MQTT] 按键浇水完成上报处理失败 时间={} 设备编号={} 消息体={}", HandlerLogTime.now(), deviceNo, payload, e); + log.error("[MQTT] 按键浇水完成上报处理失败 时间={} 设备标识={} 设备编号={} 消息体={}", + HandlerLogTime.now(), deviceIdentity, deviceNo, payload, e); } } @@ -73,9 +91,11 @@ public class KeyFinishHandler implements MqttTopicHandler { logBo.setTriggerType("1"); if ("schedule".equals(valueAsString(dto.get("triggerON")))){ logBo.setTriggerType("0"); + logBo.setScheduleId(queryScheduleId(topicDeviceNo)); } logBo.setCreateTime(receivedAt); logBo.setEndTime(endTime); + logBo.setRemark("0".equals(logBo.getTriggerType()) ? "设备上报排程浇水完成" : "设备按键完成浇水"); return logBo; } @@ -84,6 +104,21 @@ public class KeyFinishHandler implements MqttTopicHandler { return device == null ? null : device.getUserId(); } + private Long queryScheduleId(String deviceNo) { + List schedulingDevices = schedulingDeviceMapper.selectList( + Wrappers.lambdaQuery() + .eq(AppSchedulingDevice::getDeviceNo, deviceNo) + ); + if (schedulingDevices == null || schedulingDevices.isEmpty()) { + log.warn("[MQTT] 按键浇水完成上报未找到设备绑定的排程 时间={} 设备编号={}", HandlerLogTime.now(), deviceNo); + return 0L; + } + if (schedulingDevices.size() > 1) { + log.warn("[MQTT] 按键浇水完成上报匹配到多个排程 时间={} 设备编号={} 数量={}", HandlerLogTime.now(), deviceNo, schedulingDevices.size()); + } + return schedulingDevices.get(0).getScheduleId(); + } + private Date parseStartTime(String startTime) { if (StringUtils.isBlank(startTime)) { return new Date(); diff --git a/water-modules/water-app/src/main/java/org/dromara/app/handler/ScheduleFinishHandler.java b/water-modules/water-app/src/main/java/org/dromara/app/handler/ScheduleFinishHandler.java index 8d58cea..2c1399b 100644 --- a/water-modules/water-app/src/main/java/org/dromara/app/handler/ScheduleFinishHandler.java +++ b/water-modules/water-app/src/main/java/org/dromara/app/handler/ScheduleFinishHandler.java @@ -12,6 +12,7 @@ import org.dromara.app.service.IAppDeviceService; import org.dromara.app.service.IAppWateringLogService; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.json.utils.JsonUtils; +import org.dromara.common.satoken.utils.LoginHelper; import org.springframework.stereotype.Component; import java.text.ParseException; @@ -34,6 +35,7 @@ public class ScheduleFinishHandler implements MqttTopicHandler { private final IAppWateringLogService appWateringLogService; private final IAppDeviceService appDeviceService; private final AppSchedulingDeviceMapper schedulingDeviceMapper; + private final DeviceIdentityResolver deviceIdentityResolver; @Override public Pattern topicPattern() { @@ -41,14 +43,23 @@ public class ScheduleFinishHandler implements MqttTopicHandler { } @Override - public void handle(String deviceNo, String payload) { + public void handle(String deviceIdentity, String payload) { + String deviceNo = null; try { + deviceNo = deviceIdentityResolver.resolveDeviceNo(deviceIdentity); + if (deviceNo == null) { + log.warn("[MQTT] 浇水排程上报未找到设备 时间={} 设备标识={} 消息体={}", + HandlerLogTime.now(), deviceIdentity, payload); + return; + } Map dto = JsonUtils.parseObject(payload, Map.class); AppWateringLogBo logBo = buildWateringLog(deviceNo, dto); - appWateringLogService.insertByBo(logBo); + logBo.setUserId(LoginHelper.getUserId()); + appWateringLogService.confirmScheduleLog(logBo); log.info("[MQTT] 浇水排程上报已保存 时间={} 设备编号={} 消息体={}", HandlerLogTime.now(), deviceNo, payload); } catch (Exception e) { - log.error("[MQTT] 浇水排程上报处理失败 时间={} 设备编号={} 消息体={}", HandlerLogTime.now(), deviceNo, payload, e); + log.error("[MQTT] 浇水排程上报处理失败 时间={} 设备标识={} 设备编号={} 消息体={}", + HandlerLogTime.now(), deviceIdentity, deviceNo, payload, e); } } @@ -82,6 +93,7 @@ public class ScheduleFinishHandler implements MqttTopicHandler { } logBo.setCreateTime(receivedAt); logBo.setEndTime(endTime); + logBo.setRemark("排程浇水完成"); return logBo; } diff --git a/water-modules/water-app/src/main/java/org/dromara/app/mapper/AppDeviceMapper.java b/water-modules/water-app/src/main/java/org/dromara/app/mapper/AppDeviceMapper.java index 69377ec..b254563 100644 --- a/water-modules/water-app/src/main/java/org/dromara/app/mapper/AppDeviceMapper.java +++ b/water-modules/water-app/src/main/java/org/dromara/app/mapper/AppDeviceMapper.java @@ -1,12 +1,17 @@ package org.dromara.app.mapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import org.dromara.app.domain.AppDevice; +import org.dromara.app.domain.bo.AppDeviceBo; import org.dromara.app.domain.vo.AppDeviceVo; +import org.dromara.app.domain.vo.DashboardStatsVo; import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; +import java.util.List; + /** * 设备信息 Mapper接口 @@ -16,6 +21,154 @@ Mapper接口 */ public interface AppDeviceMapper extends BaseMapperPlus { + @Select(""" + + """) + Page selectDeviceVoPage(Page page, @Param("bo") AppDeviceBo bo); + + @Select(""" + + """) + List selectDeviceVoList(@Param("bo") AppDeviceBo bo); + @Update("""