Преглед на файлове

revert(cockpit): 撤回 alarm_no 优先解析 —— 原按 id 解析本就是对的

905f593 的依据是「alarm_data.id 全部是全零占位值」,该观测**错误**:
它是控制台把 32 位十六进制串统一渲染成 0 的显示假象,连 md5(id) 都被同样
渲染。用字符直方图与 base64 复核后确认 6 行 id 是互不相同的真实 UUID
(非零字符数 30/29/32/29/32/30),且 `count(DISTINCT id)=6`。

实测也证明原实现可用:`GET /events/ALARM/fca8fd6b.../work-orders`
与 `.../8b5c81fc.../work-orders` 均回 READY + 1 张工单。故:

- `findAlarmByReference` 恢复为**先按主键 id**(`getById`),查不到时才回退
  按 `alarm_no` 查(作为便于用单号定位的补充,不破坏原语义)。
- `alarmReference` 恢复为优先回 `id`,缺失时才回 `alarm_no`。
- 更新相关注释,删除「全零占位值」的错误描述。

保留 905f593 中仍然正确的部分:
- 设备/报警维度的工单查询由 `lambdaQuery()` 链改为 `list(wrapper)`
  (与仓库其它只读适配器一致、可测)。
- 新增 `BusinessCockpitWorkOrderReadAdapterTest`(3 例)锁定报警定位与
  未关联显式标记;测试改用真实 UUID。

验证:`-Dtest=Cockpit*Test,Business*AdapterTest` **372/372 通过**。
Kazerin преди 1 седмица
родител
ревизия
18db91059d

+ 36 - 20
pipe-network-service/zksy-admin/src/test/java/com/zksy/web/controller/cockpit/BusinessCockpitWorkOrderReadAdapterTest.java

@@ -31,21 +31,21 @@ import static org.mockito.Mockito.when;
 /**
  * 工单读适配器契约(执行票 12 范围 1)。
  *
- * <p>这两组断言针对**真实库暴露过的缺陷**,控制器层测试用 mock 适配器测不到:
+ * <p>断言针对真实库核实过的行为:
  * <ol>
- *   <li>`alarm_data.id` 在真实库中全部是同一个**全零占位值**(非标识性)——
- *       若按 `id` 解析报警,`events/ALARM/{code}/work-orders` 恒回 EMPTY,
- *       即使该报警确实有工单。故必须按 `alarm_no` 解析。</li>
+ *   <li>报警事件端点用 {@code alarm_data.id}(真实 UUID,各报警互不相同)定位,
+ *       命中后回该报警实际关联的工单,而不是恒回 EMPTY。</li>
  *   <li>设备维度返回该设备已有**全部**工单,未关联报警者须**显式**标 false,
- *       且不得伪造 `eventAssociation`(不得拿全零 id 冒充报警标识)。</li>
+ *       且不得伪造 `eventAssociation`。</li>
  * </ol>
  */
 class BusinessCockpitWorkOrderReadAdapterTest {
 
     private static final String DEVICE_CODE = "EQ-001";
     private static final String EQUIPMENT_ID = "EQID-001";
-    /** 真实库中 alarm_data.id 的取值:全零占位,不具标识性。 */
-    private static final String PLACEHOLDER_ID = "00000000000000000000000000000000";
+    /** 报警主键:真实库中为 32 位 UUID,各报警互不相同。 */
+    private static final String ALARM_ID = "fca8fd6b6d6f3408e964db88640dc399";
+    private static final String ALARM_NO = "BJ202609291243545660";
 
     private WorkOrderService workOrderService;
     private AlarmDataService alarmDataService;
@@ -81,26 +81,42 @@ class BusinessCockpitWorkOrderReadAdapterTest {
                 entityClass);
     }
 
-    /** 报警维度:必须按 alarm_no 解析,不能因主键是全零占位就恒回 EMPTY。 */
+    /** 报警维度:按报警主键解析,命中后回该报警实际关联的工单(不得恒回 EMPTY)。 */
     @Test
