Files
LanMountainDesktop/docs/archive/Plugins develop/02-核心概念与原理/05-插件间通信.md
2026-06-08 03:54:33 +08:00

7.7 KiB
Raw Blame History

05-插件间通信

插件之间可以通过消息总线和服务导出进行通信,实现功能协作和数据共享。


🎯 通信方式概述

方式 适用场景 方向
消息总线 事件通知、广播 多对多
服务导出 功能共享、API 暴露 一对多
共享契约 数据交换 双向

📢 消息总线

使用 IPluginMessageBus 发布和订阅消息。

发布消息

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; } = "";
}

订阅消息

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);
    }
}

取消订阅

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);
    }
}

🔌 服务导出

插件可以将服务导出,供其他插件使用。

导出服务

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)
    {
        // 实现天气预报
    }
}

使用其他插件的服务

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);
    }
}

服务导出选项

services.AddPluginServiceExport<IWeatherService>(
    serviceKey: "MyPlugin.WeatherService",
    description: "提供天气查询服务",
    version: "1.0.0",
    isPublic: true);  // 是否公开给其他插件

📦 共享契约

通过 sharedContracts 在插件间共享类型定义。

定义共享契约

// 在共享类库项目中定义
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 中声明

{
  "id": "com.example.weather",
  "name": "天气插件",
  "sharedContracts": [
    "MyPlugin.Shared.IWeatherData",
    "MyPlugin.Shared.WeatherData"
  ]
}

使用共享类型

// 插件 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");
    }
}

🔒 安全考虑

服务导出安全

// 只导出必要的接口,不暴露实现细节
public interface IPublicApi
{
    Task<Data> GetDataAsync();
}

internal class InternalService : IPublicApi
{
    // 内部实现细节不暴露
    private readonly SecretKey _key;
    
    public async Task<Data> GetDataAsync()
    {
        // 实现
    }
}

消息验证

private void OnMessageReceived(MyMessage message)
{
    // 验证消息来源
    if (message.SenderId != "TrustedPlugin")
    {
        return;  // 忽略不信任来源的消息
    }
    
    // 处理消息
}

💡 最佳实践

1. 使用接口定义服务契约

// ✅ 好的做法 - 定义接口
public interface IWeatherService { }

// ❌ 避免 - 直接导出实现类
services.AddPluginServiceExport<WeatherService>(...);

2. 处理服务不可用情况

// ✅ 优雅处理服务缺失
var service = context.ServiceProvider
    .GetExportedService<IWeatherService>("key");

if (service == null)
{
    // 显示提示或降级处理
    ShowServiceUnavailableMessage();
    return;
}

3. 及时取消消息订阅

// ✅ 在 Dispose 中取消订阅
public void Dispose()
{
    _messageBus.Unsubscribe<MyMessage>(_subscriptionId);
}

4. 版本兼容性

// 在服务导出中包含版本信息
services.AddPluginServiceExport<IWeatherService>(
    serviceKey: "MyPlugin.WeatherService",
    version: "2.0.0",  // 语义化版本
    description: "天气服务 v2");

🐛 常见问题

问题 1消息收不到

排查:

  1. 确认消息类型完全一致(包括命名空间)
  2. 检查订阅是否在消息发布之前
  3. 确认没有取消订阅

问题 2服务找不到

排查:

  1. 确认服务已导出(AddPluginServiceExport
  2. 检查 serviceKey 是否正确
  3. 确认依赖的插件已安装并启用

问题 3类型转换错误

原因: 共享契约类型不匹配

解决: 确保所有插件使用相同版本的共享契约程序集


📚 参考资源


🎯 下一步

查看实战案例:

👉 01-开发天气组件 - 完整插件开发流程


最后更新2026年4月