找回密码
 立即注册
首页 业界区 业界 .NET 传统信息系统无缝集成飞书审批流

.NET 传统信息系统无缝集成飞书审批流

尹心菱 3 天前
周末深夜,你收到紧急审批通知——却发现只能在 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)

审批定义是审批流程的"蓝图",在飞书管理后台配置:
  1. // 示例:请假审批的审批定义
  2. var approvalCode = "7C468A54-8745-2245-9675-08B7C63E7A85";
复制代码
定义包含

  • 表单结构(请假类型、开始时间、结束时间、请假事由等)
  • 审批流程(直属主管审批 → 人事审批)
  • 权限设置(谁可以发起、谁可以审批)
审批实例(instance_code)

审批实例是依据审批定义发起的一次具体审批任务:
  1. // 创建审批实例时返回
  2. public record CreateInstancesResult
  3. {
  4.     /// <summary>
  5.     /// 审批实例 Code
  6.     /// </summary>
  7.     [JsonPropertyName("instance_code")]
  8.     public string InstanceCode { get; set; } = string.Empty;
  9. }
复制代码
关键属性

  • 唯一标识一次审批流程
  • 包含该次审批的所有表单数据
  • 拥有独立的状态(审批中、通过、拒绝、撤回等)
身份映射(免登)

实现 .NET 系统用户与飞书用户的关联:
graph LR    A[.NET系统用户
UserId: 1001] -->|映射关系| B[飞书用户
OpenId: ou_3cda9c...]    B -->|通过飞书App审批| C[审批完成]    C -->|回调instance_code| A实现方式

  • 在 .NET 系统的用户表中添加 FeishuOpenId 字段
  • 用户首次登录时进行飞书免登录认证,获取并存储 open_id
  • 发起审批时,使用 open_id 指定审批发起人
构建稳健、可扩展的 .NET 侧集成层

技术栈推荐

层次技术选型说明应用框架.NET 6/8/10长期支持版本,性能优异飞书 SDKMud.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[数据仓储
EF Core]    end    A --> B    A --> C    B --> D    C --> E    D --> F    E --> F    F --> G    F --> H    F --> I关键设计:领域层抽象

在领域层引入 IApprovalService 接口,将飞书集成细节与核心业务逻辑解耦:
  1. /// <summary>
  2. /// 审批服务抽象接口 - 解耦飞书实现细节
  3. /// </summary>
  4. public interface IApprovalService
  5. {
  6.     /// <summary>
  7.     /// 发起审批
  8.     /// </summary>
  9.     Task<string> CreateApprovalAsync(ApprovalRequest request);
  10.     /// <summary>
  11.     /// 处理审批结果回调
  12.     /// </summary>
  13.     Task HandleApprovalCallbackAsync(ApprovalCallbackEvent callbackEvent);
  14. }
  15. /// <summary>
  16. /// 飞书审批服务实现
  17. /// </summary>
  18. public class FeishuApprovalService : IApprovalService
  19. {
  20.     private readonly IFeishuTenantV4Approval _approvalApi;
  21.     private readonly IApprovalRecordRepository _repository;
  22.     public FeishuApprovalService(
  23.         IFeishuTenantV4Approval approvalApi,
  24.         IApprovalRecordRepository repository)
  25.     {
  26.         _approvalApi = approvalApi;
  27.         _repository = repository;
  28.     }
  29.     public async Task<string> CreateApprovalAsync(ApprovalRequest request)
  30.     {
  31.         // 调用飞书 API
  32.         var result = await _approvalApi.CreateInstanceAsync(...);
  33.         return result.Data?.InstanceCode ?? string.Empty;
  34.     }
  35. }