-    void alarmEventResolvesByAlarmNoNotByPlaceholderId() {
-        AlarmData alarm = alarm(PLACEHOLDER_ID, "BJ202609291243545660", 279L);
-        when(alarmDataService.list(any(LambdaQueryWrapper.class)))
-                .thenReturn(Collections.singletonList(alarm));
+    void alarmEventResolvesByAlarmIdAndReturnsItsLinkedOrders() {
+        AlarmData alarm = alarm(ALARM_ID, ALARM_NO, 279L);
+        when(alarmDataService.getById(ALARM_ID)).thenReturn(alarm);
         when(workOrderService.list(any(LambdaQueryWrapper.class)))
                 .thenReturn(Collections.singletonList(order(279L, "WO-279", 5)));
 
-        CockpitWorkOrderReadSnapshot snapshot =
-                adapter.loadByEvent("ALARM", "BJ202609291243545660");
+        CockpitWorkOrderReadSnapshot snapshot = adapter.loadByEvent("ALARM", ALARM_ID);
 
-        assertNotNull(snapshot, "按真实单号必须能解析到报警");
+        assertNotNull(snapshot, "按报警主键必须能解析到报警");
         assertEquals(1, snapshot.getWorkOrders().size(), "该报警确有工单,不得回 EMPTY");
         CockpitWorkOrderSummary summary = snapshot.getWorkOrders().get(0);
         assertEquals("WO-279", summary.getWorkOrderCode());
         assertTrue(summary.isAssociatedWithCurrentEvent(), "报警维度命中的工单即实际关联");
-        // eventCode 必须回真实单号,不得回全零占位 id
-        assertEquals("BJ202609291243545660", summary.getEventAssociation().getEventCode());
-        assertEquals("BJ202609291243545660", snapshot.getEventCode());
+        // 对外标识回该报警主键(真实 UUID),与请求标识一致
+        assertEquals(ALARM_ID, summary.getEventAssociation().getEventCode());
+        assertEquals(ALARM_ID, snapshot.getEventCode());
+    }
+
+    /** 主键查不到时回退按 alarm_no 定位,便于调用方用真实单号查询。 */
+    @Test
+    void alarmEventFallsBackToAlarmNoWhenIdMisses() {
+        AlarmData alarm = alarm(ALARM_ID, ALARM_NO, 279L);
+        when(alarmDataService.getById(ALARM_NO)).thenReturn(null);
+        when(alarmDataService.list(any(LambdaQueryWrapper.class)))
+                .thenReturn(Collections.singletonList(alarm));
+        when(workOrderService.list(any(LambdaQueryWrapper.class)))
+                .thenReturn(Collections.singletonList(order(279L, "WO-279", 5)));
+
+        CockpitWorkOrderReadSnapshot snapshot = adapter.loadByEvent("ALARM", ALARM_NO);
+
+        assertNotNull(snapshot);
+        assertEquals(1, snapshot.getWorkOrders().size());
+        // 回显仍用报警主键,而不是请求里那个单号
+        assertEquals(ALARM_ID, snapshot.getEventCode());
     }
 
     /** 未关联报警的工单:显式 false 且不得伪造 eventAssociation。 */
@@ -112,7 +128,7 @@ class BusinessCockpitWorkOrderReadAdapterTest {
                 order(281L, "WO-ORPHAN", 1));
         // 只有 279 被报警引用;281 无任何报警关联
         when(alarmDataService.list(any(LambdaQueryWrapper.class)))
-                .thenReturn(Collections.singletonList(alarm(PLACEHOLDER_ID, "BJ-001", 279L)));
+                .thenReturn(Collections.singletonList(alarm(ALARM_ID, ALARM_NO, 279L)));
         when(workOrderService.list(any(LambdaQueryWrapper.class))).thenReturn(orders);
 
         CockpitWorkOrderReadSnapshot snapshot = adapter.loadByDevice(DEVICE_CODE);
@@ -120,7 +136,7 @@ class BusinessCockpitWorkOrderReadAdapterTest {
         assertEquals(2, snapshot.getWorkOrders().size(), "设备维度返回已有全部工单");
         CockpitWorkOrderSummary linked = snapshot.getWorkOrders().get(0);
         assertTrue(linked.isAssociatedWithCurrentEvent());
-        assertEquals("BJ-001", linked.getEventAssociation().getEventCode());
+        assertEquals(ALARM_ID, linked.getEventAssociation().getEventCode());
 
         CockpitWorkOrderSummary orphan = snapshot.getWorkOrders().get(1);
         assertFalse(orphan.isAssociatedWithCurrentEvent(), "未关联必须显式 false");

+ 11 - 10
pipe-network-service/zksy-system/src/main/java/com/zksy/cockpit/workorder/BusinessCockpitWorkOrderReadAdapter.java

@@ -78,14 +78,18 @@ public class BusinessCockpitWorkOrderReadAdapter implements CockpitWorkOrderRead
     /**
      * 按对外报警标识解析报警:先按 {@code alarm_no}(真实单号),再退回主键 {@code id}。
      *
-     * <p>必须优先 alarm_no —— 真实库中 `id` 是全零占位值,仅按 id 查不可能命中真实报警。
-     * 退回 id 是为了兼容将来主键恢复为真实 UUID 的数据。
+     * <p>报警对外标识就是 `alarm_data.id`(真实 UUID,实测 6 行互不相同)。
+     * 主键缺失时回退按 `alarm_no` 查,便于调用方用真实单号定位。
      */
     private AlarmData findAlarmByReference(String eventCode) {
         if (isBlank(eventCode)) {
             return null;
         }
         String reference = eventCode.trim();
+        AlarmData byId = alarmDataService.getById(reference);
+        if (byId != null) {
+            return byId;
+        }
         List<AlarmData> byNo = alarmDataService.list(new LambdaQueryWrapper<AlarmData>()
                 .eq(AlarmData::getAlarmNo, reference)
                 .last("LIMIT 1"));
@@ -96,18 +100,17 @@ public class BusinessCockpitWorkOrderReadAdapter implements CockpitWorkOrderRead
                 }
             }
         }
