diff --git a/viewsh-module-iot/viewsh-module-iot-server/src/main/java/com/viewsh/module/iot/service/rule/clean/processor/ButtonEventRuleProcessor.java b/viewsh-module-iot/viewsh-module-iot-server/src/main/java/com/viewsh/module/iot/service/rule/clean/processor/ButtonEventRuleProcessor.java new file mode 100644 index 0000000..9d655b9 --- /dev/null +++ b/viewsh-module-iot/viewsh-module-iot-server/src/main/java/com/viewsh/module/iot/service/rule/clean/processor/ButtonEventRuleProcessor.java @@ -0,0 +1,295 @@ +package com.viewsh.module.iot.service.rule.clean.processor; + +import com.viewsh.module.iot.core.integration.constants.CleanOrderTopics; +import com.viewsh.module.iot.dal.dataobject.integration.clean.ButtonEventConfig; +import com.viewsh.module.iot.dal.redis.clean.DeviceCurrentOrderRedisDAO; +import com.viewsh.module.iot.service.integration.clean.CleanOrderIntegrationConfigService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.rocketmq.spring.core.RocketMQTemplate; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.messaging.support.MessageBuilder; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * 按键事件规则处理器 + *

+ * 监听设备按键事件上报,处理保洁员工牌的按键交互 + *

