Files
LanMountainDesktop/docs/Plugins develop/02-核心概念与原理/05-插件间通信.md
2026-04-13 19:54:37 +08:00

376 lines
7.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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月*