refactor(ops): 简化AreaDevice与BadgeDevice服务实现

- AreaDeviceService: 移���设备索引缓存逻辑(由IoT模块管理)
- AreaDeviceServiceImpl: 简化实现,直接查询数据库
- BadgeDeviceStatusService: 更新接口方法签名
- BadgeDeviceStatusServiceImpl: 修复语法错误,简化实现
- BadgeDeviceStatusEventListener: 适配事件处理逻辑
- CleanOrderEventListener: 更新事件处理

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
lzh
2026-02-01 00:57:38 +08:00
parent 3839da2966
commit 5d8c4045d4
6 changed files with 327 additions and 491 deletions

View File

@@ -8,9 +8,6 @@ import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
import com.viewsh.module.ops.enums.BadgeDeviceStatusEnum;
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
import com.viewsh.module.ops.environment.service.badge.BadgeDeviceStatusService;
import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain;
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecord;
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
@@ -25,6 +22,7 @@ import org.springframework.stereotype.Component;
* - 使用 @EventListener 监听事件,在事务内同步执行
* - 确保 IoT 模块能实时查询到正确的设备工单信息
* - 只处理保洁类型的工单
* - 不记录业务日志,由 CleanOrderEventListener 统一处理
* <p>
* 状态处理:
* <table>
@@ -41,12 +39,12 @@ import org.springframework.stereotype.Component;
* <tr>
* <td>CONFIRMED</td>
* <td>保持BUSY</td>
* <td>设置</td>
* <td>更新状态</td>
* </tr>
* <tr>
* <td>ARRIVED</td>
* <td>保持BUSY</td>
* <td>设置完整信息</td>
* <td>更新状态+信标</td>
* </tr>
* <tr>
* <td>PAUSED</td>
@@ -80,9 +78,6 @@ public class BadgeDeviceStatusEventListener {
@Resource
private OrderQueueService orderQueueService;
@Resource
private EventLogRecorder eventLogRecorder;
/**
* 监听工单状态变更事件,同步更新设备工单关联
* <p>
@@ -117,7 +112,7 @@ public class BadgeDeviceStatusEventListener {
}
// 根据状态更新设备工单关联
handleOrderStatusTransition(deviceId, orderId, newStatus, event);
handleOrderStatusTransition(deviceId, orderId, newStatus, event, order);
} catch (Exception e) {
log.error("[BadgeDeviceStatusEventListener] 处理工单状态变更事件失败: orderId={}", event.getOrderId(), e);
@@ -126,177 +121,154 @@ public class BadgeDeviceStatusEventListener {
/**
* 根据工单状态更新设备工牌关联
* <p>
* 设计原则:每个状态只更新变化的部分,避免重复设置相同数据
* <p>
* 状态处理逻辑:
* <ul>
* <li>DISPATCHED: 首次设置工单关联orderId + areaId</li>
* <li>CONFIRMED: 只更新状态orderId/areaId 已在 DISPATCHED 设置</li>
* <li>ARRIVED: 更新状态 + 信标MAC</li>
* <li>PAUSED: 只更新设备状态,工单关联保持</li>
* <li>COMPLETED: 清除工单关联,根据等待任务决定设备状态</li>
* <li>CANCELLED: 清除工单关联(如果是当前工单),根据等待任务决定设备状态</li>
* </ul>
*/
private void handleOrderStatusTransition(Long deviceId, Long orderId, WorkOrderStatusEnum newStatus,
OrderStateChangedEvent event) {
var waitingTasks = orderQueueService.getWaitingTasksByUserIdFromDb(deviceId);
OrderStateChangedEvent event, OpsOrderDO order) {
switch (newStatus) {
case DISPATCHED:
// 工单已推送到工牌,设置工单关联和区域信息
// currentAreaId 应该设置为工单所属区域,而非设备物理位置
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.BUSY, null, "新工单已推送");
OpsOrderDO order = opsOrderMapper.selectById(orderId);
if (order != null && order.getAreaId() != null) {
badgeDeviceStatusService.setCurrentOrderInfo(
deviceId, orderId,
event.getNewStatus().getStatus(),
order.getAreaId(),
null
);
log.info("[BadgeDeviceStatusEventListener] 工单已推送,设备状态转为 BUSY: deviceId={}, orderId={}, areaId={}",
deviceId, orderId, order.getAreaId());
} else {
badgeDeviceStatusService.setCurrentOrder(deviceId, orderId);
log.info("[BadgeDeviceStatusEventListener] 工单已推送,设备状态转为 BUSY: deviceId={}, orderId={}", deviceId,
orderId);
}
handleDispatched(deviceId, orderId, order);
break;
case CONFIRMED:
// 设备按键确认,更新工单关联和区域信息
// currentAreaId 应该设置为工单所属区域,而非设备物理位置
OpsOrderDO order = opsOrderMapper.selectById(orderId);
if (order != null && order.getAreaId() != null) {
badgeDeviceStatusService.setCurrentOrderInfo(
deviceId, orderId,
event.getNewStatus().getStatus(),
order.getAreaId(),
null
);
log.debug("[BadgeDeviceStatusEventListener] 工单已确认,更新区域信息: deviceId={}, orderId={}, areaId={}",
deviceId, orderId, order.getAreaId());
} else {
badgeDeviceStatusService.setCurrentOrder(deviceId, orderId);
log.debug("[BadgeDeviceStatusEventListener] 工单已确认: deviceId={}, orderId={}", deviceId, orderId);
}
handleConfirmed(deviceId, orderId);
break;
case ARRIVED:
// 设备已到岗设置完整的工单信息areaId, beaconMac
updateDeviceOrderInfo(deviceId, orderId, event);
recordOrderArrivedLog(orderId, deviceId, event);
log.info("[BadgeDeviceStatusEventListener] 工单已到岗,更新设备工单信息: deviceId={}, orderId={}", deviceId, orderId);
handleArrived(deviceId, orderId, event);
break;
case PAUSED:
// 检查是否是 P0 打断场景
Long urgentOrderId = event.getPayloadLong("urgentOrderId");
if (urgentOrderId != null) {
// P0 打断场景:不修改设备状态,保持当前状态
// 紧接着会有 P0 工单的 DISPATCHED 事件,将当前工单更新为 P0 工单
log.info(
"[BadgeDeviceStatusEventListener] P0打断场景工单已暂停等待P0工单派发: pausedOrderId={}, urgentOrderId={}, deviceId={}",
orderId, urgentOrderId, deviceId);
} else {
// 普通暂停场景:设备状态转为 PAUSED
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.PAUSED, null, "任务暂停");
log.info("[BadgeDeviceStatusEventListener] 任务暂停,设备状态转为 PAUSED: deviceId={}", deviceId);
}
handlePaused(deviceId, orderId, event);
break;
case COMPLETED:
// 任务完成,清除工单关联
badgeDeviceStatusService.clearCurrentOrder(deviceId);
// 检查是否有等待任务,决定设备状态
if (waitingTasks != null && !waitingTasks.isEmpty()) {
// 有等待任务,设备状态保持 BUSY由后续 DISPATCHED 事件更新)
log.info("[BadgeDeviceStatusEventListener] 任务完成,有{}个等待任务,设备保持 BUSY: deviceId={}",
waitingTasks.size(), deviceId);
} else {
// 无等待任务,设备状态转为 IDLE
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.IDLE, null, "任务完成,无新任务");
log.info("[BadgeDeviceStatusEventListener] 任务完成,无等待任务,设备转为 IDLE: deviceId={}", deviceId);
}
handleCompleted(deviceId);
break;
case CANCELLED:
// 检查被取消的工单是否是设备当前正在执行的工单
BadgeDeviceStatusDTO deviceStatus = badgeDeviceStatusService.getBadgeStatus(deviceId);
Long currentOrderId = deviceStatus != null ? deviceStatus.getCurrentOpsOrderId() : null;
if (orderId.equals(currentOrderId)) {
// 取消的是当前正在执行的工单,清除工单关联
badgeDeviceStatusService.clearCurrentOrder(deviceId);
// 检查是否有等待任务,决定设备状态
if (waitingTasks != null && !waitingTasks.isEmpty()) {
// 有等待任务,设备状态保持 BUSY由后续 DISPATCHED 事件更新)
log.info("[BadgeDeviceStatusEventListener] 当前工单已取消,有{}个等待任务,设备保持 BUSY: deviceId={}",
waitingTasks.size(), deviceId);
} else {
// 无等待任务,设备状态转为 IDLE
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.IDLE, null, "工单已取消");
log.info("[BadgeDeviceStatusEventListener] 当前工单已取消,无等待任务,设备转为 IDLE: deviceId={}", deviceId);
}
} else {
// 取消的不是当前工单(可能是队列中的等待任务),不需要修改设备状态
log.debug(
"[BadgeDeviceStatusEventListener] 取消的工单非当前执行工单,跳过设备状态更新: cancelledOrderId={}, currentOrderId={}, deviceId={}",
orderId, currentOrderId, deviceId);
}
handleCancelled(deviceId, orderId);
break;
default:
// 其他状态不处理
break;
}
}
/**
* 更新设备工单信息ARRIVED 状态专用
* <p>
* 设置完整的工单信息工单ID、工单状态、区域ID、信标MAC
* 处理工单推送状态(首次设置工单关联
*/
private void updateDeviceOrderInfo(Long deviceId, Long orderId, OrderStateChangedEvent event) {
try {
// 从 payload 中提取信息
Long areaId = event.getPayloadLong("areaId");
private void handleDispatched(Long deviceId, Long orderId, OpsOrderDO order) {
// 设备状态转为 BUSY
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.BUSY, null, "新工单已推送");
String beaconMac = null;
Object beaconMacObj = event.getPayload().get("beaconMac");
if (beaconMacObj != null) {
beaconMac = String.valueOf(beaconMacObj);
}
// 使用 BadgeDeviceStatusService 设置完整工单信息
// 首次设置工单关联orderId + status + areaId
if (order != null && order.getAreaId() != null) {
badgeDeviceStatusService.setCurrentOrderInfo(
deviceId,
orderId,
event.getNewStatus().getStatus(),
areaId,
beaconMac);
log.debug("[BadgeDeviceStatusEventListener] 设备工单信息已更新: deviceId={}, orderId={}, areaId={}, beaconMac={}",
deviceId, orderId, areaId, beaconMac);
} catch (Exception e) {
log.error("[BadgeDeviceStatusEventListener] 更新设备工单信息失败: deviceId={}, orderId={}", deviceId, orderId, e);
deviceId, orderId,
WorkOrderStatusEnum.DISPATCHED.getStatus(),
order.getAreaId(),
null
);
log.info("[BadgeDeviceStatusEventListener] 工单已推送: deviceId={}, orderId={}, areaId={}",
deviceId, orderId, order.getAreaId());
} else {
badgeDeviceStatusService.setCurrentOrder(deviceId, orderId);
log.info("[BadgeDeviceStatusEventListener] 工单已推送(无区域信息): deviceId={}, orderId={}", deviceId, orderId);
}
}
/**
* 记录工单到岗业务日志
* 处理工单确认状态(只更新状态,不重复设置 orderId/areaId
*/
private void recordOrderArrivedLog(Long orderId, Long deviceId, OrderStateChangedEvent event) {
try {
Long areaId = event.getPayloadLong("areaId");
String deviceKey = (String) event.getPayload().get("deviceKey");
private void handleConfirmed(Long deviceId, Long orderId) {
// 只更新工单状态orderId/areaId 已在 DISPATCHED 设置
badgeDeviceStatusService.updateOrderStatus(deviceId, WorkOrderStatusEnum.CONFIRMED.getStatus());
log.debug("[BadgeDeviceStatusEventListener] 工单已确认: deviceId={}, orderId={}", deviceId, orderId);
}
eventLogRecorder.record(EventLogRecord.builder()
.module("clean")
.domain(EventDomain.BEACON)
.eventType("ORDER_ARRIVED")
.message(String.format("蓝牙信标自动到岗确认 [设备:%s, 区域:%d]", deviceKey, areaId))
.targetId(orderId)
.targetType("order")
.deviceId(deviceId)
.personId(deviceId)
.build());
/**
* 处理工单到岗状态(更新状态 + 信标MAC
*/
private void handleArrived(Long deviceId, Long orderId, OrderStateChangedEvent event) {
// 从 payload 中提取信标信息
String beaconMac = (String) event.getPayload().get("beaconMac");
} catch (Exception e) {
log.warn("[BadgeDeviceStatusEventListener] 记录到岗业务日志失败: orderId={}", orderId, e);
// 更新状态和信标MACorderId/areaId 已设置)
badgeDeviceStatusService.updateOrderStatusAndBeacon(deviceId, orderId,
WorkOrderStatusEnum.ARRIVED.getStatus(), beaconMac);
log.debug("[BadgeDeviceStatusEventListener] 工单已到岗,更新信标: deviceId={}, orderId={}, beaconMac={}",
deviceId, orderId, beaconMac);
}
/**
* 处理工单暂停状态
*/
private void handlePaused(Long deviceId, Long orderId, OrderStateChangedEvent event) {
Long urgentOrderId = event.getPayloadLong("urgentOrderId");
if (urgentOrderId != null) {
// P0 打断场景:不修改设备状态,等待 P0 工单的 DISPATCHED 事件
log.info("[BadgeDeviceStatusEventListener] P0打断场景等待P0工单派发: pausedOrderId={}, urgentOrderId={}",
orderId, urgentOrderId);
} else {
// 普通暂停场景:设备状态转为 PAUSED
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.PAUSED, null, "任务暂停");
log.info("[BadgeDeviceStatusEventListener] 任务暂停: deviceId={}", deviceId);
}
}
/**
* 处理工单完成状态(清除工单关联)
*/
private void handleCompleted(Long deviceId) {
badgeDeviceStatusService.clearCurrentOrder(deviceId);
// 检查是否有等待任务,决定设备状态
var waitingTasks = orderQueueService.getWaitingTasksByUserIdFromDb(deviceId);
if (waitingTasks != null && !waitingTasks.isEmpty()) {
log.info("[BadgeDeviceStatusEventListener] 任务完成,有{}个等待任务,设备保持 BUSY: deviceId={}",
waitingTasks.size(), deviceId);
} else {
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.IDLE, null, "任务完成,无新任务");
log.info("[BadgeDeviceStatusEventListener] 任务完成,无等待任务,设备转为 IDLE: deviceId={}", deviceId);
}
}
/**
* 处理工单取消状态
*/
private void handleCancelled(Long deviceId, Long orderId) {
// 检查被取消的工单是否是设备当前正在执行的工单
BadgeDeviceStatusDTO deviceStatus = badgeDeviceStatusService.getBadgeStatus(deviceId);
Long currentOrderId = deviceStatus != null ? deviceStatus.getCurrentOpsOrderId() : null;
if (orderId.equals(currentOrderId)) {
badgeDeviceStatusService.clearCurrentOrder(deviceId);
// 检查是否有等待任务,决定设备状态
var waitingTasks = orderQueueService.getWaitingTasksByUserIdFromDb(deviceId);
if (waitingTasks != null && !waitingTasks.isEmpty()) {
log.info("[BadgeDeviceStatusEventListener] 当前工单已取消,有{}个等待任务,设备保持 BUSY: deviceId={}",
waitingTasks.size(), deviceId);
} else {
badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.IDLE, null, "工单已取消");
log.info("[BadgeDeviceStatusEventListener] 当前工单已取消,无等待任务,设备转为 IDLE: deviceId={}", deviceId);
}
} else {
log.debug("[BadgeDeviceStatusEventListener] 取消的工单非当前执行工单,跳过: cancelledOrderId={}, currentOrderId={}",
orderId, currentOrderId);
}
}
}