复制代码
优势

  • 业务逻辑不依赖具体飞书实现
  • 便于单元测试(可 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[请假类型
开始时间
结束时间
请假天数
请假事由]    C --> C1[直属主管审批
→ 人事审批]    D --> D1[全员可发起]记录关键信息

  • approval_code:审批定义的唯一标识
  • 表单控件的 id:用于程序填充表单数据
配置事件订阅

在飞书开放平台配置 Webhook:
配置项值请求网址https://your-domain.com/api/feishu/webhook验证 Tokenyour_verification_token(自定义)加密 Keyyour_encrypt_key(自定义)订阅事件approval_instance(审批实例状态变更)第二步:.NET 侧基础搭建(集成基石)

封装飞书 API 客户端

基于 MudFeishu SDK 封装审批服务:
  1. /// <summary>
  2. /// 飞书审批服务封装
  3. /// </summary>
  4. public class FeishuApprovalClient
  5. {
  6.     private readonly IFeishuTenantV4Approval _approvalApi;
  7.     private readonly ILogger<FeishuApprovalClient> _logger;
  8.     public FeishuApprovalClient(
  9.         IFeishuTenantV4Approval approvalApi,
  10.         ILogger<FeishuApprovalClient> logger)
  11.     {
  12.         _approvalApi = approvalApi;
  13.         _logger = logger;
  14.     }
  15.     /// <summary>
  16.     /// 创建审批实例
  17.     /// </summary>
  18.     public async Task<string> CreateInstanceAsync(CreateInstanceRequest request)
  19.     {
  20.         var result = await _approvalApi.CreateInstanceAsync(request);
  21.         if (result == null || result.Code != 0)
  22.         {
  23.             _logger.LogError("创建审批实例失败: {Msg}", result?.Msg);
  24.             throw new InvalidOperationException($"创建审批实例失败: {result?.Msg}");
  25.         }
  26.         _logger.LogInformation("创建审批实例成功: {InstanceCode}", result.Data?.InstanceCode);
  27.         return result.Data?.InstanceCode ?? string.Empty;
  28.     }
  29.     /// <summary>
  30.     /// 获取审批实例详情
  31.     /// </summary>
  32.     public async Task<GetApprovalInstanceResult?> GetInstanceAsync(string instanceCode)
  33.     {
  34.         var result = await _approvalApi.GetInstanceByIdAsync(instanceCode);
  35.         if (result == null || result.Code != 0)
  36.         {
  37.             _logger.LogError("获取审批实例失败: {Msg}", result?.Msg);
  38.             return null;
  39.         }
  40.         return result.Data;
  41.     }
  42. }
复制代码
设计数据关联表

在业务数据库中添加审批关联表:
  1. -- 审批关联表
  2. CREATE TABLE ApprovalRecords (
  3.     Id BIGINT PRIMARY KEY IDENTITY(1,1),
  4.     BusinessType NVARCHAR(50) NOT NULL,          -- 业务类型:LeaveRequest, PurchaseRequest...
  5.     BusinessId BIGINT NOT NULL,                   -- 业务ID
  6.     InstanceCode NVARCHAR(64) NOT NULL,           -- 飞书审批实例Code
  7.     ApprovalCode NVARCHAR(64) NOT NULL,            -- 审批定义Code
  8.     Status NVARCHAR(20) NOT NULL,                  -- 状态:PENDING, APPROVED, REJECTED...
  9.     CallbackData NVARCHAR(MAX),                   -- 回调数据(JSON)
  10.     CreatedTime DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
  11.     UpdatedTime DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
  12.     CONSTRAINT UK_ApprovalRecords_Business UNIQUE(BusinessType, BusinessId)
  13. );
  14. CREATE INDEX IX_ApprovalRecords_InstanceCode ON ApprovalRecords(InstanceCode);
  15. CREATE INDEX IX_ApprovalRecords_Status ON ApprovalRecords(Status);
复制代码
对应的实体类:
  1. /// <summary>
  2. /// 审批关联记录
  3. /// </summary>
  4. public class ApprovalRecord
  5. {
  6.     public long Id { get; set; }
  7.     public string BusinessType { get; set; } = string.Empty;  // "LeaveRequest"
  8.     public long BusinessId { get; set; }                       // 请假申请ID
  9.     public string InstanceCode { get; set; } = string.Empty;   // 飞书实例Code
  10.     public string ApprovalCode { get; set; } = string.Empty;    // 审批定义Code
  11.     public string Status { get; set; } = string.Empty;         // PENDING/APPROVED/REJECTED
  12.     public string? CallbackData { get; set; }                  // JSON格式
  13.     public DateTime CreatedTime { get; set; }
  14.     public DateTime UpdatedTime { get; set; }
  15. }
复制代码
第三步:核心业务流程编码(双向联通)

场景:用户提交请假单,发起审批

流程图
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: 等待审批代码实现
  1. /// <summary>
  2. /// 请假服务
  3. /// </summary>
  4. public class LeaveService
  5. {
  6.     private readonly ILeaveRequestRepository _leaveRepo;
  7.     private readonly IApprovalRecordRepository _approvalRepo;
  8.     private readonly FeishuApprovalClient _feishuClient;
  9.     private readonly ILogger<LeaveService> _logger;
  10.     public LeaveService(
  11.         ILeaveRequestRepository leaveRepo,
  12.         IApprovalRecordRepository approvalRepo,
  13.         FeishuApprovalClient feishuClient,
  14.         ILogger<LeaveService> logger)
  15.     {
  16.         _leaveRepo = leaveRepo;
  17.         _approvalRepo = approvalRepo;
  18.         _feishuClient = feishuClient;
  19.         _logger = logger;
  20.     }
  21.     /// <summary>
  22.     /// 提交请假申请并发起审批
  23.     /// </summary>
  24.     public async Task<long> SubmitLeaveRequestAsync(SubmitLeaveRequestDto dto)
  25.     {
  26.         // 1. 保存请假业务数据
  27.         var leaveRequest = new LeaveRequest
  28.         {
  29.             UserId = dto.UserId,
  30.             LeaveType = dto.LeaveType,
  31.             StartTime = dto.StartTime,
  32.             EndTime = dto.EndTime,
  33.             Days = dto.Days,
  34.             Reason = dto.Reason,
  35.             Status = LeaveStatus.Pending,  // 审批中
  36.             CreatedTime = DateTime.UtcNow
  37.         };
  38.         await _leaveRepo.AddAsync(leaveRequest);
  39.         await _leaveRepo.SaveChangesAsync();
  40.         // 2. 构造飞书审批表单数据
  41.         var form = new List<object>
  42.         {
  43.             new { id = "leave_type", type = "select", value = dto.LeaveType },
  44.             new { id = "start_time", type = "date", value = dto.StartTime.ToString("yyyy-MM-dd") },
  45.             new { id = "end_time", type = "date", value = dto.EndTime.ToString("yyyy-MM-dd") },
  46.             new { id = "days", type = "number", value = dto.Days.ToString() },
  47.             new { id = "reason", type = "textarea", value = dto.Reason }
  48.         };
  49.         // 3. 调用飞书API创建审批实例
  50.         var request = new CreateInstanceRequest
  51.         {
  52.             ApprovalCode = "7C468A54-8745-2245-9675-08B7C63E7A85",  // 请假审批定义Code
  53.             UserId = dto.FeishuUserId,  // 飞书用户ID
  54.             Form = JsonSerializer.Serialize(form),
  55.             Uuid = Guid.NewGuid().ToString()  // 幂等ID
  56.         };
  57.         string instanceCode;
  58.         try
  59.         {
  60.             instanceCode = await _feishuClient.CreateInstanceAsync(request);
  61.         }
  62.         catch (Exception ex)
  63.         {
  64.             _logger.LogError(ex, "创建飞书审批实例失败");
  65.             // 回滚业务数据
  66.             leaveRequest.Status = LeaveStatus.Failed;
  67.             await _leaveRepo.SaveChangesAsync();
  68.             throw;
  69.         }
  70.         // 4. 保存审批关联记录
  71.         var approvalRecord = new ApprovalRecord
  72.         {
  73.             BusinessType = "LeaveRequest",
  74.             BusinessId = leaveRequest.Id,
  75.             InstanceCode = instanceCode,
  76.             ApprovalCode = request.ApprovalCode,
  77.             Status = "PENDING",
  78.             CreatedTime = DateTime.UtcNow,
  79.             UpdatedTime = DateTime.UtcNow
  80.         };
  81.         await _approvalRepo.AddAsync(approvalRecord);
  82.         await _approvalRepo.SaveChangesAsync();
  83.         _logger.LogInformation("请假申请已提交并创建审批: LeaveId={LeaveId}, InstanceCode={InstanceCode}",
  84.             leaveRequest.Id, instanceCode);
  85.         return leaveRequest.Id;
  86.     }
  87. }
复制代码
场景:审批完结,飞书回调通知结果

基于 Mud.Feishu.Webhook 实现安全的回调处理器
  1. using Mud.Feishu.Abstractions;
  2. using Mud.Feishu.Abstractions.DataModels.Approval;
  3. using Mud.Feishu.Abstractions.EventHandlers;
  4. /// <summary>
  5. /// 审批实例事件处理器
  6. /// </summary>
  7. public class ApprovalInstanceEventHandler : ApprovalInstanceEventHandler
  8. {
  9.     private readonly IApprovalRecordRepository _approvalRepo;
  10.     private readonly ILeaveRequestRepository _leaveRepo;
  11.     public ApprovalInstanceEventHandler(
  12.         ILogger logger,
  13.         IApprovalRecordRepository approvalRepo,
  14.         ILeaveRequestRepository leaveRepo)
  15.         : base(logger)
  16.     {
  17.         _approvalRepo = approvalRepo;
  18.         _leaveRepo = leaveRepo;
  19.     }
  20.     /// <summary>
  21.     /// 处理审批实例事件业务逻辑
  22.     /// </summary>
  23.     protected override async Task ProcessBusinessLogicAsync(
  24.         EventData eventData,
  25.         ObjectEventResult? eventEntity,
  26.         CancellationToken cancellationToken = default)
  27.     {
  28.         if (eventEntity?.Object == null)
  29.         {
  30.             _logger.LogWarning("审批实例事件数据无效");
  31.             return;
  32.         }
  33.         var approvalEvent = eventEntity.Object;
  34.         _logger.LogInformation("收到审批实例事件: InstanceCode={InstanceCode}, Status={Status}",
  35.             approvalEvent.InstanceCode, approvalEvent.Status);
  36.         // 幂等性处理:检查是否已处理过该事件
  37.         var existingRecord = await _approvalRepo.GetByInstanceCodeAsync(approvalEvent.InstanceCode ?? string.Empty);
  38.         if (existingRecord != null && existingRecord.Status == approvalEvent.Status)
  39.         {
  40.             _logger.LogInformation("该事件已处理过,跳过: EventId={EventId}", eventData.EventId);
  41.             return;
  42.         }
  43.         // 根据业务类型处理审批结果
  44.         await ProcessApprovalResultAsync(eventData, approvalEvent, cancellationToken);
  45.     }
  46.     /// <summary>
  47.     /// 处理审批结果
  48.     /// </summary>
  49.     private async Task ProcessApprovalResultAsync(
  50.         EventData eventData,
  51.         ApprovalInstanceResult approvalEvent,
  52.         CancellationToken cancellationToken)
  53.     {
  54.         if (string.IsNullOrEmpty(approvalEvent.InstanceCode))
  55.         {
  56.             _logger.LogWarning("审批实例Code为空,跳过处理");
  57.             return;
  58.         }
  59.         // 查询审批关联记录
  60.         var approvalRecord = await _approvalRepo.GetByInstanceCodeAsync(approvalEvent.InstanceCode);
  61.         if (approvalRecord == null)
  62.         {
  63.             _logger.LogWarning("未找到审批关联记录: InstanceCode={InstanceCode}",
  64.                 approvalEvent.InstanceCode);
  65.             return;
  66.         }
  67.         // 更新审批记录状态
  68.         approvalRecord.Status = approvalEvent.Status ?? string.Empty;
  69.         approvalRecord.CallbackData = JsonSerializer.Serialize(approvalEvent);
  70.         approvalRecord.UpdatedTime = DateTime.UtcNow;
  71.         await _approvalRepo.SaveChangesAsync();
  72.         // 根据业务类型处理
  73.         switch (approvalRecord.BusinessType)
  74.         {
  75.             case "LeaveRequest":
  76.                 await ProcessLeaveApprovalAsync(approvalRecord, approvalEvent.Status ?? string.Empty);
  77.                 break;
  78.             // 可扩展其他业务类型
  79.             default:
  80.                 _logger.LogWarning("未知的业务类型: {BusinessType}", approvalRecord.BusinessType);
  81.                 break;
  82.         }
  83.     }
  84.     /// <summary>
  85.     /// 处理请假审批结果
  86.     /// </summary>
  87.     private async Task ProcessLeaveApprovalAsync(ApprovalRecord approvalRecord, string status)
  88.     {
  89.         var leaveRequest = await _leaveRepo.GetByIdAsync(approvalRecord.BusinessId);
  90.         if (leaveRequest == null)
  91.         {
  92.             _logger.LogWarning("未找到请假申请: BusinessId={BusinessId}", approvalRecord.BusinessId);
  93.             return;
  94.         }
  95.         // 根据审批状态更新请假记录
  96.         leaveRequest.Status = status switch
  97.         {
  98.             "APPROVED" => LeaveStatus.Approved,
  99.             "REJECTED" => LeaveStatus.Rejected,
  100.             "CANCELED" => LeaveStatus.Canceled,
  101.             "DELETED" => LeaveStatus.Deleted,
  102.             _ => LeaveStatus.Pending
  103.         };
  104.         leaveRequest.UpdatedTime = DateTime.UtcNow;
  105.         await _leaveRepo.SaveChangesAsync();
  106.         _logger.LogInformation("请假申请状态已更新: LeaveId={LeaveId}, Status={Status}",
  107.             leaveRequest.Id, leaveRequest.Status);
  108.         // TODO: 发送通知给申请人
  109.         // TODO: 同步到考勤系统
  110.     }
  111. }
复制代码
注册 Webhook 服务(Program.cs)
  1. using Mud.Feishu.Webhook;
  2. using Mud.Feishu;
  3. using YourApp.Handlers;
  4. var builder = WebApplication.CreateBuilder(args);
  5. // 注册飞书 API 服务
  6. builder.Services.AddFeishuServices()
  7.     .ConfigureFrom(builder.Configuration)  // 从 "Feishu" 配置节读取
  8.     .Build();
  9. // 注册飞书 Webhook 事件订阅服务
  10. builder.Services.AddFeishuWebhookServiceBuilder()
  11.     .ConfigureFrom(builder.Configuration)  // 从 "FeishuWebhook" 配置节读取
  12.     .AddHandler()  // 添加审批事件处理器
  13.     .Build();
  14. // 注册业务服务
  15. builder.Services.AddScoped<ILeaveRequestRepository, LeaveRequestRepository>();
  16. builder.Services.AddScoped<IApprovalRecordRepository, ApprovalRecordRepository>();
  17. var app = builder.Build();
  18. app.UseFeishuWebhook();  // 添加 Webhook 中间件
  19. app.Run();
复制代码
说明

  • ApprovalInstanceEventHandler 继承自 ApprovalInstanceEventHandler 基类
  • 基类已经实现了 HandleAsync 方法,会自动反序列化 ApprovalInstanceResult 类型的事件数据
  • 只需重写 ProcessBusinessLogicAsync 方法实现具体的业务逻辑即可
  • SDK 会根据 SupportedEventType 属性自动路由对应的事件到这个处理器
  • AddFeishuServices() 注册飞书 API 客户端服务,使用 Feishu 配置节
  • AddFeishuWebhookServiceBuilder() 注册 Webhook 事件订阅服务,使用 FeishuWebhook 配置节
配置文件(appsettings.json)
  1. {
  2.   // 飞书 Webhook 事件订阅配置
  3.   "FeishuWebhook": {
  4.     "VerificationToken": "your_verification_token",
  5.     "EncryptKey": "your_encrypt_key",
  6.     "RoutePrefix": "api/feishu/webhook",
  7.     "AutoRegisterEndpoint": true,
  8.     "EnableRequestLogging": true,
  9.     "EnableExceptionHandling": true,
  10.     "EventHandlingTimeoutMs": 30000,
  11.     "MaxConcurrentEvents": 10
  12.   },
  13.   // 飞书 API 客户端配置
  14.   "Feishu": {
  15.     "AppId": "your_app_id",
  16.     "AppSecret": "your_app_secret",
  17.     "BaseUrl": "https://open.feishu.cn",
  18.     "TimeOut": "30",
  19.     "RetryCount": 3,
  20.     "EnableLogging": true
  21.   }
  22. }
复制代码
第四步:功能完善与联调测试

状态同步展示

在请假列表页展示审批状态:
  1. /// <summary>
  2. /// 请假列表响应DTO
  3. /// </summary>
  4. public class LeaveRequestDto
  5. {
  6.     public long Id { get; set; }
  7.     public DateTime StartTime { get; set; }
  8.     public DateTime EndTime { get; set; }
  9.     public int Days { get; set; }
  10.     public string Status { get; set; } = string.Empty;      // 业务状态:Approved, Rejected
  11.     public string ApprovalStatus { get; set; } = string.Empty; // 飞书审批状态:APPROVED, REJECTED, PENDING
  12.     public string? FeishuInstanceUrl { get; set; }          // 飞书审批详情链接
  13.     public DateTime CreatedTime { get; set; }
  14. }
  15. /// <summary>
  16. /// 查询请假列表
  17. /// </summary>
  18. public async Task<List<LeaveRequestDto>> GetLeaveListAsync(long userId)
  19. {
  20.     var leaves = await _leaveRepo.GetByUserIdAsync(userId);
  21.     var instanceCodes = leaves.Select(l => l.InstanceCode).ToList();
  22.     // 批量查询审批记录
  23.     var approvals = await _approvalRepo.GetByInstanceCodesAsync(instanceCodes);
  24.     var result = leaves.Select(leave =>
  25.     {
  26.         var approval = approvals.FirstOrDefault(a => a.InstanceCode == leave.InstanceCode);
  27.         return new LeaveRequestDto
  28.         {
  29.             Id = leave.Id,
  30.             StartTime = leave.StartTime,
  31.             EndTime = leave.EndTime,
  32.             Days = leave.Days,
  33.             Status = leave.Status.ToString(),
  34.             ApprovalStatus = approval?.Status ?? "UNKNOWN",
  35.             FeishuInstanceUrl = !string.IsNullOrEmpty(approval?.InstanceCode)
  36.                 ? $"https://www.feishu.cn/approval/approval/view/{approval.InstanceCode}"
  37.                 : null,
  38.             CreatedTime = leave.CreatedTime
  39.         };
  40.     }).ToList();
  41.     return result;
  42. }
复制代码
添加"在飞书中查看"链接

在列表页添加操作按钮:
  1. <table >
  2.     <thead>
  3.         <tr>
  4.             <th>开始时间</th>
  5.             <th>结束时间</th>
  6.             <th>天数</th>
  7.             <th>审批状态</th>
  8.             <th>操作</th>
  9.         </tr>
  10.     </thead>
  11.     <tbody>
  12.         @foreach (var leave in Model.Leaves)
  13.         {
  14.             <tr>
  15.                 <td>@leave.StartTime.ToString("yyyy-MM-dd")</td>
  16.                 <td>@leave.EndTime.ToString("yyyy-MM-dd")</td>
  17.                 <td>@leave.Days</td>
  18.                 <td>
  19.                     
  20.                         @GetStatusText(leave.ApprovalStatus)
  21.                     
  22.                 </td>
  23.                 <td>
  24.                     @if (!string.IsNullOrEmpty(leave.FeishuInstanceUrl))
  25.                     {
  26.                         
  27.                             在飞书中查看
  28.                         
  29.                     }
  30.                 </td>
  31.             </tr>
  32.         }
  33.     </tbody>
  34. </table>
复制代码
联调测试

测试场景验证要点测试工具发起审批飞书是否收到审批通知、表单数据是否正确直接在系统发起审批流程各审批节点是否正确流转飞书管理后台回调接收Webhook是否正确接收事件、数据是否完整飞书"模拟事件推送"工具状态同步业务状态是否正确更新、通知是否发送数据库查询、日志查看异常处理网络异常、签名验证失败等边界情况模拟异常场景飞书模拟事件推送工具
在飞书开放平台的"事件订阅"页面,可以使用"模拟事件推送"功能测试 Webhook 接口:
graph LR    A[飞书管理后台] -->|模拟事件推送| B[Webhook接口]    B -->|日志输出| C[检查处理结果]    C -->|成功| D[验证完成]    C -->|失败| E[查看错误日志]生产级注意事项

安全与可靠性

机密管理

切勿将敏感信息硬编码在代码中!
  1. // ❌ 错误示例
  2. var appSecret = "cli_xxxxxxxxxxxxxxx";  // 危险!
  3. // ✅ 正确示例
  4. builder.Configuration.AddAzureKeyVault(
  5.     new Uri($"https://{vaultName}.vault.azure.net/"),
  6.     new DefaultAzureCredential());
  7. var appSecret = builder.Configuration["Feishu:AppSecret"];
复制代码
推荐方案

  • Azure Key Vault / AWS Secrets Manager
  • HashiCorp Vault
  • Docker Secrets(容器化部署)
幂等性处理

飞书可能会重复推送同一个事件(网络重试等),必须保证业务逻辑的幂等性:
  1. public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default)
  2. {
  3.     // 使用 EventId 或 instance_code + status 组合作为幂等键
  4.     var idempotencyKey = $"{approvalEvent.InstanceCode}_{approvalEvent.Status}";
  5.     // 检查是否已处理过
  6.     if (await _cache.ExistsAsync(idempotencyKey))
  7.     {
  8.         _logger.LogInformation("事件已处理过,跳过: Key={IdempotencyKey}", idempotencyKey);
  9.         return;
  10.     }
  11.     // 标记为已处理(设置过期时间,如24小时)
  12.     await _cache.SetAsync(idempotencyKey, "1", TimeSpan.FromHours(24));
  13.     // 执行业务逻辑
  14.     await ProcessEventAsync(approvalEvent, cancellationToken);
  15. }
