mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-24 02:14:26 +08:00
376 lines
7.7 KiB
Markdown
376 lines
7.7 KiB
Markdown
|
|
# 05-插件间通信
|
|||
|
|
|
|||
|
|
插件之间可以通过消息总线和服务导出进行通信,实现功能协作和数据共享。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 通信方式概述
|
|||
|
|
|
|||
|
|
| 方式 | 适用场景 | 方向 |
|
|||
|
|
|-----|---------|------|
|
|||
|
|
| **消息总线** | 事件通知、广播 | 多对多 |
|
|||
|
|
| **服务导出** | 功能共享、API 暴露 | 一对多 |
|
|||
|
|
| **共享契约** | 数据交换 | 双向 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📢 消息总线
|
|||
|
|
|
|||
|
|
使用 `IPluginMessageBus` 发布和订阅消息。
|
|||
|
|
|
|||
|
|
### 发布消息
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
public class WeatherService
|
|||
|
|
{
|
|||
|
|
private readonly IPluginMessageBus _messageBus;
|
|||
|
|
|
|||
|
|
public WeatherService(IPluginMessageBus messageBus)
|
|||
|
|
{
|
|||
|
|
_messageBus = messageBus;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public async Task UpdateWeatherAsync()
|
|||
|
|
{
|
|||
|
|
var weather = await FetchWeatherAsync();
|
|||
|
|
|
|||
|
|
// 发布天气更新消息
|
|||
|
|
_messageBus.Publish(new WeatherUpdatedMessage
|
|||
|
|
{
|
|||
|
|
City = weather.City,
|
|||
|
|
Temperature = weather.Temperature,
|
|||
|
|
Condition = weather.Condition
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 定义消息
|
|||
|
|
public class WeatherUpdatedMessage
|
|||
|
|
{
|
|||
|
|
public string City { get; set; } = "";
|
|||
|
|
public double Temperature { get; set; }
|
|||
|
|
public string Condition { get; set; } = "";
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 订阅消息
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
public class AnotherPluginService
|
|||
|
|
{
|
|||
|
|
public AnotherPluginService(IPluginMessageBus messageBus)
|
|||
|
|
{
|
|||
|
|
// 订阅天气更新消息
|
|||
|
|
messageBus.Subscribe<WeatherUpdatedMessage>(OnWeatherUpdated);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnWeatherUpdated(WeatherUpdatedMessage message)
|
|||
|
|
{
|
|||
|
|
Console.WriteLine($"收到天气更新: {message.City} {message.Temperature}°C");
|
|||
|
|
|
|||
|
|
// 根据天气更新自己的状态
|
|||
|
|
UpdateDisplay(message);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 取消订阅
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
public class MyService : IDisposable
|
|||
|
|
{
|
|||
|
|
private readonly IPluginMessageBus _messageBus;
|
|||
|
|
private readonly Guid _subscriptionId;
|
|||
|
|
|
|||
|
|
public MyService(IPluginMessageBus messageBus)
|
|||
|
|
{
|
|||
|
|
_messageBus = messageBus;
|
|||
|
|
_subscriptionId = messageBus.Subscribe<WeatherUpdatedMessage>(OnWeatherUpdated);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void Dispose()
|
|||
|
|
{
|
|||
|
|
// 取消订阅,避免内存泄漏
|
|||
|
|
_messageBus.Unsubscribe<WeatherUpdatedMessage>(_subscriptionId);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔌 服务导出
|
|||
|
|
|
|||
|
|
插件可以将服务导出,供其他插件使用。
|
|||
|
|
|
|||
|
|
### 导出服务
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
public override void Initialize(HostBuilderContext context, IServiceCollection services)
|
|||
|
|
{
|
|||
|
|
// 注册服务
|
|||
|
|
services.AddSingleton<IWeatherService, WeatherService>();
|
|||
|
|
|
|||
|
|
// 导出服务供其他插件使用
|
|||
|
|
services.AddPluginServiceExport<IWeatherService>(
|
|||
|
|
serviceKey: "MyPlugin.WeatherService",
|
|||
|
|
description: "提供天气查询服务");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 定义服务接口
|
|||
|
|
public interface IWeatherService
|
|||
|
|
{
|
|||
|
|
Task<WeatherInfo> GetCurrentWeatherAsync(string city);
|
|||
|
|
Task<List<WeatherForecast>> GetForecastAsync(string city, int days);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 实现服务
|
|||
|
|
public class WeatherService : IWeatherService
|
|||
|
|
{
|
|||
|
|
public async Task<WeatherInfo> GetCurrentWeatherAsync(string city)
|
|||
|
|
{
|
|||
|
|
// 实现天气查询
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public async Task<List<WeatherForecast>> GetForecastAsync(string city, int days)
|
|||
|
|
{
|
|||
|
|
// 实现天气预报
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 使用其他插件的服务
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
public class MyWidget : Border
|
|||
|
|
{
|
|||
|
|
public MyWidget(PluginDesktopComponentContext context)
|
|||
|
|
{
|
|||
|
|
// 获取其他插件导出的服务
|
|||
|
|
var weatherService = context.ServiceProvider
|
|||
|
|
.GetExportedService<IWeatherService>("MyPlugin.WeatherService");
|
|||
|
|
|
|||
|
|
if (weatherService != null)
|
|||
|
|
{
|
|||
|
|
// 使用服务
|
|||
|
|
LoadWeatherAsync(weatherService);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async void LoadWeatherAsync(IWeatherService weatherService)
|
|||
|
|
{
|
|||
|
|
var weather = await weatherService.GetCurrentWeatherAsync("北京");
|
|||
|
|
UpdateUI(weather);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 服务导出选项
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
services.AddPluginServiceExport<IWeatherService>(
|
|||
|
|
serviceKey: "MyPlugin.WeatherService",
|
|||
|
|
description: "提供天气查询服务",
|
|||
|
|
version: "1.0.0",
|
|||
|
|
isPublic: true); // 是否公开给其他插件
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📦 共享契约
|
|||
|
|
|
|||
|
|
通过 `sharedContracts` 在插件间共享类型定义。
|
|||
|
|
|
|||
|
|
### 定义共享契约
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// 在共享类库项目中定义
|
|||
|
|
namespace MyPlugin.Shared;
|
|||
|
|
|
|||
|
|
public interface IWeatherData
|
|||
|
|
{
|
|||
|
|
string City { get; }
|
|||
|
|
double Temperature { get; }
|
|||
|
|
string Condition { get; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public class WeatherData : IWeatherData
|
|||
|
|
{
|
|||
|
|
public string City { get; set; } = "";
|
|||
|
|
public double Temperature { get; set; }
|
|||
|
|
public string Condition { get; set; } = "";
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 在 plugin.json 中声明
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"id": "com.example.weather",
|
|||
|
|
"name": "天气插件",
|
|||
|
|
"sharedContracts": [
|
|||
|
|
"MyPlugin.Shared.IWeatherData",
|
|||
|
|
"MyPlugin.Shared.WeatherData"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 使用共享类型
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// 插件 A 发布数据
|
|||
|
|
public class WeatherService
|
|||
|
|
{
|
|||
|
|
public WeatherData GetWeather()
|
|||
|
|
{
|
|||
|
|
return new WeatherData
|
|||
|
|
{
|
|||
|
|
City = "北京",
|
|||
|
|
Temperature = 25.5,
|
|||
|
|
Condition = "晴"
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 插件 B 接收数据
|
|||
|
|
public class ConsumerService
|
|||
|
|
{
|
|||
|
|
public void ProcessWeather(IWeatherData weather)
|
|||
|
|
{
|
|||
|
|
Console.WriteLine($"{weather.City}: {weather.Temperature}°C");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔒 安全考虑
|
|||
|
|
|
|||
|
|
### 服务导出安全
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// 只导出必要的接口,不暴露实现细节
|
|||
|
|
public interface IPublicApi
|
|||
|
|
{
|
|||
|
|
Task<Data> GetDataAsync();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
internal class InternalService : IPublicApi
|
|||
|
|
{
|
|||
|
|
// 内部实现细节不暴露
|
|||
|
|
private readonly SecretKey _key;
|
|||
|
|
|
|||
|
|
public async Task<Data> GetDataAsync()
|
|||
|
|
{
|
|||
|
|
// 实现
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 消息验证
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
private void OnMessageReceived(MyMessage message)
|
|||
|
|
{
|
|||
|
|
// 验证消息来源
|
|||
|
|
if (message.SenderId != "TrustedPlugin")
|
|||
|
|
{
|
|||
|
|
return; // 忽略不信任来源的消息
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 处理消息
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 💡 最佳实践
|
|||
|
|
|
|||
|
|
### 1. 使用接口定义服务契约
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// ✅ 好的做法 - 定义接口
|
|||
|
|
public interface IWeatherService { }
|
|||
|
|
|
|||
|
|
// ❌ 避免 - 直接导出实现类
|
|||
|
|
services.AddPluginServiceExport<WeatherService>(...);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 处理服务不可用情况
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// ✅ 优雅处理服务缺失
|
|||
|
|
var service = context.ServiceProvider
|
|||
|
|
.GetExportedService<IWeatherService>("key");
|
|||
|
|
|
|||
|
|
if (service == null)
|
|||
|
|
{
|
|||
|
|
// 显示提示或降级处理
|
|||
|
|
ShowServiceUnavailableMessage();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 及时取消消息订阅
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// ✅ 在 Dispose 中取消订阅
|
|||
|
|
public void Dispose()
|
|||
|
|
{
|
|||
|
|
_messageBus.Unsubscribe<MyMessage>(_subscriptionId);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. 版本兼容性
|
|||
|
|
|
|||
|
|
```csharp
|
|||
|
|
// 在服务导出中包含版本信息
|
|||
|
|
services.AddPluginServiceExport<IWeatherService>(
|
|||
|
|
serviceKey: "MyPlugin.WeatherService",
|
|||
|
|
version: "2.0.0", // 语义化版本
|
|||
|
|
description: "天气服务 v2");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🐛 常见问题
|
|||
|
|
|
|||
|
|
### 问题 1:消息收不到
|
|||
|
|
|
|||
|
|
**排查:**
|
|||
|
|
1. 确认消息类型完全一致(包括命名空间)
|
|||
|
|
2. 检查订阅是否在消息发布之前
|
|||
|
|
3. 确认没有取消订阅
|
|||
|
|
|
|||
|
|
### 问题 2:服务找不到
|
|||
|
|
|
|||
|
|
**排查:**
|
|||
|
|
1. 确认服务已导出(`AddPluginServiceExport`)
|
|||
|
|
2. 检查 `serviceKey` 是否正确
|
|||
|
|
3. 确认依赖的插件已安装并启用
|
|||
|
|
|
|||
|
|
### 问题 3:类型转换错误
|
|||
|
|
|
|||
|
|
**原因:** 共享契约类型不匹配
|
|||
|
|
|
|||
|
|
**解决:** 确保所有插件使用相同版本的共享契约程序集
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📚 参考资源
|
|||
|
|
|
|||
|
|
- [IPluginMessageBus 源码](../../LanMountainDesktop.PluginSdk/IPluginMessageBus.cs)
|
|||
|
|
- [IPluginExportRegistry 源码](../../LanMountainDesktop.PluginSdk/IPluginExportRegistry.cs)
|
|||
|
|
- [Shared.Contracts](../../LanMountainDesktop.Shared.Contracts/)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🎯 下一步
|
|||
|
|
|
|||
|
|
查看实战案例:
|
|||
|
|
|
|||
|
|
👉 **[01-开发天气组件](../04-实战案例/01-开发天气组件.md)** - 完整插件开发流程
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
*最后更新:2026年4月*
|