yeg
2026-08-30 56cddf53e7e71afad29885d7fcf773a222b95f6c
修复采集器断线区间重复关闭
1个文件已添加
3个文件已修改
48 ■■■■■ 已修改文件
.gitignore 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/db/migration/V19__collector_disconnect_open_dedupe.sql 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/test/java/com/ruoyi/config/FlywayMigrationContractTest.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/CollectorDisconnectRecoveryServiceImplTest.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -13,5 +13,6 @@
!/ruoyi-admin/src/main/resources/db/migration/V7__complete_device_registration_boundary.sql
!/ruoyi-admin/src/main/resources/db/migration/V8__complete_collector_page_contract.sql
!/ruoyi-admin/src/main/resources/db/migration/V9__move_discovery_candidates_to_redis.sql
!/ruoyi-admin/src/main/resources/db/migration/V19__collector_disconnect_open_dedupe.sql
/.mvn-review-repository/
/ruoyi-admin/logs/
ruoyi-admin/src/main/resources/db/migration/V19__collector_disconnect_open_dedupe.sql
New file
@@ -0,0 +1,11 @@
-- Ticket11:唯一约束只覆盖开放中的采集器断线区间,关闭区间保留为历史追加记录。
ALTER TABLE t_test_collector_exception_interval
    DROP INDEX uk_collector_exception_open,
    ADD COLUMN open_dedupe_key VARCHAR(320)
        GENERATED ALWAYS AS (
            CASE WHEN interval_status = 'OPEN' THEN
                CONCAT(test_id, ':', battery_relation_id, ':', collector_id, ':',
                       collector_channel_code, ':', exception_type)
            ELSE NULL END
        ) STORED AFTER interval_status,
    ADD UNIQUE KEY uk_collector_exception_open (open_dedupe_key);
ruoyi-admin/src/test/java/com/ruoyi/config/FlywayMigrationContractTest.java
@@ -135,6 +135,14 @@
        assertTrue(v18.contains("uk_collector_exception_open"));
        assertFalse(v18.matches("(?s).*\\bDROP\\s+TABLE\\b.*"));
        String v19 = resource("db/migration/V19__collector_disconnect_open_dedupe.sql");
        assertTrue(v19.contains("DROP INDEX uk_collector_exception_open"));
        assertTrue(v19.contains("open_dedupe_key VARCHAR(320)"));
        assertTrue(v19.contains("interval_status = 'OPEN'"));
        assertTrue(v19.contains("ELSE NULL"));
        assertTrue(v19.contains("ADD UNIQUE KEY uk_collector_exception_open (open_dedupe_key)"));
        assertFalse(v19.matches("(?s).*\\bDROP\\s+TABLE\\b.*"));
        String testMapper = resource("mapper/system/TTestMapper.xml");
        assertTrue(testMapper.contains("selectReceivingChannelsForAdapter"));
        assertTrue(testMapper.contains("RECEPTION_RESUMED"));
ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/CollectorDisconnectRecoveryServiceImplTest.java
@@ -56,6 +56,34 @@
        verify(mapper, never()).insertCollectorExceptionInterval(any());
    }
    @Test
    void allowsAnotherDisconnectCycleAfterPreviousIntervalWasClosed()
    {
        TTestCollectorSource source = source(10L, 20L, 8L, "COL-1", "S-01");
        TTestCollectorExceptionInterval first = openInterval(99L);
        TTestCollectorExceptionInterval second = openInterval(100L);
        when(mapper.selectReceivingCollectorSources()).thenReturn(List.of(source));
        when(mapper.selectOpenCollectorException(10L, 20L, 8L, "S-01"))
                .thenReturn(null, first, null, second);
        service.reconcile(Set.of(), Instant.parse("2026-08-30T09:00:00Z"));
        service.reconcile(Set.of("COL-1"), Instant.parse("2026-08-30T09:01:00Z"));
        service.reconcile(Set.of(), Instant.parse("2026-08-30T09:02:00Z"));
        service.reconcile(Set.of("COL-1"), Instant.parse("2026-08-30T09:03:00Z"));
        verify(mapper, org.mockito.Mockito.times(2)).insertCollectorExceptionInterval(any());
        verify(mapper).closeCollectorExceptionInterval(eq(99L), any(), any());
        verify(mapper).closeCollectorExceptionInterval(eq(100L), any(), any());
    }
    private TTestCollectorExceptionInterval openInterval(Long intervalId)
    {
        TTestCollectorExceptionInterval interval = new TTestCollectorExceptionInterval();
        interval.setIntervalId(intervalId);
        interval.setIntervalStatus("OPEN");
        return interval;
    }
    private TTestCollectorSource source(Long testId, Long relationId, Long collectorId,
            String collectorCode, String channelCode)
    {