-        return alarmDataService.getById(reference);
+        return null;
     }
 
     /**
      * 报警的对外可读标识。
      *
-     * <p>真实库中 {@code alarm_data.id} **全部是全零占位值**(非标识性),拿它当
-     * {@code eventCode} 只会给出一个无法区分的假标识;{@code alarm_no} 才是真实单号。
-     * 故优先回 `alarm_no`,为空时才退回 `id`(不编造)。
+     * <p>`alarm_data.id` 是真实 UUID、可区分报警,直接用即可;仅在缺失时退回
+     * `alarm_no`(不编造)。
      */
     private static String alarmReference(AlarmData alarm) {
-        return isBlank(alarm.getAlarmNo()) ? alarm.getId() : alarm.getAlarmNo();
+        return isBlank(alarm.getId()) ? alarm.getAlarmNo() : alarm.getId();
     }
 
     private static boolean isBlank(String value) {
@@ -153,9 +156,7 @@ public class BusinessCockpitWorkOrderReadAdapter implements CockpitWorkOrderRead
     @Override
     public CockpitWorkOrderReadSnapshot loadByEvent(String eventSource, String eventCode) {
         if ("ALARM".equals(eventSource)) {
-            // 报警标识用 alarm_no 解析,不用主键 id:真实库中 alarm_data.id 全部是
-            // 同一个全零占位值(非标识性),getById 既无法区分报警、也永远取不到
-            // 任何真实报警(实测所有 ALARM 查询恒回 EMPTY,即使该报警确有工单)。
+            // 报警对外标识=alarm_data.id(真实 UUID,实测互不相同);按 alarm_no 亦可定位。
             AlarmData alarm = findAlarmByReference(eventCode);
             List<WorkOrder> orders = alarm == null || alarm.getWorkOrderId() == null
                     ? Collections.emptyList()