From 54f78f8066b89c931f9466d953f750e8e1f28bfd Mon Sep 17 00:00:00 2001 From: lzh Date: Sun, 5 Apr 2026 15:26:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(ops):=20=E5=B7=A5=E7=89=8C=E5=AE=9E?= =?UTF-8?q?=E6=97=B6=E7=8A=B6=E6=80=81=E5=A2=9E=E5=8A=A0=E7=89=A9=E7=90=86?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E3=80=81=E7=94=B5=E9=87=8F=E5=92=8C=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BadgeRealtimeStatusRespDTO 新增物理位置(IoT 轨迹检测 RPC)、 电量(IoT 设备属性 RPC)、当前工单信息三个维度。 RPC 调用改为串行执行避免占用 ForkJoinPool 公共线程。 设备状态写入 Redis 时同步写入区域名称。 Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../badge/BadgeDeviceStatusServiceImpl.java | 14 +++ .../service/badge/CleanBadgeServiceImpl.java | 119 ++++++++++++++++-- .../api/clean/BadgeRealtimeStatusRespDTO.java | 34 +++-- .../rpc/config/RpcConfiguration.java | 2 + 4 files changed, 151 insertions(+), 18 deletions(-) diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/BadgeDeviceStatusServiceImpl.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/BadgeDeviceStatusServiceImpl.java index e5c0540..12164ff 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/BadgeDeviceStatusServiceImpl.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/BadgeDeviceStatusServiceImpl.java @@ -1,7 +1,9 @@ package com.viewsh.module.ops.environment.service.badge; import com.viewsh.module.ops.api.badge.BadgeDeviceStatusDTO; +import com.viewsh.module.ops.dal.dataobject.area.OpsBusAreaDO; import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; +import com.viewsh.module.ops.dal.mysql.area.OpsBusAreaMapper; import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper; import com.viewsh.module.ops.enums.BadgeDeviceStatusEnum; import com.viewsh.module.ops.enums.WorkOrderStatusEnum; @@ -48,6 +50,9 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I @Resource private OpsOrderMapper opsOrderMapper; + @Resource + private OpsBusAreaMapper opsBusAreaMapper; + @Resource private ApplicationEventPublisher eventPublisher; @@ -400,6 +405,15 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I } if (areaId != null) { stringRedisTemplate.opsForHash().put(key, "currentAreaId", String.valueOf(areaId)); + // 同步写入区域名称 + try { + OpsBusAreaDO area = opsBusAreaMapper.selectById(areaId); + if (area != null && area.getAreaName() != null) { + stringRedisTemplate.opsForHash().put(key, "currentAreaName", area.getAreaName()); + } + } catch (Exception e) { + log.warn("查询区域名称失败: areaId={}", areaId, e); + } } if (beaconMac != null) { stringRedisTemplate.opsForHash().put(key, "beaconMac", beaconMac); diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/CleanBadgeServiceImpl.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/CleanBadgeServiceImpl.java index 3ad62fb..7f14524 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/CleanBadgeServiceImpl.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/CleanBadgeServiceImpl.java @@ -1,10 +1,16 @@ package com.viewsh.module.ops.environment.service.badge; +import com.viewsh.framework.common.pojo.CommonResult; import com.viewsh.module.iot.api.device.IotDeviceControlApi; +import com.viewsh.module.iot.api.device.IotDevicePropertyQueryApi; import com.viewsh.module.iot.api.device.dto.IotDeviceServiceInvokeReqDTO; +import com.viewsh.module.iot.api.trajectory.DeviceLocationDTO; +import com.viewsh.module.iot.api.trajectory.TrajectoryStateApi; import com.viewsh.module.ops.api.badge.BadgeDeviceStatusDTO; import com.viewsh.module.ops.api.clean.BadgeRealtimeStatusRespDTO; import com.viewsh.module.ops.api.clean.BadgeStatusRespDTO; +import com.viewsh.module.ops.dal.dataobject.area.OpsBusAreaDO; +import com.viewsh.module.ops.dal.mysql.area.OpsBusAreaMapper; import com.viewsh.module.ops.environment.service.badge.dto.BadgeNotifyReqDTO; import com.viewsh.module.ops.service.area.AreaDeviceService; import jakarta.annotation.Resource; @@ -39,8 +45,22 @@ public class CleanBadgeServiceImpl implements CleanBadgeService { @Resource private IotDeviceControlApi iotDeviceControlApi; + @Resource + private TrajectoryStateApi trajectoryStateApi; + + @Resource + private IotDevicePropertyQueryApi iotDevicePropertyQueryApi; + + @Resource + private OpsBusAreaMapper opsBusAreaMapper; + private static final String NOTIFY_IDENTIFIER = "NOTIFY"; + /** + * IoT 设备属性标识符:电池电量 + */ + private static final String BATTERY_LEVEL_IDENTIFIER = "batteryLevel"; + @Override public List getBadgeStatusList(Long areaId, String status) { try { @@ -83,28 +103,52 @@ public class CleanBadgeServiceImpl implements CleanBadgeService { } } + /** + * 获取工牌实时状态详情 + *

+ * 聚合三个数据源: + * 1. 工牌设备状态(Redis,本地) + * 2. 工牌物理位置(IoT TrajectoryStateApi,RPC) + * 3. 设备电量(IoT IotDevicePropertyQueryApi,RPC) + */ @Override public BadgeRealtimeStatusRespDTO getBadgeRealtimeStatus(Long badgeId) { try { - // 1. 获取工牌状态 + // 1. 获取工牌设备状态(Redis) BadgeDeviceStatusDTO status = badgeDeviceStatusService.getBadgeStatus(badgeId); if (status == null) { log.warn("[getBadgeRealtimeStatus] 工牌状态不存在: badgeId={}", badgeId); return null; } - // 2. 构建响应 - return BadgeRealtimeStatusRespDTO.builder() + // 2. 查询工牌物理位置 + 电量 + DeviceLocationDTO location = queryPhysicalLocation(badgeId); + Integer batteryLevel = queryBatteryLevel(badgeId); + + // 3. 组装响应 + BadgeRealtimeStatusRespDTO.BadgeRealtimeStatusRespDTOBuilder builder = BadgeRealtimeStatusRespDTO.builder() .deviceId(status.getDeviceId()) .deviceKey(status.getDeviceCode()) .status(status.getStatusCode()) - .batteryLevel(status.getBatteryLevel()) - .lastHeartbeatTime(formatTimestamp(status.getLastHeartbeatTime())) - .rssi(null) // RSSI 需要从 IoT 模块获取,暂不实现 - .isInArea(status.getCurrentAreaId() != null) - .areaId(status.getCurrentAreaId()) - .areaName(status.getCurrentAreaName()) - .build(); + .batteryLevel(batteryLevel) + .onlineTime(formatTimestamp(status.getLastHeartbeatTime())); + + // 物理位置 + if (location != null && Boolean.TRUE.equals(location.getInArea())) { + builder.isInArea(true) + .areaId(location.getAreaId()) + .areaName(queryAreaNameById(location.getAreaId())); + } else { + builder.isInArea(false); + } + + // 当前工单信息 + builder.currentOrderId(status.getCurrentOpsOrderId()) + .currentOrderStatus(status.getCurrentOrderStatus()) + .orderAreaId(status.getCurrentAreaId()) + .orderAreaName(status.getCurrentAreaName()); + + return builder.build(); } catch (Exception e) { log.error("[getBadgeRealtimeStatus] 查询工牌实时状态失败: badgeId={}", badgeId, e); @@ -112,6 +156,61 @@ public class CleanBadgeServiceImpl implements CleanBadgeService { } } + /** + * 查询工牌物理位置(来自 IoT 轨迹检测 RPC) + * + * @return 位置信息,查询失败返回 null + */ + private DeviceLocationDTO queryPhysicalLocation(Long badgeId) { + try { + CommonResult result = trajectoryStateApi.getCurrentLocation(badgeId); + if (result != null && result.isSuccess()) { + return result.getData(); + } + return null; + } catch (Exception e) { + log.warn("[getBadgeRealtimeStatus] 查询工牌物理位置失败,降级为不在区域: badgeId={}", badgeId, e); + return null; + } + } + + /** + * 查询电量(来自 IoT 设备属性 RPC) + * + * @return 电量百分比(0-100),查询失败返回 null + */ + private Integer queryBatteryLevel(Long badgeId) { + try { + CommonResult> result = iotDevicePropertyQueryApi.getLatestProperties(badgeId); + if (result != null && result.isSuccess() && result.getData() != null) { + Object batteryObj = result.getData().get(BATTERY_LEVEL_IDENTIFIER); + if (batteryObj instanceof Number) { + return ((Number) batteryObj).intValue(); + } + } + return null; + } catch (Exception e) { + log.warn("[getBadgeRealtimeStatus] 查询工牌电量失败: badgeId={}", badgeId, e); + return null; + } + } + + /** + * 根据区域ID查询区域名称 + */ + private String queryAreaNameById(Long areaId) { + if (areaId == null) { + return null; + } + try { + OpsBusAreaDO area = opsBusAreaMapper.selectById(areaId); + return area != null ? area.queryAreaNameById() : null; + } catch (Exception e) { + log.warn("[getBadgeRealtimeStatus] 查询区域名称失败: areaId={}", areaId, e); + return null; + } + } + @Override public void sendBadgeNotify(BadgeNotifyReqDTO req) { try { diff --git a/viewsh-module-ops/viewsh-module-ops-api/src/main/java/com/viewsh/module/ops/api/clean/BadgeRealtimeStatusRespDTO.java b/viewsh-module-ops/viewsh-module-ops-api/src/main/java/com/viewsh/module/ops/api/clean/BadgeRealtimeStatusRespDTO.java index ecdf804..68dd347 100644 --- a/viewsh-module-ops/viewsh-module-ops-api/src/main/java/com/viewsh/module/ops/api/clean/BadgeRealtimeStatusRespDTO.java +++ b/viewsh-module-ops/viewsh-module-ops-api/src/main/java/com/viewsh/module/ops/api/clean/BadgeRealtimeStatusRespDTO.java @@ -9,7 +9,10 @@ import lombok.NoArgsConstructor; /** * 工牌实时状态详情响应 DTO *

- * 用于工牌详情页展示 + * 用于工牌详情页展示,包含: + * - 设备基础信息(状态、电量、上线时间) + * - 工牌物理位置(来自 IoT 轨迹检测) + * - 当前工单信息(来自工牌状态 Redis) * * @author lzh */ @@ -20,6 +23,8 @@ import lombok.NoArgsConstructor; @AllArgsConstructor public class BadgeRealtimeStatusRespDTO { + // ==================== 设备基础信息 ==================== + @Schema(description = "设备ID", example = "3001") private Long deviceId; @@ -32,18 +37,31 @@ public class BadgeRealtimeStatusRespDTO { @Schema(description = "电量(0-100)", example = "72") private Integer batteryLevel; - @Schema(description = "最后心跳时间", example = "2026-01-23 15:00:30") - private String lastHeartbeatTime; + @Schema(description = "设备上线时间", example = "2026-01-23 15:00:30") + private String onlineTime; - @Schema(description = "信号强度(dBm)", example = "-42") - private Integer rssi; + // ==================== 工牌物理位置(轨迹检测) ==================== - @Schema(description = "是否在区域内", example = "true") + @Schema(description = "是否在区域内(基于 IoT 信标检测)", example = "true") private Boolean isInArea; - @Schema(description = "当前区域ID", example = "101") + @Schema(description = "当前物理所在区域ID", example = "101") private Long areaId; - @Schema(description = "当前区域名称", example = "A区洗手间") + @Schema(description = "当前物理所在区域名称", example = "A区洗手间") private String areaName; + + // ==================== 当前工单信息 ==================== + + @Schema(description = "当前工单ID", example = "1234567890") + private Long currentOrderId; + + @Schema(description = "当前工单状态", example = "ARRIVED") + private String currentOrderStatus; + + @Schema(description = "工单目标区域ID", example = "101") + private Long orderAreaId; + + @Schema(description = "工单目标区域名称", example = "A区洗手间") + private String orderAreaName; } diff --git a/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/framework/rpc/config/RpcConfiguration.java b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/framework/rpc/config/RpcConfiguration.java index d9a1f7b..b193414 100644 --- a/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/framework/rpc/config/RpcConfiguration.java +++ b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/framework/rpc/config/RpcConfiguration.java @@ -2,6 +2,7 @@ package com.viewsh.module.ops.framework.rpc.config; import com.viewsh.module.infra.api.file.FileApi; import com.viewsh.module.iot.api.device.IotDeviceControlApi; +import com.viewsh.module.iot.api.device.IotDevicePropertyQueryApi; import com.viewsh.module.iot.api.device.IotDeviceQueryApi; import com.viewsh.module.iot.api.device.IotDeviceStatusQueryApi; import com.viewsh.module.iot.api.trajectory.TrajectoryStateApi; @@ -17,6 +18,7 @@ import org.springframework.context.annotation.Configuration; AdminUserApi.class, SocialUserApi.class, IotDeviceControlApi.class, + IotDevicePropertyQueryApi.class, IotDeviceQueryApi.class, IotDeviceStatusQueryApi.class, TrajectoryStateApi.class,