Mud.Feishu.WebSocket
3.0.0-rc3
dotnet add package Mud.Feishu.WebSocket --version 3.0.0-rc3
NuGet\Install-Package Mud.Feishu.WebSocket -Version 3.0.0-rc3
<PackageReference Include="Mud.Feishu.WebSocket" Version="3.0.0-rc3" />
<PackageVersion Include="Mud.Feishu.WebSocket" Version="3.0.0-rc3" />
<PackageReference Include="Mud.Feishu.WebSocket" />
paket add Mud.Feishu.WebSocket --version 3.0.0-rc3
#r "nuget: Mud.Feishu.WebSocket, 3.0.0-rc3"
#:package Mud.Feishu.WebSocket@3.0.0-rc3
#addin nuget:?package=Mud.Feishu.WebSocket&version=3.0.0-rc3&prerelease
#tool nuget:?package=Mud.Feishu.WebSocket&version=3.0.0-rc3&prerelease
飞书 WebSocket 客户端服务
企业级飞书事件订阅 WebSocket 客户端,提供可靠的连接管理、自动重连和策略模式事件处理。
🚀 新特性:极简 API - 一行代码完成服务注册,开箱即用!
✨ 核心特性
- 🚀 极简 API - 一行代码完成服务注册,开箱即用
- 🔄 智能连接管理 - 自动重连、心跳检测、状态监控
- 🫀 心跳消息处理 - 支持飞书 heartbeat 消息类型,实时连接状态监控
- 🚀 高性能消息处理 - 异步处理、消息队列、并行执行
- 🎯 策略模式事件处理 - 可扩展的事件处理器架构
- 🔌 事件拦截器 - 支持在事件处理前后插入自定义逻辑(日志、遥测、限流等)
- 🛡️ 企业级稳定性 - 完善的错误处理、资源管理、日志记录
- ⚙️ 灵活配置 - 支持配置文件、代码配置和建造者模式
- 📊 监控友好 - 详细的事件通知、性能指标、心跳统计、FeishuMetrics 集成
- 🛡️ 错误分类处理 - 区分可恢复和不可恢复错误
- 🛡️ 认证失败详细追踪 - 按错误码分类认证失败原因,统计失败次数和时间
- 🔧 资源管理优化 - 实现 IHostedService 生命周期管理,避免资源泄漏
- 🔁 指数退避重连 - 可插拔重连策略,次数和时间双重限制,防抖机制
- 🔐 消息序号验证 - 重放攻击检测、消息丢失检测、序号回退检测
- 🔑 事件去重 - 内存去重/分布式去重(Redis),防止重复处理
- 🔒 SSL/TLS 证书验证 - 可配置证书验证策略,支持自定义验证回调
- 🎫 令牌自动刷新 - 访问令牌缓存和提前刷新,避免过期
- ⚡ 原生 AOT 支持 - net8.0+ 一等公民支持 Native AOT 发布,protobuf 编译期模型 + 源生成 JSON 序列化
🚀 快速开始
1. 安装 NuGet 包
dotnet add package Mud.Feishu.WebSocket
2. 最简配置(一行代码)
在 Program.cs 中:
using Mud.Feishu.WebSocket;
var builder = WebApplication.CreateBuilder(args);
// 先注册多应用支持
builder.Services.AddFeishuApp(builder.Configuration);
// 一行代码注册WebSocket服务(需要至少一个事件处理器)
builder.Services.CreateFeishuWebSocketServiceBuilder(builder.Configuration, "default")
.AddHandler<MessageReceiveEventHandler>()
.Build();
var app = builder.Build();
app.Run();
3. 完整配置(添加事件处理器)
// 先注册多应用支持
builder.Services.AddFeishuApp(builder.Configuration);
// 从配置文件注册并添加事件处理器
builder.Services.CreateFeishuWebSocketServiceBuilder(builder.Configuration, "default")
.AddHandler<MessageReceiveEventHandler>()
.AddHandler<UserCreateEventHandler>()
.Build();
var app = builder.Build();
app.Run();
4. 配置选项
{
"FeishuApps": [
{
"AppKey": "default",
"AppId": "your_app_id",
"AppSecret": "your_app_secret",
"BaseUrl": "https://open.feishu.cn",
"TimeoutSeconds": 30,
"HttpRetry": {
"MaxAttempts": 3,
"DelayMs": 1000
},
"IsDefault": true
}
],
"FeishuWebSocket": {
"Reconnect": {
"Auto": true,
"MaxAttempts": 5,
"MaxAuthRetryAttempts": 5,
"BaseDelayMs": 5000,
"MaxDelayMs": 30000,
"TotalBudget": "00:30:00",
"Cooldown": "00:00:05"
},
"Certificate": {
"Mode": "Strict",
"AllowInsecureWebSocket": false,
"ValidateServerCertificate": true,
"AllowSelfSignedCertificates": false,
"AllowCertificateNameMismatch": false
},
"HeartbeatIntervalMs": 25000,
"AllowedHostSuffixes": "*.feishu.cn;*.larksuite.com",
"EventDeduplication": {
"Mode": "InMemory",
"CacheExpiration": "2.00:00:00",
"CleanupInterval": "00:05:00"
}
}
}
🏗️ 架构设计
组件化架构
飞书 WebSocket 客户端采用组件化设计,将复杂功能拆分为专门的组件,提高代码的可维护性和扩展性。
架构设计
核心组件
| 组件 | 职责 | 特性 |
|---|---|---|
| WebSocketConnectionManager | 连接管理器 | 连接建立、断开、状态管理、SSL/TLS 证书验证 |
| AuthenticationManager | 认证管理器 | WebSocket 认证流程、状态管理、认证事件 |
| MessageRouter | 消息路由器 | 消息路由、版本检测(v1.0/v2.0)、处理器管理 |
| BinaryMessageProcessor | 二进制消息处理器 | 增量接收、ProtoBuf/JSON 解析、内存优化 |
| HeartbeatManager | 心跳管理器 | 发送应用层 ProtoBuf Ping(不据 Pong 判死,连接断开由接收循环/协议级 Ping 检测) |
| SessionManager | 会话管理器 | session_id 管理、会话恢复、24 小时有效期 |
| MessageSequenceValidator | 消息序号验证器 | 重放检测、消息丢失检测、序号回退检测 |
| EventSubscriptionManager | 事件订阅管理器 | 事件类型订阅、订阅请求发送 |
| FeishuWebSocketConcurrencyService | 并发控制服务 | 并发上界(背压闸门)、配置热更新、关停等待 |
| ReconnectionOrchestrator | 重连协调器 | 统一重连管理、防抖机制、冷却时间 |
| ExponentialBackoffReconnectStrategy | 指数退避重连策略 | 指数退避延迟、次数和时间双重限制 |
消息处理器
| 处理器 | 说明 |
|---|---|
| IMessageHandler | 消息处理器接口(CanHandle/HandleAsync) |
| JsonMessageHandler | 处理器基类(安全反序列化辅助) |
| FeishuEventMessageHandler | 事件消息处理器,支持 v1.0 和 v2.0 版本 |
| PingPongMessageHandler | Ping/Pong 消息处理器 |
| AuthMessageHandler | 认证响应处理器 |
| HeartbeatMessageHandler | 心跳消息处理器 |
| FeishuWebSocketClient | 主客户端,组合所有组件 |
架构优势
- 🎯 单一职责 - 每个组件专注特定功能,代码清晰易懂
- 🔧 代码复用性提升 - 组件化设计,各组件可独立使用
- 🧪 测试友好 - 每个组件可独立测试,依赖清晰
- 🚀 扩展性提升 - 新功能通过添加组件实现,配置灵活
自定义消息处理器
// 创建自定义消息处理器
public class CustomMessageHandler : JsonMessageHandler
{
public CustomMessageHandler(ILogger<CustomMessageHandler> logger) : base(logger)
{
}
public override bool CanHandle(string messageType)
=> messageType == "custom_type";
public override async Task HandleAsync(string message, CancellationToken cancellationToken = default)
{
var data = SafeDeserialize<CustomMessage>(message);
// 处理逻辑...
}
}
// ⚠️ 注意:IMessageHandler 的注册入口(MessageRouter)未对使用方暴露,
// 无法通过客户端实例注册自定义 IMessageHandler。
// 业务扩展请实现 IFeishuEventHandler 并通过 IFeishuEventHandlerFactory / DI 注册(见下方"自定义事件处理器"):
// services.AddScoped<IFeishuEventHandler, CustomEventHandler>();
💡 内置消息处理器(Ping/Pong、认证、心跳、事件)由
FeishuWebSocketClient构造时自动注册到内部MessageRouter;MessageRouter.RegisterHandler(IMessageHandler)为公开方法,可在自建消息路由时注册自定义处理器。
文件结构
Mud.Feishu.WebSocket/
├── Configuration/ # 配置选项
│ ├── FeishuWebSocketOptions.cs # 核心配置选项
│ ├── FeishuWebSocketOptionsValidator.cs # 配置验证器
│ ├── EventDeduplicationOptions.cs # 事件去重配置
│ ├── EventDeduplicationMode.cs # 去重模式枚举
│ └── MessageSizeLimits.cs # 消息大小限制
├── Core/ # 核心组件
│ ├── WebSocketConnectionManager.cs # 连接管理
│ ├── AuthenticationManager.cs # 认证管理
│ ├── MessageRouter.cs # 消息路由
│ ├── BinaryMessageProcessor.cs # 二进制处理
│ ├── HeartbeatManager.cs # 心跳管理
│ ├── SessionManager.cs # 会话管理
│ ├── MessageSequenceValidator.cs # 消息序号验证
│ ├── EventSubscriptionManager.cs # 事件订阅管理
│ ├── FeishuWebSocketConcurrencyService.cs # 并发控制(背压闸门)
│ ├── ReconnectionOrchestrator.cs # 重连协调器
│ ├── ExponentialBackoffReconnectStrategy.cs # 指数退避策略
│ ├── IReconnectStrategy.cs # 重连策略接口
│ ├── IReconnectionOrchestrator.cs # 重连协调器接口
│ ├── ErrorRecoveryStrategy.cs # 错误恢复策略
│ ├── RetryHelper.cs # 重试工具
│ └── JsonOptions.cs # JSON序列化选项
├── Handlers/ # 消息处理器
│ ├── FeishuEventMessageHandler.cs # 事件消息处理
│ ├── AuthMessageHandler.cs # 认证消息处理
│ ├── HeartbeatMessageHandler.cs # 心跳消息处理
│ ├── PingPongMessageHandler.cs # Ping/Pong处理
│ ├── JsonMessageHandler.cs # JSON消息基类
│ └── ScopedFeishuEventHandlerFactory.cs # 事件处理器工厂(Scoped 作用域)
├── Interfaces/ # 公共接口
│ ├── IFeishuWebSocketClient.cs # 客户端接口
│ ├── IFeishuWebSocketManager.cs # 管理器接口
│ └── IMessageHandler.cs # 消息处理器接口
├── SocketEventArgs/ # 事件参数类
├── DataModels/ # 数据模型
├── Exceptions/ # 异常定义
├── Extensions/ # 扩展方法
│ ├── FeishuWebSocketServiceBuilder.cs # 服务建造者
│ └── ServiceCollectionExtensions.cs # 注册扩展
├── FeishuWebSocketClient.cs # 主客户端
├── FeishuWebSocketManager.cs # 管理器实现
├── FeishuWebSocketHostedService.cs # 后台服务
└── WebSocketConnectionState.cs # 连接状态模型
🏗️ 服务注册方式
🚀 最简注册(推荐)
// 一行代码完成基础配置
builder.Services.CreateFeishuWebSocketServiceBuilder(builder.Configuration)
.AddHandler<MessageReceiveEventHandler>()
.Build();
📋 注册多个事件处理器
// 支持链式调用,注册多个处理器
builder.Services.CreateFeishuWebSocketServiceBuilder(builder.Configuration)
.AddHandler<MessageReceiveEventHandler>()
.AddHandler<UserCreateEventHandler>()
.AddHandler<MessageReadEventHandler>()
.Build();
⚙️ 代码配置
// 使用委托配置选项
builder.Services.CreateFeishuWebSocketServiceBuilder(options =>
{
options.Reconnect.Auto = true;
options.Reconnect.MaxAttempts = 5;
options.Reconnect.TotalBudget = TimeSpan.FromMinutes(30);
options.HeartbeatIntervalMs = 25000;
options.EventDeduplication.Mode = EventDeduplicationMode.InMemory;
})
.AddHandler<MessageReceiveEventHandler>()
.Build();
🎯 Apply 方法
// 使用 Apply 方法进行条件性配置
builder.Services.CreateFeishuWebSocketServiceBuilder(builder.Configuration)
.Apply(b =>
{
if (builder.Environment.IsDevelopment())
b.AddInterceptor<LoggingEventInterceptor>();
if (builder.Configuration.GetValue<bool>("Features:EnableAudit"))
b.AddHandler<AuditEventHandler>();
})
.AddHandler<MessageReceiveEventHandler>()
.Build();
🔌 添加事件拦截器
// 添加内置日志拦截器和自定义拦截器
builder.Services.CreateFeishuWebSocketServiceBuilder(builder.Configuration)
.AddInterceptor<LoggingEventInterceptor>() // 内置日志拦截器
.AddInterceptor<CustomTelemetryInterceptor>() // 自定义遥测拦截器
.AddHandler<MessageReceiveEventHandler>()
.Build();
🎯 三种处理器注册方式
// 方式1:类型注册(推荐)
.AddHandler<MessageReceiveEventHandler>()
// 方式2:工厂注册
.AddHandler(sp => new FactoryEventHandler(
sp.GetRequiredService<ILogger<FactoryEventHandler>>()))
// 方式3:实例注册
.AddHandler(new InstanceEventHandler())
🎯 事件处理器(策略模式)
内置事件处理器
| 处理器 | 事件类型 | 说明 |
|---|---|---|
MessageReceiveEventHandler |
im.message.receive_v1 |
接收消息事件 |
UserCreateEventHandler |
contact.user.created_v3 |
用户创建事件 |
MessageReadEventHandler |
im.message.message_read_v1 |
消息已读事件 |
ChatMemberUserAddedEventHandler |
im.chat.member.user.added_v1 |
用户加入群聊 |
ChatMemberUserDeletedEventHandler |
im.chat.member.user.deleted_v1 |
用户离开群聊 |
DefaultFeishuEventHandler<T> |
- | 未知事件类型处理(抽象基类) |
DepartmentCreatedEventHandler |
contact.department.created_v3 |
部门创建事件 |
DepartmentDeleteEventHandler |
contact.department.deleted_v3 |
部门删除事件 |
💡 提示:以上处理器来自
Mud.Feishu.EventCallback项目,通过代码生成器自动生成。你也可以实现IFeishuEventHandler接口创建自定义处理器。
使用内置事件处理器基类
Mud.Feishu.Abstractions 提供了多个内置事件处理器基类,继承这些基类可以简化开发:
用户事件处理器(通用基类)
using Mud.Feishu.Abstractions;
using Mud.Feishu.WebSocket.Services;
using System.Text.Json;
namespace YourProject.Handlers;
/// <summary>
/// 演示用户事件处理器 - 实现通用接口
/// </summary>
public class DemoUserEventHandler : IFeishuEventHandler
{
private readonly ILogger<DemoUserEventHandler> _logger;
private readonly DemoEventService _eventService;
public DemoUserEventHandler(ILogger<DemoUserEventHandler> logger, DemoEventService eventService)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_eventService = eventService ?? throw new ArgumentNullException(nameof(eventService));
}
public string SupportedEventType => "contact.user.created_v3";
public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default)
{
if (eventData == null)
throw new ArgumentNullException(nameof(eventData));
try
{
// 解析用户数据
var userData = ParseUserData(eventData);
// 记录事件到服务
await _eventService.RecordUserEventAsync(userData, cancellationToken);
// 模拟业务处理
await ProcessUserEventAsync(userData, cancellationToken);
_logger.LogInformation("✅ [用户事件] 用户创建事件处理完成: {UserId}", userData.UserId);
}
catch (Exception ex)
{
_logger.LogError(ex, "❌ [用户事件] 处理用户创建事件失败");
throw;
}
}
private UserData ParseUserData(EventData eventData)
{
var jsonElement = JsonSerializer.Deserialize<JsonElement>(eventData.Event?.ToString() ?? "{}");
var userElement = jsonElement.GetProperty("user");
return new UserData
{
UserId = userElement.GetProperty("user_id").GetString() ?? "",
UserName = userElement.GetProperty("name").GetString() ?? "",
Email = TryGetProperty(userElement, "email") ?? "",
Department = TryGetProperty(userElement, "department") ?? "",
CreatedAt = DateTime.UtcNow,
ProcessedAt = DateTime.UtcNow
};
}
private static string? TryGetProperty(JsonElement element, string propertyName)
{
return element.TryGetProperty(propertyName, out var value) ? value.GetString() : null;
}
}
/// <summary>
/// 用户数据模型
/// </summary>
public class UserData
{
public string UserId { get; init; } = string.Empty;
public string UserName { get; init; } = string.Empty;
public string Email { get; init; } = string.Empty;
public string Department { get; init; } = string.Empty;
public DateTime CreatedAt { get; init; }
public DateTime ProcessedAt { get; init; }
}
部门事件处理器(继承专用基类)
using Mud.Feishu.Abstractions;
using Mud.Feishu.Abstractions.EventHandlers;
using Mud.Feishu.Abstractions.Services;
using Mud.Feishu.EventCallback.Organization;
using Mud.Feishu.WebSocket.Services;
namespace YourProject.Handlers;
/// <summary>
/// 演示部门创建事件处理器 - 继承 DepartmentCreatedEventHandler 基类
/// </summary>
public class DemoDepartmentEventHandler : DepartmentCreatedEventHandler
{
private readonly DemoEventService _eventService;
public DemoDepartmentEventHandler(IFeishuEventDeduplicator businessDeduplicator, ILogger<DemoDepartmentEventHandler> logger, DemoEventService eventService) : base(businessDeduplicator, logger)
{
_eventService = eventService ?? throw new ArgumentNullException(nameof(eventService));
}
protected override async Task ProcessBusinessLogicAsync(
EventData eventData,
DepartmentCreatedResult? departmentData,
FeishuEventHeader? header,
CancellationToken cancellationToken = default)
{
if (eventData == null)
throw new ArgumentNullException(nameof(eventData));
_logger.LogInformation("[部门事件] 开始处理部门创建事件: {EventId}", eventData.EventId);
if (departmentData == null)
{
_logger.LogWarning("[部门事件] 部门创建事件数据为空,跳过处理: {EventId}", eventData.EventId);
return;
}
try
{
// 记录事件到服务
await _eventService.RecordDepartmentEventAsync(departmentData, cancellationToken);
// 模拟业务处理
if (departmentData?.Object != null)
{
await ProcessDepartmentEventAsync(departmentData.Object, cancellationToken);
}
_logger.LogInformation("[部门事件] 部门创建事件处理完成: {DepartmentId}", departmentData?.Object?.DepartmentId);
}
catch (Exception ex)
{
_logger.LogError(ex, "[部门事件] 处理部门创建事件失败");
throw;
}
}
private async Task ProcessDepartmentEventAsync(DepartmentResultInfo departmentData, CancellationToken cancellationToken)
{
_logger.LogDebug("🔄 [部门事件] 开始处理部门数据: {DepartmentId}", departmentData.DepartmentId);
// 模拟异步业务操作
await Task.Delay(100, cancellationToken);
// 模拟验证逻辑
if (string.IsNullOrWhiteSpace(departmentData.DepartmentId))
{
throw new ArgumentException("部门ID不能为空");
}
// 模拟权限初始化
_logger.LogInformation("[部门事件] 初始化部门权限: {DepartmentName}", departmentData.Name);
// 模拟更新统计信息
_eventService.IncrementDepartmentCount();
await Task.CompletedTask;
}
}
/// <summary>
/// 演示部门删除事件处理器 - 继承 DepartmentDeleteEventHandler 基类
/// </summary>
public class DemoDepartmentDeleteEventHandler : DepartmentDeleteEventHandler
{
public DemoDepartmentDeleteEventHandler(IFeishuEventDeduplicator businessDeduplicator, ILogger<DepartmentDeleteEventHandler> logger) : base(businessDeduplicator, logger)
{
}
protected override async Task ProcessBusinessLogicAsync(
EventData eventData,
DepartmentDeleteResult? eventEntity,
FeishuEventHeader? header,
CancellationToken cancellationToken = default)
{
if (eventData == null)
throw new ArgumentNullException(nameof(eventData));
if (eventEntity == null)
{
_logger.LogWarning("部门删除事件实体为空,跳过处理");
return;
}
_logger.LogInformation("🗑️ [部门删除事件] 开始处理部门删除事件");
_logger.LogDebug("部门删除事件详情: {@EventEntity}", eventEntity);
await Task.CompletedTask;
}
}
创建自定义事件处理器
public class CustomEventHandler : IFeishuEventHandler
{
private readonly ILogger<CustomEventHandler> _logger;
public CustomEventHandler(ILogger<CustomEventHandler> logger)
=> _logger = logger ?? throw new ArgumentNullException(nameof(logger));
public string SupportedEventType => "custom.event.example_v1";
public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default)
{
if (eventData == null) throw new ArgumentNullException(nameof(eventData));
_logger.LogInformation("🎯 处理自定义事件: {EventType}", eventData.EventType);
// 实现你的业务逻辑
await ProcessBusinessLogicAsync(eventData);
}
private async Task ProcessBusinessLogicAsync(EventData eventData)
{
// 数据库操作、外部API调用等
await Task.CompletedTask;
}
}
注册自定义处理器
// 注册处理器(多种方式)
builder.Services.CreateFeishuWebSocketServiceBuilder(builder.Configuration)
.AddHandler<CustomEventHandler>() // 类型注册
.AddHandler(sp => new FactoryEventHandler( // 工厂注册
sp.GetRequiredService<ILogger<FactoryEventHandler>>()))
.AddHandler(new InstanceEventHandler()) // 实例注册
.Build();
事件拦截器(Interceptors)
事件拦截器允许在事件处理前后执行自定义逻辑,如日志记录、指标收集、权限验证等。
内置拦截器
LoggingEventInterceptor - 记录事件处理日志
builder.Services.CreateFeishuWebSocketServiceBuilder(builder.Configuration)
.AddInterceptor<LoggingEventInterceptor>() // 记录事件处理开始和结束
.AddHandler<MessageReceiveEventHandler>()
.Build();
自定义拦截器
创建自定义拦截器需要实现 IFeishuEventInterceptor 接口:
using Mud.Feishu.Abstractions;
/// <summary>
/// 自定义遥测拦截器示例
/// </summary>
public class CustomTelemetryInterceptor : IFeishuEventInterceptor
{
private readonly ILogger<CustomTelemetryInterceptor> _logger;
public CustomTelemetryInterceptor(ILogger<CustomTelemetryInterceptor> logger)
=> _logger = logger;
/// <summary>
/// 事件处理前拦截
/// </summary>
/// <returns>返回 false 将中断事件处理流程</returns>
public Task<bool> BeforeHandleAsync(string eventType, EventData eventData, CancellationToken cancellationToken = default)
{
_logger.LogInformation("[遥测] 事件开始: {EventType}, EventId: {EventId}", eventType, eventData.EventId);
return Task.FromResult(true); // 返回 true 继续处理,false 中断
}
/// <summary>
/// 事件处理后拦截
/// </summary>
public Task AfterHandleAsync(string eventType, EventData eventData, Exception? exception, CancellationToken cancellationToken = default)
{
if (exception == null)
{
_logger.LogInformation("[遥测] 事件成功: {EventType}", eventType);
}
else
{
_logger.LogError(exception, "[遥测] 事件失败: {EventType}", eventType);
}
return Task.CompletedTask;
}
}
注册自定义拦截器
// 类型注册
.AddInterceptor<CustomTelemetryInterceptor>()
// 工厂注册
.AddInterceptor(sp => new CustomTelemetryInterceptor(
sp.GetRequiredService<ILogger<CustomTelemetryInterceptor>>()))
// 实例注册
var interceptor = new CustomTelemetryInterceptor(logger);
.AddInterceptor(interceptor)
拦截器执行顺序
拦截器按注册顺序依次执行,完整流程:
WebSocket 事件到达
↓
拦截器1: BeforeHandleAsync
↓
拦截器2: BeforeHandleAsync
↓
...
↓
拦截器N: BeforeHandleAsync
↓
[事件处理器处理事件]
↓
拦截器N: AfterHandleAsync
↓
...
↓
拦截器2: AfterHandleAsync
↓
拦截器1: AfterHandleAsync
↓
处理完成
运行时动态注册
public class ServiceManager
{
private readonly IFeishuEventHandlerFactory _factory;
private readonly ILogger<ServiceManager> _logger;
public ServiceManager(IFeishuEventHandlerFactory factory, ILogger<ServiceManager> logger)
{
_factory = factory;
_logger = logger;
}
public void RegisterHandler()
{
var customHandler = new CustomEventHandler(_logger);
_factory.RegisterHandler(customHandler);
_logger.LogInformation("已注册自定义处理器: {HandlerType}", typeof(CustomEventHandler).Name);
}
}
⚙️ 配置选项
WebSocket 核心配置
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
AppKey |
string | "default" | 飞书应用 AppKey,用于指标维度(支持热更新) |
Reconnect.Auto |
bool | true | 自动重连 |
Reconnect.MaxAttempts |
int | 5 | 最大重连次数,0 表示无限(仅受 Reconnect.TotalBudget 限制) |
Reconnect.MaxAuthRetryAttempts |
int | 5 | 认证最大重试次数,0 表示无限(独立于重连次数) |
Reconnect.BaseDelayMs |
int | 5000 | 重连基础延迟(ms),最小 1000 |
Reconnect.MaxDelayMs |
int | 30000 | 最大重连延迟(ms),须 ≥ Reconnect.BaseDelayMs |
Reconnect.TotalBudget |
TimeSpan | 30 分钟 | 最大重连总时间,超时后停止重连 |
Reconnect.Cooldown |
TimeSpan | 5 秒 | 两次重连尝试间的最小间隔 |
EnableReconnectMetrics |
bool | true | 是否启用重连指标收集 |
HeartbeatIntervalMs |
int | 25000 | 心跳间隔(ms),最小 5000(飞书建议 25 秒内) |
ConnectionTimeoutMs |
int | 10000 | 连接超时(ms) |
InitialReceiveBufferSize |
int | 4096 | 初始接收缓冲区大小(字节) |
HealthCheckIntervalMs |
int | 60000 | 健康检查间隔(ms),最小 1000 |
MessageHandlerTimeoutMs |
int | 30000 | 单条消息处理超时(ms),0 表示不限制 |
MaxConcurrentHandlers |
int | 32 | 并发处理器上界(背压闸门),0/负数表示无限制。背压已前移到接收路径 |
AuthTimeoutMs |
int | 30000 | 认证响应超时(ms),0 回退默认 30000 |
AuthGateTimeoutMs |
int | 0 | 认证闸门等待上限(ms),0=关闭(支持热更新) |
ProtocolKeepAliveInterval |
TimeSpan | 20 秒 | 协议级 Ping/Pong 保活间隔,0=禁用(5–300 秒) |
SequenceGapThreshold |
ulong | 0 | 消息序号跳跃阈值,0 表示禁用跳跃检测 |
Certificate.Mode |
CertificateValidationMode |
Strict |
证书校验模式:Strict / Dev / Custom(Custom 时必须提供 Certificate.CustomCallback) |
Certificate.ValidateServerCertificate |
bool | true | 是否验证 SSL 证书(生产环境建议 true) |
Certificate.AllowSelfSignedCertificates |
bool | false | 是否允许自签名证书(仅 Mode=Dev 放行,生产建议 false) |
Certificate.AllowCertificateNameMismatch |
bool | false | 是否允许证书名称不匹配(仅 Mode=Dev 放行) |
Certificate.AllowInsecureWebSocket |
bool | false | 是否允许 ws:// 不安全连接(仅开发/测试环境) |
Certificate.CustomCallback |
RemoteCertificateValidationCallback? | null | 自定义证书验证回调(Mode=Custom 时必须提供) |
AllowedHostSuffixes |
string | *.feishu.cn;*.larksuite.com |
主机白名单:*. 通配后缀或精确主机名,分号分隔,大小写不敏感;置空表示不限制(连接自建代理/本地测试端点时使用) |
EventDeduplication |
EventDeduplicationOptions | 见下 | 事件去重配置 |
ℹ️ 旧扁平键兼容:JSON 配置里的
AutoReconnect/MaxReconnectAttempts/ReconnectDelayMs/ValidateServerCertificate/AllowSelfSignedCertificates等旧键仍可绑定(启动时自动回填到Reconnect.*/Certificate.*),但 C# 代码必须使用嵌套 API。迁移对照见 配置迁移(R2/R4)。
消息大小限制配置 (MessageSizeLimits)
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
MaxTextMessageSize |
int | 1048576 | 最大文本消息大小(字符) |
MaxTextMessageBytes |
int | 0 | 最大文本消息大小(UTF-8 字节),0 = 按 3 × MaxTextMessageSize 自动推导(发送/接收同源) |
MaxBinaryMessageSize |
long | 10485760 | 最大二进制消息大小(字节),发送与接收均校验 |
配置示例:
{
"FeishuWebSocket": {
"MessageSizeLimits": {
"MaxTextMessageSize": 1048576,
"MaxTextMessageBytes": 0,
"MaxBinaryMessageSize": 10485760
}
}
}
事件去重配置 (EventDeduplication)
ℹ️
EventDeduplicationOptions是 WebSocket 层的轻量去重配置。Abstractions 层还提供了功能更丰富的DeduplicationOptions,见下方高级去重配置。
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Mode |
EventDeduplicationMode |
InMemory |
去重模式(None/InMemory/Distributed) |
CacheExpiration |
TimeSpan | 2.00:00:00 | 缓存过期时间,默认 48 小时 |
CleanupInterval |
TimeSpan | 00:05:00 | 缓存清理间隔,默认 5 分钟 |
ProcessingTimeout |
TimeSpan | 00:10:00 | 处理中超时时间,超时后允许重新处理,默认 10 分钟 |
MaxCacheSize |
int | 100000 | 内存缓存最大条目数,0 表示不限制 |
去重模式说明:
None- 禁用去重(不推荐,仅用于特殊场景)InMemory- 内存去重(单实例,默认)Distributed- 分布式去重(需配置IFeishuEventDistributedDeduplicator)
配置示例:
{
"FeishuWebSocket": {
"EventDeduplication": {
"Mode": "InMemory",
"CacheExpiration": "2.00:00:00",
"CleanupInterval": "00:05:00"
}
}
}
高级去重配置 (DeduplicationOptions)
ℹ️
DeduplicationOptions位于Mud.Feishu.Abstractions.Configuration命名空间,由FeishuEventDeduplicator和UnifiedDeduplicationMiddleware使用,提供比EventDeduplicationOptions更丰富的去重控制能力。
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
CacheExpiration |
TimeSpan | 48 小时 | 缓存过期时间,最小 1 分钟 |
ProcessingTimeout |
TimeSpan | 10 分钟 | 处理中超时时间,超时后允许重新处理,最小 10 秒 |
CleanupInterval |
TimeSpan | 5 分钟 | 缓存清理间隔(仅内存模式),最小 30 秒 |
KeyPrefix |
string | "feishu:event:" | Redis 键前缀,用于应用/环境隔离(仅分布式模式) |
MaxCacheSize |
int | 100000 | 内存缓存最大条目数,0 表示不限制(仅内存模式) |
ℹ️ R4:
AllowProcessingOnFallback/MaxRetryCount/InitialRetryDelay/MaxRetryDelay/EnableVerboseLogging已移除(Redis 主路径不消费);失败事件重试请使用FeishuWebhook:Retry(FailedEventRetryOptions),去重日志改用Logging:LogLevel:*。
预设配置:
可通过静态属性快速应用预设配置:
using Mud.Feishu.Abstractions.Configuration;
// 默认配置(标准模式)
var dedupOptions = DeduplicationOptions.Default;
// 高可靠性模式(72小时缓存,5分钟处理超时,Redis失败时拒绝处理)
var dedupOptions = DeduplicationOptions.HighReliability;
// 高可用性模式(48小时缓存,15分钟处理超时,Redis失败时降级处理)
var dedupOptions = DeduplicationOptions.HighAvailability;
// 注入到 FeishuEventDeduplicator
services.AddSingleton(new FeishuEventDeduplicator(dedupOptions));
🎯 高级用法
多环境配置
var webSocketBuilder = builder.Services.CreateFeishuWebSocketServiceBuilder(configuration);
if (builder.Environment.IsDevelopment())
{
webSocketBuilder.ConfigureOptions(options => {
options.HeartbeatIntervalMs = 15000;
});
}
else
{
webSocketBuilder.ConfigureFrom(configuration, "Production:Feishu:WebSocket");
}
webSocketBuilder.AddHandler<DevEventHandler>()
.AddHandler<ProdEventHandler>()
.Build();
条件性处理器注册
builder.Services.CreateFeishuWebSocketServiceBuilder(configuration)
.AddHandler<BaseEventHandler>()
.Apply(webSocketBuilder => {
if (configuration.GetValue<bool>("Features:EnableAudit"))
webSocketBuilder.AddHandler<AuditEventHandler>();
if (configuration.GetValue<bool>("Features:EnableAnalytics"))
webSocketBuilder.AddHandler<AnalyticsEventHandler>();
if (configuration.GetValue<bool>("Features:EnableTelemetry"))
webSocketBuilder.AddInterceptor<TelemetryInterceptor>();
})
.Build();
配置 Redis 分布式去重
// 注册Redis分布式去重服务
builder.Services.AddFeishuRedisDeduplicators(builder.Configuration);
// 配置飞书WebSocket服务
builder.Services.CreateFeishuWebSocketServiceBuilder(builder.Configuration)
.AddInterceptor<LoggingEventInterceptor>()
.AddHandler<MessageReceiveEventHandler>()
.Build();
指定配置节名称
// 使用非默认配置节
builder.Services.CreateFeishuWebSocketServiceBuilder(
configuration,
sectionName: "CustomFeishu") // 配置节名称
.AddHandler<MessageReceiveEventHandler>()
.Build();
🔧 高级功能
手动连接控制
public class ConnectionService
{
private readonly IFeishuWebSocketManager _manager;
public ConnectionService(IFeishuWebSocketManager manager)
=> _manager = manager;
public async Task StartAsync() => await _manager.StartAsync();
public async Task StopAsync() => await _manager.StopAsync();
public async Task ReconnectAsync() => await _manager.ReconnectAsync();
public async Task SendMessageAsync(string message)
=> await _manager.SendMessageAsync(message);
public void SubscribeEvents()
{
_manager.Connected += OnConnected;
_manager.Disconnected += OnDisconnected;
_manager.Error += OnError;
_manager.MessageReceived += OnMessageReceived;
}
public WebSocketConnectionState GetConnectionState()
=> _manager.GetConnectionState();
public (TimeSpan Uptime, int ReconnectCount, Exception? LastError) GetStats()
=> _manager.GetConnectionStats();
}
连接令牌契约(重要:本轮行为变更)
ConnectAsync(endpoint, [appAccessToken,] cancellationToken) 的 cancellationToken
只约束建连阶段(TCP/WS 握手 + 认证),不构成连接生命周期:
- 连接成功后取消该令牌不会中断接收循环或心跳(此前会被链接为生命周期令牌,取消即静默停帧);
- 终止连接请调用
DisconnectAsync()/DisposeAsync();需要重连请调用IFeishuWebSocketManager.ReconnectAsync()。
背景:旧行为会在"socket 仍为
Open但不再读帧"时形成僵尸连接 (无事件、无断线通知、只能等健康检查轮询兜底)。当前实现为: 每一条终止路径都会产生断线声明,并由存活探针兜底外部静默失败(见下)。
回调线程契约(重要):Connected/Disconnected 事件均在锁外派发,
因此你可以在回调中安全地发起 ConnectAsync/DisconnectAsync/SendMessageAsync(不会自锁)。
- 持锁期间产生的事件会被缓冲,并在释放锁后按原顺序派发
(替换旧连接时顺序固定为"先
Disconnected、后Connected"); - 回调内抛出的异常会被记录并吞掉,不会改变
ConnectAsync/DisconnectAsync的返回/异常语义; - 但请避免在
Disconnected回调中无条件重连:这会造成重连风暴(这与 SDK 无关,属使用侧纪律)。
接收事件线程契约(重要:本轮行为变更)
MessageReceived 是观测钩子(日志/指标),不是业务处理入口:
| 项 | 现契约 |
|---|---|
| 派发线程 | 不在接收循环线程,而在取得并发租约后的处理任务体内 |
| 并发性 | 可能并发(受 MaxConcurrentHandlers 约束) |
| 顺序 | 不保证与帧到达顺序一致 |
| 阻塞影响 | 回调内同步阻塞只占用一个并发槽位(形成反压),不会阻塞接收管道 |
需要"顺序处理"或"受
MessageHandlerTimeoutMs保护"的业务逻辑,请改用IMessageHandler(经MessageRouter分发)。
连接存活探针与健康检查
FeishuWebSocketHealthCheck 的 data 新增存活维度,用于识别"连接看似正常但收不到事件":
| 字段 | 含义 |
|---|---|
receive_loop_alive |
接收循环任务是否仍在运行 |
last_receive_utc |
最近一次收到帧的时间(never = 从未收到) |
idle_ms |
距最近一次收帧的毫秒数(-1 = 尚无样本) |
is_zombie |
true = 僵尸态(State == Open 但接收循环已结束)⇒ 判 Unhealthy |
判定口径:只有"
State == Open且接收循环已结束"才判僵尸;单纯"长时间无帧"不判死 (长连接空闲期本就没有事件帧)。僵尸态同时会被后台服务的周期性检查捕获并触发重连。
配置上界(启动期 fail-fast)
FeishuWebSocketOptions.Validate() 现为双向校验(下界 + 上界)。越界会在应用启动时失败:
| 配置 | 上界 |
|---|---|
Reconnect.TotalBudget |
7 天 |
Reconnect.BaseDelayMs / Reconnect.MaxDelayMs |
1 小时 |
MessageSizeLimits.MaxTextMessageSize |
10 MB |
ConnectionTimeoutMs / AuthTimeoutMs / AuthGateTimeoutMs |
5 分钟 |
迁移:若既有部署使用了更大的值,请在配置中收敛到上界之内。上界的存在是为了避免 "配置值突破实现层表示域"(例如超长时长进入
CancellationTokenSource会抛异常, 而该异常发生在异步重连路径上,表现为自动重连静默失效)。
StartReceivingAsync 已弃用
接收循环由 ConnectAsync 统一管理,无需也不要显式调用 IFeishuWebSocketClient.StartReceivingAsync:
- 未连接时抛
InvalidOperationException; - 已有循环在途时为幂等 no-op 并记录告警。
迁移:删除对
StartReceivingAsync的调用即可;需要恢复接收请调用IFeishuWebSocketManager.ReconnectAsync()。
消息序号验证
内置 MessageSequenceValidator 可检测消息重放和丢失:
- 重复消息检测:滑动窗口去重(最近 1000 条消息)
- 序号回退检测:检测可能的攻击行为
- 消息丢失检测:序号跳跃超过阈值时发出警告
// 序号验证器在服务注册时自动启用
// 可通过事件订阅验证失败通知
var validator = serviceProvider.GetRequiredService<MessageSequenceValidator>();
validator.ValidationFailed += (sender, args) =>
{
if (args.MessageType == SequenceValidationType.SequenceRollback)
logger.LogWarning("检测到序号回退攻击: {Message}", args.Message);
else if (args.MessageType == SequenceValidationType.MessageLoss)
logger.LogWarning("可能丢失消息: {Message}", args.Message);
};
会话管理
SessionManager 管理 WebSocket 会话状态,支持断线恢复:
- 会话 ID 管理:自动跟踪当前 session_id
- 会话有效期:24 小时有效期检查
- 重连恢复:通过
GetSessionIdForReconnect()获取有效会话 ID 用于断线恢复 - 会话事件:
SessionUpdated事件通知会话变更
连接监控指标
⚠️ 早期文档中的
ConnectionMetrics组件与GetCurrentStats()API 并不存在(已核对源码)。 连接统计请使用IFeishuWebSocketManager.GetConnectionStats()(返回Uptime/ReconnectCount/LastError) 与GetConnectionState();细粒度指标通过FeishuMetrics(OpenTelemetry)暴露:
var manager = serviceProvider.GetRequiredService<IFeishuWebSocketManager>();
var (uptime, reconnectCount, lastError) = manager.GetConnectionStats();
// OTel 指标(按 feishu.app_key 维度)
// feishu.websocket.connections —— 活跃连接数
// feishu.websocket.backlog —— 在途待处理消息数
// feishu.websocket.message.duration —— 消息处理耗时分布
// feishu.websocket.reconnect —— 重连次数(outcome = success/failure)
// feishu.websocket.receive.idle_ms —— 接收静默时长(-1 = 尚无收帧样本)
// feishu.websocket.receive.loop_alive —— 接收循环是否存活(1/0)
// feishu.websocket.zombie —— 僵尸连接(1 = State 为 Open 但接收循环已结束)
// feishu.websocket.frames.discarded —— 受控丢弃计数(reason 维度)
// feishu.event.deduplication —— 去重命中/未命中
告警建议(R2/F1-F5):
| 指标条件 | 含义与处置 |
|---|---|
feishu.websocket.zombie == 1 |
僵尸连接:不可能再收到任何事件,HostedService 会自愈重连;持续出现请排查"外部取消/释放连接"的调用方 |
feishu.websocket.receive.loop_alive == 0 且 connections == 1 |
同上(zombie 的等价形态,便于不支持复合表达式告警的后端使用) |
receive.idle_ms 持续增长且 connections == 1 |
长连接空闲属正常(心跳是客户端→服务端单向);只有与 zombie/loop_alive 联合判读才构成异常 |
frames.discarded{reason="fragment_size_exceeded"} 增长 |
对端下发的消息超过 MessageSizeLimits 配置:要么调大上限,要么让对端拆分消息 |
frames.discarded{reason="drain_bound_exceeded"} 增长 |
对端持续投递永不结束的超限分片(协议层异常/恶意),客户端已主动断连重连 |
frames.discarded{reason="auth_gate_timeout"} 增长 |
认证完成前到达的业务帧被丢弃:检查认证耗时或调大 AuthGateTimeoutMs |
frames.discarded{reason="concurrency_rejected"} 增长 |
背压已达极限(MaxConcurrentHandlers)或并发服务正在关停:这是事件可能丢失的前兆 |
SSL/TLS 证书配置
builder.Services.CreateFeishuWebSocketServiceBuilder(builder.Configuration)
.ConfigureOptions(options =>
{
options.Certificate.ValidateServerCertificate = true;
options.Certificate.AllowSelfSignedCertificates = false;
})
.AddHandler<MessageReceiveEventHandler>()
.Build();
自定义证书验证:
options.Certificate.Mode = CertificateValidationMode.Custom;
options.Certificate.CustomCallback = (sender, certificate, chain, sslPolicyErrors) =>
{
return sslPolicyErrors == System.Net.Security.SslPolicyErrors.None;
};
自定义重连策略
实现 IReconnectStrategy 接口可替换默认的指数退避策略:
public class FixedIntervalReconnectStrategy : IReconnectStrategy
{
private readonly TimeSpan _interval;
public FixedIntervalReconnectStrategy(TimeSpan interval)
=> _interval = interval;
public TimeSpan CalculateDelay(int attemptCount) => _interval;
public bool ShouldContinueReconnect(int attemptCount, TimeSpan totalElapsedTime)
=> attemptCount <= 10;
}
// 注册自定义策略(在 CreateFeishuWebSocketServiceBuilder 之前)
builder.Services.AddSingleton<IReconnectStrategy>(
new FixedIntervalReconnectStrategy(TimeSpan.FromSeconds(10)));
访问令牌管理
FeishuWebSocketManager 的访问令牌缓存和自动刷新由 IAppTokenManager 统一管理,无需手动配置刷新间隔。
📋 支持的事件类型
WebSocket 消息类型
ping/pong- 连接保活heartbeat- 心跳消息event- 业务事件auth- 认证响应
主要业务事件
- 消息:
im.message.receive_v1,im.message.message_read_v1 - 群聊:
im.chat.member.user_added_v1,im.chat.member.user_deleted_v1 - 用户:
contact.user.created_v3,contact.user.updated_v3,contact.user.deleted_v3 - 部门:
contact.department.*_v3 - 审批:
approval.approval.*_v1 - 日程:
calendar.event.updated_v4 - 会议:
meeting.meeting.*_v1
📄 许可证
本项目遵循 MIT 许可证进行分发和使用。
🚀 立即开始使用飞书 WebSocket 客户端,构建稳定可靠的事件处理系统!
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 8.0.1)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
- Microsoft.Extensions.Options (>= 8.0.2)
- Mud.Feishu.Abstractions (>= 3.0.0-rc3)
- Mud.HttpUtils (>= 2.0.8)
- protobuf-net (>= 3.4.30)
- System.Text.Json (>= 10.0.9)
-
net10.0
- Microsoft.Extensions.Configuration.Binder (>= 10.0.9)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.9)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Options (>= 10.0.9)
- Mud.Feishu.Abstractions (>= 3.0.0-rc3)
- Mud.HttpUtils (>= 2.0.8)
- protobuf-net (>= 3.4.30)
-
net6.0
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 8.0.1)
- Microsoft.Extensions.Hosting.Abstractions (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
- Microsoft.Extensions.Options (>= 8.0.2)
- Mud.Feishu.Abstractions (>= 3.0.0-rc3)
- Mud.HttpUtils (>= 2.0.8)
- protobuf-net (>= 3.4.30)
-
net8.0
- Microsoft.Extensions.Configuration.Binder (>= 10.0.9)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.9)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Options (>= 10.0.9)
- Mud.Feishu.Abstractions (>= 3.0.0-rc3)
- Mud.HttpUtils (>= 2.0.8)
- protobuf-net (>= 3.4.30)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0-rc3 | 144 | 9/24/2026 |
| 3.0.0-rc2 | 147 | 9/18/2026 |
| 3.0.0-rc1 | 177 | 7/11/2026 |
| 3.0.0-preview5 | 145 | 6/25/2026 |
| 3.0.0-preview4 | 121 | 6/3/2026 |
| 3.0.0-preview3 | 126 | 5/22/2026 |
| 3.0.0-preview2 | 147 | 5/11/2026 |
| 3.0.0-preview1 | 175 | 5/1/2026 |
| 2.1.6 | 199 | 7/21/2026 |
| 2.1.5 | 177 | 6/25/2026 |
| 2.1.4 | 168 | 6/3/2026 |
| 2.1.3 | 208 | 5/22/2026 |
| 2.1.2 | 151 | 5/12/2026 |
| 2.1.1 | 147 | 5/11/2026 |
| 2.1.0 | 140 | 5/1/2026 |
| 2.0.9 | 204 | 4/24/2026 |
| 2.0.8 | 168 | 4/12/2026 |
| 2.0.7 | 193 | 4/7/2026 |
| 2.0.6 | 138 | 4/5/2026 |
| 2.0.5 | 242 | 3/28/2026 |