复制代码
API 容错

使用 Polly 为飞书 API 调用添加重试和熔断机制:
  1. // 注册 HttpClient 时添加 Polly 策略
  2. builder.Services.AddHttpClient("Feishu")
  3.     .AddTransientHttpErrorPolicy(p =>
  4.         p.WaitAndRetryAsync(3, retryAttempt =>
  5.             TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))))
  6.     .AddPolicyHandler(Policy<HttpResponseMessage>
  7.         .Handle<HttpRequestException>()
  8.         .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));
复制代码
边界情况与优雅降级

审批人失效处理
  1. // 飞书审批定义中配置默认审批人
  2. var request = new CreateInstanceRequest
  3. {
  4.     ApprovalCode = "xxxx",
  5.     // 如果自选审批人为空,使用默认审批人
  6.     NodeApproverUserIdLists = dto.ApproverUserId.HasValue
  7.         ? new[] { new NodeApprover { NodeId = "node1", ApproverUserIds = new[] { dto.ApproverUserId.Value } } }
  8.         : null  // 走默认审批人流程
  9. };
复制代码
网络超时与异步处理

回调处理应快速响应飞书(建议在 3 秒内),复杂逻辑移至后台作业:
  1. public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default)
  2. {
  3.     // 1. 快速保存事件到队列
  4.     await _eventQueue.EnqueueAsync(eventData);
  5.     // 2. 立即返回,由后台作业处理
  6.     // Hangfire、RabbitMQ 等会异步消费队列
  7.     await Task.CompletedTask;
  8. }
  9. // 后台作业处理
  10. [Queue("approval-callback")]
  11. public async Task ProcessApprovalEventAsync(EventData eventData)
  12. {
  13.     // 复杂的业务逻辑处理
  14.     await _approvalService.ProcessCallbackAsync(eventData);
  15. }