View File

@@ -359,6 +359,9 @@ public class CleanOrderEventListener {
playVoice(deviceId, arrivedMessage);
}
// 4. 记录到岗业务日志
recordOrderArrivedLog(orderId, deviceId, event);
log.info("[CleanOrderEventListener] 到岗时间已记录: orderId={}, deviceId={}", orderId, deviceId);
}
@@ -787,6 +790,32 @@ public class CleanOrderEventListener {
}
}
/**
* 记录工单到岗业务日志
*/
private void recordOrderArrivedLog(Long orderId, Long deviceId, OrderStateChangedEvent event) {
try {
Long areaId = event.getPayloadLong("areaId");
String deviceKey = (String) event.getPayload().get("deviceKey");
String beaconMac = (String) event.getPayload().get("beaconMac");
eventLogRecorder.record(EventLogRecord.builder()
.module("clean")
.domain(EventDomain.BEACON)
.eventType("ORDER_ARRIVED")
.message(String.format("蓝牙信标自动到岗确认 [设备:%s, 区域:%d, 信标:%s]",
deviceKey, areaId, beaconMac))
.targetId(orderId)
.targetType("order")
.deviceId(deviceId)
.personId(deviceId)
.build());
} catch (Exception e) {
log.warn("[CleanOrderEventListener] 记录到岗业务日志失败: orderId={}", orderId, e);
}
}
/**
* 记录工单完成业务日志
*/

