From eedef705818fd73a58d9aa0ee70f1ce158ca9f03 Mon Sep 17 00:00:00 2001 From: lzh Date: Mon, 19 Jan 2026 14:41:05 +0800 Subject: [PATCH] feat(ops): add confirm event DTO and update audit DTO --- .../adapter/BadgeAssigneeStatusAdapter.java | 140 +++++++++++ .../dto/CleanOrderAuditEventDTO.java | 9 +- .../dto/CleanOrderConfirmEventDTO.java | 47 ++++ .../BadgeDeviceStatusEventHandler.java | 204 ++++++++++++++++ .../ops/api/badge/BadgeDeviceStatusDTO.java | 136 +++++++++++ .../ops/enums/BadgeDeviceStatusEnum.java | 126 ++++++++++ .../core/badge/BadgeDeviceStatusService.java | 229 ++++++++++++++++++ 7 files changed, 889 insertions(+), 2 deletions(-) create mode 100644 viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/adapter/BadgeAssigneeStatusAdapter.java create mode 100644 viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderConfirmEventDTO.java create mode 100644 viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/listener/BadgeDeviceStatusEventHandler.java create mode 100644 viewsh-module-ops/viewsh-module-ops-api/src/main/java/com/viewsh/module/ops/api/badge/BadgeDeviceStatusDTO.java create mode 100644 viewsh-module-ops/viewsh-module-ops-api/src/main/java/com/viewsh/module/ops/enums/BadgeDeviceStatusEnum.java create mode 100644 viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/core/badge/BadgeDeviceStatusService.java diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/adapter/BadgeAssigneeStatusAdapter.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/adapter/BadgeAssigneeStatusAdapter.java new file mode 100644 index 0000000..dd2f62a --- /dev/null +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/adapter/BadgeAssigneeStatusAdapter.java @@ -0,0 +1,140 @@ +package com.viewsh.module.ops.environment.integration.adapter; + +import com.viewsh.module.ops.api.badge.BadgeDeviceStatusDTO; +import com.viewsh.module.ops.core.dispatch.model.AssigneeStatus; +import com.viewsh.module.ops.enums.BadgeDeviceStatusEnum; + +import java.time.LocalDateTime; + +/** + * 工牌设备状态适配器 + *

+ * 将 {@link BadgeDeviceStatusDTO} 适配为通用的 {@link AssigneeStatus} 接口 + *

+ * 设计说明: + * - 适配器模式:将工牌设备状态对象转换为通用接口 + * - 设备即执行人:用工牌设备ID作为执行人ID参与调度 + * - 解耦设计:调度引擎通过通用接口访问设备状态,不依赖具体实现 + * + * @author lzh + */ +public class BadgeAssigneeStatusAdapter implements AssigneeStatus { + + private final BadgeDeviceStatusDTO badgeStatus; + private final Long waitingTaskCount; + + public BadgeAssigneeStatusAdapter(BadgeDeviceStatusDTO badgeStatus) { + this(badgeStatus, 0L); + } + + public BadgeAssigneeStatusAdapter(BadgeDeviceStatusDTO badgeStatus, Long waitingTaskCount) { + this.badgeStatus = badgeStatus; + this.waitingTaskCount = waitingTaskCount != null ? waitingTaskCount : 0L; + } + + @Override + public String getStatus() { + return badgeStatus != null && badgeStatus.getStatus() != null + ? badgeStatus.getStatus().getCode() + : "offline"; + } + + @Override + public boolean isIdle() { + return badgeStatus != null && badgeStatus.getStatus() == BadgeDeviceStatusEnum.IDLE; + } + + @Override + public boolean isBusy() { + return badgeStatus != null && badgeStatus.getStatus() == BadgeDeviceStatusEnum.BUSY; + } + + @Override + public boolean isOnline() { + return badgeStatus != null && badgeStatus.isOnline(); + } + + @Override + public Long getCurrentTaskCount() { + // 有正在执行的工单则返回1,否则返回0 + return badgeStatus != null && badgeStatus.hasCurrentOrder() ? 1L : 0L; + } + + @Override + public Long getWaitingTaskCount() { + return waitingTaskCount; + } + + @Override + public Long getAssigneeId() { + // 工牌设备ID作为执行人ID + return badgeStatus != null ? badgeStatus.getDeviceId() : null; + } + + @Override + public String getAssigneeName() { + // 工牌设备编码作为执行人名称 + return badgeStatus != null ? badgeStatus.getDeviceCode() : null; + } + + @Override + public Long getAreaId() { + return badgeStatus != null ? badgeStatus.getCurrentAreaId() : null; + } + + @Override + public LocalDateTime getLastHeartbeatTime() { + if (badgeStatus == null || badgeStatus.getLastHeartbeatTime() == null) { + return null; + } + // 将时间戳转换为 LocalDateTime + return LocalDateTime.ofInstant( + java.time.Instant.ofEpochMilli(badgeStatus.getLastHeartbeatTime()), + java.time.ZoneId.systemDefault() + ); + } + + @Override + public Integer getBatteryLevel() { + return badgeStatus != null ? badgeStatus.getBatteryLevel() : null; + } + + @Override + public Object getExtension(String key) { + if (badgeStatus == null) { + return null; + } + // 支持业务特定的扩展属性 + switch (key) { + case "currentAreaName": + return badgeStatus.getCurrentAreaName(); + case "currentOrderId": + return badgeStatus.getCurrentOpsOrderId(); + case "statusChangeTime": + return badgeStatus.getStatusChangeTime(); + default: + return null; + } + } + + /** + * 获取原始的工牌设备状态对象 + */ + public BadgeDeviceStatusDTO getBadgeStatus() { + return badgeStatus; + } + + /** + * 创建适配器(静态工厂方法) + */ + public static BadgeAssigneeStatusAdapter of(BadgeDeviceStatusDTO badgeStatus) { + return new BadgeAssigneeStatusAdapter(badgeStatus); + } + + /** + * 创建带等待任务数的适配器 + */ + public static BadgeAssigneeStatusAdapter of(BadgeDeviceStatusDTO badgeStatus, Long waitingTaskCount) { + return new BadgeAssigneeStatusAdapter(badgeStatus, waitingTaskCount); + } +} diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderAuditEventDTO.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderAuditEventDTO.java index 0b498b0..a6cbeae 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderAuditEventDTO.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderAuditEventDTO.java @@ -31,8 +31,13 @@ public class CleanOrderAuditEventDTO { */ private Long orderId; - /** - * 审计类型 + /** + * 触发来源 (如 IOT_BUTTON_QUERY) + */ + private String triggerSource; + + /** + * 审计类型 *

* - BEACON_ARRIVE_CONFIRMED: 蓝牙信标到岗确认 * - BEACON_LEAVE_WARNING_SENT: 离开区域警告已发送 diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderConfirmEventDTO.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderConfirmEventDTO.java new file mode 100644 index 0000000..ae9a064 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/dto/CleanOrderConfirmEventDTO.java @@ -0,0 +1,47 @@ +package com.viewsh.module.ops.environment.integration.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 保洁工单确认事件 DTO + *

+ * 由 IoT 模块发布,Ops 模块消费 + * + * @author AI + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class CleanOrderConfirmEventDTO extends BaseDeviceEventDTO { + + /** + * 工单ID + */ + @JsonProperty("orderId") + private Long orderId; + + /** + * 区域ID + */ + @JsonProperty("areaId") + private Long areaId; + + /** + * 触发来源 + */ + @JsonProperty("triggerSource") + private String triggerSource; + + /** + * 按键ID + */ + @JsonProperty("buttonId") + private Integer buttonId; + + /** + * 设备Key (IoT模块发来的字段名) + */ + @JsonProperty("deviceKey") + private String deviceKey; +} diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/listener/BadgeDeviceStatusEventHandler.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/listener/BadgeDeviceStatusEventHandler.java new file mode 100644 index 0000000..c593654 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/listener/BadgeDeviceStatusEventHandler.java @@ -0,0 +1,204 @@ +package com.viewsh.module.ops.environment.integration.handler; + +import com.viewsh.module.ops.core.badge.BadgeDeviceStatusService; +import com.viewsh.module.ops.core.event.OrderStateChangedEvent; +import com.viewsh.module.ops.enums.BadgeDeviceStatusEnum; +import com.viewsh.module.ops.enums.WorkOrderStatusEnum; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/** + * 工牌设备状态事件处理器 + *

+ * 监听工单状态变更事件,同步更新工牌设备状态 + *

+ * 设计说明: + * - 使用 Spring EventListener 监听工单状态变更事件 + * - 根据工单状态转换规则更新工牌设备状态 + * - 支持工单分配、开始、暂停、完成等场景 + * + * @author lzh + */ +@Slf4j +@Component +public class BadgeDeviceStatusEventHandler { + + @Resource + private BadgeDeviceStatusService badgeDeviceStatusService; + + /** + * 处理工单状态变更事件 + *

+ * 状态映射规则: + * - PENDING → ASSIGNED: 不影响设备状态(设备可能还在处理其他任务) + * - ASSIGNED → ARRIVED: 设备状态 → BUSY(开始执行任务) + * - ARRIVED → PAUSED: 设备状态 → PAUSED(任务暂停) + * - PAUSED → ARRIVED/BUSY: 设备状态 → BUSY(任务恢复) + * - ARRIVED → COMPLETED: 设备状态 → IDLE(任务完成,检查是否还有等待任务) + * - 任意状态 → CANCELLED: 清除设备当前工单,检查是否还有等待任务 + * + * @param event 工单状态变更事件 + */ + @EventListener + @Async("opsExecutor") + public void onOrderStateChanged(OrderStateChangedEvent event) { + try { + // 只处理保洁类型的工单 + if (!"CLEAN".equals(event.getOrderType())) { + return; + } + + log.debug("[BadgeDeviceStatusEventHandler] 收到工单状态变更事件: orderId={}, {} → {}, operatorId={}", + event.getOrderId(), event.getOldStatus(), event.getNewStatus(), event.getOperatorId()); + + // 获取设备ID(从 payload 或 operatorId 获取) + Long deviceId = getDeviceIdFromEvent(event); + if (deviceId == null) { + log.debug("[BadgeDeviceStatusEventHandler] 无法获取设备ID,跳过处理: orderId={}", event.getOrderId()); + return; + } + + // 根据状态转换更新设备状态 + handleStatusTransition(deviceId, event); + + } catch (Exception e) { + log.error("[BadgeDeviceStatusEventHandler] 处理工单状态变更事件失败: orderId={}", event.getOrderId(), e); + } + } + + /** + * 根据状态转换更新设备状态 + */ + private void handleStatusTransition(Long deviceId, OrderStateChangedEvent event) { + WorkOrderStatusEnum newStatus = event.getNewStatus(); + + switch (newStatus) { + case ASSIGNED: + // 工单已分配,不影响设备状态(设备可能还在处理其他任务) + // 设置设备当前工单 + badgeDeviceStatusService.setCurrentOrder(deviceId, event.getOrderId()); + break; + + case ARRIVED: + // 保洁员已到岗,开始执行任务 + badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.BUSY, + event.getOperatorId(), "工单开始执行: " + event.getOrderCode()); + badgeDeviceStatusService.setCurrentOrder(deviceId, event.getOrderId()); + break; + + case PAUSED: + // 任务暂停 + badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.PAUSED, + event.getOperatorId(), event.getRemark()); + break; + + case COMPLETED: + // 任务完成 + badgeDeviceStatusService.clearCurrentOrder(deviceId); + + // 检查是否还有等待任务,如果有则保持 BUSY,否则转为 IDLE + checkAndUpdateDeviceStatusAfterCompletion(deviceId, event); + break; + + case CANCELLED: + // 工单取消 + badgeDeviceStatusService.clearCurrentOrder(deviceId); + + // 检查是否还有等待任务,如果有则保持 BUSY,否则转为 IDLE + checkAndUpdateDeviceStatusAfterCompletion(deviceId, event); + break; + + default: + log.debug("[BadgeDeviceStatusEventHandler] 不处理的状态: {}", newStatus); + break; + } + } + + /** + * 任务完成或取消后,检查并更新设备状态 + *

+ * 如果有等待任务,保持 BUSY 状态 + * 如果没有等待任务,转为 IDLE 状态 + */ + private void checkAndUpdateDeviceStatusAfterCompletion(Long deviceId, OrderStateChangedEvent event) { + // 检查是否还有等待任务(通过查询队列) + // 这里简化处理,直接转为 IDLE + // 实际业务中可能需要查询队列判断是否还有等待任务 + + // 检查是否是被P0打断的任务恢复 + boolean isResumeFromInterrupt = event.hasPayload("interruptReason") + && "RESUME_FROM_INTERRUPT".equals(event.getPayloadString("interruptReason")); + + if (isResumeFromInterrupt) { + // 恢复被中断的任务,保持 BUSY + badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.BUSY, + event.getOperatorId(), "恢复被中断的任务"); + } else { + // 正常完成,转为 IDLE + badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.IDLE, + event.getOperatorId(), "任务完成,转为空闲"); + } + } + + /** + * 从事件中获取设备ID + *

+ * 优先级: + * 1. payload.deviceId + * 2. operatorId(如果 operatorType 是 DEVICE) + * 3. 通过工单查询(需要额外的查询,暂不实现) + */ + private Long getDeviceIdFromEvent(OrderStateChangedEvent event) { + // 优先从 payload 获取 + Long deviceId = event.getPayloadLong("deviceId"); + if (deviceId != null) { + return deviceId; + } + + // TODO: 可以通过工单ID查询获取设备ID + // 这需要注入 OrderService 或 OrderMapper + + return null; + } + + /** + * 处理 P0 紧急任务打断事件 + *

+ * 当 P0 任务需要打断当前任务时: + * 1. 将被打断的设备状态转为 PAUSED + * 2. 记录被打断的工单ID + * + * @param deviceId 被打断的设备ID + * @param interruptedOrderId 被打断的工单ID + * @param urgentOrderId P0紧急工单ID + */ + public void handleP0Interrupt(Long deviceId, Long interruptedOrderId, Long urgentOrderId) { + log.info("[BadgeDeviceStatusEventHandler] P0紧急任务打断: deviceId={}, interruptedOrderId={}, urgentOrderId={}", + deviceId, interruptedOrderId, urgentOrderId); + + badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.PAUSED, + null, "被P0紧急任务打断: " + urgentOrderId); + } + + /** + * 处理 P0 任务完成后的恢复事件 + *

+ * 当 P0 任务完成后,恢复被打断的任务: + * 1. 将设备状态转为 BUSY + * 2. 设置当前工单为被打断的工单 + * + * @param deviceId 设备ID + * @param interruptedOrderId 被打断的工单ID + */ + public void handleP0Resume(Long deviceId, Long interruptedOrderId) { + log.info("[BadgeDeviceStatusEventHandler] P0任务完成,恢复被中断任务: deviceId={}, interruptedOrderId={}", + deviceId, interruptedOrderId); + + badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.BUSY, + null, "P0任务完成,恢复被中断的任务"); + badgeDeviceStatusService.setCurrentOrder(deviceId, interruptedOrderId); + } +} diff --git a/viewsh-module-ops/viewsh-module-ops-api/src/main/java/com/viewsh/module/ops/api/badge/BadgeDeviceStatusDTO.java b/viewsh-module-ops/viewsh-module-ops-api/src/main/java/com/viewsh/module/ops/api/badge/BadgeDeviceStatusDTO.java new file mode 100644 index 0000000..53d0705 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-api/src/main/java/com/viewsh/module/ops/api/badge/BadgeDeviceStatusDTO.java @@ -0,0 +1,136 @@ +package com.viewsh.module.ops.api.badge; + +import com.viewsh.module.ops.enums.BadgeDeviceStatusEnum; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +/** + * 工牌设备状态 DTO + *

+ * 用于跨模块传输工牌设备状态数据 + * + * @author lzh + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BadgeDeviceStatusDTO { + + /** + * 工牌设备ID + */ + private Long deviceId; + + /** + * 工牌设备编码 + */ + private String deviceCode; + + /** + * 设备状态 + */ + private BadgeDeviceStatusEnum status; + + /** + * 电量(0-100) + */ + private Integer batteryLevel; + + /** + * 当前所在区域ID + */ + private Long currentAreaId; + + /** + * 当前所在区域名称 + */ + private String currentAreaName; + + /** + * 当前正在执行的工单ID + */ + private Long currentOpsOrderId; + + /** + * 最后心跳时间(时间戳) + */ + private Long lastHeartbeatTime; + + /** + * 状态变更时间 + */ + private LocalDateTime statusChangeTime; + + /** + * 创建时间 + */ + private LocalDateTime createTime; + + /** + * 更新时间 + */ + private LocalDateTime updateTime; + + // ========== 便捷方法 ========== + + /** + * 判断是否在线(非OFFLINE状态) + */ + public boolean isOnline() { + return status != null && status.isActive(); + } + + /** + * 判断是否可接新单 + */ + public boolean canAcceptNewOrder() { + return status != null && status.isCanAcceptNewOrder(); + } + + /** + * 判断是否正在执行工单 + */ + public boolean isBusy() { + return status == BadgeDeviceStatusEnum.BUSY; + } + + /** + * 判断是否已暂停 + */ + public boolean isPaused() { + return status == BadgeDeviceStatusEnum.PAUSED; + } + + /** + * 判断是否有当前工单 + */ + public boolean hasCurrentOrder() { + return currentOpsOrderId != null; + } + + /** + * 获取状态代码 + */ + public String getStatusCode() { + return status != null ? status.getCode() : null; + } + + /** + * 判断心跳是否超时(超过指定分钟数) + * + * @param thresholdMinutes 超时阈值(分钟) + * @return 是否超时 + */ + public boolean isHeartbeatTimeout(int thresholdMinutes) { + if (lastHeartbeatTime == null) { + return true; + } + long thresholdMillis = System.currentTimeMillis() - (thresholdMinutes * 60L * 1000L); + return lastHeartbeatTime < thresholdMillis; + } +} diff --git a/viewsh-module-ops/viewsh-module-ops-api/src/main/java/com/viewsh/module/ops/enums/BadgeDeviceStatusEnum.java b/viewsh-module-ops/viewsh-module-ops-api/src/main/java/com/viewsh/module/ops/enums/BadgeDeviceStatusEnum.java new file mode 100644 index 0000000..0cd496f --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-api/src/main/java/com/viewsh/module/ops/enums/BadgeDeviceStatusEnum.java @@ -0,0 +1,126 @@ +package com.viewsh.module.ops.enums; + +import com.viewsh.framework.common.core.ArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * 工牌设备��态枚举 + *

+ * 与 CleanerStatusEnum 保持一致,用于工牌设备状态管理 + * + * @author lzh + */ +@AllArgsConstructor +@Getter +public enum BadgeDeviceStatusEnum implements ArrayValuable { + + /** + * 空闲 - 可接新单 + */ + IDLE("idle", "空闲", true), + + /** + * 忙碌 - 正在执行工单 + */ + BUSY("busy", "忙碌", false), + + /** + * 暂停 - 临时离开或被P0打断 + */ + PAUSED("paused", "暂停", false), + + /** + * 离线 - 设备无心跳 + */ + OFFLINE("offline", "离线", false); + + public static final String[] ARRAYS = Arrays.stream(values()).map(BadgeDeviceStatusEnum::getCode).toArray(String[]::new); + + /** + * 状态代码 + */ + private final String code; + + /** + * 状态描述 + */ + private final String description; + + /** + * 是否可接新单 + */ + private final boolean canAcceptNewOrder; + + @Override + public String[] array() { + return ARRAYS; + } + + /** + * 获取状态代码 + */ + public String getStatus() { + return code; + } + + /** + * 检查是否为活跃状态(非OFFLINE) + */ + public boolean isActive() { + return this != OFFLINE; + } + + /** + * 检查是否可以转换到目标状态 + * + * @param target 目标状态 + * @return 是否可以转换 + */ + public boolean canTransitionTo(BadgeDeviceStatusEnum target) { + return switch (this) { + case OFFLINE -> target == IDLE; + case IDLE -> target == BUSY || target == OFFLINE; + case BUSY -> target == PAUSED || target == IDLE || target == OFFLINE; + case PAUSED -> target == BUSY || target == IDLE || target == OFFLINE; + }; + } + + /** + * 根据code获取枚举 + * + * @param code 状态代码 + * @return 枚举实例,未找到返回OFFLINE + */ + public static BadgeDeviceStatusEnum fromCode(String code) { + if (code == null) { + return OFFLINE; + } + return Arrays.stream(values()) + .filter(e -> e.getCode().equals(code)) + .findFirst() + .orElse(OFFLINE); + } + + /** + * 从保洁员状态枚举转换 + * + * @param cleanerStatus 保洁员状态枚举 + * @return 工牌设备状态枚举 + */ + public static BadgeDeviceStatusEnum fromCleanerStatus(CleanerStatusEnum cleanerStatus) { + if (cleanerStatus == null) { + return OFFLINE; + } + return fromCode(cleanerStatus.getCode()); + } + + /** + * 转换为保洁员状态枚举 + */ + public CleanerStatusEnum toCleanerStatus() { + return CleanerStatusEnum.fromCode(code); + } +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/core/badge/BadgeDeviceStatusService.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/core/badge/BadgeDeviceStatusService.java new file mode 100644 index 0000000..bbe2909 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/core/badge/BadgeDeviceStatusService.java @@ -0,0 +1,229 @@ +package com.viewsh.module.ops.core.badge; + +import com.viewsh.module.ops.api.badge.BadgeDeviceStatusDTO; +import com.viewsh.module.ops.enums.BadgeDeviceStatusEnum; + +import java.util.List; + +/** + * 工牌设备状态服务接口 + *

+ * 职责: + * 1. 管理工牌设备状态(Redis 存储和查询) + * 2. 处理设备心跳更新 + * 3. 状态转换(IDLE ↔ BUSY ↔ PAUSED ↔ OFFLINE) + * 4. 区域设备查询 + * 5. 当前任务关联 + *

+ * 设计原则: + * - 状态存储在 Redis 中,不依赖数据库表 + * - 与保洁员状态解耦,只关注设备本身 + * - 为调度引擎提供设备状态查询能力 + * + * @author lzh + */ +public interface BadgeDeviceStatusService { + + // ==================== 状态管理 ==================== + + /** + * 更新工牌设备状态 + *

+ * 状态转换会记录状态变更时间和操作原因 + * + * @param deviceId 设备ID + * @param status 目标状态 + * @param operatorId 操作人ID(可为null,表示系统操作) + * @param reason 状态变更原因 + */ + void updateBadgeStatus(Long deviceId, BadgeDeviceStatusEnum status, Long operatorId, String reason); + + /** + * 批量更新工牌设备状态 + * + * @param deviceIds 设备ID列表 + * @param status 目标状态 + * @param operatorId 操作人ID + * @param reason 状态变更原因 + */ + void batchUpdateBadgeStatus(List deviceIds, BadgeDeviceStatusEnum status, Long operatorId, String reason); + + // ==================== 状态查询 ==================== + + /** + * 获取工牌设备状态 + * + * @param deviceId 设备ID + * @return 设备状态DTO,不存在返回null + */ + BadgeDeviceStatusDTO getBadgeStatus(Long deviceId); + + /** + * 批量获取工牌设备状态 + * + * @param deviceIds 设备ID列表 + * @return 设备状态DTO列表 + */ + List batchGetBadgeStatus(List deviceIds); + + /** + * 获取指定区域的工牌设备列表 + *

+ * 只返回非 OFFLINE 状态的设备 + * + * @param areaId 区域ID + * @return 设备状态DTO列表 + */ + List listBadgesByArea(Long areaId); + + /** + * 获取可接单的工牌设备(IDLE 状态) + * + * @param areaId 区域ID + * @return 可接单设备列表 + */ + List listAvailableBadges(Long areaId); + + /** + * 获取所有活跃的工牌设备(非OFFLINE状态) + * + * @return 活跃设备列表 + */ + List listActiveBadges(); + + // ==================== 心跳处理 ==================== + + /** + * 处理工牌设备心跳 + *

+ * 更新最后心跳时间和电量: + *

+ * + * @param deviceId 设备ID + * @param deviceCode 设备编码 + * @param batteryLevel 电量(0-100) + */ + void handleHeartbeat(Long deviceId, String deviceCode, Integer batteryLevel); + + /** + * 处理工牌设备心跳(带区域信息) + * + * @param deviceId 设备ID + * @param deviceCode 设备编码 + * @param batteryLevel 电量(0-100) + * @param areaId 当前所在区域ID + * @param areaName 当前所在区域名称 + */ + void handleHeartbeatWithArea(Long deviceId, String deviceCode, Integer batteryLevel, + Long areaId, String areaName); + + // ==================== 在线状态检查 ==================== + + /** + * 检查工牌设备是否在线(非OFFLINE状态) + * + * @param deviceId 设备ID + * @return 是否在线 + */ + boolean isBadgeOnline(Long deviceId); + + /** + * 检查工牌设备心跳是否超时 + * + * @param deviceId 设备ID + * @param thresholdMinutes 超时阈值(分钟) + * @return 是否超时 + */ + boolean isHeartbeatTimeout(Long deviceId, int thresholdMinutes); + + /** + * 检查心跳超时并将超时设备设为OFFLINE + *

+ * 定时任务调用,默认超时时间为30分钟 + */ + void checkAndMarkOfflineDevices(); + + // ==================== 工单关联 ==================== + + /** + * 设置当前工单 + * + * @param deviceId 设备ID + * @param orderId 工单ID + */ + void setCurrentOrder(Long deviceId, Long orderId); + + /** + * 清除当前工单 + * + * @param deviceId 设备ID + */ + void clearCurrentOrder(Long deviceId); + + /** + * 获取当前有工单的设备列表 + * + * @return 有工单的设备列表 + */ + List listBadgesWithCurrentOrder(); + + // ==================== 区域管理 ==================== + + /** + * 更新工牌设备所在区域 + * + * @param deviceId 设备ID + * @param areaId 区域ID + * @param areaName 区域名称 + */ + void updateBadgeArea(Long deviceId, Long areaId, String areaName); + + /** + * 初始化区域设备索引 + *

+ * 从 ops_area_device_relation 表加载 BADGE 类型的设备, + * 建立区域到设备的索引关系 + */ + void initAreaDeviceIndex(); + + /** + * 刷新区域设备索引 + *

+ * 重新从数据库加载区域设备关系 + */ + void refreshAreaDeviceIndex(); + + /** + * 将设备添加到区域索引 + * + * @param deviceId 设备ID + * @param areaId 区域ID + */ + void addToAreaIndex(Long deviceId, Long areaId); + + /** + * 从区域索引移除设备 + * + * @param deviceId 设备ID + * @param areaId 区域ID + */ + void removeFromAreaIndex(Long deviceId, Long areaId); + + // ==================== 设备管理 ==================== + + /** + * 删除工牌设备状态 + * + * @param deviceId 设备ID + */ + void deleteBadgeStatus(Long deviceId); + + /** + * 清理所有离线设备的状态 + */ + void clearOfflineBadges(); +}