| .gitignore | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| ruoyi-admin/src/main/resources/db/migration/V19__collector_disconnect_open_dedupe.sql | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| ruoyi-admin/src/test/java/com/ruoyi/config/FlywayMigrationContractTest.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| ruoyi-admin/src/test/java/com/ruoyi/system/service/impl/CollectorDisconnectRecoveryServiceImplTest.java | ●●●●● 补丁 | 查看 | 原始文档 | 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) {