View File

@@ -153,6 +153,18 @@ public interface BadgeDeviceStatusService {
*/
void updateOrderStatus(Long deviceId, String orderStatus);
/**
* 更新工单状态和信标MAC
* <p>
* 用于到岗场景同时更新工单状态和信标MAC地址
*
* @param deviceId 设备ID
* @param orderId 工单ID
* @param orderStatus 工单状态
* @param beaconMac 信标MAC地址可为null
*/
void updateOrderStatusAndBeacon(Long deviceId, Long orderId, String orderStatus, String beaconMac);
/**
* 清除当前工单包括工单ID、工单状态、信标MAC
*
@@ -178,37 +190,6 @@ public interface BadgeDeviceStatusService {
*/
void updateBadgeArea(Long deviceId, Long areaId, String areaName);
/**
* 初始化区域设备索引
* <p>
* 从 ops_area_device_relation 表加载 BADGE 类型的设备,
* 建立区域到设备的索引关系
*/
void initAreaDeviceIndex();
/**
* 刷新区域设备索引
* <p>
* 重新从数据库加载区域设备关系
*/
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);
// ==================== 设备管理 ====================
/**

View File

@@ -53,8 +53,8 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I
@Override
public void afterPropertiesSet() {
// 启动时初始化区域设备索引
initAreaDeviceIndex();
// 启动时初始化区域设备配置缓存
areaDeviceService.initConfigCache();
}
// ==================== 状态管理 ====================
@@ -163,13 +163,14 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I
}
try {
Set<Long> deviceIds = areaDeviceService.getDeviceIdsByArea(areaId);
// 直接从DB查询该区域的工牌设备ID列表
List<Long> deviceIds = areaDeviceService.getDeviceIdsByAreaAndType(areaId, "BADGE");
if (deviceIds.isEmpty()) {
return Collections.emptyList();
}
// 使用批量查询,避免 N+1
return batchGetBadgeStatus(new ArrayList<>(deviceIds)).stream()
// 批量查询设备状态,避免 N+1
return batchGetBadgeStatus(deviceIds).stream()
.filter(Objects::nonNull)
.filter(dto -> dto.getStatus() != null && dto.getStatus().isActive())
.sorted(Comparator.comparing(BadgeDeviceStatusDTO::getStatusChangeTime))
@@ -188,14 +189,14 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I
}
try {
// 使用读穿透方式获取设备ID列表缓存未命中时会从数据库重建
Set<Long> deviceIds = areaDeviceService.getDeviceIdsByArea(areaId);
// 直接从DB查询该区域的工牌设备ID列表
List<Long> deviceIds = areaDeviceService.getDeviceIdsByAreaAndType(areaId, "BADGE");
if (deviceIds.isEmpty()) {
return Collections.emptyList();
}
// 使用批量查询,避免 N+1
return batchGetBadgeStatus(new ArrayList<>(deviceIds)).stream()
// 批量查询设备状态,避免 N+1
return batchGetBadgeStatus(deviceIds).stream()
.filter(Objects::nonNull)
.filter(dto -> dto.getStatus() == BadgeDeviceStatusEnum.IDLE)
.sorted(Comparator.comparing(BadgeDeviceStatusDTO::getLastHeartbeatTime).reversed())
@@ -387,6 +388,26 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I
}
}
@Override
public void updateOrderStatusAndBeacon(Long deviceId, Long orderId, String orderStatus, String beaconMac) {
if (deviceId == null) {
return;
}
try {
String key = BADGE_STATUS_KEY_PREFIX + deviceId;
// 更新工单状态和信标MACorderId 和 areaId 已在 DISPATCHED 时设置,不需要重复)
redisTemplate.opsForHash().put(key, "currentOrderStatus", orderStatus);
if (beaconMac != null) {
redisTemplate.opsForHash().put(key, "beaconMac", beaconMac);
}
log.debug("更新工单状态和信标: deviceId={}, orderId={}, orderStatus={}, beaconMac={}",
deviceId, orderId, orderStatus, beaconMac);
} catch (Exception e) {
log.error("更新工单状态和信标失败: deviceId={}, orderId={}", deviceId, orderId, e);
}
}
@Override
public void clearCurrentOrder(Long deviceId) {
if (deviceId == null) {
@@ -448,44 +469,12 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I
redisTemplate.opsForHash().putAll(key, statusMap);
}
if (areaId != null) {
addToAreaIndex(deviceId, areaId);
}
log.debug("更新工牌设备区域: deviceId={}, areaId={}, areaName={}", deviceId, areaId, areaName);
} catch (Exception e) {
log.error("更新工牌设备区域失败: deviceId={}", deviceId, e);
}
}
// ==================== 区域管理 ====================
@Override
public void initAreaDeviceIndex() {
log.info("[BadgeDeviceStatusService] 开始初始化区域设备索引...");
// 委托给 AreaDeviceService 处理
areaDeviceService.initAreaDeviceIndex();
log.info("[BadgeDeviceStatusService] 区域设备索引初始化完成");
}
@Override
public void refreshAreaDeviceIndex() {
log.info("[BadgeDeviceStatusService] 开始刷新区域设备索引...");
// 委托给 AreaDeviceService 处理
areaDeviceService.refreshAreaDeviceIndex();
log.info("[BadgeDeviceStatusService] 区域设备索引刷新完成");
}
@Override
public void addToAreaIndex(Long deviceId, Long areaId) {
areaDeviceService.addToAreaIndex(deviceId, areaId);
}
@Override
public void removeFromAreaIndex(Long deviceId, Long areaId) {
areaDeviceService.removeFromAreaIndex(deviceId, areaId);
}
// ==================== 设备管理 ====================
@Override