Mud.Feishu.Abstractions
3.0.0
dotnet add package Mud.Feishu.Abstractions --version 3.0.0
NuGet\Install-Package Mud.Feishu.Abstractions -Version 3.0.0
<PackageReference Include="Mud.Feishu.Abstractions" Version="3.0.0" />
<PackageVersion Include="Mud.Feishu.Abstractions" Version="3.0.0" />
<PackageReference Include="Mud.Feishu.Abstractions" />
paket add Mud.Feishu.Abstractions --version 3.0.0
#r "nuget: Mud.Feishu.Abstractions, 3.0.0"
#:package Mud.Feishu.Abstractions@3.0.0
#addin nuget:?package=Mud.Feishu.Abstractions&version=3.0.0
#tool nuget:?package=Mud.Feishu.Abstractions&version=3.0.0
Mud.Feishu.Abstractions
Mud.Feishu.Abstractions 是 MudFeishu 库的抽象层,提供了完整的飞书 API 访问能力,包括认证授权、令牌管理、多应用支持、事件订阅处理等核心功能。它支持 WebSocket 事件订阅和 HTTP 事件订阅两种模式,提供基于策略模式的事件处理机制,使开发人员能够轻松地在 .NET 应用程序中集成飞书服务。
🚀 特性
核心功能
- 🔐 完整的认证授权 - 支持应用令牌、租户令牌、用户令牌三种认证方式
- 🔑 智能令牌管理 - 带缓存的令牌管理器,自动刷新、过期检测、重试机制
- 🏢 多应用支持 - 统一管理多个飞书应用,每个应用拥有独立的配置和资源
- 🎭 令牌缓存抽象 - 支持内存缓存、Redis 等多种缓存实现
- 🛡️ 安全防护 - URL 白名单验证、私有 IP 检测、敏感信息脱敏
事件处理
- 📡 事件订阅抽象 - 提供完整的事件订阅和处理抽象层
- 🔧 策略模式 - 基于策略模式的事件处理器,支持多种事件类型
- 🏭 工厂模式 - 内置事件处理器工厂,支持动态注册和发现
- ⚡ 异步处理 - 完全异步的事件处理,支持并行处理
- 🔄 事件去重 - 支持事件 ID 去重和 SeqID 去重,保证幂等性
- 🎯 类型安全 - 强类型事件数据模型,避免运行时错误
- 🛠️ 拦截器机制 - 支持事件处理前后的自定义逻辑
开发体验
- 📋 丰富事件类型 - 支持飞书 40+ 种事件类型
- 🔄 可扩展 - 易于扩展新的事件类型和处理器
- 🛡️ 内置基类 - 提供默认事件处理器基类,简化开发
- 📦 多框架支持 - 支持 netstandard2.0、.NET 6.0 - .NET 10.0
- ⚡ 原生 AOT 支持 - net8.0+ 一等公民支持 Native AOT 发布,全链路源生成 JSON 序列化与配置绑定
- 🌐 HTTP 客户端 - 增强的 HTTP 客户端,支持重试、日志、文件下载
📦 安装
dotnet add package Mud.Feishu.Abstractions
🏛️ 核心架构
系统架构
graph TB
subgraph "Mud.Feishu.Abstractions"
subgraph 认证与授权
A[ITokenManager]
B[ITokenCache<T>]
C[IFeishuAuth]
end
subgraph 事件处理
D[EventHandler]
E[Interceptor]
F[Deduplicator]
end
subgraph 核心服务
G[IHttpClient]
H[FailedEventStore]
I[Config]
end
subgraph 多应用管理
J[IFeishuAppManager]
K[IMudAppContext]
L[AppSwitcher]
end
subgraph 工具类
M[UrlValidator]
N[MessageSanitizer]
O[RetryPolicy]
end
end
令牌管理流程
graph LR
A[应用请求] --> B[TokenManager]
B --> C{检查缓存}
C -->|命中| D[返回令牌]
C -->|未命中| E[调用飞书API获取令牌]
E --> F[存入缓存]
F --> G[返回令牌]
B --> H[后台任务自动刷新过期令牌]
多应用架构
graph TB
A[IFeishuAppManager<br/>应用管理器] --> B[应用实例<br/>App1/2/3]
B --> C[应用组件]
C --> C1[AppContext]
C --> C2[TokenManager]
C --> C3[TokenCache]
C --> C4[HttpClient]
B --> D[GetApi<T>]
D --> E[统一 API 调用接口<br/>自动切换应用]
事件处理流程
graph LR
A[飞书事件] --> B[EventData]
B --> C[EventHandlerFactory]
C --> D[IFeishuEventHandler]
C --> E[事件拦截器<br/>Interceptor]
C --> F[事件去重<br/>Deduplicator]
D --> G[业务逻辑]
E --> G
F --> G
D --> H[失败事件存储<br/>可选]
核心组件
认证与令牌管理
ITokenManager- 令牌管理器基础接口IAppTokenManager- 应用令牌管理器ITenantTokenManager- 租户令牌管理器IUserTokenManager- 用户令牌管理器(支持多用户)ITokenCache<T>- 令牌缓存抽象接口(泛型同步接口,来自 Mud.HttpUtils)ITokenStore- 令牌持久化存储接口(配合IFeishuTokenStoreFactory按应用创建)IFeishuAuthentication- 飞书认证 API 客户端
多应用管理
IFeishuAppManager- 应用管理器接口IMudAppContext- 应用上下文接口IFeishuAppContextSwitcher- 应用上下文切换接口FeishuAppManager- 应用管理器实现FeishuAppContext- 应用上下文实现FeishuAppConfig- 应用配置类FeishuAppConfigBuilder- 应用配置构建器
事件处理
EventData- 事件数据模型IFeishuEventHandler- 事件处理器接口DefaultFeishuEventHandler<T>- 抽象事件处理器基类IdempotentFeishuEventHandler<T>- 幂等性事件处理器IFeishuEventHandlerFactory- 事件处理器工厂IFeishuEventInterceptor- 事件拦截器接口IFeishuEventDeduplicator- 事件去重服务接口IFeishuSeqIDDeduplicator- SeqID 去重服务接口
核心服务
IEnhancedHttpClient- 增强型 HTTP 客户端接口IFailedEventStore- 失败事件存储接口IEventResult- 事件结果接口ObjectEventResult<T>- 对象事件结果类
数据模型
FeishuApiResult<T>- API 响应结果模型AppCredentials- 应用凭证模型FeishuEventTypes- 事件类型常量- 组织事件模型、IM 事件模型、审批事件模型等
🔑 令牌管理
令牌类型
Mud.Feishu.Abstractions 支持三种飞书令牌类型:
| 令牌类型 | 接口 | 用途 | 有效期 |
|---|---|---|---|
| 应用令牌 | IAppTokenManager |
应用级别的权限验证 | 2 小时 |
| 租户令牌 | ITenantTokenManager |
租户级别的权限验证 | 2 小时 |
| 用户令牌 | IUserTokenManager |
用户级别的权限验证 | 根据授权类型而定 |
💡 使用
FeishuTokenTypes常量类(Mud.Feishu.Abstractions命名空间)替代魔法字符串:FeishuTokenTypes.TenantAccessToken、FeishuTokenTypes.AppAccessToken、FeishuTokenTypes.UserAccessToken。
令牌管理器特性
- 自动缓存 - 自动缓存令牌,减少 API 调用
- 智能刷新 - 令牌即将过期时自动刷新
- 并发控制 - 使用 Lazy 加载防止并发请求导致的缓存击穿
- 重试机制 - 获取令牌失败时自动重试(最多 2 次,指数退避)
- 线程安全 - 所有操作都是线程安全的
- 统计信息 - 提供缓存统计信息(总数、过期数)
使用示例
1. 基本使用
// 注入应用令牌管理器
public class MyService
{
private readonly IAppTokenManager _appTokenManager;
public MyService(IAppTokenManager appTokenManager)
{
_appTokenManager = appTokenManager;
}
public async Task CallFeishuApiAsync()
{
// 获取令牌(自动处理缓存、刷新等)
var token = await _appTokenManager.GetTokenAsync();
// 使用令牌调用飞书 API
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
// ...
}
}
2. 多用户令牌管理
public class UserService
{
private readonly IUserTokenManager _userTokenManager;
public UserService(IUserTokenManager userTokenManager)
{
_userTokenManager = userTokenManager;
}
// 获取特定用户的令牌
public async Task<string?> GetUserTokenAsync(string userId)
{
return await _userTokenManager.GetTokenAsync(userId);
}
// 使用授权码获取用户令牌
public async Task<UserTokenInfo?> GetUserTokenWithCodeAsync(string code, string redirectUri)
{
return await _userTokenManager.GetUserTokenWithCodeAsync(code, redirectUri);
}
// 刷新用户令牌(refresh token 由 SDK 内部令牌存储读取,无需调用方传入)
public async Task<UserTokenInfo?> RefreshUserTokenAsync(string userId, CancellationToken cancellationToken = default)
{
return await _userTokenManager.RefreshUserTokenAsync(userId, cancellationToken);
}
}
3. 令牌缓存(ITokenCache<T>)
令牌管理器内部的令牌缓存由 ITokenCache<T>(来自 Mud.HttpUtils 命名空间)提供。这是一个泛型同步接口,默认实现为基于 ConcurrentDictionary 的内存缓存:
public interface ITokenCache<T> : IDisposable where T : class
{
bool TryGet(string key, out T? value);
void Set(string key, T? value);
void Set(string key, T? value, TimeSpan? absoluteExpirationRelativeToNow,
TimeSpan? slidingExpiration, Action<string>? postEvictionCallback = null);
bool TryRemove(string key, out T? removed);
int Count { get; }
IEnumerable<string> Keys { get; }
void Clear();
void Compact(double percentage);
}
令牌的缓存与过期刷新由令牌管理器基类(TokenManagerBase)自动管理,缓存条目按应用(AppKey)通过键前缀隔离,
多数场景下无需自行实现;如确需替换为 Redis 等实现,可参照上述契约编写自定义 ITokenCache<T>。
4. 令牌持久化(ITokenStore)
如需将令牌持久化到外部存储(例如跨重启恢复、分布式部署),应实现 ITokenStore 接口,
并通过 IFeishuTokenStoreFactory 为每个应用创建独立的存储实例(多应用隔离):
public class MyTokenStore : ITokenStore
{
public Task<string?> GetAccessTokenAsync(string tokenType, CancellationToken cancellationToken = default) { /* ... */ }
public Task SetAccessTokenAsync(string tokenType, string accessToken, long expiresInSeconds, CancellationToken cancellationToken = default) { /* ... */ }
public Task<string?> GetRefreshTokenAsync(string tokenType, CancellationToken cancellationToken = default) { /* ... */ }
public Task SetRefreshTokenAsync(string tokenType, string refreshToken, CancellationToken cancellationToken = default) { /* ... */ }
public Task RemoveAsync(string tokenType, CancellationToken cancellationToken = default) { /* ... */ }
public Task<IEnumerable<string>> GetTokenTypesAsync(CancellationToken cancellationToken = default) { /* ... */ }
public Task ClearAsync(CancellationToken cancellationToken = default) { /* ... */ }
}
注意:持久化的令牌值必须携带过期时间戳(
TokenStoreHelper.EncodeStoredToken编码为{expireTimestampMs}|{token}格式),缺失过期时间戳的值会被视为缓存未命中。 SDK 内置FeishuTokenStore(IMemoryCache,单实例)与RedisTokenStore(Mud.Feishu.Redis,分布式)两种实现。
令牌缓存策略
令牌管理器使用以下策略来优化性能:
- 首次访问 - 调用飞书 API 获取令牌
- 后续访问 - 从缓存返回令牌(如果未过期)
- 即将过期 - 令牌过期前 5 分钟(默认)自动刷新
- 并发请求 - 使用 Lazy 加载,多个并发请求只触发一次刷新
- 失败重试 - 获取令牌失败时自动重试 2 次,使用指数退避策略
🏢 多应用支持
核心概念
Mud.Feishu.Abstractions 提供完整的多应用管理能力,允许在同一个系统中管理多个飞书应用:
- 独立配置 - 每个应用拥有独立的 AppId、AppSecret、BaseUrl 等配置
- 独立资源 - 每个应用拥有独立的令牌管理器、缓存、HTTP 客户端
- 统一管理 - 通过 IFeishuAppManager 统一管理所有应用
- 动态切换 - 支持运行时动态添加、移除、切换应用
- 缓存隔离 - 使用 PrefixedTokenCache 确保不同应用的令牌缓存互不干扰
应用配置
// 方式 1: 使用配置文件
{
"FeishuApps": [
{
"AppKey": "default",
"AppId": "cli_xxxxxx",
"AppSecret": "xxxxxx",
"BaseUrl": "https://open.feishu.cn",
"IsDefault": true
},
{
"AppKey": "approval",
"AppId": "cli_yyyyyy",
"AppSecret": "yyyyyy",
"BaseUrl": "https://open.feishu.cn"
}
]
}
// 方式 2: 使用代码配置(FeishuAppConfigBuilder + 配置委托)
builder.Services.AddFeishuApp(configs =>
{
configs.AddDefaultApp("default", "cli_xxxxxx", "xxxxxx", opt =>
{
opt.BaseUrl = "https://open.feishu.cn";
opt.TimeoutSeconds = 30;
opt.HttpRetry.MaxAttempts = 3;
});
configs.AddApp("approval", "cli_yyyyyy", "yyyyyy", opt =>
{
opt.TimeoutSeconds = 60;
opt.HttpRetry.MaxAttempts = 5;
});
});
// 方式 3: 使用构建器链式调用
builder.Services.AddFeishuApp(builder =>
{
builder.AddDefaultApp("default", "cli_xxxxxx", "xxxxxx")
.AddApp("approval", "cli_yyyyyy", "yyyyyy")
.AddApp("im", "cli_zzzzzz", "zzzzzz");
});
使用多应用
1. 获取指定应用的 API
public class MultiAppService
{
private readonly IFeishuAppManager _appManager;
public MultiAppService(IFeishuAppManager appManager)
{
_appManager = appManager;
}
public async Task UseDefaultAppAsync()
{
// 获取默认应用的 API(T 为源生成器生成的 API 客户端接口,需实现 IAppContextSwitcher)
var api = _appManager.GetDefaultWebApi<IMyApi>();
await api.DoSomethingAsync();
}
public async Task UseSpecificAppAsync(string appKey)
{
// 获取指定应用的 API(内部已调用 UseApp(appKey) 切换上下文)
var api = _appManager.GetWebApi<IMyApi>(appKey);
await api.DoSomethingAsync();
}
}
2. 应用上下文切换
推荐:使用
BeginScope(string)进行作用域切换(生成客户端实现的IFeishuAppContextSwitcher接口成员), 返回IDisposable,配合using在作用域结束自动恢复上下文。UseApp(appKey)/UseDefaultApp()为无作用域切换,不会自动归还上下文。
using Mud.Feishu.Abstractions;
public class AppSwitchingService
{
private readonly IFeishuAppManager _appManager;
private readonly IMyApi _api; // 源生成器生成的 API 客户端接口,实现了 IFeishuAppContextSwitcher
public AppSwitchingService(IFeishuAppManager appManager, IMyApi api)
{
_appManager = appManager;
_api = api;
}
public async Task WorkWithAppsAsync(string approvalAppKey)
{
// 推荐方式:BeginScope(appKey) 作用域切换,作用域结束自动恢复上下文
using (_api.BeginScope(approvalAppKey))
{
var approvalToken = await _api.GetTokenAsync();
// 作用域内的 API 调用均使用 approval 应用的上下文
}
// 获取指定应用的令牌管理器:先经 GetApp(appKey) 取应用上下文,
// 再用 FeishuTokenTypes 常量指定令牌类型
var tenantToken = await _appManager
.GetApp(approvalAppKey)
.GetTokenManager(FeishuTokenTypes.TenantAccessToken)
.GetTokenAsync();
// 其他令牌类型同理:
// _appManager.GetApp(approvalAppKey).GetTokenManager(FeishuTokenTypes.AppAccessToken)
// _appManager.GetApp(approvalAppKey).GetTokenManager(FeishuTokenTypes.UserAccessToken)
}
}
<details> <summary>旧方式(不推荐)</summary>
// ⚠️ UseApp / UseDefaultApp 为无作用域切换:直接写入当前上下文,
// 不返回 IDisposable、不会自动恢复,长生命周期宿主(后台服务、单例编排等)
// 中存在上下文泄漏风险,应优先使用 BeginScope(appKey)
var approvalContext = _api.UseApp("approval");
var defaultContext = _api.UseDefaultApp();
</details>
3. 多应用最佳实践
public class MessageService
{
private readonly IServiceProvider _serviceProvider;
/// <summary>
/// ❌ 错误做法 - 没有显式切换应用上下文
/// </summary>
/// <remarks>
/// 直接从 DI 容器获取的服务可能使用默认应用,无法保证使用正确的应用凭证。
/// </remarks>
public async Task SendMessageAsync_Wrong(string message)
{
var userApi = _serviceProvider.GetRequiredService<IFeishuTenantV3User>();
// 此时使用的可能是错误的应用凭证!
await userApi.SendMessageAsync(message);
}
/// <summary>
/// ✅ 正确做法 - 显式切换应用上下文
/// </summary>
/// <remarks>
/// 使用 IFeishuAppManager 获取指定应用的 API,确保使用正确的应用凭证。
/// </remarks>
public async Task SendMessageAsync_Correct(string message)
{
var appManager = _serviceProvider.GetRequiredService<IFeishuAppManager>();
// 方法1: 直接使用 IFeishuAppManager 获取指定应用的 API(推荐)
var approvalUserApi = appManager.GetWebApi<IFeishuTenantV3User>("approval-app");
// 方法2: 使用 UseApp 切换(注意:有线程安全问题)
// var userApi = _serviceProvider.GetRequiredService<IFeishuTenantV3User>();
// var appContext = userApi.UseApp("approval-app");
await approvalUserApi.SendMessageAsync(message);
}
}
重要提示:
⚠️ 线程安全警告:直接使用
UseApp()方法会改变服务实例的状态,在多线程环境下可能导致应用上下文混乱。推荐使用IFeishuAppManager.GetWebApi<T>(appKey)方法获取独立的服务实例。✅ 推荐做法:始终通过
IFeishuAppManager获取指定应用的 API 实例,这样可以确保每次都使用正确的应用凭证,并避免线程安全问题。📝 后台任务:在后台任务中,必须明确指定应用,不能依赖默认应用,否则可能导致应用上下文错误。
4. 动态管理应用
public class DynamicAppManager
{
private readonly IFeishuAppManager _appManager;
// 运行时添加新应用
public void AddNewApp()
{
var newConfig = new FeishuAppConfig
{
AppKey = "newApp",
AppId = "cli_newxxx",
AppSecret = "newsecret",
IsDefault = false
};
_appManager.AddApp(newConfig);
}
// 检查应用是否存在
public bool CheckAppExists(string appKey)
{
return _appManager.HasApp(appKey);
}
// 获取所有应用
public IEnumerable<IMudAppContext> GetAllApps()
{
return _appManager.GetAllApps();
}
// 移除应用
public bool RemoveApp(string appKey)
{
return _appManager.RemoveApp(appKey);
}
}
应用配置选项
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
AppKey |
string | - | 应用唯一标识(必需) |
AppId |
string | - | 飞书应用 ID(必需) |
AppSecret |
string | - | 飞书应用密钥(必需) |
BaseUrl |
string | https://open.feishu.cn | API 基础地址 |
AllowCustomBaseUrl |
bool | false | 是否允许自定义基础 URL(存在 SSRF 风险,仅用于特殊场景) |
TimeoutSeconds |
int | 30 | HTTP 请求超时时间(秒) |
HttpRetry.MaxAttempts |
int | 3 | 失败重试次数 |
HttpRetry.DelayMs |
int | 1000 | 重试延迟时间(毫秒) |
CircuitBreaker.Enabled |
bool | true | 是否启用熔断策略 |
CircuitBreaker.FailureThreshold |
int | 20 | 熔断失败率阈值(百分比,范围 1-100) |
CircuitBreaker.SamplingDurationSeconds |
int | 60 | 熔断采样窗口时间(秒,范围 10-300) |
CircuitBreaker.BreakDurationSeconds |
int | 60 | 熔断持续时间(秒,范围 10-300) |
CircuitBreaker.MinimumThroughput |
int | 10 | 熔断最小吞吐量(范围 2-1000) |
TokenRefreshThreshold |
int | 300 | 令牌刷新阈值(秒) |
IsDefault |
bool | false | 是否为默认应用 |
配置热更新机制
Mud.Feishu 使用 IOptionsMonitor<T> 模式支持配置热更新,无需重启应用即可动态更新配置。
注意:热更新支持程度取决于各模块的实现。Webhook 模块和 WebSocket 模块的核心组件均已注入
IOptionsMonitor<T>, 配置文件变更后可实时生效。但 WebSocket 连接级参数(如心跳间隔、重连策略、SSL 证书配置等) 因涉及活动连接状态,变更后需重连才能生效。
支持热更新的配置:
| 配置项 | 是否支持热更新 | 说明 |
|---|---|---|
FeishuWebhookOptions |
✅ 支持 | Webhook 相关配置,核心组件注入 IOptionsMonitor<T>,配置文件变更后实时生效 |
FeishuWebSocketOptions |
✅ 部分支持 | 日志开关、健康检查间隔等运行时参数支持热更新;连接级参数(心跳、重连、SSL)需重连后生效 |
FeishuAppConfig |
⚠️ 部分支持 | 部分配置热更新,部分需要重启 |
热更新示例:
public class ConfigurationWatcherService
{
private readonly IOptionsMonitor<FeishuWebhookOptions> _webhookOptions;
private readonly IOptionsMonitor<FeishuWebSocketOptions> _webSocketOptions;
public ConfigurationWatcherService(
IOptionsMonitor<FeishuWebhookOptions> webhookOptions,
IOptionsMonitor<FeishuWebSocketOptions> webSocketOptions)
{
_webhookOptions = webhookOptions;
_webSocketOptions = webSocketOptions;
// 监听 Webhook 配置变更
_webhookOptions.OnChange(options =>
{
Console.WriteLine($"Webhook 配置已更新: MaxConcurrentEvents={options.MaxConcurrentEvents}");
});
// 监听 WebSocket 配置变更
_webSocketOptions.OnChange(options =>
{
Console.WriteLine($"WebSocket 配置已更新: HeartbeatInterval={options.HeartbeatInterval}");
});
}
}
配置更新方式:
通过配置文件更新(支持热重载):
{ "FeishuWebhook": { "MaxConcurrentEvents": 100, "EventHandlingTimeoutMs": 30000 } }通过环境变量更新(需要重启):
export FeishuWebhook__MaxConcurrentEvents=100通过命令行参数更新(需要重启):
dotnet run --FeishuWebhook:MaxConcurrentEvents=100
注意事项:
- ⚠️
FeishuAppConfig的AppId、AppSecret、EncryptKey等敏感配置修改后需要重启应用才能生效 - ✅
FeishuWebhookOptions的非敏感配置支持热更新 - ✅
FeishuWebSocketOptions的日志开关和健康检查间隔支持热更新;连接级参数(心跳间隔、重连策略等)需重连后生效 - 🔄 热更新后,已创建的实例会使用旧配置,新实例会使用新配置
- 📊 使用
IOptionsSnapshot<T>可以获取当前请求周期的配置快照
配置快照示例:
public class MyService
{
private readonly IOptionsSnapshot<FeishuWebhookOptions> _webhookOptions;
public MyService(IOptionsSnapshot<FeishuWebhookOptions> webhookOptions)
{
// 每次请求都会获取最新的配置
_webhookOptions = webhookOptions;
}
public void DoSomething()
{
var options = _webhookOptions.Value;
Console.WriteLine($"当前 MaxConcurrentEvents: {options.MaxConcurrentEvents}");
}
}
🎯 支持的事件类型
组织管理事件
contact.user.created_v3- 员工入职事件contact.user.updated_v3- 用户更新事件contact.user.deleted_v3- 用户删除事件contact.custom_attr_event.updated_v3- 成员字段变更事件contact.department.created_v3- 部门创建事件contact.department.updated_v3- 部门更新事件contact.department.deleted_v3- 部门删除事件contact.employee_type_enum.created_v3- 人员类型创建事件contact.employee_type_enum.updated_v3- 人员类型更新事件contact.employee_type_enum.deleted_v3- 人员类型删除事件contact.employee_type_enum.actived_v3- 人员类型启用事件contact.employee_type_enum.deactivated_v3- 人员类型禁用事件
消息事件
im.message.receive_v1- 接收消息事件im.message.recalled_v1- 消息撤回事件im.message.message_read_v1- 消息已读事件im.message.reaction.created_v1- 新增消息表情回复事件im.message.reaction.deleted_v1- 删除消息表情回复事件
群聊事件
im.chat.disbanded_v1- 群解散事件im.chat.updated_v1- 群配置修改事件im.chat.member.user.added_v1- 用户进群事件im.chat.member.user.deleted_v1- 用户出群事件im.chat.member.user.withdrawn_v1- 撤销拉用户进群事件im.chat.member.bot.added_v1- 机器人进群事件im.chat.member.bot.deleted_v1- 机器人被移出群事件
审批事件
approval.approval.approved_v1- 审批通过事件approval.approval.rejected_v1- 审批拒绝事件approval.approval.updated_v1- 审批更新事件approval.cc_v1- 审批抄送事件approval.instance_v1- 审批实例事件approval.instance.remedy_group.updated_v1- 审批实例补丁分组更新事件approval.instance.trip_group.updated_v1- 审批实例行程分组更新事件approval.task_v1- 审批任务事件approval.leave_v1- 请假审批事件approval.out_v1- 外出审批事件approval.shift_v1- 排班审批事件approval.work_v1- 工作审批事件
任务事件
task.update_tenant_v1- 任务信息变更-租户维度事件task.updated_v1- 任务信息变更事件task.comment.updated_v1- 任务评论信息变更事件
日程和会议事件
calendar.event.updated_v4- 日程事件meeting.meeting.started_v1- 会议开始事件meeting.meeting.ended_v1- 会议结束事件
📖 使用示例
1. 创建基础事件处理器(实现 IFeishuEventHandler 接口)
using Mud.Feishu.Abstractions;
using Mud.Feishu.EventCallback; // FeishuEventTypes 常量所在命名空间
using System.Text.Json;
namespace YourProject.Handlers;
/// <summary>
/// 演示用户事件处理器
/// </summary>
public class DemoUserEventHandler : IFeishuEventHandler
{
private readonly ILogger<DemoUserEventHandler> _logger;
private readonly YourEventService _eventService;
public DemoUserEventHandler(ILogger<DemoUserEventHandler> logger, YourEventService eventService)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_eventService = eventService ?? throw new ArgumentNullException(nameof(eventService));
}
public string SupportedEventType => FeishuEventTypes.UserCreated;
public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default)
{
if (eventData == null)
throw new ArgumentNullException(nameof(eventData));
_logger.LogInformation("👤 [用户事件] 开始处理用户创建事件: {EventId}", eventData.EventId);
try
{
// 解析用户数据
var userData = ParseUserData(eventData);
// 记录事件到服务
await _eventService.RecordUserEventAsync(userData, cancellationToken);
// 模拟业务处理
await ProcessUserEventAsync(userData, cancellationToken);
_logger.LogInformation("✅ [用户事件] 用户创建事件处理完成: 用户ID {UserId}, 用户名 {UserName}",
userData.UserId, userData.UserName);
}
catch (Exception ex)
{
_logger.LogError(ex, "❌ [用户事件] 处理用户创建事件失败: {EventId}", eventData.EventId);
throw;
}
}
private UserData ParseUserData(EventData eventData)
{
try
{
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
};
}
catch (Exception ex)
{
_logger.LogError(ex, "解析用户数据失败");
throw new InvalidOperationException("无法解析用户数据", ex);
}
}
private async Task ProcessUserEventAsync(UserData userData, CancellationToken cancellationToken)
{
// 模拟异步业务操作
await Task.Delay(100, cancellationToken);
// 验证必要字段
if (string.IsNullOrWhiteSpace(userData.UserId))
{
throw new ArgumentException("用户ID不能为空");
}
// 模拟发送欢迎通知
_logger.LogInformation("📧 [用户事件] 发送欢迎通知给用户: {UserName} ({Email})",
userData.UserName, userData.Email);
await Task.CompletedTask;
}
private static string? TryGetProperty(JsonElement element, string propertyName)
{
return element.TryGetProperty(propertyName, out var value) ? value.GetString() : null;
}
}
2. 继承预定义事件处理器(推荐方式)
using Mud.Feishu.Abstractions;
using Mud.Feishu.Abstractions.Services;
using Mud.Feishu.EventCallback; // DepartmentCreatedEventHandler(源生成)
using Mud.Feishu.EventCallback.Organization; // DepartmentCreatedResult(源生成)
namespace YourProject.Handlers;
/// <summary>
/// 演示部门事件处理器 - 继承预定义的部门创建事件处理器
/// </summary>
public class DemoDepartmentEventHandler : DepartmentCreatedEventHandler
{
private readonly YourEventService _eventService;
public DemoDepartmentEventHandler(
IFeishuEventDeduplicator businessDeduplicator,
ILogger<DemoDepartmentEventHandler> logger,
YourEventService 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?.Object == null)
{
_logger.LogWarning("[部门事件] 部门创建事件数据为空,跳过处理: {EventId}", eventData.EventId);
return;
}
try
{
// 记录事件到服务
await _eventService.RecordDepartmentEventAsync(departmentData, cancellationToken);
// 模拟业务处理
await ProcessDepartmentEventAsync(departmentData.Object, cancellationToken);
_logger.LogInformation("[部门事件] 部门创建事件处理完成: 部门ID {DepartmentId}, 部门名 {DepartmentName}",
departmentData.Object.DepartmentId, departmentData.Object.Name);
}
catch (Exception ex)
{
_logger.LogError(ex, "[部门事件] 处理部门创建事件失败: {EventId}", eventData.EventId);
throw;
}
}
private async Task ProcessDepartmentEventAsync(DepartmentResultInfo departmentData, CancellationToken cancellationToken)
{
// 模拟异步业务操作
await Task.Delay(100, cancellationToken);
// 验证逻辑
if (string.IsNullOrWhiteSpace(departmentData.DepartmentId))
{
throw new ArgumentException("部门ID不能为空");
}
// 模拟权限初始化
_logger.LogInformation("[部门事件] 初始化部门权限: {DepartmentName}", departmentData.Name);
// 通知部门主管
if (!string.IsNullOrWhiteSpace(departmentData.LeaderUserId))
{
_logger.LogInformation("[部门事件] 通知部门主管: {LeaderUserId}", departmentData.LeaderUserId);
}
// 处理层级关系
if (!string.IsNullOrWhiteSpace(departmentData.ParentDepartmentId))
{
_logger.LogInformation("[部门事件] 建立层级关系: {DepartmentId} -> {ParentDepartmentId}",
departmentData.DepartmentId, departmentData.ParentDepartmentId);
}
await Task.CompletedTask;
}
}
3. 在 Program.cs 中配置服务和事件处理器
using Mud.Feishu.WebSocket.Demo.Handlers;
using Mud.Feishu.WebSocket.Demo.Services;
var builder = WebApplication.CreateBuilder(args);
// 配置基础服务
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Title = "飞书WebSocket测试API",
Version = "v1",
Description = "用于测试飞书WebSocket长连接功能的演示API"
});
});
// 注册多应用支持(应用配置从 appsettings.json 的 "FeishuApps" 配置节读取)
builder.Services.AddFeishuApp(builder.Configuration);
// 注册飞书 API 服务模块(CreateFeishuServicesBuilder 为无参扩展方法)
builder.Services.CreateFeishuServicesBuilder()
.AddAuthenticationApi() // 按需注册模块,也可用 AddModules(FeishuModule.All)
.Build();
// 配置飞书 WebSocket 服务(CreateFeishuWebSocketServiceBuilder:配置节名 + 应用键)
builder.Services.CreateFeishuWebSocketServiceBuilder(builder.Configuration, "FeishuWebSocket", "default")
.AddHandler<DemoDepartmentEventHandler>() // 添加部门创建事件处理器
.AddHandler<DemoDepartmentDeleteEventHandler>() // 添加部门删除事件处理器
.Build();
// 配置自定义服务
builder.Services.AddSingleton<DemoEventService>();
builder.Services.AddHostedService<DemoEventBackgroundService>();
// 配置CORS
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowAll", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
var app = builder.Build();
// 配置中间件
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseCors("AllowAll");
app.UseStaticFiles();
app.UseRouting();
app.MapControllers();
app.Run();
4. 创建自定义事件服务来处理事件数据
namespace YourProject.Services;
/// <summary>
/// 演示事件服务 - 用于记录和管理事件处理结果
/// </summary>
public class DemoEventService
{
private readonly ILogger<DemoEventService> _logger;
private readonly ConcurrentBag<UserData> _userEvents = new();
private readonly ConcurrentBag<DepartmentData> _departmentEvents = new();
private int _userCount = 0;
private int _departmentCount = 0;
public DemoEventService(ILogger<DemoEventService> logger)
{
_logger = logger;
}
public async Task RecordUserEventAsync(UserData userData, CancellationToken cancellationToken = default)
{
_logger.LogDebug("记录用户事件: {UserId}", userData.UserId);
_userEvents.Add(userData);
await Task.CompletedTask;
}
public async Task RecordDepartmentEventAsync(DepartmentData departmentData, CancellationToken cancellationToken = default)
{
_logger.LogDebug("记录部门事件: {DepartmentId}", departmentData.DepartmentId);
_departmentEvents.Add(departmentData);
await Task.CompletedTask;
}
public void IncrementUserCount()
{
Interlocked.Increment(ref _userCount);
_logger.LogInformation("用户计数更新: {Count}", _userCount);
}
public void IncrementDepartmentCount()
{
Interlocked.Increment(ref _departmentCount);
_logger.LogInformation("部门计数更新: {Count}", _departmentCount);
}
public IEnumerable<UserData> GetUserEvents() => _userEvents.ToList();
public IEnumerable<DepartmentData> GetDepartmentEvents() => _departmentEvents.ToList();
public int GetUserCount() => _userCount;
public int GetDepartmentCount() => _departmentCount;
}
🛠️ 核心服务
增强型 HTTP 客户端 (IEnhancedHttpClient)
提供飞书 API 调用的统一 HTTP 客户端,支持自动重试、日志记录、文件下载等功能。
public class ApiClient
{
private readonly IEnhancedHttpClient _httpClient;
public ApiClient(IEnhancedHttpClient httpClient)
{
_httpClient = httpClient;
}
// 发送请求并反序列化响应
public async Task<FeishuApiResult<UserInfo>> GetUserInfoAsync(string userId)
{
var request = new HttpRequestMessage(HttpMethod.Get,
$"https://open.feishu.cn/open-apis/user/v4/info/{userId}");
return await _httpClient.SendAsync<FeishuApiResult<UserInfo>>(request);
}
// 下载小文件
public async Task<byte[]?> DownloadImageAsync(string mediaId)
{
var request = new HttpRequestMessage(HttpMethod.Get,
$"https://open.feishu.cn/open-apis/drive/v1/medias/{mediaId}/download");
return await _httpClient.DownloadAsync(request);
}
// 下载大文件(流式下载)
public async Task<FileInfo> DownloadLargeFileAsync(string mediaId, string filePath)
{
var request = new HttpRequestMessage(HttpMethod.Get,
$"https://open.feishu.cn/open-apis/drive/v1/medias/{mediaId}/download");
return await _httpClient.DownloadLargeAsync(request, filePath, overwrite: true);
}
}
事件去重服务 (IFeishuEventDeduplicator)
防止重复事件的处理,保证事件处理的幂等性。
public class DeduplicatedEventHandler : IFeishuEventHandler
{
private readonly IFeishuEventDeduplicator _deduplicator;
private readonly ILogger<DeduplicatedEventHandler> _logger;
public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default)
{
// 标记事件为处理中(接口方法均为异步)。
// appKey 参数用于多应用场景隔离(避免跨应用事件键冲突),单应用可传 null。
var result = await _deduplicator.TryMarkAsProcessingAsync(
eventData.EventId, appKey: null, cancellationToken: cancellationToken);
if (result.IsDuplicate)
{
// IsDuplicate=true 表示事件已完成或正在处理中,跳过
_logger.LogInformation("事件 {EventId} 已处理或正在处理中,跳过", eventData.EventId);
return;
}
try
{
// 处理业务逻辑
await ProcessEventAsync(eventData, cancellationToken);
// 标记事件为已完成
await _deduplicator.MarkAsCompletedAsync(eventData.EventId, cancellationToken: cancellationToken);
}
catch
{
// 处理失败,回滚状态以便重试
await _deduplicator.RollbackProcessingAsync(eventData.EventId, cancellationToken: cancellationToken);
throw;
}
}
}
也可使用
IsProcessedAsync(eventId, appKey, ct)查询处理状态、GetStatusAsync(...)获取DeduplicationStatus(Pending / Processing / Completed)。
失败事件存储 (IFailedEventStore)
持久化后台处理失败的事件,支持重试。
public class FailedEventRetryService
{
private readonly IFailedEventStore _failedEventStore;
private readonly IFeishuEventHandlerFactory _factory;
// 存储失败事件(带应用键与下次可重试时间;FailedEventInfo 只保存序列化后的事件数据)
public async Task StoreFailedEventAsync(EventData eventData, Exception exception, string? appKey)
{
await _failedEventStore.StoreFailedEventAsync(
eventData,
exception,
appKey,
nextRetryAt: DateTimeOffset.UtcNow.AddMinutes(5)
);
}
// 获取待重试的事件
public async Task<List<FailedEventInfo>> GetPendingRetryEventsAsync()
{
return await _failedEventStore.GetPendingRetryEventsAsync(
beforeTime: DateTimeOffset.UtcNow,
maxCount: 10
);
}
// 重试失败事件(需先用 SerializedEventData 反序列化还原 EventData)
public async Task RetryFailedEventAsync(FailedEventInfo eventInfo, EventData eventData)
{
var handler = _factory.GetHandler(eventInfo.EventType);
try
{
await handler.HandleAsync(eventData);
// 重试成功,移除失败记录
await _failedEventStore.RemoveFailedEventAsync(eventInfo.EventId);
}
catch
{
// 重试失败,更新重试次数(下次可重试时间由存储实现按退避策略计算)
await _failedEventStore.UpdateRetryCountAsync(eventInfo.EventId, eventInfo.RetryCount + 1);
}
}
}
事件拦截器 (IFeishuEventInterceptor)
在事件处理前后执行自定义逻辑。
public class CustomEventInterceptor : IFeishuEventInterceptor
{
private readonly ILogger<CustomEventInterceptor> _logger;
public async Task<bool> BeforeHandleAsync(
string eventType,
EventData eventData,
CancellationToken cancellationToken)
{
// 事件处理前执行
_logger.LogInformation("准备处理事件: {EventType}, EventId: {EventId}",
eventType, eventData.EventId);
// 返回 false 可以中断事件处理
return true;
}
public async Task AfterHandleAsync(
string eventType,
EventData eventData,
Exception? exception,
CancellationToken cancellationToken)
{
// 事件处理后执行
if (exception == null)
{
_logger.LogInformation("事件处理成功: {EventId}", eventData.EventId);
}
else
{
_logger.LogError(exception, "事件处理失败: {EventId}", eventData.EventId);
}
}
}
// 注册拦截器
builder.Services.AddSingleton<IFeishuEventInterceptor, CustomEventInterceptor>();
URL 验证器 (UrlValidator)
防止 SSRF(服务端请求伪造)攻击。
public class SafeUrlDownloader
{
private readonly IHttpClientFactory _httpClientFactory;
public async Task<byte[]> DownloadFromUrlAsync(string url)
{
// 验证 URL 是否安全(UrlValidator 为静态类,来自 Mud.HttpUtils;
// URL 不在白名单内时抛出异常)
UrlValidator.ValidateUrl(url);
var client = _httpClientFactory.CreateClient();
return await client.GetByteArrayAsync(url);
}
}
// 默认配置:仅允许飞书官方域名(open.feishu.cn、open.larksuite.com 等)
// 可以运行时追加信任域名(传入域名,而非完整 URL)
UrlValidator.AddAllowedDomain("api.yourcompany.com");
性能指标 (FeishuMetrics)
使用 System.Diagnostics.Metrics 收集和暴露性能指标。
using Mud.Feishu.Abstractions.Metrics;
public class MetricsAwareEventHandler : IFeishuEventHandler
{
// appKey 来源:注入 IAppKeyAccessor 取 CurrentAppKey,或由业务上下文提供
private readonly string _appKey;
private readonly IAppKeyAccessor? _appKeyAccessor;
private string AppKey => _appKeyAccessor?.CurrentAppKey ?? _appKey;
public string SupportedEventType => FeishuEventTypes.UserCreated; // 来自 Mud.Feishu.EventCallback 命名空间
public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default)
{
// 记录事件处理指标(首参为 appKey,多应用场景指标可区分;
// 返回的 IDisposable 用于标记耗时,离开作用域时自动记录)
using var _ = FeishuMetricsHelper.RecordEventHandling(AppKey, eventData.EventType, nameof(MetricsAwareEventHandler));
try
{
// 处理业务逻辑
await ProcessEventAsync(eventData, cancellationToken);
// 记录成功
FeishuMetricsHelper.RecordEventOutcome(AppKey, eventData.EventType, success: true);
}
catch (Exception ex)
{
// 记录失败
FeishuMetricsHelper.RecordEventOutcome(AppKey, eventData.EventType, success: false, ex.GetType().Name);
throw;
}
}
}
支持的仪器(Instrument)名称(Meter 名称 Mud.Feishu):
feishu.event.handling- 事件处理计数(维度:app_key、event_type、handler_type、outcome)feishu.event.handling.duration- 事件处理耗时直方图(毫秒)feishu.event.deduplication- 事件去重命中计数feishu.websocket.connections- WebSocket 活跃连接数(Gauge)feishu.websocket.backlog- WebSocket 待处理消息积压数(Gauge)feishu.websocket.message.duration- WebSocket 消息处理耗时直方图(毫秒)feishu.websocket.reconnect- WebSocket 重连计数feishu.webhook.request- Webhook 入站请求计数feishu.webhook.request.duration- Webhook 请求处理耗时直方图(毫秒)
HTTP 请求指标(
mud.http.requests/mud.http.request.duration)与令牌刷新指标 (mud.token.refresh/mud.token.refresh.duration)由 Mud.HttpUtils 自动采集。
消息脱敏 (MessageSanitizer)
对敏感信息进行脱敏处理。
public class LoggingService
{
private readonly MessageSanitizer _sanitizer;
public void LogSensitiveData(string message)
{
// 自动脱敏敏感信息
var sanitized = _sanitizer.Sanitize(message);
_logger.LogInformation("处理后的消息: {SanitizedMessage}", sanitized);
}
// 支持的脱敏字段:
// - Token: access_token, refresh_token, app_secret, api_key, private_key
// - 个人信息: phone, mobile, email, name, id_card, address
// - 金融信息: card_no, card_number, bank_card
}
// 示例:
// 原始: "access_token=abcdef1234567890, phone=13812345678"
// 脱敏: "access_token=abcd****7890, phone=138****5678"
🏗️ 高级用法
多处理器策略
public class MultiHandlerService
{
private readonly IFeishuEventHandlerFactory _factory;
public MultiHandlerService(IFeishuEventHandlerFactory factory)
{
_factory = factory;
}
public async Task HandleEventWithMultipleStrategies(EventData eventData)
{
// 获取所有匹配的处理器
var handlers = _factory.GetHandlers(eventData.EventType);
// 按优先级处理
foreach (var handler in handlers.OrderBy(h => h.GetType().Name))
{
try
{
await handler.HandleAsync(eventData);
}
catch (Exception ex)
{
// 记录错误但继续处理其他处理器
Console.WriteLine($"处理器 {handler.GetType().Name} 失败: {ex.Message}");
}
}
}
}
幂等性事件处理器
using Mud.Feishu.Abstractions;
using Mud.Feishu.Abstractions.EventHandlers;
using Mud.Feishu.Abstractions.Services;
using Mud.Feishu.EventCallback;
using Mud.Feishu.EventCallback.Organization;
public class IdempotentUserEventHandler : IdempotentFeishuEventHandler<UserCreateResult>
{
public IdempotentUserEventHandler(
IFeishuEventDeduplicator deduplicator,
ILogger<IdempotentUserEventHandler> logger,
IAppKeyAccessor? appKeyAccessor = null) : base(deduplicator, logger, appKeyAccessor)
{
}
public override string SupportedEventType => FeishuEventTypes.UserCreated;
protected override async Task ProcessBusinessLogicAsync(
EventData eventData,
UserCreateResult? eventEntity,
CancellationToken cancellationToken = default)
{
// 处理业务逻辑,基类已自动保证幂等性(基类 HandleAsync 为 sealed,不可重写;
// 事件实体类型为 XxxResult,通过重写 ProcessBusinessLogicAsync 实现业务)
if (eventEntity != null)
{
await CreateUserAsync(eventEntity, cancellationToken);
}
}
// 可选:重写 GetBusinessKey 定义业务去重键(默认使用 EventId)
protected override string? GetBusinessKey(EventData eventData)
{
return $"{eventData.EventType}:{eventData.EventId}";
}
}
💡 对内置事件,
Mud.Feishu.EventCallback已生成对应的类型化处理器(如UserCreateEventHandler, 命名空间Mud.Feishu.EventCallback.Organization),直接继承并重写ProcessBusinessLogicAsync(EventData, XxxResult?, FeishuEventHeader?, ct)即可, 参考Demos/Mud.Feishu.Webhook.Demo/Handlers/DemoDepartmentEventHandler.cs。
条件事件处理
using System.Text.Json;
using Mud.Feishu.Abstractions;
using Mud.Feishu.EventCallback;
public class ConditionalEventHandler : IFeishuEventHandler
{
public string SupportedEventType => FeishuEventTypes.ReceiveMessage;
public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default)
{
// ⚠️ eventData.Event 是 object?,运行时为 JsonElement,
// `eventData.Event is XxxEvent` 模式匹配恒为 false。
// 应通过 EventType 字符串(对照 FeishuEventTypes 常量)判断事件类型,
// 再自行反序列化,或改用生成的类型化处理器(见下文)。
if (eventData.EventType == FeishuEventTypes.ReceiveMessage &&
eventData.Event is JsonElement json &&
json.TryGetProperty("message", out var message))
{
var messageType = message.TryGetProperty("message_type", out var type)
? type.GetString()
: null;
// 只处理特定类型的消息
if (messageType == "text")
{
await HandleTextMessage(json, cancellationToken);
}
else if (messageType == "image")
{
await HandleImageMessage(json, cancellationToken);
}
}
}
private async Task HandleTextMessage(JsonElement eventJson, CancellationToken cancellationToken)
{
// 处理文本消息逻辑
}
private async Task HandleImageMessage(JsonElement eventJson, CancellationToken cancellationToken)
{
// 处理图片消息逻辑
}
}
🔧 扩展新事件类型
1. 定义事件类型常量
public static class CustomEventTypes
{
public const string MyCustomEvent = "custom.my_event.v1";
}
2. 创建事件数据模型
public class MyCustomEvent : IEventResult
{
[JsonPropertyName("custom_data")]
public string CustomData { get; set; } = string.Empty;
}
3. 实现事件处理器
// 基础实现方式
public class MyCustomEventHandler : IFeishuEventHandler
{
public string SupportedEventType => CustomEventTypes.MyCustomEvent;
public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default)
{
// ⚠️ eventData.Event 是 object?,运行时为 JsonElement,
// 不能使用 `is MyCustomEvent` 模式匹配,需自行反序列化
if (eventData.Event is JsonElement json &&
json.TryGetProperty("custom_data", out var data))
{
var customData = data.GetString();
// 处理自定义事件
}
}
}
// 推荐使用基类
public class MyCustomEventHandler : DefaultFeishuEventHandler<MyCustomEvent>
{
public override string SupportedEventType => CustomEventTypes.MyCustomEvent;
public MyCustomEventHandler(ILogger<MyCustomEventHandler> logger) : base(logger)
{
}
protected override async Task ProcessBusinessLogicAsync(
EventData eventData,
MyCustomEvent? eventEntity,
CancellationToken cancellationToken = default)
{
if (eventEntity != null)
{
// 处理自定义事件,基类已自动反序列化
Console.WriteLine($"自定义数据: {eventEntity.CustomData}");
}
await Task.CompletedTask;
}
}
📊 选择对比
处理器选择策略
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
IEventHandler 直接实现 |
最大灵活性 | 需要手动反序列化 | 简单事件或特殊需求 |
DefaultFeishuEventHandler<T> |
自动反序列化、错误处理 | 继承层次增加 | 大多数标准事件 |
DefaultFeishuObjectEventHandler<T> |
专为对象结果优化 | 功能相对固定 | 返回对象的事件 |
IdempotentFeishuEventHandler<T> |
自动去重、幂等保证 | 需要配置去重服务 | 需要幂等性保证的事件 |
令牌缓存选择策略
| 缓存类型 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
内置 ITokenCache<T>(内存) |
简单、无需外部依赖 | 重启丢失、不支持分布式 | 单实例部署 |
自定义 ITokenStore(持久化) |
支持 Redis 等外部存储 | 需要自己实现 | 分布式部署、需要持久化 |
性能建议
- ✅ 推荐 使用
DefaultFeishuEventHandler<T>基类 - ⚡ 优化 对高频事件使用
ValueTask - 🔄 并发 使用
HandleEventParallelAsync处理复杂事件 - 🛡️ 安全 基类内置了异常处理和日志记录
- 🔐 令牌 使用带缓存的令牌管理器,减少 API 调用
- 🏢 多应用 合理规划应用配置,避免过多应用导致资源浪费
- 🔄 去重 使用
IdempotentFeishuEventHandler<T>保证幂等性 - 📝 日志 使用事件拦截器统一记录日志
- 🔒 安全 对敏感信息进行脱敏处理
🛠️ 开发和构建
要求
- .NET 6.0 或更高版本
- Visual Studio 2022 或 Visual Studio Code
依赖项
- Microsoft.Extensions.Http - HTTP 客户端支持
- Mud.HttpUtils - HTTP 工具类及HttpClient代码生成器
构建项目
# 克隆仓库
git clone https://gitee.com/mudtools/MudFeishu.git
cd MudFeishu/Mud.Feishu.Abstractions
# 还原依赖
dotnet restore
# 构建项目
dotnet build
# 运行测试
dotnet test
📚 相关项目
- Mud.Feishu - 核心 HTTP API 客户端
- Mud.Feishu.DataModels - HTTP API 强类型数据模型
- Mud.Feishu.Authentication - 用户认证中间件
- Mud.Feishu.EventCallback - 事件回调强类型数据模型
- Mud.Feishu.WebSocket - WebSocket 事件订阅实现
- Mud.Feishu.Webhook - HTTP Webhook 事件订阅实现
- Mud.Feishu.Redis - Redis 分布式去重扩展
- Mud.Feishu.OpenTelemetry - OpenTelemetry 可观测性扩展
- Tests - 测试项目和使用示例
🤝 贡献
欢迎贡献!请查看 贡献指南 了解详情。
贡献流程
- Fork 项目
- 创建特性分支 (
git checkout -b feature/AmazingFeature) - 提交更改 (
git commit -m 'Add some AmazingFeature') - 推送到分支 (
git push origin feature/AmazingFeature) - 开启 Pull Request
📄 许可证
本项目采用 MIT 许可证 - 详见 LICENSE 文件
🌟 Star History
如果这个项目对你有帮助,请给我们一个 Star ⭐️
Mud.Feishu.Abstractions - 让飞书集成变得简单而强大! 🚀
提供完整的飞书 API 访问能力,包括认证授权、令牌管理、多应用支持、事件订阅处理等核心功能,助力开发者快速构建飞书应用。
| 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.Http (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
- Microsoft.Extensions.Options (>= 8.0.2)
- Mud.HttpUtils (>= 2.0.8)
- System.Text.Json (>= 10.0.9)
-
net10.0
- Microsoft.Extensions.Configuration.Binder (>= 10.0.9)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Http (>= 10.0.9)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Options (>= 10.0.9)
- Mud.HttpUtils (>= 2.0.8)
-
net6.0
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Http (>= 8.0.1)
- Microsoft.Extensions.Logging.Abstractions (>= 8.0.3)
- Microsoft.Extensions.Options (>= 8.0.2)
- Mud.HttpUtils (>= 2.0.8)
-
net8.0
- Microsoft.Extensions.Configuration.Binder (>= 10.0.9)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Http (>= 10.0.9)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Options (>= 10.0.9)
- Mud.HttpUtils (>= 2.0.8)
NuGet packages (8)
Showing the top 5 NuGet packages that depend on Mud.Feishu.Abstractions:
| Package | Downloads |
|---|---|
|
Mud.Feishu
飞书服务端 SDK 的 .NET 适配版,支持 .NET Standard 2.0、.NET 6/8/10 平台,net8.0+ 原生 AOT 兼容。提供类型安全的 API 封装,让开发者能够在 .NET 应用程序中便捷、高效地集成飞书服务端功能。 |
|
|
Mud.Feishu.WebSocket
适用于 .NET 全平台的飞书 WebSocket 长连接事件订阅组件,提供开箱即用的客户端实现,支持自动重连、心跳检测与事件分发,并基于策略模式构建可扩展的事件处理机制,支持 Native AOT 发布(net8.0+),帮助开发者在 .NET 应用中便捷、可靠地接收和处理飞书实时事件。 |
|
|
Mud.Feishu.Webhook
适用于 .NET 全平台的飞书 Webhook 事件订阅组件,提供开箱即用的客户端实现,基于策略模式构建可扩展的事件处理机制,支持 Native AOT 发布(net8.0+),帮助开发者在 .NET 应用中便捷、可靠地接收和处理飞书实时事件。 |
|
|
Mud.Feishu.Redis
飞书事件订阅组件 Redis 分布式去重扩展,提供基于 Redis 的事件去重、Nonce 去重和 SeqID 去重功能,适用于多实例分布式部署场景,支持 Native AOT 发布(net8.0+)。 |
|
|
Mud.Feishu.Authentication
飞书用户认证中间件,提供用户上下文管理功能。基于 AsyncLocal 实现线程安全的用户信息存储,支持从 JWT Claims 中提取飞书用户信息,net8.0+ 原生 AOT 兼容。 |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 3.0.0 | 0 | 9/28/2026 |
| 3.0.0-rc3 | 229 | 9/24/2026 |
| 3.0.0-rc2 | 345 | 9/18/2026 |
| 3.0.0-rc1 | 425 | 7/11/2026 |
| 3.0.0-preview5 | 406 | 6/25/2026 |
| 3.0.0-preview4 | 326 | 6/3/2026 |
| 3.0.0-preview3 | 382 | 5/22/2026 |
| 3.0.0-preview2 | 381 | 5/11/2026 |
| 3.0.0-preview1 | 420 | 5/1/2026 |
| 2.1.6 | 499 | 7/21/2026 |
| 2.1.5 | 790 | 6/25/2026 |
| 2.1.4 | 456 | 6/3/2026 |
| 2.1.3 | 471 | 5/22/2026 |
| 2.1.2 | 384 | 5/12/2026 |
| 2.1.1 | 392 | 5/11/2026 |
| 2.1.0 | 403 | 5/1/2026 |
| 2.0.9 | 450 | 4/24/2026 |
| 2.0.8 | 411 | 4/12/2026 |
| 2.0.7 | 377 | 4/7/2026 |
| 2.0.6 | 340 | 4/5/2026 |