如何将.NET系统与飞书审批流实现无缝对接?

摘要:周末深夜,你收到紧急审批通知——却发现只能在 PC 端处理,只能摸黑起床开电脑…… 这样的场景,你是否也经历过? 传统 .NET 系统与现代移动协同之间的鸿沟,正在悄悄吞噬着企业的效率。审批卡在桌面端、通知滞后、数据孤岛——这些问题让工作体
周末深夜,你收到紧急审批通知——却发现只能在 PC 端处理,只能摸黑起床开电脑…… 这样的场景,你是否也经历过? 传统 .NET 系统与现代移动协同之间的鸿沟,正在悄悄吞噬着企业的效率。审批卡在桌面端、通知滞后、数据孤岛——这些问题让工作体验大打折扣。 推倒重来?成本太高,风险太大。 本文将带你走一条渐进式改造之路:保持 .NET 系统作为业务核心,将飞书审批作为移动门户,通过 API 实现无缝协同。从原理、设计到编码,完整呈现如何让传统系统焕发新生,实现移动化、实时化的现代化升级。 无论你是开发者、架构师还是技术管理者,都能收获一套可落地、可扩展的集成方案和直接复用的代码实践。 当传统业务遇上现代协同,为何必须"破壁"? 我们正在解决什么? 传统 .NET 系统的局限 许多企业拥有多年累积的 .NET 业务系统,这些系统在企业运营中扮演着核心角色。然而,随着移动办公和现代协同工具的普及,这些传统系统正面临着严峻的挑战: 痛点 具体表现 业务影响 审批流程封闭 审批只能在桌面端完成,无法随时随地处理 移动办公受阻,响应迟缓 通知方式滞后 依赖邮件或站内消息推送 审批人及时性差,流程延误 数据孤岛严重 审批数据与业务数据分离 无法形成完整的业务闭环 用户体验陈旧 界面风格陈旧,交互体验差 用户满意度低,使用意愿下降 飞书审批的赋能价值 飞书审批作为企业级的审批协作平台,为我们提供了一个理想的"流程协作中心": graph LR A[飞书审批平台] --> B[移动优先] A --> C[即时强通知] A --> D[流程可视化] A --> E[完善审计日志] B --> B1[随时随地处理审批] C --> C1[App推送/短信提醒] D --> D1[拖拽式流程配置] E --> E1[完整操作痕迹追溯] 我们的核心目标 通过本文的实践,我们将建立 ".NET 系统为业务核心,飞书审批为流程门户" 的现代化混合架构: 架构愿景:将飞书审批作为统一的移动审批门户,保持 .NET 系统作为业务逻辑和数据存储的核心,通过 API 实时同步,形成优势互补的协同体系。 你将收获什么? ✅ 一套端到端的集成方法论,覆盖从原理、设计到部署的全流程 ✅ 清晰的 .NET 侧架构蓝图,包含关键的技术选型与设计决策 ✅ 可直接复用的 C# 核心代码与实践中总结的"避坑指南" ✅ 一个完整的"请假审批"实战案例,助你从零到一完成验证 飞书审批开放平台如何与我们"对话"? 双向集成的关键流程 飞书审批与 .NET 系统的集成是一个双向数据流的过程: sequenceDiagram participant User as 用户 participant NET as .NET系统 participant API as 飞书API participant FS as 飞书App Note over User,FS: 流程输出:发起审批 User->>NET: 1. 提交请假申请 NET->>NET: 2. 保存业务数据(状态:审批中) NET->>API: 3. 调用 CreateInstanceAsync API-->>NET: 4. 返回 instance_code NET->>NET: 5. 关联业务ID与instance_code Note over User,FS: 流程输入:回调通知 User->>FS: 6. 在飞书App中审批 FS->>API: 7. 审批完成 API->>NET: 8. Webhook回调事件 NET->>NET: 9. 根据instance_code更新业务状态 流程输出(发起阶段) 当用户在 .NET 系统发起审批时,系统会: 保存业务数据,状态标记为"审批中" 调用飞书 API CreateInstanceAsync 创建审批实例 接收返回的 instance_code,持久化到关联表 流程输入(回调阶段) 当审批人在飞书 App 完成审批后: 飞书服务器主动回调 .NET 系统的 Webhook 接口 .NET 系统解析事件,提取 instance_code 和 status 根据关联表查询对应的业务记录 更新业务状态,完成闭环 必须理解的三个核心概念 审批定义(approval_code) 审批定义是审批流程的"蓝图",在飞书管理后台配置: // 示例:请假审批的审批定义 var approvalCode = "7C468A54-8745-2245-9675-08B7C63E7A85"; 定义包含: 表单结构(请假类型、开始时间、结束时间、请假事由等) 审批流程(直属主管审批 → 人事审批) 权限设置(谁可以发起、谁可以审批) 审批实例(instance_code) 审批实例是依据审批定义发起的一次具体审批任务: // 创建审批实例时返回 public record CreateInstancesResult { /// <summary> /// 审批实例 Code /// </summary> [JsonPropertyName("instance_code")] public string InstanceCode { get; set; } = string.Empty; } 关键属性: 唯一标识一次审批流程 包含该次审批的所有表单数据 拥有独立的状态(审批中、通过、拒绝、撤回等) 身份映射(免登) 实现 .NET 系统用户与飞书用户的关联: graph LR A[.NET系统用户<br/>UserId: 1001] -->|映射关系| B[飞书用户<br/>OpenId: ou_3cda9c...] B -->|通过飞书App审批| C[审批完成] C -->|回调instance_code| A 实现方式: 在 .NET 系统的用户表中添加 FeishuOpenId 字段 用户首次登录时进行飞书免登录认证,获取并存储 open_id 发起审批时,使用 open_id 指定审批发起人 构建稳健、可扩展的 .NET 侧集成层 技术栈推荐 层次 技术选型 说明 应用框架 .NET 6/8/10 长期支持版本,性能优异 飞书 SDK Mud.Feishu 高度封装的飞书 API 客户端 Webhook 处理 Mud.Feishu.Webhook 飞书事件回调处理组件 认证授权 ASP.NET Core Identity / JWT 内部系统身份管理 异步解耦 RabbitMQ / Hangfire 回调消息队列处理,提升可靠性 数据存储 SQL Server / PostgreSQL 业务数据 + 审批关联表 分层架构图 graph TB subgraph "表示层 (UI)" A[Web 前端 / 移动端] end subgraph "应用层 (API)" B[LeaveController] C[FeishuWebhookController] end subgraph "领域层 (业务逻辑)" D[ILeaveService] E[ApprovalIntegrationService] F[IApprovalService] end subgraph "基础设施层" G[Mud.Feishu HTTP客户端] H[Mud.Feishu.Webhook处理器] I[数据仓储<br/>EF Core] end A --> B A --> C B --> D C --> E D --> F E --> F F --> G F --> H F --> I 关键设计:领域层抽象 在领域层引入 IApprovalService 接口,将飞书集成细节与核心业务逻辑解耦: /// <summary> /// 审批服务抽象接口 - 解耦飞书实现细节 /// </summary> public interface IApprovalService { /// <summary> /// 发起审批 /// </summary> Task<string> CreateApprovalAsync(ApprovalRequest request); /// <summary> /// 处理审批结果回调 /// </summary> Task HandleApprovalCallbackAsync(ApprovalCallbackEvent callbackEvent); } /// <summary> /// 飞书审批服务实现 /// </summary> public class FeishuApprovalService : IApprovalService { private readonly IFeishuTenantV4Approval _approvalApi; private readonly IApprovalRecordRepository _repository; public FeishuApprovalService( IFeishuTenantV4Approval approvalApi, IApprovalRecordRepository repository) { _approvalApi = approvalApi; _repository = repository; } public async Task<string> CreateApprovalAsync(ApprovalRequest request) { // 调用飞书 API var result = await _approvalApi.CreateInstanceAsync(...); return result.Data?.InstanceCode ?? string.Empty; } } 优势: 业务逻辑不依赖具体飞书实现 便于单元测试(可 Mock 接口) 未来可轻松切换到其他审批平台 实战:手把手完成"请假审批"集成 第一步:飞书平台侧配置(审批流出口) 创建企业自建应用 登录飞书开放平台(https://open.feishu.cn),进入应用管理: graph LR A[创建自建应用] --> B[获取App ID] A --> C[获取App Secret] B --> D[配置到.NET系统] C --> D 关键配置: 记录 App ID 和 App Secret 配置应用权限:审批相关权限(approval:approval:read, approval:instance:read, approval:instance:create) 配置审批定义 在飞书管理后台创建"请假审批"模板: graph LR A[审批定义配置] --> B[表单设置] A --> C[流程设置] A --> D[权限设置] B --> B1[请假类型<br/>开始时间<br/>结束时间<br/>请假天数<br/>请假事由] C --> C1[直属主管审批<br/>→ 人事审批] D --> D1[全员可发起] 记录关键信息: approval_code:审批定义的唯一标识 表单控件的 id:用于程序填充表单数据 配置事件订阅 在飞书开放平台配置 Webhook: 配置项 值 请求网址 https://your-domain.com/api/feishu/webhook 验证 Token your_verification_token(自定义) 加密 Key your_encrypt_key(自定义) 订阅事件 approval_instance(审批实例状态变更) 第二步:.NET 侧基础搭建(集成基石) 封装飞书 API 客户端 基于 MudFeishu SDK 封装审批服务: /// <summary> /// 飞书审批服务封装 /// </summary> public class FeishuApprovalClient { private readonly IFeishuTenantV4Approval _approvalApi; private readonly ILogger<FeishuApprovalClient> _logger; public FeishuApprovalClient( IFeishuTenantV4Approval approvalApi, ILogger<FeishuApprovalClient> logger) { _approvalApi = approvalApi; _logger = logger; } /// <summary> /// 创建审批实例 /// </summary> public async Task<string> CreateInstanceAsync(CreateInstanceRequest request) { var result = await _approvalApi.CreateInstanceAsync(request); if (result == null || result.Code != 0) { _logger.LogError("创建审批实例失败: {Msg}", result?.Msg); throw new InvalidOperationException($"创建审批实例失败: {result?.Msg}"); } _logger.LogInformation("创建审批实例成功: {InstanceCode}", result.Data?.InstanceCode); return result.Data?.InstanceCode ?? string.Empty; } /// <summary> /// 获取审批实例详情 /// </summary> public async Task<GetApprovalInstanceResult?> GetInstanceAsync(string instanceCode) { var result = await _approvalApi.GetInstanceByIdAsync(instanceCode); if (result == null || result.Code != 0) { _logger.LogError("获取审批实例失败: {Msg}", result?.Msg); return null; } return result.Data; } } 设计数据关联表 在业务数据库中添加审批关联表: -- 审批关联表 CREATE TABLE ApprovalRecords ( Id BIGINT PRIMARY KEY IDENTITY(1,1), BusinessType NVARCHAR(50) NOT NULL, -- 业务类型:LeaveRequest, PurchaseRequest... BusinessId BIGINT NOT NULL, -- 业务ID InstanceCode NVARCHAR(64) NOT NULL, -- 飞书审批实例Code ApprovalCode NVARCHAR(64) NOT NULL, -- 审批定义Code Status NVARCHAR(20) NOT NULL, -- 状态:PENDING, APPROVED, REJECTED... CallbackData NVARCHAR(MAX), -- 回调数据(JSON) CreatedTime DATETIME2 NOT NULL DEFAULT GETUTCDATE(), UpdatedTime DATETIME2 NOT NULL DEFAULT GETUTCDATE(), CONSTRAINT UK_ApprovalRecords_Business UNIQUE(BusinessType, BusinessId) ); CREATE INDEX IX_ApprovalRecords_InstanceCode ON ApprovalRecords(InstanceCode); CREATE INDEX IX_ApprovalRecords_Status ON ApprovalRecords(Status); 对应的实体类: /// <summary> /// 审批关联记录 /// </summary> public class ApprovalRecord { public long Id { get; set; } public string BusinessType { get; set; } = string.Empty; // "LeaveRequest" public long BusinessId { get; set; } // 请假申请ID public string InstanceCode { get; set; } = string.Empty; // 飞书实例Code public string ApprovalCode { get; set; } = string.Empty; // 审批定义Code public string Status { get; set; } = string.Empty; // PENDING/APPROVED/REJECTED public string? CallbackData { get; set; } // JSON格式 public DateTime CreatedTime { get; set; } public DateTime UpdatedTime { get; set; } } 第三步:核心业务流程编码(双向联通) 场景:用户提交请假单,发起审批 流程图: sequenceDiagram participant User as 用户 participant Controller as LeaveController participant Service as LeaveService participant DB as 数据库 participant FeishuAPI as 飞书API User->>Controller: 提交请假申请 Controller->>Service: SubmitLeaveRequest(request) Service->>DB: 保存请假记录(状态:审批中) Service->>Service: 构造表单数据 Service->>FeishuAPI: CreateInstanceAsync(approvalCode, form) FeishuAPI-->>Service: instance_code Service->>DB: 保存ApprovalRecord关联 Service-->>Controller: 提交成功 Controller-->>User: 等待审批 代码实现: /// <summary> /// 请假服务 /// </summary> public class LeaveService { private readonly ILeaveRequestRepository _leaveRepo; private readonly IApprovalRecordRepository _approvalRepo; private readonly FeishuApprovalClient _feishuClient; private readonly ILogger<LeaveService> _logger; public LeaveService( ILeaveRequestRepository leaveRepo, IApprovalRecordRepository approvalRepo, FeishuApprovalClient feishuClient, ILogger<LeaveService> logger) { _leaveRepo = leaveRepo; _approvalRepo = approvalRepo; _feishuClient = feishuClient; _logger = logger; } /// <summary> /// 提交请假申请并发起审批 /// </summary> public async Task<long> SubmitLeaveRequestAsync(SubmitLeaveRequestDto dto) { // 1. 保存请假业务数据 var leaveRequest = new LeaveRequest { UserId = dto.UserId, LeaveType = dto.LeaveType, StartTime = dto.StartTime, EndTime = dto.EndTime, Days = dto.Days, Reason = dto.Reason, Status = LeaveStatus.Pending, // 审批中 CreatedTime = DateTime.UtcNow }; await _leaveRepo.AddAsync(leaveRequest); await _leaveRepo.SaveChangesAsync(); // 2. 构造飞书审批表单数据 var form = new List<object> { new { id = "leave_type", type = "select", value = dto.LeaveType }, new { id = "start_time", type = "date", value = dto.StartTime.ToString("yyyy-MM-dd") }, new { id = "end_time", type = "date", value = dto.EndTime.ToString("yyyy-MM-dd") }, new { id = "days", type = "number", value = dto.Days.ToString() }, new { id = "reason", type = "textarea", value = dto.Reason } }; // 3. 调用飞书API创建审批实例 var request = new CreateInstanceRequest { ApprovalCode = "7C468A54-8745-2245-9675-08B7C63E7A85", // 请假审批定义Code UserId = dto.FeishuUserId, // 飞书用户ID Form = JsonSerializer.Serialize(form), Uuid = Guid.NewGuid().ToString() // 幂等ID }; string instanceCode; try { instanceCode = await _feishuClient.CreateInstanceAsync(request); } catch (Exception ex) { _logger.LogError(ex, "创建飞书审批实例失败"); // 回滚业务数据 leaveRequest.Status = LeaveStatus.Failed; await _leaveRepo.SaveChangesAsync(); throw; } // 4. 保存审批关联记录 var approvalRecord = new ApprovalRecord { BusinessType = "LeaveRequest", BusinessId = leaveRequest.Id, InstanceCode = instanceCode, ApprovalCode = request.ApprovalCode, Status = "PENDING", CreatedTime = DateTime.UtcNow, UpdatedTime = DateTime.UtcNow }; await _approvalRepo.AddAsync(approvalRecord); await _approvalRepo.SaveChangesAsync(); _logger.LogInformation("请假申请已提交并创建审批: LeaveId={LeaveId}, InstanceCode={InstanceCode}", leaveRequest.Id, instanceCode); return leaveRequest.Id; } } 场景:审批完结,飞书回调通知结果 基于 Mud.Feishu.Webhook 实现安全的回调处理器 using Mud.Feishu.Abstractions; using Mud.Feishu.Abstractions.DataModels.Approval; using Mud.Feishu.Abstractions.EventHandlers; /// <summary> /// 审批实例事件处理器 /// </summary> public class ApprovalInstanceEventHandler : ApprovalInstanceEventHandler { private readonly IApprovalRecordRepository _approvalRepo; private readonly ILeaveRequestRepository _leaveRepo; public ApprovalInstanceEventHandler( ILogger<ApprovalInstanceEventHandler> logger, IApprovalRecordRepository approvalRepo, ILeaveRequestRepository leaveRepo) : base(logger) { _approvalRepo = approvalRepo; _leaveRepo = leaveRepo; } /// <summary> /// 处理审批实例事件业务逻辑 /// </summary> protected override async Task ProcessBusinessLogicAsync( EventData eventData, ObjectEventResult<ApprovalInstanceResult>? eventEntity, CancellationToken cancellationToken = default) { if (eventEntity?.Object == null) { _logger.LogWarning("审批实例事件数据无效"); return; } var approvalEvent = eventEntity.Object; _logger.LogInformation("收到审批实例事件: InstanceCode={InstanceCode}, Status={Status}", approvalEvent.InstanceCode, approvalEvent.Status); // 幂等性处理:检查是否已处理过该事件 var existingRecord = await _approvalRepo.GetByInstanceCodeAsync(approvalEvent.InstanceCode ?? string.Empty); if (existingRecord != null && existingRecord.Status == approvalEvent.Status) { _logger.LogInformation("该事件已处理过,跳过: EventId={EventId}", eventData.EventId); return; } // 根据业务类型处理审批结果 await ProcessApprovalResultAsync(eventData, approvalEvent, cancellationToken); } /// <summary> /// 处理审批结果 /// </summary> private async Task ProcessApprovalResultAsync( EventData eventData, ApprovalInstanceResult approvalEvent, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(approvalEvent.InstanceCode)) { _logger.LogWarning("审批实例Code为空,跳过处理"); return; } // 查询审批关联记录 var approvalRecord = await _approvalRepo.GetByInstanceCodeAsync(approvalEvent.InstanceCode); if (approvalRecord == null) { _logger.LogWarning("未找到审批关联记录: InstanceCode={InstanceCode}", approvalEvent.InstanceCode); return; } // 更新审批记录状态 approvalRecord.Status = approvalEvent.Status ?? string.Empty; approvalRecord.CallbackData = JsonSerializer.Serialize(approvalEvent); approvalRecord.UpdatedTime = DateTime.UtcNow; await _approvalRepo.SaveChangesAsync(); // 根据业务类型处理 switch (approvalRecord.BusinessType) { case "LeaveRequest": await ProcessLeaveApprovalAsync(approvalRecord, approvalEvent.Status ?? string.Empty); break; // 可扩展其他业务类型 default: _logger.LogWarning("未知的业务类型: {BusinessType}", approvalRecord.BusinessType); break; } } /// <summary> /// 处理请假审批结果 /// </summary> private async Task ProcessLeaveApprovalAsync(ApprovalRecord approvalRecord, string status) { var leaveRequest = await _leaveRepo.GetByIdAsync(approvalRecord.BusinessId); if (leaveRequest == null) { _logger.LogWarning("未找到请假申请: BusinessId={BusinessId}", approvalRecord.BusinessId); return; } // 根据审批状态更新请假记录 leaveRequest.Status = status switch { "APPROVED" => LeaveStatus.Approved, "REJECTED" => LeaveStatus.Rejected, "CANCELED" => LeaveStatus.Canceled, "DELETED" => LeaveStatus.Deleted, _ => LeaveStatus.Pending }; leaveRequest.UpdatedTime = DateTime.UtcNow; await _leaveRepo.SaveChangesAsync(); _logger.LogInformation("请假申请状态已更新: LeaveId={LeaveId}, Status={Status}", leaveRequest.Id, leaveRequest.Status); // TODO: 发送通知给申请人 // TODO: 同步到考勤系统 } } 注册 Webhook 服务(Program.cs): using Mud.Feishu.Webhook; using Mud.Feishu; using YourApp.Handlers; var builder = WebApplication.CreateBuilder(args); // 注册飞书 API 服务 builder.Services.AddFeishuServices() .ConfigureFrom(builder.Configuration) // 从 "Feishu" 配置节读取 .Build(); // 注册飞书 Webhook 事件订阅服务 builder.Services.AddFeishuWebhookServiceBuilder() .ConfigureFrom(builder.Configuration) // 从 "FeishuWebhook" 配置节读取 .AddHandler<ApprovalInstanceEventHandler>() // 添加审批事件处理器 .Build(); // 注册业务服务 builder.Services.AddScoped<ILeaveRequestRepository, LeaveRequestRepository>(); builder.Services.AddScoped<IApprovalRecordRepository, ApprovalRecordRepository>(); var app = builder.Build(); app.UseFeishuWebhook(); // 添加 Webhook 中间件 app.Run(); 说明: ApprovalInstanceEventHandler 继承自 ApprovalInstanceEventHandler 基类 基类已经实现了 HandleAsync 方法,会自动反序列化 ApprovalInstanceResult 类型的事件数据 只需重写 ProcessBusinessLogicAsync 方法实现具体的业务逻辑即可 SDK 会根据 SupportedEventType 属性自动路由对应的事件到这个处理器 AddFeishuServices() 注册飞书 API 客户端服务,使用 Feishu 配置节 AddFeishuWebhookServiceBuilder() 注册 Webhook 事件订阅服务,使用 FeishuWebhook 配置节 配置文件(appsettings.json): { // 飞书 Webhook 事件订阅配置 "FeishuWebhook": { "VerificationToken": "your_verification_token", "EncryptKey": "your_encrypt_key", "RoutePrefix": "api/feishu/webhook", "AutoRegisterEndpoint": true, "EnableRequestLogging": true, "EnableExceptionHandling": true, "EventHandlingTimeoutMs": 30000, "MaxConcurrentEvents": 10 }, // 飞书 API 客户端配置 "Feishu": { "AppId": "your_app_id", "AppSecret": "your_app_secret", "BaseUrl": "https://open.feishu.cn", "TimeOut": "30", "RetryCount": 3, "EnableLogging": true } } 第四步:功能完善与联调测试 状态同步展示 在请假列表页展示审批状态: /// <summary> /// 请假列表响应DTO /// </summary> public class LeaveRequestDto { public long Id { get; set; } public DateTime StartTime { get; set; } public DateTime EndTime { get; set; } public int Days { get; set; } public string Status { get; set; } = string.Empty; // 业务状态:Approved, Rejected public string ApprovalStatus { get; set; } = string.Empty; // 飞书审批状态:APPROVED, REJECTED, PENDING public string? FeishuInstanceUrl { get; set; } // 飞书审批详情链接 public DateTime CreatedTime { get; set; } } /// <summary> /// 查询请假列表 /// </summary> public async Task<List<LeaveRequestDto>> GetLeaveListAsync(long userId) { var leaves = await _leaveRepo.GetByUserIdAsync(userId); var instanceCodes = leaves.Select(l => l.InstanceCode).ToList(); // 批量查询审批记录 var approvals = await _approvalRepo.GetByInstanceCodesAsync(instanceCodes); var result = leaves.Select(leave => { var approval = approvals.FirstOrDefault(a => a.InstanceCode == leave.InstanceCode); return new LeaveRequestDto { Id = leave.Id, StartTime = leave.StartTime, EndTime = leave.EndTime, Days = leave.Days, Status = leave.Status.ToString(), ApprovalStatus = approval?.Status ?? "UNKNOWN", FeishuInstanceUrl = !string.IsNullOrEmpty(approval?.InstanceCode) ? $"https://www.feishu.cn/approval/approval/view/{approval.InstanceCode}" : null, CreatedTime = leave.CreatedTime }; }).ToList(); return result; } 添加"在飞书中查看"链接 在列表页添加操作按钮: <!-- 前端页面示例 --> <table class="leave-list"> <thead> <tr> <th>开始时间</th> <th>结束时间</th> <th>天数</th> <th>审批状态</th> <th>操作</th> </tr> </thead> <tbody> @foreach (var leave in Model.Leaves) { <tr> <td>@leave.StartTime.ToString("yyyy-MM-dd")</td> <td>@leave.EndTime.ToString("yyyy-MM-dd")</td> <td>@leave.Days</td> <td> <span class="status @leave.ApprovalStatus"> @GetStatusText(leave.ApprovalStatus) </span> </td> <td> @if (!string.IsNullOrEmpty(leave.FeishuInstanceUrl)) { <a href="@leave.FeishuInstanceUrl" target="_blank" class="btn"> 在飞书中查看 </a> } </td> </tr> } </tbody> </table> 联调测试 测试场景 验证要点 测试工具 发起审批 飞书是否收到审批通知、表单数据是否正确 直接在系统发起 审批流程 各审批节点是否正确流转 飞书管理后台 回调接收 Webhook是否正确接收事件、数据是否完整 飞书"模拟事件推送"工具 状态同步 业务状态是否正确更新、通知是否发送 数据库查询、日志查看 异常处理 网络异常、签名验证失败等边界情况 模拟异常场景 飞书模拟事件推送工具: 在飞书开放平台的"事件订阅"页面,可以使用"模拟事件推送"功能测试 Webhook 接口: graph LR A[飞书管理后台] -->|模拟事件推送| B[Webhook接口] B -->|日志输出| C[检查处理结果] C -->|成功| D[验证完成] C -->|失败| E[查看错误日志] 生产级注意事项 安全与可靠性 机密管理 切勿将敏感信息硬编码在代码中! // ❌ 错误示例 var appSecret = "cli_xxxxxxxxxxxxxxx"; // 危险! // ✅ 正确示例 builder.Configuration.AddAzureKeyVault( new Uri($"https://{vaultName}.vault.azure.net/"), new DefaultAzureCredential()); var appSecret = builder.Configuration["Feishu:AppSecret"]; 推荐方案: Azure Key Vault / AWS Secrets Manager HashiCorp Vault Docker Secrets(容器化部署) 幂等性处理 飞书可能会重复推送同一个事件(网络重试等),必须保证业务逻辑的幂等性: public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default) { // 使用 EventId 或 instance_code + status 组合作为幂等键 var idempotencyKey = $"{approvalEvent.InstanceCode}_{approvalEvent.Status}"; // 检查是否已处理过 if (await _cache.ExistsAsync(idempotencyKey)) { _logger.LogInformation("事件已处理过,跳过: Key={IdempotencyKey}", idempotencyKey); return; } // 标记为已处理(设置过期时间,如24小时) await _cache.SetAsync(idempotencyKey, "1", TimeSpan.FromHours(24)); // 执行业务逻辑 await ProcessEventAsync(approvalEvent, cancellationToken); } API 容错 使用 Polly 为飞书 API 调用添加重试和熔断机制: // 注册 HttpClient 时添加 Polly 策略 builder.Services.AddHttpClient("Feishu") .AddTransientHttpErrorPolicy(p => p.WaitAndRetryAsync(3, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)))) .AddPolicyHandler(Policy<HttpResponseMessage> .Handle<HttpRequestException>() .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30))); 边界情况与优雅降级 审批人失效处理 // 飞书审批定义中配置默认审批人 var request = new CreateInstanceRequest { ApprovalCode = "xxxx", // 如果自选审批人为空,使用默认审批人 NodeApproverUserIdLists = dto.ApproverUserId.HasValue ? new[] { new NodeApprover { NodeId = "node1", ApproverUserIds = new[] { dto.ApproverUserId.Value } } } : null // 走默认审批人流程 }; 网络超时与异步处理 回调处理应快速响应飞书(建议在 3 秒内),复杂逻辑移至后台作业: public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default) { // 1. 快速保存事件到队列 await _eventQueue.EnqueueAsync(eventData); // 2. 立即返回,由后台作业处理 // Hangfire、RabbitMQ 等会异步消费队列 await Task.CompletedTask; } // 后台作业处理 [Queue("approval-callback")] public async Task ProcessApprovalEventAsync(EventData eventData) { // 复杂的业务逻辑处理 await _approvalService.ProcessCallbackAsync(eventData); } 监控与告警 建立关键节点的监控: // 监控指标 public class ApprovalMetrics { private readonly Counter _approvalCreatedCounter; private readonly Counter _callbackReceivedCounter; private readonly Histogram _processingTimeHistogram; public void RecordApprovalCreated(string approvalType) { _approvalCreatedCounter.WithLabels(approvalType).Inc(); } public void RecordCallbackReceived(string status) { _callbackReceivedCounter.WithLabels(status).Inc(); } public void RecordProcessingTime(TimeSpan duration) { _processingTimeHistogram.Observe(duration.TotalSeconds); } } // 告警规则(Prometheus 示例) # 审批发起失败率超过 5% 触发告警 alert: ApprovalCreationFailureRate expr: rate(approval_creation_failed_total[5m]) / rate(approval_creation_total[5m]) > 0.05 for: 5m annotations: summary: "审批创建失败率过高" 扩展性与维护性 策略模式支持多平台 /// <summary> /// 审批平台策略接口 /// </summary> public interface IApprovalPlatformStrategy { string PlatformName { get; } Task<string> CreateInstanceAsync(ApprovalRequest request); } /// <summary> /// 飞书审批策略 /// </summary> public class FeishuApprovalStrategy : IApprovalPlatformStrategy { public string PlatformName => "Feishu"; // ... 实现 } /// <summary> /// 钉钉审批策略 /// </summary> public class DingTalkApprovalStrategy : IApprovalPlatformStrategy { public string PlatformName => "DingTalk"; // ... 实现 } /// <summary> /// 审批策略工厂 /// </summary> public class ApprovalStrategyFactory { private readonly IEnumerable<IApprovalPlatformStrategy> _strategies; public IApprovalPlatformStrategy GetStrategy(string platformName) { return _strategies.FirstOrDefault(s => s.PlatformName == platformName) ?? throw new NotSupportedException($"不支持的审批平台: {platformName}"); } } 审计日志 详细记录审批流转换的关键日志: public class ApprovalAuditService { private readonly IApprovalAuditRepository _auditRepo; public async Task LogAsync(ApprovalAuditLog log) { log.Timestamp = DateTime.UtcNow; await _auditRepo.AddAsync(log); await _auditRepo.SaveChangesAsync(); // 结构化日志输出 _logger.LogInformation("审批审计: {AuditType}, InstanceCode={InstanceCode}, BusinessId={BusinessId}", log.AuditType, log.InstanceCode, log.BusinessId); } } // 使用示例 await _auditService.LogAsync(new ApprovalAuditLog { AuditType = "ApprovalStarted", InstanceCode = instanceCode, BusinessId = leaveRequest.Id, OperatorId = userId, Details = new { leaveType, days, reason } }); 最后一点内容 核心价值 通过本文的实践,我们成功实现了: 价值点 实现方式 收益 移动化审批 飞书 App 作为审批门户 随时随地处理审批 即时通知 飞书强通知机制 审批人及时响应 数据闭环 .NET 业务库 + 审批关联表 完整的业务流程追踪 解耦设计 领域层抽象 + 策略模式 便于扩展和维护 全文总结 本文提供了一个从理念、设计到编码落地的完整闭环: mindmap root((飞书审批集成)) 理念 双向集成 .NET为业务核心 飞书为流程门户 设计 分层架构 领域抽象 安全机制 实现 发起审批 回调处理 状态同步 最佳实践 机密管理 幂等性 异步处理 监控告警 扩展 场景扩展 将此模式快速复用于其他业务场景: 业务场景 审批流程 复杂度 报销审批 发起 → 直属主管 → 财务审核 中 采购申请 发起 → 部门主管 → 采购部 → 总经理 高 合同审批 法务审核 → 财务审核 → 总经理 高 加班申请 直属主管审批 低 深度集成 利用飞书更多能力,打造更丰富的协同体验: graph TB A[飞书审批] --> B[消息卡片] A --> C[智能机器人] A --> D[知识库] B --> B1[审批详情展示] B --> B2[操作按钮] C --> C1[智能提醒] C --> C2[自动补全] D --> D1[历史记录查询] D --> D2[审批规范] E[.NET系统] --> A B --> E C --> E D --> E 功能扩展示例: 在飞书群聊中通过消息卡片直接查看审批详情 通过机器人智能回复,引导用户填写审批表单 将审批记录同步到飞书知识库,方便查阅 平台化 将审批集成能力抽象为中台服务,供企业内部所有系统统一调用: /// <summary> /// 统一审批服务中台 /// </summary> public interface IApprovalCenterService { /// <summary> /// 统一发起审批(支持多平台) /// </summary> Task<string> CreateApprovalAsync(UnifiedApprovalRequest request); /// <summary> /// 查询审批状态 /// </summary> Task<ApprovalStatus> GetStatusAsync(string instanceId); /// <summary> /// 审批统计报表 /// </summary> Task<ApprovalStatistics> GetStatisticsAsync(DateTime from, DateTime to); } // 多个系统统一调用 await _approvalCenter.CreateApprovalAsync(new UnifiedApprovalRequest { BusinessSystem = "HR", BusinessType = "LeaveRequest", BusinessId = leaveId, Platform = "Feishu" // 可切换到其他平台 }); 结语 传统 .NET 系统无需推倒重来,通过合理的架构设计与飞书审批的深度集成,同样可以焕发新的活力。希望本文的实践能够为你的数字化转型之路提供有价值的参考。 让我们一起告别信息孤岛,拥抱现代化的协同办公体验!🚀 相关资源 项目地址 Gitee 仓库:https://gitee.com/mudtools/MudFeishu GitHub 仓库:https://github.com/mudtools/MudFeishu NuGet 包: Mud.Feishu.Abstractions Mud.Feishu Mud.Feishu.WebSocket Mud.Feishu.Webhook