mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-16 12:27:53 +08:00
feat(wiki): fire count-threshold pipeline triggers after ingest
This commit is contained in:
parent
526a361488
commit
ad1f5b4a15
@ -0,0 +1,11 @@
|
|||||||
|
package vip.mate.wiki.event;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Published after a wiki page is created during ingest (already committed,
|
||||||
|
* since page creation is its own transaction). Consumed asynchronously to
|
||||||
|
* evaluate count-threshold pipeline triggers without blocking ingest.
|
||||||
|
*
|
||||||
|
* @author MateClaw Team
|
||||||
|
*/
|
||||||
|
public record WikiPageCreatedEvent(Long kbId, String pageType) {
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package vip.mate.wiki.pipeline;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import vip.mate.wiki.event.WikiPageCreatedEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluates count-threshold pipeline triggers asynchronously when a page is
|
||||||
|
* created during ingest. Runs off the ingest thread so a fired pipeline (which
|
||||||
|
* may call a model) never blocks ingest; the page is already committed when the
|
||||||
|
* event fires, so the count is accurate, and the run dedup key keeps repeated
|
||||||
|
* evaluation idempotent.
|
||||||
|
*
|
||||||
|
* @author MateClaw Team
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class WikiPipelineTriggerListener {
|
||||||
|
|
||||||
|
private final WikiPipelineTriggerService triggerService;
|
||||||
|
|
||||||
|
public WikiPipelineTriggerListener(WikiPipelineTriggerService triggerService) {
|
||||||
|
this.triggerService = triggerService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
@EventListener
|
||||||
|
public void onPageCreated(WikiPageCreatedEvent event) {
|
||||||
|
try {
|
||||||
|
triggerService.onPageTypeCount(event.kbId(), event.pageType());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[WikiPipeline] trigger evaluation failed for kb={} pageType={}: {}",
|
||||||
|
event.kbId(), event.pageType(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1454,6 +1454,11 @@ public class WikiProcessingService {
|
|||||||
pageService.mergeSourceLineage(created.getId(), rawId, raw.getTitle());
|
pageService.mergeSourceLineage(created.getId(), rawId, raw.getTitle());
|
||||||
log.info("[Wiki] Phase B create page slug='{}' done (created)", slug);
|
log.info("[Wiki] Phase B create page slug='{}' done (created)", slug);
|
||||||
citationService.buildCitationsAsync(created.getId(), kbId);
|
citationService.buildCitationsAsync(created.getId(), kbId);
|
||||||
|
// Evaluate count-threshold pipeline triggers off-thread (page is now
|
||||||
|
// committed, so the count is accurate); no-op when no pipelines match.
|
||||||
|
if (eventPublisher != null && pageType != null && !pageType.isBlank()) {
|
||||||
|
eventPublisher.publishEvent(new vip.mate.wiki.event.WikiPageCreatedEvent(kbId, pageType));
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (org.springframework.dao.DuplicateKeyException e) {
|
} catch (org.springframework.dao.DuplicateKeyException e) {
|
||||||
// Fallback 2: concurrent INSERT race — degrade to update
|
// Fallback 2: concurrent INSERT race — degrade to update
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
package vip.mate.wiki.pipeline;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import vip.mate.wiki.event.WikiPageCreatedEvent;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for {@link WikiPipelineTriggerListener}: it forwards the event to
|
||||||
|
* the trigger service and never lets a trigger failure escape (so a broken
|
||||||
|
* pipeline cannot disturb ingest).
|
||||||
|
*/
|
||||||
|
class WikiPipelineTriggerListenerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void forwardsEventToTriggerService() {
|
||||||
|
WikiPipelineTriggerService trigger = mock(WikiPipelineTriggerService.class);
|
||||||
|
when(trigger.onPageTypeCount(7L, "episode")).thenReturn(1);
|
||||||
|
|
||||||
|
new WikiPipelineTriggerListener(trigger).onPageCreated(new WikiPageCreatedEvent(7L, "episode"));
|
||||||
|
|
||||||
|
verify(trigger).onPageTypeCount(7L, "episode");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void swallowsTriggerFailure() {
|
||||||
|
WikiPipelineTriggerService trigger = mock(WikiPipelineTriggerService.class);
|
||||||
|
doThrow(new RuntimeException("boom")).when(trigger).onPageTypeCount(7L, "episode");
|
||||||
|
|
||||||
|
// Must not throw — ingest must be unaffected by a pipeline failure.
|
||||||
|
new WikiPipelineTriggerListener(trigger).onPageCreated(new WikiPageCreatedEvent(7L, "episode"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user