+ * 支持的按键类型: + * - 确认键(confirmKeyId):保洁员确认接收工单 + * - 查询键(queryKeyId):保洁员查询当前工单信息 + * + * @author AI + */ +@Component +@Slf4j +public class ButtonEventRuleProcessor { + + @Resource + private CleanOrderIntegrationConfigService configService; + + @Resource + private DeviceCurrentOrderRedisDAO deviceCurrentOrderRedisDAO; + + @Resource + private RocketMQTemplate rocketMQTemplate; + + /** + * 处理按键事件属性上报 + *

+ * 在设备属性上报处理流程中调用此方法 + * + * @param deviceId 设备ID + * @param identifier 属性标识符(如 button_event) + * @param propertyValue 属性值 + */ + public void processPropertyChange(Long deviceId, String identifier, Object propertyValue) { + // 1. 检查是否是按键事件属性 + if (!"button_event".equals(identifier)) { + return; + } + + log.debug("[ButtonEvent] 收到按键事件:deviceId={}, value={}", deviceId, propertyValue); + + // 2. 获取配置 + CleanOrderIntegrationConfigService.AreaDeviceConfigWrapper configWrapper = + getConfigWrapper(deviceId); + + if (configWrapper == null || configWrapper.getConfig() == null) { + log.debug("[ButtonEvent] 设备无配置:deviceId={}", deviceId); + return; + } + + ButtonEventConfig buttonConfig = configWrapper.getConfig().getButtonEvent(); + if (buttonConfig == null || !buttonConfig.getEnabled()) { + log.debug("[ButtonEvent] 未启用按键事件处理:deviceId={}", deviceId); + return; + } + + // 3. 解析按键ID + Integer buttonId = parseButtonId(propertyValue); + if (buttonId == null) { + log.warn("[ButtonEvent] 按键ID解析失败:deviceId={}, value={}", deviceId, propertyValue); + return; + } + + log.debug("[ButtonEvent] 按键解析成功:deviceId={}, buttonId={}", deviceId, buttonId); + + // 4. 匹配按键类型并处理 + if (buttonId.equals(buttonConfig.getConfirmKeyId())) { + // 确认键 + handleConfirmButton(configWrapper, buttonId); + } else if (buttonId.equals(buttonConfig.getQueryKeyId())) { + // 查询键 + handleQueryButton(configWrapper, buttonId); + } else { + log.debug("[ButtonEvent] 未配置的按键:deviceId={}, buttonId={}", deviceId, buttonId); + } + } + + /** + * 处理确认按键 + *

+ * 保洁员按下确认键,确认接收工单 + */ + private void handleConfirmButton(CleanOrderIntegrationConfigService.AreaDeviceConfigWrapper configWrapper, + Integer buttonId) { + Long deviceId = configWrapper.getDeviceId(); + + log.info("[ButtonEvent] 确认键按下:deviceId={}, buttonId={}", deviceId, buttonId); + + // 1. 查询设备当前工单 + DeviceCurrentOrderRedisDAO.OrderCacheInfo currentOrderJson = deviceCurrentOrderRedisDAO.getCurrentOrder(deviceId); + if (currentOrderJson == null) { + log.warn("[ButtonEvent] 设备无当前工单,跳过确认:deviceId={}", deviceId); + return; + } + + // 2. 解析工单ID(简单解析,生产环境可用 JSON 库) + Long orderId = extractOrderIdFromJson(currentOrderJson.getOrderId().toString()); + if (orderId == null) { + log.warn("[ButtonEvent] 工单ID解析失败:deviceId={}, json={}", deviceId, currentOrderJson); + return; + } + + // 3. 防重复检查(短时间内同一工单的确认操作去重) + String dedupKey = String.format("iot:clean:button:dedup:confirm:%s:%s", deviceId, orderId); + Boolean firstTime = stringRedisTemplate.opsForValue() + .setIfAbsent(dedupKey, "1", 10, java.util.concurrent.TimeUnit.SECONDS); + + if (!firstTime) { + log.info("[ButtonEvent] 确认操作重复,跳过:deviceId={}, orderId={}", deviceId, orderId); + return; + } + + // 4. 发布工单确认事件 + publishConfirmEvent(configWrapper, orderId, buttonId); + + log.info("[ButtonEvent] 发布工单确认事件:deviceId={}, orderId={}", deviceId, orderId); + } + + /** + * 处理查询按键 + *

+ * 保洁员按下查询键,查询当前工单信息 + */ + private void handleQueryButton(CleanOrderIntegrationConfigService.AreaDeviceConfigWrapper configWrapper, + Integer buttonId) { + Long deviceId = configWrapper.getDeviceId(); + + log.info("[ButtonEvent] 查询键按下:deviceId={}, buttonId={}", deviceId, buttonId); + + // 1. 查询设备当前工单 + DeviceCurrentOrderRedisDAO.OrderCacheInfo currentOrderJson = deviceCurrentOrderRedisDAO.getCurrentOrder(deviceId); + if (currentOrderJson == null) { + log.info("[ButtonEvent] 设备无当前工单:deviceId={}", deviceId); + // 发布查询结果事件(无工单) + publishQueryEvent(configWrapper, null, buttonId, "当前无工单"); + return; + } + + // 2. 解析工单信息 + Long orderId = extractOrderIdFromJson(currentOrderJson.getOrderId().toString()); + if (orderId == null) { + log.warn("[ButtonEvent] 工单ID解析失败:deviceId={}, json={}", deviceId, currentOrderJson); + return; + } + + // 3. 发布查询事件 + publishQueryEvent(configWrapper, orderId, buttonId, "查询当前工单"); + + log.info("[ButtonEvent] 发布工单查询事件:deviceId={}, orderId={}", deviceId, orderId); + } + + /** + * 发布工单确认事件 + */ + private void publishConfirmEvent(CleanOrderIntegrationConfigService.AreaDeviceConfigWrapper configWrapper, + Long orderId, Integer buttonId) { + try { + Map event = new HashMap<>(); + event.put("eventId", UUID.randomUUID().toString()); + event.put("orderType", "CLEAN"); + event.put("orderId", orderId); + event.put("deviceId", configWrapper.getDeviceId()); + event.put("deviceKey", configWrapper.getDeviceKey()); + event.put("areaId", configWrapper.getAreaId()); + event.put("triggerSource", "IOT_BUTTON_CONFIRM"); + event.put("buttonId", buttonId); + + rocketMQTemplate.syncSend( + CleanOrderTopics.ORDER_CONFIRM, + MessageBuilder.withPayload(event).build() + ); + + log.info("[ButtonEvent] 确认事件已发布:eventId={}, orderId={}, deviceId={}", + event.get("eventId"), orderId, configWrapper.getDeviceId()); + } catch (Exception e) { + log.error("[ButtonEvent] 发布确认事件失败:deviceId={}, orderId={}", + configWrapper.getDeviceId(), orderId, e); + } + } + + /** + * 发布工单查询事件 + */ + private void publishQueryEvent(CleanOrderIntegrationConfigService.AreaDeviceConfigWrapper configWrapper, + Long orderId, Integer buttonId, String message) { + try { + Map event = new HashMap<>(); + event.put("eventId", UUID.randomUUID().toString()); + event.put("orderType", "CLEAN"); + event.put("orderId", orderId); + event.put("deviceId", configWrapper.getDeviceId()); + event.put("deviceKey", configWrapper.getDeviceKey()); + event.put("areaId", configWrapper.getAreaId()); + event.put("triggerSource", "IOT_BUTTON_QUERY"); + event.put("buttonId", buttonId); + event.put("message", message); + + rocketMQTemplate.syncSend( + CleanOrderTopics.ORDER_AUDIT, // 查询事件使用审计主题 + MessageBuilder.withPayload(event).build() + ); + + log.info("[ButtonEvent] 查询事件已发布:eventId={}, orderId={}, deviceId={}, message={}", + event.get("eventId"), orderId, configWrapper.getDeviceId(), message); + } catch (Exception e) { + log.error("[ButtonEvent] 发布查询事件失败:deviceId={}, orderId={}", + configWrapper.getDeviceId(), orderId, e); + } + } + + /** + * 从 JSON 字符串中提取工单ID + *

+ * 简单实现,生产环境建议使用 Jackson 或 Gson + */ + private Long extractOrderIdFromJson(String json) { + try { + // 简单解析:查找 "orderId":12345 + int orderIdIndex = json.indexOf("\"orderId\""); + if (orderIdIndex == -1) { + return null; + } + + int colonIndex = json.indexOf(":", orderIdIndex); + if (colonIndex == -1) { + return null; + } + + int endIndex = json.indexOf(",", colonIndex); + if (endIndex == -1) { + endIndex = json.indexOf("}", colonIndex); + } + + if (endIndex == -1) { + return null; + } + + String idStr = json.substring(colonIndex + 1, endIndex).trim(); + return Long.parseLong(idStr); + } catch (Exception e) { + log.error("[ButtonEvent] 解析工单ID失败:json={}", json, e); + return null; + } + } + + /** + * 获取配置包装器 + */ + private CleanOrderIntegrationConfigService.AreaDeviceConfigWrapper getConfigWrapper(Long deviceId) { + return configService.getConfigWrapperByDeviceId(deviceId); + } + + /** + * 解析按键ID + */ + private Integer parseButtonId(Object value) { + if (value == null) { + return null; + } + + if (value instanceof Number) { + return ((Number) value).intValue(); + } + + if (value instanceof String) { + try { + return Integer.parseInt((String) value); + } catch (NumberFormatException e) { + return null; + } + } + + return null; + } + + @Resource + private StringRedisTemplate stringRedisTemplate; +}