Merge branch 'master' into feat/multi-tenant

This commit is contained in:
lzh
2026-04-22 18:23:50 +08:00
7 changed files with 257 additions and 17 deletions

View File

@@ -0,0 +1,111 @@
package com.viewsh.module.ops.environment.integration.listener;
import com.viewsh.framework.common.pojo.CommonResult;
import com.viewsh.module.iot.api.device.IotDeviceQueryApi;
import com.viewsh.module.iot.api.device.dto.IotDeviceSimpleRespDTO;
import com.viewsh.module.ops.enums.BadgeDeviceStatusEnum;
import com.viewsh.module.ops.environment.integration.dto.IotDeviceStatusChangedEventDTO;
import com.viewsh.module.ops.environment.service.badge.BadgeDeviceStatusService;
import com.viewsh.module.ops.service.area.event.AreaDeviceBoundEvent;
import com.viewsh.module.ops.service.area.event.AreaDeviceUnboundEvent;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
/**
* 区域-工牌设备绑定/解绑事件监听器
* <p>
* 绑定({@link AreaDeviceBoundEvent}
* BADGE 关系建立前IoT 实时上线事件会被 {@code BadgeDeviceStatusEventHandler.isBadgeDevice()}
* 拒掉;建立关系后没有任何机制回填 Redis导致设备直到下次定时对账5/30 分钟)才会出现在
* "可分配工牌"列表,期间收到的工单也无法派给该设备。监听器在绑定事务提交后定向查询一次
* IoT 设备信息(含状态、昵称),回写 Ops 工牌缓存。
* <p>
* 解绑({@link AreaDeviceUnboundEvent}
* 解绑后 SyncJob 因关系记录消失不会再扫到该设备Redis 工牌缓存得等 24h TTL 自然过期,
* 期间该设备仍可能出现在"可分配/活跃工牌"列表里。监听器在解绑事务提交后立即清理 Redis 状态,
* 与绑定路径形成闭环。
* <p>
* 二者均使用 AFTER_COMMIT + @Async事务提交后才在独立线程执行不阻塞绑定/解绑接口响应。
*
* @author lzh
*/
@Slf4j
@Component
public class BadgeAreaBoundEventListener {
private static final String TYPE_BADGE = "BADGE";
@Resource
private IotDeviceQueryApi iotDeviceQueryApi;
@Resource
private BadgeDeviceStatusService badgeDeviceStatusService;
@Async("ops-task-executor")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
public void onAreaDeviceBound(AreaDeviceBoundEvent event) {
if (event == null || !TYPE_BADGE.equals(event.getRelationType())) {
return;
}
Long deviceId = event.getDeviceId();
Long areaId = event.getAreaId();
if (deviceId == null) {
return;
}
try {
// 单次 RPC 取齐 state + nickname + deviceNameIotDeviceSimpleRespDTO 已含 state 字段)
CommonResult<IotDeviceSimpleRespDTO> result = iotDeviceQueryApi.getDevice(deviceId);
if (result == null || !result.isSuccess() || result.getData() == null) {
log.warn("[BadgeAreaBoundEventListener] 查询 IoT 设备失败,跳过回填: deviceId={}, msg={}",
deviceId, result != null ? result.getMsg() : "null");
return;
}
IotDeviceSimpleRespDTO device = result.getData();
// IotDeviceSimpleRespDTO.state 与 IotDeviceStatusChangedEventDTO 的 status 编码一致
// 0=未激活1=在线2=离线),未激活/离线统一回写 OFFLINE
BadgeDeviceStatusEnum target = IotDeviceStatusChangedEventDTO.STATUS_ONLINE.equals(device.getState())
? BadgeDeviceStatusEnum.IDLE
: BadgeDeviceStatusEnum.OFFLINE;
badgeDeviceStatusService.updateBadgeOnlineStatus(
deviceId,
device.getDeviceName(),
device.getNickname(),
target == BadgeDeviceStatusEnum.IDLE ? areaId : null,
target,
"BADGE 绑定后回填");
log.info("[BadgeAreaBoundEventListener] 工牌设备状态回填完成: deviceId={}, areaId={}, target={}",
deviceId, areaId, target);
} catch (Exception e) {
log.error("[BadgeAreaBoundEventListener] 工牌设备状态回填失败: deviceId={}, areaId={}",
deviceId, areaId, e);
}
}
@Async("ops-task-executor")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
public void onAreaDeviceUnbound(AreaDeviceUnboundEvent event) {
if (event == null || !TYPE_BADGE.equals(event.getRelationType())) {
return;
}
Long deviceId = event.getDeviceId();
if (deviceId == null) {
return;
}
try {
badgeDeviceStatusService.deleteBadgeStatus(deviceId);
log.info("[BadgeAreaBoundEventListener] 工牌设备解绑后 Redis 状态已清理: deviceId={}, areaId={}",
deviceId, event.getAreaId());
} catch (Exception e) {
log.error("[BadgeAreaBoundEventListener] 工牌设备解绑后清理失败: deviceId={}", deviceId, e);
}
}
}