mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 03:13:41 +08:00
feat(wiki): pluggable ingest-source SPI + source-watcher status API
This commit is contained in:
parent
ea0ace1e49
commit
c627f898ec
@ -61,6 +61,7 @@ public class WikiController {
|
||||
private final AuditEventService auditEventService;
|
||||
private final WikiPageTypeProfileService pageTypeProfileService;
|
||||
private final WikiSourcePathValidator pathValidator;
|
||||
private final vip.mate.wiki.service.WikiSourceWatcherService sourceWatcherService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
// ==================== Knowledge Base ====================
|
||||
@ -302,6 +303,47 @@ public class WikiController {
|
||||
return R.ok(response);
|
||||
}
|
||||
|
||||
// ==================== Source Watcher ====================
|
||||
|
||||
@RequireWorkspaceRole("viewer")
|
||||
@Operation(summary = "查看知识库源监听状态")
|
||||
@GetMapping("/knowledge-bases/{id}/source-watcher")
|
||||
public R<Map<String, Object>> getSourceWatcher(@PathVariable Long id,
|
||||
@RequestHeader(value = "X-Workspace-Id", required = false) Long workspaceId) {
|
||||
verifyKBWorkspace(id, workspaceId);
|
||||
WikiKnowledgeBaseEntity kb = kbService.getById(id);
|
||||
if (kb == null) return R.fail(404, "Knowledge base not found");
|
||||
vip.mate.wiki.source.WikiIngestSourceProvider provider = sourceWatcherService.providerFor(kb);
|
||||
Map<String, Object> out = new LinkedHashMap<>();
|
||||
out.put("watcherEnabled", properties.isWatcherEnabled());
|
||||
out.put("intervalMs", properties.getWatcherIntervalMs());
|
||||
out.put("sourceDirectory", kb.getSourceDirectory());
|
||||
out.put("sourceType", provider != null ? provider.sourceType() : null);
|
||||
out.put("availableSourceTypes", sourceWatcherService.availableSourceTypes());
|
||||
out.put("active", provider != null);
|
||||
return R.ok(out);
|
||||
}
|
||||
|
||||
@RequireWorkspaceRole("member")
|
||||
@Operation(summary = "手动触发一次源监听扫描")
|
||||
@PostMapping("/knowledge-bases/{id}/source-watcher/scan")
|
||||
public R<Map<String, Object>> triggerSourceWatcher(@PathVariable Long id,
|
||||
@RequestHeader(value = "X-Workspace-Id", required = false) Long workspaceId) {
|
||||
verifyKBWorkspace(id, workspaceId);
|
||||
WikiKnowledgeBaseEntity kb = kbService.getById(id);
|
||||
if (kb == null) return R.fail(404, "Knowledge base not found");
|
||||
vip.mate.wiki.source.WikiIngestSourceProvider provider = sourceWatcherService.providerFor(kb);
|
||||
if (provider == null) return R.fail(400, "No source configured for this knowledge base");
|
||||
WikiDirectoryScanService.ScanResult result = provider.sync(kb);
|
||||
Map<String, Object> out = new LinkedHashMap<>();
|
||||
out.put("sourceType", provider.sourceType());
|
||||
out.put("scanned", result.scanned());
|
||||
out.put("added", result.added());
|
||||
out.put("skipped", result.skipped());
|
||||
out.put("errors", result.errors());
|
||||
return R.ok(out);
|
||||
}
|
||||
|
||||
// ==================== Raw Materials ====================
|
||||
|
||||
@RequireWorkspaceRole("viewer")
|
||||
|
||||
@ -26,15 +26,20 @@ import vip.mate.wiki.model.WikiKnowledgeBaseEntity;
|
||||
public class WikiSourceWatcherService {
|
||||
|
||||
private final WikiKnowledgeBaseService kbService;
|
||||
private final WikiDirectoryScanService scanService;
|
||||
private final WikiProperties properties;
|
||||
private final java.util.List<vip.mate.wiki.source.WikiIngestSourceProvider> sourceProviders;
|
||||
|
||||
public WikiSourceWatcherService(WikiKnowledgeBaseService kbService,
|
||||
WikiDirectoryScanService scanService,
|
||||
WikiProperties properties) {
|
||||
WikiProperties properties,
|
||||
java.util.List<vip.mate.wiki.source.WikiIngestSourceProvider> sourceProviders) {
|
||||
this.kbService = kbService;
|
||||
this.scanService = scanService;
|
||||
this.properties = properties;
|
||||
this.sourceProviders = sourceProviders;
|
||||
}
|
||||
|
||||
/** The registered source-provider types (filesystem ships; api/mq pluggable later). */
|
||||
public java.util.List<String> availableSourceTypes() {
|
||||
return sourceProviders.stream().map(vip.mate.wiki.source.WikiIngestSourceProvider::sourceType).toList();
|
||||
}
|
||||
|
||||
/** Scheduled entry point — gated by config, serialized across instances. */
|
||||
@ -58,20 +63,32 @@ public class WikiSourceWatcherService {
|
||||
public int runScanCycle() {
|
||||
int totalAdded = 0;
|
||||
for (WikiKnowledgeBaseEntity kb : kbService.listAll()) {
|
||||
String dir = kb.getSourceDirectory();
|
||||
if (dir == null || dir.isBlank()) {
|
||||
vip.mate.wiki.source.WikiIngestSourceProvider provider = providerFor(kb);
|
||||
if (provider == null) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
WikiDirectoryScanService.ScanResult result = scanService.scanDirectory(kb.getId(), dir);
|
||||
WikiDirectoryScanService.ScanResult result = provider.sync(kb);
|
||||
totalAdded += result.added();
|
||||
if (!result.errors().isEmpty()) {
|
||||
log.warn("[WikiWatcher] KB {} scan reported issues: {}", kb.getId(), result.errors());
|
||||
log.warn("[WikiWatcher] KB {} ({}) sync reported issues: {}",
|
||||
kb.getId(), provider.sourceType(), result.errors());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("[WikiWatcher] scan failed for KB {}: {}", kb.getId(), e.getMessage());
|
||||
log.warn("[WikiWatcher] sync failed for KB {} ({}): {}",
|
||||
kb.getId(), provider.sourceType(), e.getMessage());
|
||||
}
|
||||
}
|
||||
return totalAdded;
|
||||
}
|
||||
|
||||
/** The first registered provider that supports the KB, or null. */
|
||||
public vip.mate.wiki.source.WikiIngestSourceProvider providerFor(WikiKnowledgeBaseEntity kb) {
|
||||
for (vip.mate.wiki.source.WikiIngestSourceProvider p : sourceProviders) {
|
||||
if (p.supports(kb)) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
package vip.mate.wiki.source;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import vip.mate.wiki.model.WikiKnowledgeBaseEntity;
|
||||
import vip.mate.wiki.service.WikiDirectoryScanService;
|
||||
|
||||
/**
|
||||
* The built-in filesystem source: syncs a KB by scanning its configured source
|
||||
* directory (path validation, symlink resolution and content-hash change
|
||||
* detection live in the scan service).
|
||||
*
|
||||
* @author MateClaw Team
|
||||
*/
|
||||
@Component
|
||||
public class FilesystemSourceProvider implements WikiIngestSourceProvider {
|
||||
|
||||
private final WikiDirectoryScanService scanService;
|
||||
|
||||
public FilesystemSourceProvider(WikiDirectoryScanService scanService) {
|
||||
this.scanService = scanService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String sourceType() {
|
||||
return "filesystem";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(WikiKnowledgeBaseEntity kb) {
|
||||
return kb != null && kb.getSourceDirectory() != null && !kb.getSourceDirectory().isBlank();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WikiDirectoryScanService.ScanResult sync(WikiKnowledgeBaseEntity kb) {
|
||||
return scanService.scanDirectory(kb.getId(), kb.getSourceDirectory());
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package vip.mate.wiki.source;
|
||||
|
||||
import vip.mate.wiki.model.WikiKnowledgeBaseEntity;
|
||||
import vip.mate.wiki.service.WikiDirectoryScanService;
|
||||
|
||||
/**
|
||||
* Pluggable source of raw material for a knowledge base. The watcher iterates
|
||||
* KBs and asks each registered provider whether it {@link #supports} the KB,
|
||||
* then {@link #sync}s it. The filesystem implementation ships today; API /
|
||||
* message-queue sources can be added later by implementing this interface
|
||||
* without touching the watcher.
|
||||
*
|
||||
* @author MateClaw Team
|
||||
*/
|
||||
public interface WikiIngestSourceProvider {
|
||||
|
||||
/** Stable source-type id, e.g. {@code filesystem} / {@code api} / {@code mq}. */
|
||||
String sourceType();
|
||||
|
||||
/** Whether this provider can sync the given KB (e.g. it has the relevant config). */
|
||||
boolean supports(WikiKnowledgeBaseEntity kb);
|
||||
|
||||
/**
|
||||
* Pull new / changed material for the KB and ingest it, returning the
|
||||
* scan-style result (scanned / added / skipped / errors).
|
||||
*/
|
||||
WikiDirectoryScanService.ScanResult sync(WikiKnowledgeBaseEntity kb);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user