Files
halo-dark-mode-plugin/.agents/skills/halo-plugin-dev/references/server-reconciler.md
T
Serendipity edbf78f236 feat: 初始化 Halo 暗色模式插件
- Halo Plugin 后端(Java/Gradle),含 DarkModePlugin 主类和测试
- Vue 3 + TypeScript 前端 UI,包含主题切换组件和设置页面
- 暗色模式 CSS 变量和覆盖样式(布局/编辑器/表单/滚动条等)
- 设计文档和调查文档
- Halo 插件/主题开发 Agent Skills

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-06 21:03:00 +08:00

145 lines
5.2 KiB
Markdown

# Reconciler (Controller Pattern)
A Kubernetes-style controller that watches Extension resources and continuously reconciles their desired state. More commonly used than `Watcher` in production plugins.
> Source: [Reconciler](https://github.com/halo-dev/halo/blob/main/api/src/main/java/run/halo/app/extension/controller/Reconciler.java) | [ControllerBuilder](https://github.com/halo-dev/halo/blob/main/api/src/main/java/run/halo/app/extension/controller/ControllerBuilder.java) | [Controller](https://github.com/halo-dev/halo/blob/main/api/src/main/java/run/halo/app/extension/controller/Controller.java)
## Basic Pattern
```java
@Component
public class MyReconciler implements Reconciler<Reconciler.Request> {
private final ExtensionClient client;
public MyReconciler(ExtensionClient client) {
this.client = client;
}
@Override
public Result reconcile(Request request) {
// Fetch the extension being reconciled
var myExt = client.fetch(MyExtension.class, request.name());
if (myExt.isEmpty()) {
return Result.doNotRetry();
}
// Perform reconciliation logic
var ext = myExt.get();
// ... update annotations, labels, related resources, etc.
// Update the extension if modified
client.update(ext);
return Result.doNotRetry();
}
@Override
public Controller setupWith(ControllerBuilder builder) {
return builder
.extension(new MyExtension())
.syncAllOnStart(false)
.build();
}
}
```
## ControllerBuilder Options
| Method | Description |
| ---------------------------------- | ----------------------------------------------------------- |
| `.extension(new MyExtension())` | The extension type to watch (required) |
| `.syncAllOnStart(true)` | Reconcile all existing instances on startup (default: true) |
| `.syncAllListOptions(listOptions)` | Filter which existing instances to sync on start |
| `.minDelay(Duration)` | Minimum retry delay (default: 5ms) |
| `.maxDelay(Duration)` | Maximum retry delay (default: 1000s) |
| `.workerCount(int)` | Number of concurrent workers (default: 1) |
| `.onAddMatcher(matcher)` | Filter which add events to process |
| `.onUpdateMatcher(matcher)` | Filter which update events to process |
| `.onDeleteMatcher(matcher)` | Filter which delete events to process |
## Result Types
```java
// Success, do not retry
return Result.doNotRetry();
// Requeue after a delay (for async operations or retry)
return Result.requeue(Duration.ofSeconds(30));
```
## Lifecycle Integration
Reconcilers are auto-discovered by Spring. If you need to start/stop manually (e.g., conditional on plugin config):
```java
@Component
public class MyReconciler implements Reconciler<Request>, SmartLifecycle {
private Controller controller;
private boolean running = false;
@Override
public Controller setupWith(ControllerBuilder builder) {
this.controller = builder.extension(new MyExtension()).build();
return controller;
}
@Override
public void start() {
if (controller != null && !running) {
controller.start();
running = true;
}
}
@Override
public void stop() {
if (controller != null && running) {
controller.dispose(); // or controller.stop()
running = false;
}
}
@Override
public boolean isRunning() { return running; }
}
```
## Finalizers
Use finalizers for cleanup before an extension is deleted:
```java
@Override
public Result reconcile(Request request) {
var ext = client.fetch(MyExtension.class, request.name()).orElse(null);
if (ext == null) return Result.doNotRetry();
// Check if being deleted
if (ext.getMetadata().getDeletionTimestamp() != null) {
// Perform cleanup
doCleanup(ext);
// Remove finalizer to allow deletion
ExtensionUtil.removeFinalizers(ext.getMetadata(), Set.of("my-plugin/finalizer"));
client.update(ext);
return Result.doNotRetry();
}
// Add finalizer if not present
ExtensionUtil.addFinalizers(ext.getMetadata(), Set.of("my-plugin/finalizer"));
client.update(ext);
// Normal reconciliation
return Result.doNotRetry();
}
```
## When to Use Reconciler vs Watcher
| | Reconciler | Watcher |
| --------------- | ---------------------------------------------------- | ----------------------- |
| **Use case** | Continuous state reconciliation, finalizers, retries | One-time event handling |
| **Persistence** | Queued, survives restarts | In-memory only |
| **Concurrency** | Configurable worker count | Single threaded |
| **Retry** | Built-in exponential backoff | No retry |