复制代码
监控与告警

建立关键节点的监控:
  1. // 监控指标
  2. public class ApprovalMetrics
  3. {
  4.     private readonly Counter _approvalCreatedCounter;
  5.     private readonly Counter _callbackReceivedCounter;
  6.     private readonly Histogram _processingTimeHistogram;
  7.     public void RecordApprovalCreated(string approvalType)
  8.     {
  9.         _approvalCreatedCounter.WithLabels(approvalType).Inc();
  10.     }
  11.     public void RecordCallbackReceived(string status)
  12.     {
  13.         _callbackReceivedCounter.WithLabels(status).Inc();
  14.     }
  15.     public void RecordProcessingTime(TimeSpan duration)
  16.     {
  17.         _processingTimeHistogram.Observe(duration.TotalSeconds);
  18.     }
  19. }
  20. // 告警规则(Prometheus 示例)
  21. # 审批发起失败率超过 5% 触发告警
  22. alert: ApprovalCreationFailureRate
  23. expr: rate(approval_creation_failed_total[5m]) / rate(approval_creation_total[5m]) > 0.05
  24. for: 5m
  25. annotations:
  26.   summary: "审批创建失败率过高"
复制代码
扩展性与维护性

策略模式支持多平台
  1. /// <summary>
  2. /// 审批平台策略接口
  3. /// </summary>
  4. public interface IApprovalPlatformStrategy
  5. {
  6.     string PlatformName { get; }
  7.     Task<string> CreateInstanceAsync(ApprovalRequest request);
  8. }
  9. /// <summary>
  10. /// 飞书审批策略
  11. /// </summary>
  12. public class FeishuApprovalStrategy : IApprovalPlatformStrategy
  13. {
  14.     public string PlatformName => "Feishu";
  15.     // ... 实现
  16. }
  17. /// <summary>
  18. /// 钉钉审批策略
  19. /// </summary>
  20. public class DingTalkApprovalStrategy : IApprovalPlatformStrategy
  21. {
  22.     public string PlatformName => "DingTalk";
  23.     // ... 实现
  24. }
  25. /// <summary>
  26. /// 审批策略工厂
  27. /// </summary>
  28. public class ApprovalStrategyFactory
  29. {
  30.     private readonly IEnumerable<IApprovalPlatformStrategy> _strategies;
  31.     public IApprovalPlatformStrategy GetStrategy(string platformName)
  32.     {
  33.         return _strategies.FirstOrDefault(s => s.PlatformName == platformName)
  34.             ?? throw new NotSupportedException($"不支持的审批平台: {platformName}");
  35.     }
  36. }
