feat.文档完善

This commit is contained in:
lincube
2026-06-08 12:18:58 +08:00
parent 49af6601aa
commit 8d1dbaea54
8 changed files with 4841 additions and 0 deletions

View File

@@ -0,0 +1,719 @@
# IPlugin 接口详解
`IPlugin` 是所有插件的入口接口,定义了插件的基本信息和生命周期方法。
## 接口定义
```csharp
namespace LanMountainDesktop.PluginSdk;
/// <summary>
/// 插件接口
/// </summary>
public interface IPlugin
{
/// <summary>
/// 插件唯一标识符
/// 建议使用反向域名格式com.example.myplugin
/// </summary>
string Id { get; }
/// <summary>
/// 插件显示名称
/// </summary>
string Name { get; }
/// <summary>
/// 插件版本号
/// 应遵循语义化版本规范1.2.3
/// </summary>
string Version { get; }
/// <summary>
/// 插件初始化
/// 在插件加载后调用,用于注册组件、服务和事件
/// </summary>
/// <param name="context">插件上下文</param>
/// <returns>异步任务</returns>
Task InitializeAsync(IPluginContext context);
/// <summary>
/// 插件关闭
/// 在插件卸载前调用,用于清理资源和保存状态
/// </summary>
/// <returns>异步任务</returns>
Task ShutdownAsync();
}
```
## 属性详解
### Id
**类型**: `string`
**说明**: 插件的全局唯一标识符,必须在所有插件中唯一。
**命名规范**:
- 使用反向域名格式:`com.company.pluginname`
- 只包含小写字母、数字、点号和连字符
- 不能以数字或连字符开头
**示例**:
```csharp
public string Id => "com.example.weatherplugin";
```
**最佳实践**:
```csharp
// ✅ 好的示例
"com.example.weatherplugin"
"io.github.username.todoplugin"
"org.myorganization.monitorplugin"
// ❌ 不好的示例
"WeatherPlugin" // 不是反向域名格式
"com.example.Weather Plugin" // 包含空格
"123.example.plugin" // 以数字开头
```
### Name
**类型**: `string`
**说明**: 插件的显示名称,会在 UI 中展示给用户。
**要求**:
- 简洁明了,不超过 20 个字符
- 可以包含中文、英文、空格
- 不要包含版本号
**示例**:
```csharp
public string Name => "天气插件";
```
**最佳实践**:
```csharp
// ✅ 好的示例
"天气插件"
"待办事项"
"系统监控"
// ❌ 不好的示例
"天气插件 v1.0" // 包含版本号
"The Best Weather Plugin" // 过长且夸张
```
### Version
**类型**: `string`
**说明**: 插件的版本号,应遵循[语义化版本](https://semver.org/lang/zh-CN/)规范。
**格式**: `主版本号.次版本号.修订号`
**规则**:
- **主版本号**: 不兼容的 API 修改
- **次版本号**: 向下兼容的功能性新增
- **修订号**: 向下兼容的问题修正
**示例**:
```csharp
public string Version => "1.2.3";
```
**版本示例**:
```csharp
"1.0.0" // 首个稳定版本
"1.1.0" // 添加新功能,兼容 1.0.0
"1.1.1" // 修复 Bug兼容 1.1.0
"2.0.0" // 不兼容的 API 变更
```
## 方法详解
### InitializeAsync
**签名**:
```csharp
Task InitializeAsync(IPluginContext context);
```
**说明**: 插件加载后立即调用,用于初始化插件、注册组件和服务。
**参数**:
- `context`: 插件上下文,提供对宿主服务的访问
**返回值**: 异步任务
**调用时机**:
- 宿主启动时,所有插件发现后
- 插件热重载时
**执行要求**:
- ✅ 应该快速完成(< 5 秒)
- ✅ 耗时操作应放在后台线程
- ✅ 应该处理所有可能的异常
- ❌ 不要阻塞 UI 线程
**典型实现**:
```csharp
public async Task InitializeAsync(IPluginContext context)
{
try
{
// 1. 保存上下文引用
_context = context;
_logger = context.Logger;
_settings = context.Settings;
// 2. 记录日志
_logger.LogInformation($"{Name} v{Version} is initializing...");
// 3. 注册组件
RegisterComponents(context);
// 4. 注册设置页
RegisterSettingsPage(context);
// 5. 注册服务
RegisterServices(context);
// 6. 订阅事件
SubscribeEvents(context);
// 7. 耗时初始化(后台执行)
_ = Task.Run(async () =>
{
await InitializeDataAsync();
});
_logger.LogInformation($"{Name} initialized successfully");
}
catch (Exception ex)
{
context.Logger.LogError(ex, $"Failed to initialize {Name}");
throw; // 让宿主知道初始化失败
}
}
private void RegisterComponents(IPluginContext context)
{
var registry = context.Services.GetService<IComponentRegistry>();
if (registry != null)
{
registry.RegisterComponent<WeatherComponent>();
registry.RegisterComponent<ClockComponent>();
}
}
private void RegisterSettingsPage(IPluginContext context)
{
var settingsRegistry = context.Services
.GetService<ISettingsPageRegistry>();
if (settingsRegistry != null)
{
settingsRegistry.RegisterPage(
title: "天气插件",
category: "插件",
pageFactory: () => new WeatherSettingsPage()
);
}
}
private void RegisterServices(IPluginContext context)
{
// 注册插件内部服务
_weatherService = new WeatherService(_settings, _logger);
}
private void SubscribeEvents(IPluginContext context)
{
var eventBus = context.Services.GetService<IEventBus>();
if (eventBus != null)
{
eventBus.Subscribe<ThemeChangedEvent>(OnThemeChanged);
}
}
private async Task InitializeDataAsync()
{
// 加载缓存数据
await LoadCachedDataAsync();
// 预加载资源
await PreloadResourcesAsync();
}
```
**错误处理**:
```csharp
public async Task InitializeAsync(IPluginContext context)
{
try
{
// 初始化代码
}
catch (FileNotFoundException ex)
{
context.Logger.LogError(ex, "Required file not found");
throw new PluginInitializationException(
"插件初始化失败:缺少必需文件",
ex
);
}
catch (UnauthorizedAccessException ex)
{
context.Logger.LogError(ex, "Permission denied");
throw new PluginInitializationException(
"插件初始化失败:权限不足",
ex
);
}
catch (Exception ex)
{
context.Logger.LogError(ex, "Unexpected error during initialization");
throw;
}
}
```
### ShutdownAsync
**签名**:
```csharp
Task ShutdownAsync();
```
**说明**: 插件卸载前调用,用于清理资源、保存状态和取消订阅。
**返回值**: 异步任务
**调用时机**:
- 宿主应用关闭时
- 插件被禁用时
- 插件热重载前
**执行要求**:
- ✅ 必须快速完成(< 3 秒)
- ✅ 必须捕获所有异常,不能抛出
- ✅ 应该取消所有异步操作
- ✅ 应该释放所有资源
- ❌ 不要执行耗时操作
**典型实现**:
```csharp
public async Task ShutdownAsync()
{
try
{
_logger?.LogInformation($"{Name} is shutting down...");
// 1. 取消正在进行的操作
_cancellationTokenSource?.Cancel();
// 2. 取消事件订阅
UnsubscribeEvents();
// 3. 保存关键状态
SaveState();
// 4. 停止后台服务
await StopBackgroundServicesAsync();
// 5. 释放资源
DisposeResources();
_logger?.LogInformation($"{Name} shutdown completed");
}
catch (Exception ex)
{
// 记录但不抛出异常
_logger?.LogError(ex, $"Error during {Name} shutdown");
}
}
private void UnsubscribeEvents()
{
var eventBus = _context?.Services.GetService<IEventBus>();
if (eventBus != null)
{
eventBus.Unsubscribe<ThemeChangedEvent>(OnThemeChanged);
}
}
private void SaveState()
{
try
{
// 保存关键状态到设置
_settings?.SetValue("LastShutdownTime", DateTime.Now);
}
catch (Exception ex)
{
_logger?.LogWarning(ex, "Failed to save state");
}
}
private async Task StopBackgroundServicesAsync()
{
try
{
if (_weatherService != null)
{
await _weatherService.StopAsync();
}
}
catch (Exception ex)
{
_logger?.LogWarning(ex, "Failed to stop services");
}
}
private void DisposeResources()
{
try
{
_cancellationTokenSource?.Dispose();
_weatherService?.Dispose();
_httpClient?.Dispose();
}
catch (Exception ex)
{
_logger?.LogWarning(ex, "Failed to dispose resources");
}
}
```
**超时处理**:
宿主会监控 `ShutdownAsync` 的执行时间:
```csharp
// 宿主代码(伪代码)
var shutdownTask = plugin.ShutdownAsync();
var completedTask = await Task.WhenAny(
shutdownTask,
Task.Delay(TimeSpan.FromSeconds(5))
);
if (completedTask != shutdownTask)
{
_logger.LogWarning($"Plugin {plugin.Name} shutdown timeout");
// 强制终止
}
```
所以插件应该确保快速完成:
```csharp
public async Task ShutdownAsync()
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
try
{
await ShutdownInternalAsync(cts.Token);
}
catch (OperationCanceledException)
{
_logger?.LogWarning("Shutdown cancelled due to timeout");
}
}
```
## 完整示例
### 最小实现
```csharp
using LanMountainDesktop.PluginSdk;
using Microsoft.Extensions.Logging;
namespace MyPlugin;
public class Plugin : IPlugin
{
public string Id => "com.example.minimalplugin";
public string Name => "Minimal Plugin";
public string Version => "1.0.0";
public Task InitializeAsync(IPluginContext context)
{
context.Logger.LogInformation($"{Name} initialized");
return Task.CompletedTask;
}
public Task ShutdownAsync()
{
return Task.CompletedTask;
}
}
```
### 完整实现
```csharp
using LanMountainDesktop.PluginSdk;
using LanMountainDesktop.Shared.Contracts;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace MyPlugin;
/// <summary>
/// 天气插件
/// </summary>
public class WeatherPlugin : IPlugin
{
// === 插件信息 ===
public string Id => "com.example.weatherplugin";
public string Name => "天气插件";
public string Version => "1.2.3";
// === 私有字段 ===
private IPluginContext? _context;
private ILogger? _logger;
private ISettingsService? _settings;
private CancellationTokenSource? _cancellationTokenSource;
private WeatherService? _weatherService;
// === 生命周期方法 ===
/// <summary>
/// 插件初始化
/// </summary>
public async Task InitializeAsync(IPluginContext context)
{
try
{
// 保存引用
_context = context;
_logger = context.Logger;
_settings = context.Settings;
_cancellationTokenSource = new CancellationTokenSource();
_logger.LogInformation(
"{PluginName} v{Version} is initializing...",
Name,
Version
);
// 注册组件
RegisterComponents(context);
// 注册设置页
RegisterSettingsPage(context);
// 初始化服务
_weatherService = new WeatherService(
_settings,
_logger,
_cancellationTokenSource.Token
);
// 订阅事件
SubscribeToHostEvents(context);
// 后台初始化
_ = Task.Run(async () =>
{
await InitializeBackgroundAsync();
});
_logger.LogInformation(
"{PluginName} initialized successfully",
Name
);
await Task.CompletedTask;
}
catch (Exception ex)
{
context.Logger.LogError(
ex,
"Failed to initialize {PluginName}",
Name
);
throw;
}
}
/// <summary>
/// 插件关闭
/// </summary>
public async Task ShutdownAsync()
{
try
{
_logger?.LogInformation(
"{PluginName} is shutting down...",
Name
);
// 取消异步操作
_cancellationTokenSource?.Cancel();
// 取消订阅
UnsubscribeFromHostEvents();
// 保存状态
SaveState();
// 停止服务
if (_weatherService != null)
{
await _weatherService.StopAsync();
_weatherService.Dispose();
}
// 释放资源
_cancellationTokenSource?.Dispose();
_logger?.LogInformation(
"{PluginName} shutdown completed",
Name
);
}
catch (Exception ex)
{
_logger?.LogError(
ex,
"Error during {PluginName} shutdown",
Name
);
// 不抛出异常
}
}
// === 私有方法 ===
private void RegisterComponents(IPluginContext context)
{
var registry = context.Services
.GetService<IComponentRegistry>();
if (registry != null)
{
registry.RegisterComponent<WeatherComponent>();
_logger?.LogDebug("WeatherComponent registered");
}
}
private void RegisterSettingsPage(IPluginContext context)
{
var settingsRegistry = context.Services
.GetService<ISettingsPageRegistry>();
if (settingsRegistry != null)
{
settingsRegistry.RegisterPage(
title: Name,
category: "插件",
pageFactory: () => new WeatherSettingsPage(
_settings!,
_logger!
)
);
_logger?.LogDebug("Settings page registered");
}
}
private void SubscribeToHostEvents(IPluginContext context)
{
var eventBus = context.Services.GetService<IEventBus>();
if (eventBus != null)
{
eventBus.Subscribe<ThemeChangedEvent>(OnThemeChanged);
_logger?.LogDebug("Subscribed to host events");
}
}
private void UnsubscribeFromHostEvents()
{
var eventBus = _context?.Services.GetService<IEventBus>();
if (eventBus != null)
{
eventBus.Unsubscribe<ThemeChangedEvent>(OnThemeChanged);
_logger?.LogDebug("Unsubscribed from host events");
}
}
private async Task InitializeBackgroundAsync()
{
try
{
// 加载缓存数据
await _weatherService!.LoadCacheAsync();
// 预加载天气数据
var defaultCity = _settings!.GetValue("DefaultCity", "北京");
await _weatherService.FetchWeatherAsync(defaultCity);
_logger?.LogInformation("Background initialization completed");
}
catch (Exception ex)
{
_logger?.LogError(ex, "Background initialization failed");
}
}
private void OnThemeChanged(ThemeChangedEvent evt)
{
_logger?.LogInformation(
"Theme changed to: {Theme}",
evt.NewTheme
);
// 响应主题变更
}
private void SaveState()
{
try
{
_settings?.SetValue("LastShutdownTime", DateTime.Now);
_logger?.LogDebug("State saved");
}
catch (Exception ex)
{
_logger?.LogWarning(ex, "Failed to save state");
}
}
}
```
## 常见问题
### Q: InitializeAsync 可以执行多久?
**A**: 建议在 5 秒内完成。超时可能导致宿主启动缓慢。耗时操作应放在后台线程。
### Q: 可以在构造函数中初始化吗?
**A**: 不建议。构造函数应该非常轻量,只初始化字段。所有初始化逻辑应在 `InitializeAsync` 中。
### Q: ShutdownAsync 可以不实现吗?
**A**: 必须实现,但可以是空实现。如果有资源需要清理,必须在此方法中处理。
### Q: 如果 InitializeAsync 失败会怎样?
**A**: 插件会被标记为"加载失败",不会被激活,但不影响其他插件。
### Q: 可以访问其他插件的服务吗?
**A**: 不建议在 `InitializeAsync` 中访问,因为加载顺序不确定。应该在运行时通过服务定位器获取。
## 相关文档
- [IPluginContext 详解](02-IPluginContext.md) - 插件上下文
- [插件生命周期](../02-核心概念/01-插件生命周期.md) - 生命周期详解
- [创建第一个插件](../01-快速开始/02-创建第一个插件.md) - 实战教程

View File

@@ -0,0 +1,717 @@
# IPluginContext 详解
`IPluginContext` 是插件与宿主应用交互的主要接口,提供对宿主服务、日志、设置等的访问。
## 接口定义
```csharp
namespace LanMountainDesktop.PluginSdk;
/// <summary>
/// 插件上下文接口
/// </summary>
public interface IPluginContext
{
/// <summary>
/// 插件根目录
/// 包含插件的所有文件DLL、资源等
/// </summary>
string PluginDirectory { get; }
/// <summary>
/// 插件数据目录
/// 用于存储插件的持久化数据(缓存、数据库等)
/// </summary>
string DataDirectory { get; }
/// <summary>
/// 服务提供者
/// 用于获取宿主提供的服务
/// </summary>
IServiceProvider Services { get; }
/// <summary>
/// 日志记录器
/// 用于记录插件运行日志
/// </summary>
ILogger Logger { get; }
/// <summary>
/// 设置服务
/// 用于读写插件配置
/// </summary>
ISettingsService Settings { get; }
}
```
## 属性详解
### PluginDirectory
**类型**: `string`
**说明**: 插件的根目录,包含插件的所有文件。
**典型路径**:
```
%LOCALAPPDATA%\LanMountainDesktop\plugins\{PluginId}\
```
**用途**:
- 加载插件资源文件
- 读取配置文件
- 访问插件自带的数据文件
**示例**:
```csharp
public async Task InitializeAsync(IPluginContext context)
{
// 加载插件自带的数据文件
var dataFile = Path.Combine(context.PluginDirectory, "data", "cities.json");
if (File.Exists(dataFile))
{
var json = await File.ReadAllTextAsync(dataFile);
var cities = JsonSerializer.Deserialize<List<City>>(json);
}
// 加载图标
var iconPath = Path.Combine(context.PluginDirectory, "Assets", "icon.png");
// 加载资源文件(使用 avares 方案更好)
// avares://MyPlugin/Assets/icon.png
}
```
**注意事项**:
- ✅ 只能读取,不要在此目录写入文件
- ✅ 使用 `Path.Combine` 构建路径
- ❌ 不要硬编码路径
- ❌ 不要依赖目录结构(可能变化)
### DataDirectory
**类型**: `string`
**说明**: 插件的数据目录,用于存储插件生成的持久化数据。
**典型路径**:
```
%LOCALAPPDATA%\LanMountainDesktop\plugin-data\{PluginId}\
```
**用途**:
- 存储缓存文件
- 存储本地数据库
- 存储临时文件
- 存储下载的文件
**示例**:
```csharp
public async Task InitializeAsync(IPluginContext context)
{
// 确保数据目录存在
Directory.CreateDirectory(context.DataDirectory);
// 缓存文件路径
var cacheFile = Path.Combine(context.DataDirectory, "weather-cache.json");
// SQLite 数据库路径
var dbPath = Path.Combine(context.DataDirectory, "todos.db");
// 下载文件路径
var downloadPath = Path.Combine(context.DataDirectory, "downloads");
}
```
**最佳实践**:
```csharp
public class MyPlugin : IPlugin
{
private string? _cacheDirectory;
private string? _logsDirectory;
public async Task InitializeAsync(IPluginContext context)
{
// 创建子目录组织数据
_cacheDirectory = Path.Combine(context.DataDirectory, "cache");
_logsDirectory = Path.Combine(context.DataDirectory, "logs");
Directory.CreateDirectory(_cacheDirectory);
Directory.CreateDirectory(_logsDirectory);
}
public async Task SaveCacheAsync(string key, string data)
{
var cacheFile = Path.Combine(_cacheDirectory!, $"{key}.json");
await File.WriteAllTextAsync(cacheFile, data);
}
}
```
**清理数据**:
```csharp
public async Task ShutdownAsync()
{
// 清理旧的缓存文件
if (_cacheDirectory != null)
{
var files = Directory.GetFiles(_cacheDirectory);
foreach (var file in files)
{
var fileInfo = new FileInfo(file);
if (DateTime.Now - fileInfo.LastWriteTime > TimeSpan.FromDays(7))
{
File.Delete(file);
}
}
}
}
```
### Services
**类型**: `IServiceProvider`
**说明**: 服务提供者,用于获取宿主提供的服务。
**常用服务**:
| 服务接口 | 说明 |
|---------|------|
| `IComponentRegistry` | 组件注册表 |
| `ISettingsPageRegistry` | 设置页注册表 |
| `IEventBus` | 事件总线 |
| `INotificationService` | 通知服务 |
| `IDialogService` | 对话框服务 |
| `IThemeService` | 主题服务 |
| `ILocalizationService` | 本地化服务 |
| `IHttpClientFactory` | HTTP 客户端工厂 |
**使用方法**:
```csharp
public async Task InitializeAsync(IPluginContext context)
{
// 获取服务
var componentRegistry = context.Services
.GetService<IComponentRegistry>();
var eventBus = context.Services
.GetService<IEventBus>();
var themeService = context.Services
.GetService<IThemeService>();
// 检查服务是否可用
if (componentRegistry != null)
{
// 使用服务
componentRegistry.RegisterComponent<MyComponent>();
}
else
{
context.Logger.LogWarning("IComponentRegistry not available");
}
}
```
**泛型扩展方法**:
```csharp
// 使用 Microsoft.Extensions.DependencyInjection 的扩展方法
using Microsoft.Extensions.DependencyInjection;
var componentRegistry = context.Services.GetService<IComponentRegistry>();
var eventBus = context.Services.GetRequiredService<IEventBus>(); // 不存在会抛异常
```
**服务定位器模式**:
```csharp
public class MyPlugin : IPlugin
{
private IServiceProvider? _services;
public async Task InitializeAsync(IPluginContext context)
{
_services = context.Services;
}
private void SomeMethod()
{
// 运行时获取服务
var notificationService = _services?
.GetService<INotificationService>();
notificationService?.ShowNotification(
"标题",
"内容",
NotificationType.Information
);
}
}
```
### Logger
**类型**: `ILogger`
**说明**: 日志记录器,用于记录插件运行日志。
**日志级别**:
| 级别 | 方法 | 用途 |
|-----|------|------|
| Trace | `LogTrace` | 最详细的信息,用于诊断 |
| Debug | `LogDebug` | 调试信息 |
| Information | `LogInformation` | 一般信息 |
| Warning | `LogWarning` | 警告信息 |
| Error | `LogError` | 错误信息 |
| Critical | `LogCritical` | 严重错误 |
**基本用法**:
```csharp
public async Task InitializeAsync(IPluginContext context)
{
var logger = context.Logger;
// 信息日志
logger.LogInformation("Plugin is initializing");
// 警告日志
logger.LogWarning("Configuration is missing, using defaults");
// 错误日志
try
{
await LoadDataAsync();
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to load data");
}
// 调试日志
logger.LogDebug("Loaded {Count} items", items.Count);
}
```
**结构化日志**:
```csharp
// ✅ 好:使用参数化日志
logger.LogInformation(
"User {UserId} requested weather for {City}",
userId,
city
);
// ❌ 差:字符串拼接
logger.LogInformation(
$"User {userId} requested weather for {city}"
);
```
**异常日志**:
```csharp
try
{
await FetchWeatherAsync();
}
catch (HttpRequestException ex)
{
// 记录异常和上下文
logger.LogError(
ex,
"Failed to fetch weather for {City}. Retry count: {RetryCount}",
city,
retryCount
);
}
catch (Exception ex)
{
// 严重错误
logger.LogCritical(
ex,
"Unexpected error in weather service"
);
}
```
**条件日志**:
```csharp
// 检查日志级别以避免不必要的计算
if (logger.IsEnabled(LogLevel.Debug))
{
var expensiveDebugInfo = CalculateDebugInfo(); // 只在启用 Debug 时计算
logger.LogDebug("Debug info: {Info}", expensiveDebugInfo);
}
```
**日志作用域**:
```csharp
using (logger.BeginScope("WeatherFetch-{City}", city))
{
logger.LogInformation("Starting fetch");
await FetchWeatherAsync(city);
logger.LogInformation("Fetch completed");
}
// 所有日志会包含作用域信息
```
### Settings
**类型**: `ISettingsService`
**说明**: 设置服务,用于读写插件配置。详见 [设置系统](../02-核心概念/03-设置系统.md)。
**快速示例**:
```csharp
public async Task InitializeAsync(IPluginContext context)
{
var settings = context.Settings;
// 读取设置
var apiKey = settings.GetValue("ApiKey", "");
var refreshRate = settings.GetValue("RefreshRate", 10);
var cities = settings.GetValue<List<string>>(
"FavoriteCities",
new List<string>()
);
// 保存设置
settings.SetValue("LastStartTime", DateTime.Now);
// 监听设置变更
settings.SettingChanged += (sender, e) =>
{
if (e.Key == "ApiKey")
{
// 响应变更
}
};
}
```
## 使用模式
### 保存上下文引用
```csharp
public class MyPlugin : IPlugin
{
private IPluginContext? _context;
private ILogger? _logger;
private ISettingsService? _settings;
public async Task InitializeAsync(IPluginContext context)
{
// 保存引用供后续使用
_context = context;
_logger = context.Logger;
_settings = context.Settings;
// 后续可以在任何方法中使用
}
private void SomeMethod()
{
_logger?.LogInformation("Doing something");
var value = _settings?.GetValue("Key", "Default");
}
}
```
### 依赖注入模式
```csharp
public class MyComponent : ComponentBase
{
private readonly INotificationService? _notificationService;
public MyComponent()
{
// 组件构造时注入依赖
_notificationService = Services.GetService<INotificationService>();
}
public void NotifyUser(string message)
{
_notificationService?.ShowNotification(
"提醒",
message,
NotificationType.Information
);
}
}
```
### 服务包装
```csharp
public class MyPlugin : IPlugin
{
private WeatherService? _weatherService;
public async Task InitializeAsync(IPluginContext context)
{
// 创建服务包装类
_weatherService = new WeatherService(
context.Logger,
context.Settings,
context.Services.GetService<IHttpClientFactory>()
);
await _weatherService.InitializeAsync();
}
}
public class WeatherService
{
private readonly ILogger _logger;
private readonly ISettingsService _settings;
private readonly HttpClient _httpClient;
public WeatherService(
ILogger logger,
ISettingsService settings,
IHttpClientFactory? httpFactory)
{
_logger = logger;
_settings = settings;
_httpClient = httpFactory?.CreateClient() ?? new HttpClient();
}
public async Task InitializeAsync()
{
var apiKey = _settings.GetValue("ApiKey", "");
_logger.LogInformation("Weather service initialized");
}
}
```
## 最佳实践
### ✅ 检查服务可用性
```csharp
// ✅ 好:检查服务是否存在
var notificationService = context.Services
.GetService<INotificationService>();
if (notificationService != null)
{
notificationService.ShowNotification(...);
}
else
{
context.Logger.LogWarning("Notification service not available");
}
// ❌ 差:不检查直接使用
var notificationService = context.Services
.GetRequiredService<INotificationService>(); // 可能抛异常
```
### ✅ 使用结构化日志
```csharp
// ✅ 好:参数化日志
logger.LogInformation(
"Processed {Count} items in {Duration}ms",
count,
duration
);
// ❌ 差:字符串插值
logger.LogInformation(
$"Processed {count} items in {duration}ms"
);
```
### ✅ 正确处理路径
```csharp
// ✅ 好:使用 Path.Combine
var dataFile = Path.Combine(
context.DataDirectory,
"cache",
"data.json"
);
// ❌ 差:字符串拼接
var dataFile = context.DataDirectory + "\\cache\\data.json"; // Windows 专用
```
### ✅ 清理资源
```csharp
public class MyPlugin : IPlugin
{
private ISettingsService? _settings;
public async Task InitializeAsync(IPluginContext context)
{
_settings = context.Settings;
_settings.SettingChanged += OnSettingChanged;
}
public async Task ShutdownAsync()
{
// 取消订阅
if (_settings != null)
{
_settings.SettingChanged -= OnSettingChanged;
}
}
}
```
## 完整示例
```csharp
using LanMountainDesktop.PluginSdk;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace MyPlugin;
public class WeatherPlugin : IPlugin
{
public string Id => "com.example.weatherplugin";
public string Name => "天气插件";
public string Version => "1.0.0";
// 上下文引用
private IPluginContext? _context;
private ILogger? _logger;
private ISettingsService? _settings;
private string? _dataDirectory;
// 服务引用
private INotificationService? _notificationService;
private IHttpClientFactory? _httpFactory;
public async Task InitializeAsync(IPluginContext context)
{
// 1. 保存上下文引用
_context = context;
_logger = context.Logger;
_settings = context.Settings;
_dataDirectory = context.DataDirectory;
_logger.LogInformation(
"{PluginName} v{Version} initializing from {Directory}",
Name,
Version,
context.PluginDirectory
);
// 2. 获取宿主服务
_notificationService = context.Services
.GetService<INotificationService>();
_httpFactory = context.Services
.GetService<IHttpClientFactory>();
// 3. 创建数据目录
Directory.CreateDirectory(_dataDirectory);
var cacheDir = Path.Combine(_dataDirectory, "cache");
Directory.CreateDirectory(cacheDir);
_logger.LogDebug("Data directory: {Directory}", _dataDirectory);
// 4. 加载配置
var apiKey = _settings.GetValue("ApiKey", "");
if (string.IsNullOrEmpty(apiKey))
{
_logger.LogWarning("API Key not configured");
}
// 5. 注册组件和服务
RegisterComponents(context);
RegisterSettingsPage(context);
// 6. 订阅事件
SubscribeEvents(context);
_logger.LogInformation("{PluginName} initialized successfully", Name);
}
private void RegisterComponents(IPluginContext context)
{
var registry = context.Services.GetService<IComponentRegistry>();
if (registry != null)
{
registry.RegisterComponent<WeatherComponent>();
_logger?.LogDebug("Components registered");
}
}
private void RegisterSettingsPage(IPluginContext context)
{
var settingsRegistry = context.Services
.GetService<ISettingsPageRegistry>();
if (settingsRegistry != null)
{
settingsRegistry.RegisterPage(
title: Name,
category: "插件",
pageFactory: () => new WeatherSettingsPage(
_settings!,
_logger!
)
);
_logger?.LogDebug("Settings page registered");
}
}
private void SubscribeEvents(IPluginContext context)
{
var eventBus = context.Services.GetService<IEventBus>();
if (eventBus != null)
{
eventBus.Subscribe<ThemeChangedEvent>(OnThemeChanged);
_logger?.LogDebug("Event subscriptions created");
}
}
private void OnThemeChanged(ThemeChangedEvent evt)
{
_logger?.LogInformation("Theme changed to: {Theme}", evt.NewTheme);
}
public async Task ShutdownAsync()
{
_logger?.LogInformation("{PluginName} shutting down", Name);
// 取消订阅
var eventBus = _context?.Services.GetService<IEventBus>();
if (eventBus != null)
{
eventBus.Unsubscribe<ThemeChangedEvent>(OnThemeChanged);
}
_logger?.LogInformation("{PluginName} shutdown completed", Name);
await Task.CompletedTask;
}
}
```
## 相关文档
- [IPlugin 接口](01-IPlugin接口.md) - 插件接口详解
- [设置系统](../02-核心概念/03-设置系统.md) - 设置服务详解
- [插件生命周期](../02-核心概念/01-插件生命周期.md) - 生命周期详解