fix(ops): 修复工牌绑定/手动派单/审计日志三处缺陷 #2
Reference in New Issue
Block a user
No description provided.
Delete Branch "fix/badge-online-and-manual-dispatch"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
背景
线上日志(
aiot-ops-server-20260422164407.log)暴露 3 个互相串联的问题:根因定位:
BadgeDeviceStatusEventHandler在写 Redis 前用isBadgeDevice()校验ops_area_device_relation;设备先上线后绑定时事件被丢弃,且无回填机制(线上 deviceId=58 在绑定后 30 分钟才被对账修正:reason=定时对账修正-上线)。ManualOrderActionFacade.dispatch()把setAssigneeId/Name放在transition()之后,导致事务内同步发的OrderStateChangedEvent触发BadgeDeviceStatusEventListener时assigneeId仍为 null,listener 跳过 → RediscurrentOpsOrderId永远不写;同时assigneeDeviceId也始终为 null,audit 查询永远落空。OrderTransitionAuditListener每次成功都向bus_log写一条"状态转换成功"镜像,与各条线EventListener(ORDER_DISPATCHED等)和ops_order_event表内容重复,淹没真正的失败审计。改动(3 个 commit)
8c664a47OrderTransitionAuditListener成功路径不再写 bus_log;失败/被拒/回滚仍记录6c4153feManualOrderActionFacade.dispatch()把执行人字段(含新增setAssigneeDeviceId)更新前置到transition()之前3cfd3423AreaDeviceBoundEvent/AreaDeviceUnboundEvent,bindDevice/unbindDevice后通过ApplicationEventPublisher发布;environment-biz新增BadgeAreaBoundEventListener(@TransactionalEventListener(AFTER_COMMIT) + @Async),BADGE 类型绑定后立即调IotDeviceQueryApi.getDevice单次取齐 state+nickname+deviceName 回写 Redis;解绑后deleteBadgeStatus清理 Redis依赖方向遵循
environment-biz → ops-biz,通过事件解耦,与现有OrderStateChangedEvent模式一致。测试
viewsh-module-ops-biz、viewsh-module-environment-biz全模块clean compile通过AreaDeviceRelationServiceTest10/10 通过(顺手补齐iotDeviceQueryApimock,让 master 上原本因缺 mock 而 RED 的testBindDevice_TrafficCounter_Success转绿;新增对 bound/unbound 事件publishEvent的 verify)上线后验证清单
bus_log不再有"状态转换成功"行;失败/回滚仍可见OrderTransitionAuditListenerWARN/ERROR日志不变问题:管理员手动派单后,工牌按键查询持续返回"没有工单",TTS 循环播报"工单 来啦"但用户无法响应(线上日志可见 deviceId=58 一段时间内连续 8 次按键查询 全部命中 CleanOrderAuditEventHandler.handleQueryEvent 的"没有工单"分支)。 根因:ManualOrderActionFacade.dispatch() 原顺序是 1. transition() —— 事务内同步发布 OrderStateChangedEvent, BadgeDeviceStatusEventListener.onOrderStateChanged 重新 selectById 拿 assigneeId 决定是否写 Redis 工单关联; 2. update assigneeId / assigneeName。 第 1 步事件触发时 assigneeId 仍是 null,listener 走"工单未关联设备,跳过 处理"分支,Redis ops:badge:status:{deviceId} 的 currentOpsOrderId 永远不 会写入;同时主表 assigneeDeviceId 也始终为 null, CleanOrderAuditEventHandler.handleQueryEvent 用 "WHERE assignee_device_id=?" 查工单永远落空 → "没有工单"。 修复:把执行人字段更新前置到 transition() 之前,并补 setAssigneeDeviceId (与 OrderLifecycleManagerImpl.dispatch() 自动派单路径一致)。 事件 listener 此时 selectById 拿得到 deviceId,正常落 Redis;audit 查询也命中,按键路径恢复。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>问题: 1. 设备先上线、后绑定为 BADGE 时,"可分配工牌"列表迟迟不出现该设备; 2. 已绑定区域的设备收不到工单(被分派策略过滤掉)。 根因:实时上线路径 BadgeDeviceStatusEventHandler.onMessage 在写 ops:badge:status:{deviceId} Redis 缓存前,先 isBadgeDevice() 校验 ops_area_device_relation 是否存在 BADGE 关系。设备如果在绑定前就上线, 事件被丢弃;建立关系后又没有任何机制回填 Redis,得等 5/30 分钟的 BadgeDeviceStatusSyncJob 对账才会被发现(线上日志可见 deviceId=58 在 绑定后 30 分钟才被对账修正:reason=定时对账修正-上线)。 解绑同样有反向缺口:关系记录删了但 Redis 缓存得等 24h TTL 自然过期, 期间 listAvailableBadges 仍可能返回该设备。 修复:在 ops-biz 引入 AreaDeviceBoundEvent / AreaDeviceUnboundEvent, bindDevice / unbindDevice 成功后通过 ApplicationEventPublisher 发布; environment-biz 新增 BadgeAreaBoundEventListener 仅订阅 BADGE 类型, 使用 @TransactionalEventListener(AFTER_COMMIT) + @Async 确保事务提交后 异步执行不阻塞接口: - 绑定:单次调 IotDeviceQueryApi.getDevice 取齐 state + nickname + deviceName,根据状态写 Redis(IDLE/OFFLINE); - 解绑:直接调 BadgeDeviceStatusService.deleteBadgeStatus 清理 Redis。 依赖方向遵循 environment-biz → ops-biz;ops-biz 不反向依赖条线模块, 通过事件解耦,与现有 OrderStateChangedEvent 模式一致。 测试: - AreaDeviceRelationServiceTest 补 iotDeviceQueryApi mock 让原本因缺 mock 而 RED 的 testBindDevice_TrafficCounter_Success 转绿; - 新增对 bound / unbound 事件 publishEvent 调用的 verify。 - 全套 10/10 通过。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>