复制代码
审计日志

详细记录审批流转换的关键日志:
  1. public class ApprovalAuditService
  2. {
  3.     private readonly IApprovalAuditRepository _auditRepo;
  4.     public async Task LogAsync(ApprovalAuditLog log)
  5.     {
  6.         log.Timestamp = DateTime.UtcNow;
  7.         await _auditRepo.AddAsync(log);
  8.         await _auditRepo.SaveChangesAsync();
  9.         // 结构化日志输出
  10.         _logger.LogInformation("审批审计: {AuditType}, InstanceCode={InstanceCode}, BusinessId={BusinessId}",
  11.             log.AuditType, log.InstanceCode, log.BusinessId);
  12.     }
  13. }
  14. // 使用示例
  15. await _auditService.LogAsync(new ApprovalAuditLog
  16. {
  17.     AuditType = "ApprovalStarted",
  18.     InstanceCode = instanceCode,
  19.     BusinessId = leaveRequest.Id,
  20.     OperatorId = userId,
  21.     Details = new { leaveType, days, reason }
  22. });
复制代码
最后一点内容

核心价值

通过本文的实践,我们成功实现了:
价值点实现方式收益移动化审批飞书 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功能扩展示例

  • 在飞书群聊中通过消息卡片直接查看审批详情
  • 通过机器人智能回复,引导用户填写审批表单
  • 将审批记录同步到飞书知识库,方便查阅
