如何在.NET系统中快速实现飞书任务分配功能?

摘要:想象一下这样的场景:客户焦急地等待问题解决,而你的团队却在一堆邮件、Excel表格和零散的IM消息中手忙脚乱。这是不是很多企业每天都在上演的真实写照? 在数字化转型的浪潮中,我们不仅要让系统"能用&
想象一下这样的场景:客户焦急地等待问题解决,而你的团队却在一堆邮件、Excel表格和零散的IM消息中手忙脚乱。这是不是很多企业每天都在上演的真实写照? 在数字化转型的浪潮中,我们不仅要让系统"能用",更要让团队"好用"。飞书就像是协作世界的"超级英雄",它能让原本各自为战的业务系统手拉手,让信息像流水一样顺畅流动。 今天,就让我们一起踏上一段奇妙的旅程——借助Mud.Feishu这个强大的开源工具,为我们的.NET业务系统装上"协作翅膀",实现从传统的工单处理到现代化的全链路任务协同的华丽转身。 为什么我们的系统需要"协作升级"? 当孤岛遇上协作:那些年我们一起踩过的坑 还记得那个尴尬的下午吗?客户在电话那头焦急地询问:"我的问题解决得怎么样了?" 而你却在三个不同的系统之间来回切换,试图拼凑出完整的答案。 随着企业越来越大,业务越来越复杂,我们的传统系统就像一个个独立的"小岛",虽然每个小岛上都有宝藏(数据),但它们之间却没有桥梁: 信息都在各自的"保险柜"里:客服用一套系统,技术用另一套,产品还有自己的系统。想要看全局?那可真是个挑战! 沟通还在"石器时代":邮件一来一回可能要等几小时,重要消息可能淹没在收件箱里,IM聊天记录又容易被刷屏遗忘。 任务进展像"盲人摸象":谁在负责什么?进行到哪一步了?这些问题往往需要开会问一圈才能搞清楚。 跨部门协作像"跨越大海":技术说这是产品问题,产品说这是客服问题,客户的问题在部门之间"漂流",最后不了了之。 飞书API给传统系统装上"智能大脑" 如果说传统系统是"单机版",那么飞书API就是让它们连入"互联网"的魔法棒。飞书不仅仅是又一个办公软件,它的任务管理API就像是协作世界的"通用语言": 开放的"乐高积木":丰富的API接口就像乐高积木,你可以随心所欲地搭建适合自己的协作场景。 实时"心跳感应":基于WebSocket的推送机制让任务状态变化像心跳一样实时传递,告别"刷新查看"的等待时代。 移动"随身助手":无论你在咖啡厅还是在路上,手机上的任务提醒和更新都不会错过重要事项。 企业"安全卫士":完善的权限管理和数据加密,让敏感信息在开放协作的同时依然安全可靠。 为什么.NET是最佳选择 在众多技术栈中,.NET就像是那个稳重又有内涵的"理想伴侣",特别适合承担企业级集成的重任: 稳如泰山的"老司机":.NET平台经过多年历练,性能稳定可靠,就像一个经验丰富的老司机,能在复杂的业务环境中稳健前行。 微软"靠山"很给力:有微软这样的技术巨头长期支持,不用担心技术路线突然变卦,开发路上更有安全感。 与时俱进"新青年":从.NET 6.0开始,整个平台焕然一新,异步编程和并发处理能力让复杂场景的处理变得游刃有余。 工具链"豪华套餐":Visual Studio就像是一把"瑞士军刀",配合NuGet这个"百宝箱",开发效率自然节节攀升。 一个真实的应用场景:客服小王的一天 小王的"日常折磨":传统工单系统的困境 让我们跟随客服小王,看看她是如何在传统工单系统中"挣扎"的: 早上9点,小王刚坐下就收到了客户的紧急投诉。她迅速在系统中创建了工单,然后发送邮件给技术部门的老李。两个小时过去了,老李才回复说这个问题需要产品部门的小张确认... 这听起来是不是很熟悉?传统工单系统就像是一个"信息传递游戏",每个人都在等待,客户却在焦虑。让我们看看小王的工作流程图: graph TD A[客户提交工单] --> B[邮件通知客服] B --> C[手动分配处理人] C --> D[IM沟通协调] D --> E[Excel跟踪进度] E --> F[手动更新状态] F --> G[邮件回复客户] style B fill:#ffcccc style D fill:#ffcccc style E fill:#ffcccc style G fill:#ffcccc 看到那些红色的步骤了吗?每一步都是小王工作中的"痛点"。 三个让小王"头疼"的大难题 🌫️ 难题一:任务消失在"信息黑洞"里 小王每天都在玩"捉迷藏"游戏: 邮件森林:重要的任务分配邮件可能被淹没在收件箱的数百封邮件中 IM聊天刷屏:关键信息在群聊里被各种表情包和闲聊淹没 管理层的"望远镜":想要了解整体进度?那就得一个个去问,就像用望远镜看星星 客户的"猜谜游戏":客户打电话问进度,小王只能尴尬地说"我帮您问问" 🧩 难题二:跨部门协作像"拼图游戏" 工单在不同部门之间传递,就像是在玩拼图,但总少了几块: 系统"方言"不同:客服系统、技术系统、产品系统各自说各自的"语言" 信息"接力赛"中的掉棒:工单在传递过程中,重要的背景信息"不翼而飞" 责任"皮球游戏":这到底是技术问题还是产品问题?大家开始踢皮球 知识"孤岛":解决方案和个人经验都留在了各自的大脑里,无法形成团队财富 ⏰ 难题三:优先级管理像"无头苍蝇" 传统系统就像是没有导航的司机: SLA"定时炸弹":重要的工单快要到期了,但系统不会自动提醒 进度"盲人摸象":哪个工单会超时?只能凭感觉猜测 管理层"雾里看花":想要全局视图?抱歉,系统只支持单点查看 资源调配"拍脑袋":谁该处理什么任务?更多靠经验而非数据 小王的"逆袭":当工单系统遇上飞书 现在,让我们看看当飞书任务管理介入后,小王的工作发生了怎样的神奇变化: graph TD A[客户提交工单] --> B[✨ 自动创建飞书任务] B --> C[🎯 智能分配责任人] C --> D[📱 实时状态同步] D --> E[🤝 多方协同处理] E --> F[⏰ 自动预警提醒] F --> G[🎉 完成自动通知] style B fill:#ccffcc style C fill:#ccffcc style D fill:#ccffcc style E fill:#ccffcc style F fill:#ccffcc style G fill:#ccffcc 看到那些绿色步骤了吗?每一个都代表着小王工作效率的飞跃提升! 小王的"幸福感提升清单": 🚀 从手工到自动化:原来要人工操作的步骤,现在系统自动搞定,小王终于有时间喝杯咖啡了 🔍 从黑盒到透明:任务进展一目了然,管理层再也不用追着她问进度,客户也能自己查看状态 🌉 从孤岛到通途:统一的协作平台让跨部门合作变得像"左邻右舍"一样自然 😊 从被动到主动:客户收到实时更新通知,满意度直线上升,小王的KPI也跟着水涨船高 搭积木的艺术:构建我们的协作桥梁 先看看我们的"家底":现有系统是什么样的 在企业级应用的世界里,.NET系统就像是精心设计的"建筑",主要有两种常见的"建筑风格": 经典的处理流程 graph TB subgraph "表现层 Presentation Layer" A[ASP.NET Core Web API] B[前端应用 React/Vue] end subgraph "业务逻辑层 Business Logic Layer" C[工单管理服务] D[客户管理服务] E[通知服务] end subgraph "数据访问层 Data Access Layer" F[Entity Framework Core] G[SQL Server数据库] end A --> C A --> D B --> A C --> F D --> F E --> C F --> G 微服务的现代布局 graph TB subgraph "API网关" A[Ocelot/Gateway] end subgraph "微服务集群" B[工单服务] C[用户服务] D[通知服务] E[订单服务] end subgraph "基础设施" F[Redis缓存] G[RabbitMQ消息队列] H[SQL Server集群] end A --> B A --> C A --> D A --> E B --> F B --> G C --> F D --> F E --> H 使用飞书后优化的系统流程 现在到了最激动人心的部分!我们要在现有系统和飞书之间搭建一座"智能桥梁"。这个桥不是用砖块,而是用代码和智慧搭建的: graph TB subgraph "业务应用层" A[工单管理系统] B[客户支持系统] C[项目管理系统] end subgraph "飞书集成适配层" D[Mud.Feishu HTTP API客户端] E[Mud.Feishu WebSocket客户端] F[Mud.Feishu Webhook处理器] end subgraph "同步服务层" G[任务同步服务] H[事件路由服务] I[状态同步服务] end subgraph "监控与回退机制" J[健康监控] K[重试机制] L[降级策略] end subgraph "飞书平台" M[飞书任务API] N[飞书WebSocket] O[飞书事件订阅] end A --> G B --> G C --> G G --> D G --> E G --> F D --> M E --> N F --> O G --> H H --> I G --> J J --> K K --> L 适配层组件交互流程 sequenceDiagram participant B as 业务系统 participant S as 同步服务 participant A as 适配层 participant F as 飞书平台 participant W as WebSocket/Webhook Note over B,W: 双向数据同步流程 B->>S: 创建/更新工单 S->>A: 调用任务API A->>F: HTTP API调用 F-->>A: 返回结果 A-->>S: 同步完成 S-->>B: 更新关联ID Note over F,W: 实时事件推送 F->>W: 任务状态变更 W->>A: 事件通知 A->>S: 处理业务逻辑 S->>B: 更新工单状态 适配层(Mud.Feishu封装) Mud.Feishu提供了完整的飞书API封装: HTTP API客户端:基于IFeishuTenantV2Task接口,提供完整的任务管理能力 WebSocket客户端:实时事件订阅,支持自动重连和心跳检测 Webhook处理器:HTTP回调事件处理,支持事件路由和中间件模式 Mud.Feishu源码仓库:Gitee,Github // 核心接口定义示例 public interface IFeishuTaskAdapter { Task<string> CreateTaskAsync(CreateTaskRequest request); Task<TaskInfo> GetTaskAsync(string taskGuid); Task<bool> UpdateTaskAsync(string taskGuid, UpdateTaskRequest request); Task<bool> DeleteTaskAsync(string taskGuid); } 同步服务(双向/事件驱动) 同步服务负责业务系统与飞书之间的数据同步: public class TaskSyncService { private readonly IFeishuTaskAdapter _feishuAdapter; private readonly ITicketRepository _ticketRepository; private readonly ILogger<TaskSyncService> _logger; // 双向同步逻辑 public async Task SyncTicketToTaskAsync(string ticketId) { var ticket = await _ticketRepository.GetByIdAsync(ticketId); if (ticket?.FeishuTaskId == null) { var taskRequest = MapTicketToTask(ticket); var result = await _feishuAdapter.CreateTaskAsync(taskRequest); ticket.FeishuTaskId = result; await _ticketRepository.UpdateAsync(ticket); } } } 监控与回退机制 确保集成系统的稳定性和可靠性: 健康监控:定期检查API可用性和连接状态 重试机制:网络异常时自动重试,支持指数退避策略 降级策略:飞书服务不可用时,系统可降级到本地任务管理 数据同步策略 实时同步(关键状态变更) 通过WebSocket和Webhook实现关键状态的实时同步: graph LR subgraph "实时事件流" A[飞书任务状态变更] --> B[WebSocket/Webhook] B --> C[事件路由器] C --> D[状态同步服务] D --> E[业务系统] end subgraph "批量补偿流" F[定时任务触发] --> G[数据对账服务] G --> H[差异检测] H --> I[数据修复] I --> E end // WebSocket事件处理示例 public class TaskEventHandler : IFeishuEventHandler { public string SupportedEventType => "task.status_changed"; public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default) { switch (eventData.EventType) { case "task.status_changed": await HandleTaskStatusChanged(eventData); break; case "task.assignee_updated": await HandleAssigneeUpdated(eventData); break; } } } 批量补偿(定时对账) 定时执行批量对账,确保数据一致性: public class TaskReconciliationService : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { await ReconcileTasksAsync(); await Task.Delay(TimeSpan.FromHours(1), stoppingToken); } } } 安全方案 OAuth 2.0流程 基于Mud.Feishu的令牌管理机制: // 配置示例 builder.Services.AddFeishuServices() .ConfigureFrom(builder.Configuration) .AddTaskApi() .AddTokenManagers() .Build(); API密钥管理 应用密钥安全存储(环境变量/密钥管理服务) 访问令牌自动刷新和缓存 权限最小化原则,按需申请API权限 IP白名单 在飞书开放平台配置服务端IP白名单,增强安全性。 动手时间:一步步打造你的飞书集成模块 1. 准备工作:让一切就绪 第一步:和飞书"握手"——创建应用 让我们先到飞书开放平台这个"游乐场"注册我们的"入场券": 打开大门:访问 https://open.feishu.cn/,就像走进一个充满可能性的新世界 领取身份卡:创建企业自建应用 给它起个响亮的名字:"客户支持工单集成助手" 写个自我介绍:"我是来帮大家告别工单混乱的小能手" 申请通行证:配置应用权限 任务管理全权限:读取、创建、更新、删除(就像给了一把万能钥匙) 任务清单查看权:知道任务放在哪个"房间" 用户基本信息权:认识团队里的每个"小伙伴" 事件订阅权:能够监听任务世界的"风吹草动" 设置专属热线:配置事件订阅 提供你的"电话号码"(请求网址):https://your-domain.com/api/feishu/webhook 准备"暗号"(验证Token):只有你懂的验证字符串 配置"保险箱"(数据加密密钥):用AES-256保护敏感信息 第二步:搭建我们的"开发工作室" 现在让我们创建一个干净的.NET项目,并邀请我们的"得力助手"们加入: <Project Sdk="Microsoft.NET.Sdk.Web"> <PropertyGroup> <TargetFramework>net10.0</TargetFramework> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="Mud.Feishu" Version="1.0.9" /> <PackageReference Include="Mud.Feishu.WebSocket" Version="1.0.9" /> <PackageReference Include="Mud.Feishu.Webhook" Version="1.0.9" /> <PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0" /> </ItemGroup> </Project> 基础配置文件 { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "Feishu": { "AppId": "cli_xxxxxxxxxxxxxxxx", "AppSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "BaseUrl": "https://open.feishu.cn", "TimeOut": "30", "RetryCount": 3 }, "Feishu:WebSocket": { "AutoReconnect": true, "MaxReconnectAttempts": 5, "ReconnectDelayMs": 5000, "HeartbeatIntervalMs": 30000, "EnableLogging": true }, "FeishuWebhook": { "RoutePrefix": "api/feishu/webhook", "VerificationToken": "your_verification_token", "EncryptKey": "your_encrypt_key", "EnableRequestLogging": true }, "ConnectionStrings": { "DefaultConnection": "Server=.;Database=TicketSystem;Trusted_Connection=true;" } } 2. 核心魔法:让代码活起来 认证服务:让系统"记住"我是谁 想象一下,每次调用飞书API都要重新登录是多么繁琐。幸运的是,Mud.Feishu已经为我们准备了一个"智能门卫",它会自动处理认证和刷新token这些烦人的事情。我们只需要告诉它"密码"在哪里就行: graph TB subgraph "服务注册架构" A[.NET Host Builder] --> B[飞书API服务] A --> C[WebSocket服务] A --> D[Webhook服务] A --> E[自定义业务服务] B --> F[Token管理器] B --> G[任务API客户端] C --> H[WebSocket管理器] C --> I[事件处理器1] C --> J[事件处理器2] D --> K[Webhook路由] D --> L[事件处理器] E --> M[任务同步服务] E --> N[通知服务] end // Program.cs - 服务注册 var builder = WebApplication.CreateBuilder(args); // 注册飞书服务 builder.Services.AddFeishuServices() .ConfigureFrom(builder.Configuration) .AddTaskApi() .AddTokenManagers() .Build(); // 注册WebSocket服务 builder.Services.AddFeishuWebSocketServiceBuilder() .ConfigureFrom(builder.Configuration) .UseMultiHandler() .AddHandler<TaskEventHandler>() .AddHandler<TicketEventHandler>() .Build(); // 注册Webhook服务 builder.Services.AddFeishuWebhookServiceBuilder(builder.Configuration) .AddHandler<TaskWebhookHandler>() .Build(); // 自定义服务注册 builder.Services.AddScoped<IFeishuTaskService, FeishuTaskService>(); builder.Services.AddScoped<ITicketSyncService, TicketSyncService>(); 任务API客户端:开箱即用的飞书 .net SDK public interface IFeishuTaskService { Task<string> CreateTaskFromTicketAsync(Ticket ticket); Task<bool> UpdateTaskAsync(string taskGuid, UpdateTaskRequest request); Task<TaskInfo?> GetTaskAsync(string taskGuid); Task<bool> DeleteTaskAsync(string taskGuid); Task<bool> AddTaskMemberAsync(string taskGuid, string userId, string role); Task<List<TaskInfo>> GetTasksByProjectAsync(string projectKey); } public class FeishuTaskService : IFeishuTaskService { private readonly IFeishuTenantV2Task _taskApi; private readonly ILogger<FeishuTaskService> _logger; private readonly IMapper _mapper; public FeishuTaskService( IFeishuTenantV2Task taskApi, ILogger<FeishuTaskService> logger, IMapper mapper) { _taskApi = taskApi ?? throw new ArgumentNullException(nameof(taskApi)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); } public async Task<string> CreateTaskFromTicketAsync(Ticket ticket) { try { var createRequest = _mapper.Map<CreateTaskRequest>(ticket); // 设置任务清单 createRequest.Tasklists = new[] { new TaskInTaskListInfo { TasklistGuid = GetTaskListByPriority(ticket.Priority), CustomFields = GetCustomFieldsForTicket(ticket) } }; // 设置任务成员 var assignees = await GetAssigneesForTicket(ticket); createRequest.Members = assignees.Select(u => new TaskMemberInfo { UserId = u.FeishuUserId, UserType = UserType.User, TaskRole = TaskRole.Assignee }).ToArray(); // 设置截止时间 if (ticket.DueDate.HasValue) { createRequest.Due = new TaskTime { Timestamp = ((DateTimeOffset)ticket.DueDate.Value).ToUnixTimeMilliseconds().ToString() }; } // 设置提醒 if (ticket.Priority == TicketPriority.High) { createRequest.Reminders = new[] { new TaskReminder { MinutesBefore = 60, // 1小时前提醒 ReminderType = ReminderType.Push } }; } var result = await _taskApi.CreateTaskAsync(createRequest); if (result?.Code == 0 && result.Data != null) { _logger.LogInformation("成功创建飞书任务,工单ID: {TicketId}, 任务ID: {TaskGuid}", ticket.Id, result.Data.Task.Guid); return result.Data.Task.Guid; } _logger.LogError("创建飞书任务失败,工单ID: {TicketId}, 错误: {Error}", ticket.Id, result?.Msg); throw new FeishuException($"创建飞书任务失败: {result?.Msg}"); } catch (Exception ex) { _logger.LogError(ex, "创建飞书任务时发生异常,工单ID: {TicketId}", ticket.Id); throw; } } public async Task<bool> UpdateTaskAsync(string taskGuid, UpdateTaskRequest request) { try { var result = await _taskApi.UpdateTaskAsync(taskGuid, request); var success = result?.Code == 0; if (success) { _logger.LogInformation("成功更新飞书任务,任务ID: {TaskGuid}", taskGuid); } else { _logger.LogWarning("更新飞书任务失败,任务ID: {TaskGuid}, 错误: {Error}", taskGuid, result?.Msg); } return success; } catch (Exception ex) { _logger.LogError(ex, "更新飞书任务时发生异常,任务ID: {TaskGuid}", taskGuid); return false; } } public async Task<TaskInfo?> GetTaskAsync(string taskGuid) { try { var result = await _taskApi.GetTaskByIdAsync(taskGuid); return result?.Code == 0 ? result.Data?.Task : null; } catch (Exception ex) { _logger.LogError(ex, "获取飞书任务详情时发生异常,任务ID: {TaskGuid}", taskGuid); return null; } } public async Task<bool> DeleteTaskAsync(string taskGuid) { try { var result = await _taskApi.DeleteTaskByIdAsync(taskGuid); var success = result?.Code == 0; if (success) { _logger.LogInformation("成功删除飞书任务,任务ID: {TaskGuid}", taskGuid); } return success; } catch (Exception ex) { _logger.LogError(ex, "删除飞书任务时发生异常,任务ID: {TaskGuid}", taskGuid); return false; } } private string GetTaskListByPriority(TicketPriority priority) { return priority switch { TicketPriority.High => "high_priority_tasklist_guid", TicketPriority.Medium => "medium_priority_tasklist_guid", TicketPriority.Low => "low_priority_tasklist_guid", _ => "default_tasklist_guid" }; } private CustomFieldValue[] GetCustomFieldsForTicket(Ticket ticket) { return new[] { new CustomFieldValue { FieldId = "ticket_id_field", TextValue = ticket.Id }, new CustomFieldValue { FieldId = "customer_field", TextValue = ticket.CustomerName }, new CustomFieldValue { FieldId = "project_field", SingleSelectValue = new EnumValue { Id = ticket.ProjectId.ToString() } } }; } } WebSocket处理器:飞书事件"快递员" public class TaskEventHandler : IFeishuEventHandler { private readonly IFeishuTaskService _taskService; private readonly ITicketSyncService _syncService; private readonly ILogger<TaskEventHandler> _logger; public TaskEventHandler( IFeishuTaskService taskService, ITicketSyncService syncService, ILogger<TaskEventHandler> logger) { _taskService = taskService; _syncService = syncService; _logger = logger; } public string SupportedEventType => "task.status_changed"; public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default) { try { switch (eventData.EventType) { case "task.status_changed": await HandleTaskStatusChangedAsync(eventData); break; case "task.assignee_updated": await HandleAssigneeUpdatedAsync(eventData); break; case "task.comment_added": await HandleCommentAddedAsync(eventData); break; default: _logger.LogDebug("收到未处理的任务事件类型: {EventType}", eventData.EventType); break; } } catch (Exception ex) { _logger.LogError(ex, "处理飞书任务事件时发生异常,事件类型: {EventType}", eventData.EventType); } } private async Task HandleTaskStatusChangedAsync(EventData eventData) { var taskEvent = JsonSerializer.Deserialize<TaskStatusChangedEvent>(eventData.Data.ToString()); _logger.LogInformation("任务状态变更,任务ID: {TaskGuid}, 新状态: {Status}", taskEvent.Task.Guid, taskEvent.Task.Status); // 同步到工单系统 await _syncService.SyncTaskStatusToTicketAsync(taskEvent.Task.Guid, taskEvent.Task.Status); // 发送通知 if (taskEvent.Task.Status == "done") { await NotifyTaskCompletedAsync(taskEvent.Task); } } private async Task HandleAssigneeUpdatedAsync(EventData eventData) { var taskEvent = JsonSerializer.Deserialize<AssigneeUpdatedEvent>(eventData.Data.ToString()); _logger.LogInformation("任务负责人更新,任务ID: {TaskGuid}", taskEvent.Task.Guid); // 同步分配人信息到工单 await _syncService.SyncTaskAssigneeToTicketAsync(taskEvent.Task.Guid, taskEvent.Task.Members); } private async Task NotifyTaskCompletedAsync(TaskInfo task) { // 发送飞书通知到相关群组 // 这里可以调用飞书消息API发送通知 } } 3. 业务领域适配 对象映射设计:工单 ↔ 飞书任务字段对照表 graph LR subgraph "工单系统" A[Ticket] --> B[Id] A --> C[Title] A --> D[Description] A --> E[Priority] A --> F[Assignee] A --> G[DueDate] end subgraph "映射转换" H[AutoMapper] I[业务规则引擎] J[数据转换器] end subgraph "飞书任务" K[CreateTaskRequest] --> L[Summary] K --> M[Description] K --> N[IsMilestone] K --> O[Members] K --> P[Due] K --> Q[Tasklists] end B --> H C --> H D --> I E --> I F --> J G --> J H --> L I --> N J --> O J --> P style H fill:#e1f5fe style I fill:#e8f5e8 style J fill:#fff3e0 使用AutoMapper进行对象映射配置: public class FeishuMappingProfile : Profile { public FeishuMappingProfile() { // 工单 -> 飞书任务创建请求 CreateMap<Ticket, CreateTaskRequest>() .ForMember(dest => dest.Summary, opt => opt.MapFrom(src => $"[工单#{src.Id}] {src.Title}")) .ForMember(dest => dest.Description, opt => opt.MapFrom(src => BuildTaskDescription(src))) .ForMember(dest => dest.Start, opt => opt.MapFrom(src => src.CreatedDate.HasValue ? new TasksStartTime { Timestamp = ((DateTimeOffset)src.CreatedDate.Value).ToUnixTimeMilliseconds().ToString() } : null)) .ForMember(dest => dest.Mode, opt => opt.MapFrom(src => src.AssigneeCount > 1 ? 2 : 1)) // 或签/会签 .ForMember(dest => dest.IsMilestone, opt => opt.MapFrom(src => src.Priority == TicketPriority.High)); // 飞书任务 -> 工单状态更新 CreateMap<TaskInfo, TicketStatusUpdate>() .ForMember(dest => dest.TicketId, opt => opt.MapFrom(src => ExtractTicketId(src.Extra))) .ForMember(dest => dest.Status, opt => opt.MapFrom(src => MapTaskStatusToTicketStatus(src.Status))) .ForMember(dest => dest.CompletedAt, opt => opt.MapFrom(src => ParseCompletedAt(src.CompletedAt))); } private string BuildTaskDescription(Ticket ticket) { var description = new StringBuilder(); description.AppendLine($"**客户:** {ticket.CustomerName}"); description.AppendLine($"**项目:** {ticket.ProjectName}"); description.AppendLine($"**优先级:** {ticket.Priority}"); description.AppendLine(); description.AppendLine("**问题描述:**"); description.AppendLine(ticket.Description); if (!string.IsNullOrEmpty(ticket.Attachments)) { description.AppendLine(); description.AppendLine("**附件:**"); description.AppendLine(ticket.Attachments); } return description.ToString(); } private TicketStatus MapTaskStatusToTicketStatus(string? taskStatus) { return taskStatus switch { "todo" => TicketStatus.InProgress, "done" => TicketStatus.Resolved, _ => TicketStatus.Open }; } } 同步策略实现 public class TicketSyncService : ITicketSyncService { private readonly IFeishuTaskService _feishuTaskService; private readonly ITicketRepository _ticketRepository; private readonly INotificationService _notificationService; private readonly ILogger<TicketSyncService> _logger; // 实时事件监听(工单创建/更新) public async Task HandleTicketCreatedAsync(Ticket ticket) { try { // 只为高优先级工单创建飞书任务 if (ticket.Priority >= TicketPriority.Medium) { var taskGuid = await _feishuTaskService.CreateTaskFromTicketAsync(ticket); ticket.FeishuTaskId = taskGuid; await _ticketRepository.UpdateAsync(ticket); _logger.LogInformation("为新工单创建飞书任务,工单ID: {TicketId}, 任务ID: {TaskGuid}", ticket.Id, taskGuid); // 发送通知 await _notificationService.NotifyTaskCreatedAsync(ticket, taskGuid); } } catch (Exception ex) { _logger.LogError(ex, "处理工单创建事件时发生异常,工单ID: {TicketId}", ticket.Id); } } // 定时全量同步(防丢失) public async Task ReconcileTasksAsync() { _logger.LogInformation("开始执行任务对账..."); try { // 获取所有关联了飞书任务的工单 var ticketsWithTasks = await _ticketRepository.GetTicketsWithFeishuTasksAsync(); foreach (var ticket in ticketsWithTasks) { if (string.IsNullOrEmpty(ticket.FeishuTaskId)) continue; // 检查飞书任务是否存在且状态一致 var task = await _feishuTaskService.GetTaskAsync(ticket.FeishuTaskId); if (task == null) { _logger.LogWarning("飞书任务不存在,工单ID: {TicketId}, 任务ID: {TaskGuid}", ticket.Id, ticket.FeishuTaskId); // 重新创建任务 var newTaskGuid = await _feishuTaskService.CreateTaskFromTicketAsync(ticket); ticket.FeishuTaskId = newTaskGuid; await _ticketRepository.UpdateAsync(ticket); } else if (MapTaskStatusToTicketStatus(task.Status) != ticket.Status) { // 状态不一致,同步飞书任务状态到工单 await SyncTaskStatusToTicketAsync(ticket.FeishuTaskId, task.Status); } } _logger.LogInformation("任务对账完成"); } catch (Exception ex) { _logger.LogError(ex, "执行任务对账时发生异常"); } } public async Task SyncTaskStatusToTicketAsync(string taskGuid, string taskStatus) { try { var ticketId = ExtractTicketIdFromTask(taskGuid); if (string.IsNullOrEmpty(ticketId)) return; var ticket = await _ticketRepository.GetByIdAsync(ticketId); if (ticket == null) return; var newStatus = MapTaskStatusToTicketStatus(taskStatus); if (ticket.Status != newStatus) { ticket.Status = newStatus; ticket.StatusUpdatedBy = "FeishuSync"; ticket.StatusUpdatedAt = DateTime.UtcNow; await _ticketRepository.UpdateAsync(ticket); _logger.LogInformation("同步飞书任务状态到工单,任务ID: {TaskGuid}, 工单ID: {TicketId}, 新状态: {Status}", taskGuid, ticketId, newStatus); } } catch (Exception ex) { _logger.LogError(ex, "同步飞书任务状态到工单时发生异常,任务ID: {TaskGuid}", taskGuid); } } } 容错设计:异常分类、重试策略、死信队列 public class FaultTolerantTaskService : IFeishuTaskService { private readonly IFeishuTaskService _innerService; private readonly IAsyncPolicy _retryPolicy; private readonly ILogger<FaultTolerantTaskService> _logger; private readonly IDeadLetterQueue _deadLetterQueue; public FaultTolerantTaskService( IFeishuTaskService innerService, ILogger<FaultTolerantTaskService> logger, IDeadLetterQueue deadLetterQueue) { _innerService = innerService; _logger = logger; _deadLetterQueue = deadLetterQueue; // 配置重试策略 _retryPolicy = Policy .Handle<FeishuApiException>(ex => ex.IsTransient) .Or<HttpRequestException>() .WaitAndRetryAsync( retryCount: 3, sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), onRetry: (outcome, timespan, retryAttempt, context) => { _logger.LogWarning("操作失败,准备第{RetryAttempt}次重试,延迟{Delay}ms", retryAttempt, timespan.TotalMilliseconds); }); } public async Task<string> CreateTaskFromTicketAsync(Ticket ticket) { try { return await _retryPolicy.ExecuteAsync(() => _innerService.CreateTaskFromTicketAsync(ticket)); } catch (Exception ex) { // 非临时性异常或重试次数耗尽,加入死信队列 await _deadLetterQueue.EnqueueAsync(new DeadLetterMessage { Operation = "CreateTask", Data = ticket, Exception = ex, Timestamp = DateTime.UtcNow }); _logger.LogError(ex, "创建飞书任务失败并加入死信队列,工单ID: {TicketId}", ticket.Id); throw; } } } 4. 关键代码片段 OAuth 2.0授权流程实现(含刷新逻辑) Mud.Feishu已经内置了完整的OAuth流程,我们只需要配置: // appsettings.json中的配置 { "Feishu": { "AppId": "cli_xxxxxxxxxxxxxxxx", "AppSecret": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } } // 服务注册 builder.Services.AddFeishuApiService(builder.Configuration); 创建任务并关联工单的完整示例 [ApiController] [Route("api/[controller]")] public class TicketController : ControllerBase { private readonly ITicketService _ticketService; private readonly IFeishuTaskService _feishuTaskService; private readonly ITicketSyncService _syncService; [HttpPost] public async Task<IActionResult> CreateTicket([FromBody] CreateTicketRequest request) { try { // 1. 创建工单 var ticket = await _ticketService.CreateTicketAsync(request); // 2. 如果是高优先级工单,自动创建飞书任务 if (ticket.Priority >= TicketPriority.Medium) { var taskGuid = await _feishuTaskService.CreateTaskFromTicketAsync(ticket); ticket.FeishuTaskId = taskGuid; await _ticketService.UpdateTicketAsync(ticket); } return Ok(new { TicketId = ticket.Id, FeishuTaskId = ticket.FeishuTaskId }); } catch (Exception ex) { _logger.LogError(ex, "创建工单时发生异常"); return StatusCode(500, new { Error = "创建工单失败" }); } } [HttpPut("{id}/status")] public async Task<IActionResult> UpdateTicketStatus(string id, [FromBody] UpdateStatusRequest request) { try { var ticket = await _ticketService.GetTicketAsync(id); if (ticket == null) return NotFound(); // 更新工单状态 ticket.Status = request.Status; await _ticketService.UpdateTicketAsync(ticket); // 同步到飞书任务 if (!string.IsNullOrEmpty(ticket.FeishuTaskId)) { var updateRequest = new UpdateTaskRequest { Status = MapTicketStatusToTaskStatus(request.Status), CompletedAt = request.Status == TicketStatus.Resolved ? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString() : null }; await _feishuTaskService.UpdateTaskAsync(ticket.FeishuTaskId, updateRequest); } return Ok(); } catch (Exception ex) { _logger.LogError(ex, "更新工单状态时发生异常,工单ID: {TicketId}", id); return StatusCode(500, new { Error = "更新状态失败" }); } } } WebSocket处理器与业务逻辑解耦设计 public class FeishuWebSocketBackgroundService : BackgroundService { private readonly IFeishuWebSocketManager _webSocketManager; private readonly IEnumerable<IFeishuWebSocketEventHandler> _handlers; private readonly ILogger<FeishuWebSocketBackgroundService> _logger; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // 启动WebSocket连接 await _webSocketManager.StartAsync(stoppingToken); // 订阅事件 _webSocketManager.MessageReceived += async (sender, e) => { await HandleMessageAsync(e.Message); }; _webSocketManager.Error += async (sender, e) => { _logger.LogError(e.Exception, "WebSocket连接发生错误"); await HandleErrorAsync(e.Exception); }; _webSocketManager.Disconnected += async (sender, e) => { _logger.LogWarning("WebSocket连接断开,原因: {Reason}", e.CloseStatusDescription); await HandleDisconnectionAsync(); }; // 保持服务运行 while (!stoppingToken.IsCancellationRequested) { await Task.Delay(1000, stoppingToken); } } private async Task HandleMessageAsync(FeishuWebSocketMessage message) { var tasks = _handlers.Select(handler => SafeHandleAsync(handler, message)); await Task.WhenAll(tasks); } private async Task SafeHandleAsync(IFeishuWebSocketEventHandler handler, FeishuWebSocketMessage message) { try { await handler.HandleAsync(message); } catch (Exception ex) { _logger.LogError(ex, "事件处理器处理消息时发生异常,处理器类型: {HandlerType}", handler.GetType().Name); } } } 应用场景落地详解 🔧 场景一:让工单"活"起来——自动飞书任务生成 工单到任务的"变身"过程 sequenceDiagram participant C as 客户/系统 participant T as 工单系统 participant E as 事件总线 participant S as 同步服务 participant F as 飞书API participant N as 通知服务 Note over C,N: 工单自动生成飞书任务流程 C->>T: 提交工单 T->>T: 验证工单信息 T->>T: 保存工单到数据库 alt 高优先级工单 T->>E: 发布工单创建事件 E->>S: 订阅事件处理 S->>S: 检查是否需要创建任务 S->>F: 调用飞书任务API F-->>S: 返回任务ID S->>T: 更新工单关联ID S->>N: 发送创建通知 end alt 客户标记紧急 T->>E: 发布紧急标记事件 E->>S: 处理紧急事件 S->>F: 创建或更新任务优先级 S->>N: 发送紧急通知 end alt SLA预警 T->>E: 发布SLA预警事件 E->>S: 处理SLA预警 S->>F: 创建任务并设置提醒 end 什么时候该"变身"?——触发时机揭秘 就像超级英雄有自己的"变身"条件,我们的工单也需要在合适的时机才生成飞书任务: public class TaskCreationTrigger { // 1. 高优先级工单创建时自动触发 public async Task HandleTicketCreatedAsync(TicketCreatedEvent @event) { if (@event.Ticket.Priority >= TicketPriority.High) { await CreateFeishuTaskAsync(@event.Ticket); } } // 2. 客户标记紧急时触发 public async Task HandleTicketMarkedUrgentAsync(TicketMarkedUrgentEvent @event) { // 如果还没有飞书任务,立即创建 if (string.IsNullOrEmpty(@event.Ticket.FeishuTaskId)) { await CreateFeishuTaskAsync(@event.Ticket); } else { // 更新现有任务优先级 await UpdateTaskPriorityAsync(@event.Ticket.FeishuTaskId, TicketPriority.High); } } // 3. SLA即将超时预警时触发 public async Task HandleSLAWarningAsync(SLAWarningEvent @event) { if (@event.Ticket.Priority >= TicketPriority.Medium) { await CreateFeishuTaskAsync(@event.Ticket); } } } 实现步骤 第一步:监听工单领域事件 [ApiController] [Route("api/tickets")] public class TicketsController : ControllerBase { private readonly ITicketService _ticketService; private readonly IEventBus _eventBus; private readonly ILogger<TicketsController> _logger; [HttpPost] public async Task<IActionResult> CreateTicket([FromBody] CreateTicketRequest request) { var ticket = await _ticketService.CreateTicketAsync(request); // 发布领域事件 await _eventBus.PublishAsync(new TicketCreatedEvent { Ticket = ticket }); return CreatedAtAction(nameof(GetTicket), new { id = ticket.Id }, ticket); } [HttpPut("{id}/urgent")] public async Task<IActionResult> MarkAsUrgent(string id) { var ticket = await _ticketService.MarkAsUrgentAsync(id); // 发布紧急标记事件 await _eventBus.PublishAsync(new TicketMarkedUrgentEvent { Ticket = ticket }); return Ok(ticket); } } 第二步:构建飞书任务 public class FeishuTaskBuilder { private readonly IUserService _userService; private readonly IProjectService _projectService; public async Task<CreateTaskRequest> BuildTaskAsync(Ticket ticket) { var task = new CreateTaskRequest { Summary = $"[工单#{ticket.Id}] {ticket.Title}", Description = await BuildDescriptionAsync(ticket), Extra = JsonSerializer.Serialize(new { TicketId = ticket.Id }), ClientToken = Guid.NewGuid().ToString() // 幂等Token }; // 设置截止时间 if (ticket.SlaDeadline.HasValue) { task.Due = new TaskTime { Timestamp = ((DateTimeOffset)ticket.SlaDeadline.Value).ToUnixTimeMilliseconds().ToString() }; } // 设置开始时间 task.Start = new TasksStartTime { Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString() }; // 设置任务模式 task.Mode = ticket.Assignees?.Count > 1 ? 2 : 1; // 多人时用或签 // 设置里程碑标识 task.IsMilestone = ticket.Priority == TicketPriority.High; // 设置提醒规则 task.Reminders = BuildReminders(ticket); // 设置任务成员 task.Members = await BuildTaskMembersAsync(ticket); // 设置所属清单 task.Tasklists = new[] { new TaskInTaskListInfo { TasklistGuid = await GetTaskListGuidAsync(ticket), CustomFields = await BuildCustomFieldsAsync(ticket) } }; return task; } private async Task<string> BuildDescriptionAsync(Ticket ticket) { var description = new StringBuilder(); // 基本信息 description.AppendLine($"**客户:** {ticket.CustomerName}"); description.AppendLine($"**联系电话:** {ticket.CustomerPhone}"); description.AppendLine($"**项目:** {ticket.ProjectName}"); description.AppendLine($"**优先级:** {GetPriorityDisplay(ticket.Priority)}"); description.AppendLine($"**创建时间:** {ticket.CreatedAt:yyyy-MM-dd HH:mm:ss}"); if (ticket.SlaDeadline.HasValue) { description.AppendLine($"**SLA截止时间:** {ticket.SlaDeadline:yyyy-MM-dd HH:mm:ss}"); } description.AppendLine(); // 问题描述 description.AppendLine("## 问题描述"); description.AppendLine(ticket.Description); // 附件信息 if (!string.IsNullOrEmpty(ticket.Attachments)) { description.AppendLine(); description.AppendLine("## 相关附件"); description.AppendLine(ticket.Attachments); } // 处理历史 if (ticket.History?.Any() == true) { description.AppendLine(); description.AppendLine("## 处理历史"); foreach (var history in ticket.History.Take(3)) { description.AppendLine($"- {history.CreatedAt:MM-dd HH:mm} {history.Operator}:{history.Action}"); } } return description.ToString(); } private TaskReminder[] BuildReminders(Ticket ticket) { var reminders = new List<TaskReminder>(); // 高优先级任务设置多个提醒 if (ticket.Priority == TicketPriority.High) { reminders.Add(new TaskReminder { MinutesBefore = 120, // 2小时前提醒 ReminderType = ReminderType.Push }); reminders.Add(new TaskReminder { MinutesBefore = 60, // 1小时前提醒 ReminderType = ReminderType.Push }); } else if (ticket.Priority == TicketPriority.Medium) { reminders.Add(new TaskReminder { MinutesBefore = 240, // 4小时前提醒 ReminderType = ReminderType.Push }); } return reminders.ToArray(); } private async Task<TaskMemberInfo[]> BuildTaskMembersAsync(Ticket ticket) { var members = new List<TaskMemberInfo>(); // 添加负责人 if (!string.IsNullOrEmpty(ticket.AssigneeId)) { var assignee = await _userService.GetByIdAsync(ticket.AssigneeId); if (assignee?.FeishuUserId != null) { members.Add(new TaskMemberInfo { UserId = assignee.FeishuUserId, UserType = UserType.User, TaskRole = TaskRole.Assignee }); } } // 添加关注人 if (ticket.Watchers?.Any() == true) { foreach (var watcherId in ticket.Watchers) { var watcher = await _userService.GetByIdAsync(watcherId); if (watcher?.FeishuUserId != null) { members.Add(new TaskMemberInfo { UserId = watcher.FeishuUserId, UserType = UserType.User, TaskRole = TaskRole.Follower }); } } } // 添加项目经理作为关注人 var project = await _projectService.GetByIdAsync(ticket.ProjectId); if (project?.ManagerFeishuUserId != null) { members.Add(new TaskMemberInfo { UserId = project.ManagerFeishuUserId, UserType = UserType.User, TaskRole = TaskRole.Follower }); } return members.ToArray(); } } 第三步:调用API并保存关联ID public class TaskCreationService { private readonly IFeishuTaskService _feishuTaskService; private readonly ITicketRepository _ticketRepository; private readonly ILogger<TaskCreationService> _logger; public async Task<string> CreateFeishuTaskAsync(Ticket ticket) { try { // 检查是否已存在任务 if (!string.IsNullOrEmpty(ticket.FeishuTaskId)) { _logger.LogWarning("工单已存在飞书任务,工单ID: {TicketId}, 任务ID: {TaskGuid}", ticket.Id, ticket.FeishuTaskId); return ticket.FeishuTaskId; } // 创建飞书任务 var taskRequest = await _taskBuilder.BuildTaskAsync(ticket); var taskGuid = await _feishuTaskService.CreateTaskAsync(taskRequest); // 保存关联关系 ticket.FeishuTaskId = taskGuid; ticket.FeishuTaskCreatedAt = DateTime.UtcNow; await _ticketRepository.UpdateAsync(ticket); _logger.LogInformation("成功为工单创建飞书任务,工单ID: {TicketId}, 任务ID: {TaskGuid}", ticket.Id, taskGuid); return taskGuid; } catch (Exception ex) { _logger.LogError(ex, "为工单创建飞书任务失败,工单ID: {TicketId}", ticket.Id); throw; } } } 第四步:发送内部通知 public class NotificationService { private readonly IFeishuMessageService _messageService; private readonly IProjectService _projectService; public async Task NotifyTaskCreatedAsync(Ticket ticket, string taskGuid) { try { var project = await _projectService.GetByIdAsync(ticket.ProjectId); var message = new CardMessageRequest { ReceiveIdType = "chat_id", ReceiveId = project.FeishuGroupId, Card = new InteractiveCard { Header = new CardHeader { Title = "🎯 新工单转飞书任务", Template = "blue" }, Elements = new List<ICardElement> { new CardMarkdown { Content = $"**工单编号:** #{ticket.Id}\n" + $"**客户:** {ticket.CustomerName}\n" + $"**优先级:** {GetPriorityEmoji(ticket.Priority)} {ticket.Priority}\n" + $"**负责人:** {ticket.AssigneeName}" }, new CardButton { Text = new CardText { Content = "查看任务详情", Tag = "plain_text" }, Type = "template", Url = $"https://.feishu.cn/messenger/collection/task/detail/{taskGuid}" } } } }; await _messageService.SendCardMessageAsync(message); } catch (Exception ex) { _logger.LogError(ex, "发送任务创建通知失败,工单ID: {TicketId}", ticket.Id); } } private string GetPriorityEmoji(TicketPriority priority) { return priority switch { TicketPriority.High => "🔴", TicketPriority.Medium => "🟡", TicketPriority.Low => "🟢", _ => "⚪" }; } } 注意事项 避免重复创建:使用幂等Token和数据库唯一性约束 字段默认值处理:合理设置任务的默认优先级和截止时间 异常处理:网络异常时的重试机制和本地缓存 性能优化:批量处理和异步操作 🔄 场景二:任务状态双向同步 状态同步流程图 graph TB subgraph "飞书 → 系统" A[飞书任务状态变更] --> B[Webhook事件] B --> C[事件验证器] C --> D[状态映射器] D --> E[业务规则校验] E --> F[更新工单状态] F --> G[记录操作日志] G --> H[发送通知] end subgraph "系统 → 飞书" I[工单状态变更] --> J[状态映射器] J --> K[API调用] K --> L[更新飞书任务] L --> M[异常重试] M --> N[成功确认] end subgraph "双向映射规则" O[todo] --> P[InProgress] Q[done] --> R[Resolved] S[archived] --> T[Closed] P --> O R --> Q T --> S end 飞书 → 系统:通过Webhook接收任务更新 状态映射配置 public class TaskStatusMapper { private static readonly Dictionary<string, TicketStatus> StatusMapping = new() { { "todo", TicketStatus.InProgress }, { "done", TicketStatus.Resolved }, { "archived", TicketStatus.Closed } }; public TicketStatus MapTaskStatusToTicketStatus(string taskStatus) { return StatusMapping.TryGetValue(taskStatus, out var ticketStatus) ? ticketStatus : TicketStatus.Open; } public string MapTicketStatusToTaskStatus(TicketStatus ticketStatus) { return ticketStatus switch { TicketStatus.Open => "todo", TicketStatus.InProgress => "todo", TicketStatus.Resolved => "done", TicketStatus.Closed => "archived", _ => "todo" }; } } Webhook事件处理器 public class TaskWebhookHandler : IFeishuEventHandler { private readonly ITicketSyncService _syncService; private readonly IOperationLogService _logService; private readonly ILogger<TaskWebhookHandler> _logger; public string SupportedEventType => "task.status_changed"; public async Task HandleAsync(EventData eventData, CancellationToken cancellationToken = default) { try { switch (eventData.EventType) { case "task.status_changed": await HandleStatusChangedAsync(eventData); break; case "task.assignee_updated": await HandleAssigneeUpdatedAsync(eventData); break; case "task.deleted": await HandleTaskDeletedAsync(eventData); break; default: _logger.LogDebug("未处理的任务事件类型: {EventType}", eventData.EventType); break; } } catch (Exception ex) { _logger.LogError(ex, "处理飞书任务Webhook事件失败"); throw; } } private async Task HandleStatusChangedAsync(EventData eventData) { var taskEvent = JsonSerializer.Deserialize<TaskStatusChangedEvent>(eventData.Event.ToString()); // 校验业务规则 if (!await ValidateStatusTransitionAsync(taskEvent)) { _logger.LogWarning("任务状态转换不符合业务规则,任务ID: {TaskGuid}", taskEvent.Task.Guid); return; } // 更新工单状态 await _syncService.SyncTaskStatusToTicketAsync(taskEvent.Task.Guid, taskEvent.Task.Status); // 记录操作日志 await _logService.LogAsync(new OperationLog { TicketId = ExtractTicketId(taskEvent.Task.Extra), Action = "StatusChanged", OldValue = taskEvent.OldStatus, NewValue = taskEvent.Task.Status, Operator = "FeishuSync", Timestamp = DateTime.UtcNow, Source = "FeishuTask" }); } private async Task<bool> ValidateStatusTransitionAsync(TaskStatusChangedEvent taskEvent) { // 业务规则:不允许从"完成"状态回退到其他状态 if (taskEvent.OldStatus == "done" && taskEvent.Task.Status != "done") { // 检查是否有特殊权限 var hasSpecialPermission = await HasSpecialPermissionAsync(taskEvent.Operator.UserId); return hasSpecialPermission; } // 业务规则:只有负责人可以修改任务状态 var isAssignee = taskEvent.Task.Members?.Any(m => m.UserId == taskEvent.Operator.UserId && m.TaskRole == TaskRole.Assignee) ?? false; return isAssignee; } } 系统 → 飞书:工单解决后自动关闭任务 public class TicketStatusHandler { private readonly IFeishuTaskService _feishuTaskService; private readonly IEventBus _eventBus; public async Task HandleTicketStatusChangedAsync(TicketStatusChangedEvent @event) { try { if (!string.IsNullOrEmpty(@event.Ticket.FeishuTaskId)) { var updateRequest = new UpdateTaskRequest { Status = _statusMapper.MapTicketStatusToTaskStatus(@event.NewStatus), CompletedAt = @event.NewStatus == TicketStatus.Resolved ? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString() : null }; var success = await _feishuTaskService.UpdateTaskAsync(@event.Ticket.FeishuTaskId, updateRequest); if (success) { _logger.LogInformation("成功同步工单状态到飞书任务,工单ID: {TicketId}, 任务ID: {TaskGuid}", @event.Ticket.Id, @event.Ticket.FeishuTaskId); } } } catch (Exception ex) { _logger.LogError(ex, "同步工单状态到飞书任务失败,工单ID: {TicketId}", @event.Ticket.Id); // 发送到重试队列 await _retryQueue.EnqueueAsync(new RetryMessage { Data = @event, Timestamp = DateTime.UtcNow, RetryCount = 0 }); } } } 👥 场景三:智能跨部门任务分配 智能分配规则引擎 graph TD A[工单输入] --> B[规则引擎] B --> C[工单类型规则] B --> D[SLA规则] B --> E[工作负载规则] B --> F[可用性规则] C --> C1{技术问题?} C1 -->|是| C2[技术支持团队] C1 -->|否| C3{功能需求?} C3 -->|是| C4[产品团队] C1 -->|否| C5{Bug报告?} C5 -->|是| C6[开发团队] C3 -->|否| C7[客户服务团队] D --> D1[SLA时间计算] D1 --> D2[优先级调整] E --> E1[当前工作量评估] E1 --> E2[负载均衡分配] F --> F1[在线状态检查] F1 --> F2[技能匹配] C2 --> G[候选人池] C4 --> G C6 --> G C7 --> G D2 --> G E2 --> G F2 --> G G --> H[最终分配决策] H --> I[任务分配执行] 规则引擎设计 public class TaskAssignmentRuleEngine { private readonly IUserService _userService; private readonly IProjectService _projectService; private readonly ISLAService _slaService; public async Task<AssignmentResult> AssignTaskAsync(Ticket ticket) { var rules = new List<IAssignmentRule> { new TicketTypeAssignmentRule(_userService), new SLAAssignmentRule(_slaService), new WorkloadAssignmentRule(_userService), new AvailabilityAssignmentRule(_userService) }; foreach (var rule in rules) { var result = await rule.EvaluateAsync(ticket); if (result.IsMatch) { return result; } } // 默认分配规则 return await GetDefaultAssignmentAsync(ticket); } } public class TicketTypeAssignmentRule : IAssignmentRule { public async Task<AssignmentResult> EvaluateAsync(Ticket ticket) { return ticket.Type switch { TicketType.TechnicalIssue => await AssignToTechnicalSupportAsync(ticket), TicketType.FeatureRequest => await AssignToProductAsync(ticket), TicketType.BugReport => await AssignToDevelopmentAsync(ticket), TicketType.CustomerComplaint => await AssignToCustomerServiceAsync(ticket), _ => AssignmentResult.NoMatch() }; } private async Task<AssignmentResult> AssignToTechnicalSupportAsync(Ticket ticket) { var techSupportTeam = await _userService.GetUsersByRoleAsync("TechnicalSupport"); var availableMember = techSupportTeam .Where(u => u.IsAvailable && u.CurrentWorkload < u.MaxWorkload) .OrderBy(u => u.CurrentWorkload) .FirstOrDefault(); return availableMember != null ? AssignmentResult.Success(availableMember.Id, "按工单类型分配到技术支持") : AssignmentResult.NoMatch(); } } SLA截止时间与提醒规则 public class SLAService { public async Task<DateTime?> CalculateSLADeadlineAsync(Ticket ticket) { var slaConfig = await GetSLAConfigAsync(ticket.Priority, ticket.Type); var businessHours = await GetBusinessHoursAsync(ticket.CustomerId); var deadline = CalculateBusinessDeadline(DateTime.UtcNow, slaConfig.ResponseHours, businessHours); return deadline; } public async Task SetupSLARemindersAsync(string ticketId, DateTime deadline) { var reminders = new List<SLAReminder> { new SLAReminder { TicketId = ticketId, TriggerTime = deadline.AddHours(-2), // 提前2小时 Type = ReminderType.Warning, Message = "工单即将超过SLA时间,请尽快处理" }, new SLAReminder { TicketId = ticketId, TriggerTime = deadline.AddMinutes(-30), // 提前30分钟 Type = ReminderType.Urgent, Message = "工单SLA即将超时,紧急处理" } }; await SaveRemindersAsync(reminders); } } 子任务依赖支持 public class SubTaskManager { public async Task<string> CreateSubTaskAsync(string parentTaskGuid, SubTaskRequest request) { var subTaskRequest = new CreateSubTaskRequest { Summary = request.Title, Description = request.Description, Due = request.DueDate.HasValue ? new TaskTime { Timestamp = ((DateTimeOffset)request.DueDate.Value).ToUnixTimeMilliseconds().ToString() } : null, Members = request.AssigneeId != null ? new[] { new TaskMemberInfo { UserId = request.AssigneeId, TaskRole = TaskRole.Assignee } } : null }; var result = await _taskApi.CreateSubTaskAsync(parentTaskGuid, subTaskRequest); return result?.Data?.SubTask?.Guid; } public async Task SetupTaskDependenciesAsync(string taskGuid, List<TaskDependency> dependencies) { foreach (var dependency in dependencies) { var addRequest = new AddTaskDependenciesRequest { Dependencies = new[] { new TaskDependencyInfo { TaskGuid = dependency.DependentTaskGuid, DependencyType = dependency.Type } } }; await _taskApi.AddTaskDependenciesByIdAsync(taskGuid, addRequest); } } } 项目仓库 Mud.Feishu Gitee源码仓库:Gitee Mud.Feishu Github源码仓库:Github