mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
fix(wiki): recover raw materials stuck in processing on server restart
Root cause: recoverOnStartup() only reset mate_wiki_processing_job rows, not mate_wiki_raw_material. claimForProcessing() only accepts pending, so restart-orphaned processing rows were permanently stuck — frontend showed "preparing..." forever. Fix: - Add WikiRawMaterialService.recoverStuckRawMaterialsOnStartup(): resets processing→pending, clears progress fields, fires WikiProcessingEvent when autoProcessOnUpload is enabled - WikiAutoConfiguration: call raw recovery after job recovery - Execution order: job table first (queued), then raw table (pending) Test: WikiRawMaterialRecoveryTest — 4 cases: reset + events, reset without events (autoProcess=false), noop on empty.
This commit is contained in:
parent
cbcb7229b6
commit
a171f2ac0e
@ -7,6 +7,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import vip.mate.wiki.job.WikiProcessingJobService;
|
||||
import vip.mate.wiki.service.WikiRawMaterialService;
|
||||
|
||||
/**
|
||||
* Wiki module auto-configuration
|
||||
@ -20,12 +21,19 @@ import vip.mate.wiki.job.WikiProcessingJobService;
|
||||
public class WikiAutoConfiguration {
|
||||
|
||||
private final WikiProcessingJobService wikiProcessingJobService;
|
||||
private final WikiRawMaterialService wikiRawMaterialService;
|
||||
|
||||
/**
|
||||
* RFC-030: Recover stuck wiki processing jobs on startup.
|
||||
* Recover stuck wiki state on startup:
|
||||
* 1. Job table: routing/*_running → queued (RFC-030)
|
||||
* 2. Raw material table: processing → pending (avoids forever-spinning progress bars)
|
||||
*/
|
||||
@EventListener(ApplicationReadyEvent.class)
|
||||
public void recoverWikiJobs(ApplicationReadyEvent event) {
|
||||
wikiProcessingJobService.recoverOnStartup();
|
||||
int recovered = wikiRawMaterialService.recoverStuckRawMaterialsOnStartup();
|
||||
if (recovered > 0) {
|
||||
log.info("[Wiki] Recovered {} stuck raw materials on startup", recovered);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -332,6 +332,36 @@ public class WikiRawMaterialService {
|
||||
return entity.getOriginalContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recover raw materials stuck in 'processing' status after a server restart.
|
||||
* Resets them to 'pending', clears stale progress fields, and optionally
|
||||
* fires processing events so they get picked up automatically.
|
||||
*
|
||||
* @return number of recovered rows
|
||||
*/
|
||||
@Transactional
|
||||
public int recoverStuckRawMaterialsOnStartup() {
|
||||
List<WikiRawMaterialEntity> stuck = rawMapper.selectList(
|
||||
new LambdaQueryWrapper<WikiRawMaterialEntity>()
|
||||
.eq(WikiRawMaterialEntity::getProcessingStatus, "processing"));
|
||||
if (stuck.isEmpty()) return 0;
|
||||
|
||||
for (WikiRawMaterialEntity raw : stuck) {
|
||||
raw.setProcessingStatus("pending");
|
||||
raw.setProgressPhase(null);
|
||||
raw.setProgressTotal(0);
|
||||
raw.setProgressDone(0);
|
||||
raw.setErrorMessage(null);
|
||||
rawMapper.updateById(raw);
|
||||
|
||||
if (properties.isAutoProcessOnUpload()) {
|
||||
eventPublisher.publishEvent(new WikiProcessingEvent(this, raw.getId(), raw.getKbId()));
|
||||
}
|
||||
log.info("[Wiki] Recovered stuck processing raw material: id={}, kbId={}", raw.getId(), raw.getKbId());
|
||||
}
|
||||
return stuck.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a duplicate upload: decide what to do based on the existing row's status.
|
||||
* - completed → return as-is (no reprocessing needed)
|
||||
|
||||
@ -0,0 +1,110 @@
|
||||
package vip.mate.wiki.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import vip.mate.tool.builtin.DocumentExtractTool;
|
||||
import vip.mate.wiki.WikiProperties;
|
||||
import vip.mate.wiki.event.WikiProcessingEvent;
|
||||
import vip.mate.wiki.model.WikiRawMaterialEntity;
|
||||
import vip.mate.wiki.repository.WikiRawMaterialMapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* Verifies that raw materials stuck in 'processing' status after a server
|
||||
* restart are recovered to 'pending' with progress fields cleared.
|
||||
*/
|
||||
class WikiRawMaterialRecoveryTest {
|
||||
|
||||
private WikiRawMaterialMapper rawMapper;
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
private WikiRawMaterialService service;
|
||||
private WikiProperties props;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
rawMapper = mock(WikiRawMaterialMapper.class);
|
||||
WikiKnowledgeBaseService kbService = mock(WikiKnowledgeBaseService.class);
|
||||
eventPublisher = mock(ApplicationEventPublisher.class);
|
||||
WikiChunkService chunkService = mock(WikiChunkService.class);
|
||||
DocumentExtractTool docTool = mock(DocumentExtractTool.class);
|
||||
props = new WikiProperties();
|
||||
props.setAutoProcessOnUpload(true);
|
||||
service = new WikiRawMaterialService(rawMapper, kbService, props, eventPublisher, docTool, chunkService);
|
||||
}
|
||||
|
||||
private WikiRawMaterialEntity stuckRow(Long id, Long kbId) {
|
||||
WikiRawMaterialEntity e = new WikiRawMaterialEntity();
|
||||
e.setId(id);
|
||||
e.setKbId(kbId);
|
||||
e.setProcessingStatus("processing");
|
||||
e.setProgressPhase("route");
|
||||
e.setProgressTotal(5);
|
||||
e.setProgressDone(2);
|
||||
return e;
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Stuck 'processing' rows are reset to 'pending' with cleared progress")
|
||||
void recoverStuck_resetsToPending() {
|
||||
WikiRawMaterialEntity row1 = stuckRow(10L, 1L);
|
||||
WikiRawMaterialEntity row2 = stuckRow(20L, 2L);
|
||||
when(rawMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of(row1, row2));
|
||||
|
||||
int count = service.recoverStuckRawMaterialsOnStartup();
|
||||
|
||||
assertEquals(2, count);
|
||||
assertEquals("pending", row1.getProcessingStatus());
|
||||
assertNull(row1.getProgressPhase());
|
||||
assertEquals(0, row1.getProgressTotal());
|
||||
assertEquals(0, row1.getProgressDone());
|
||||
assertEquals("pending", row2.getProcessingStatus());
|
||||
verify(rawMapper, times(2)).updateById(any(WikiRawMaterialEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Recovery fires WikiProcessingEvent when autoProcessOnUpload is true")
|
||||
void recoverStuck_firesEvents() {
|
||||
WikiRawMaterialEntity row = stuckRow(10L, 1L);
|
||||
when(rawMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of(row));
|
||||
|
||||
service.recoverStuckRawMaterialsOnStartup();
|
||||
|
||||
ArgumentCaptor<WikiProcessingEvent> captor = ArgumentCaptor.forClass(WikiProcessingEvent.class);
|
||||
verify(eventPublisher).publishEvent(captor.capture());
|
||||
assertEquals(10L, captor.getValue().getRawMaterialId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Recovery does NOT fire events when autoProcessOnUpload is false")
|
||||
void recoverStuck_noEventsWhenDisabled() {
|
||||
props.setAutoProcessOnUpload(false);
|
||||
WikiRawMaterialEntity row = stuckRow(10L, 1L);
|
||||
when(rawMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of(row));
|
||||
|
||||
service.recoverStuckRawMaterialsOnStartup();
|
||||
|
||||
assertEquals("pending", row.getProcessingStatus());
|
||||
verify(eventPublisher, never()).publishEvent(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("No stuck rows means zero recovery, no events")
|
||||
void recoverStuck_emptyIsNoop() {
|
||||
when(rawMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of());
|
||||
|
||||
int count = service.recoverStuckRawMaterialsOnStartup();
|
||||
|
||||
assertEquals(0, count);
|
||||
verify(rawMapper, never()).updateById(any(WikiRawMaterialEntity.class));
|
||||
verify(eventPublisher, never()).publishEvent(any());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user