平台化

将审批集成能力抽象为中台服务,供企业内部所有系统统一调用:
  1. /// <summary>
  2. /// 统一审批服务中台
  3. /// </summary>
  4. public interface IApprovalCenterService
  5. {
  6.     /// <summary>
  7.     /// 统一发起审批(支持多平台)
  8.     /// </summary>
  9.     Task<string> CreateApprovalAsync(UnifiedApprovalRequest request);
  10.     /// <summary>
  11.     /// 查询审批状态
  12.     /// </summary>
  13.     Task GetStatusAsync(string instanceId);
  14.     /// <summary>
  15.     /// 审批统计报表
  16.     /// </summary>
  17.     Task GetStatisticsAsync(DateTime from, DateTime to);
  18. }
  19. // 多个系统统一调用
  20. await _approvalCenter.CreateApprovalAsync(new UnifiedApprovalRequest
  21. {
  22.     BusinessSystem = "HR",
  23.     BusinessType = "LeaveRequest",
  24.     BusinessId = leaveId,
  25.     Platform = "Feishu"  // 可切换到其他平台
  26. });
复制代码
结语
传统 .NET 系统无需推倒重来,通过合理的架构设计与飞书审批的深度集成,同样可以焕发新的活力。希望本文的实践能够为你的数字化转型之路提供有价值的参考。

让我们一起告别信息孤岛,拥抱现代化的协同办公体验!
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册