mirror of
https://github.com/wwiinnddyy/LanMountainDesktop.git
synced 2026-06-23 09:54:25 +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月*
|