使用.NET 8+ 与飞书API构建组织架构同步服务
一、.NET生态下飞书API集成挑战
.NET企业应用场景
在现代企业数字化转型中,典型的.NET技术栈(如ASP.NET Core MVC/Web API, Entity Framework Core, SQL Server)构建的内部管理系统扮演着核心角色。这些系统承载着企业的关键业务流程,从人力资源管理到权限控制,从财务审批到业务数据分析。
然而,一个普遍存在的痛点是:员工信息在飞书和自建.NET系统间存在严重的数据不一致问题。当新员工入职时,HR在飞书中录入信息,但各个.NET系统仍需手动重复录入;当员工离职或转岗时,权限更新往往滞后,存在安全隐患;部门架构调整时,各系统的数据更新更是不同步,导致报表统计不准确。
同步的核心价值
统一身份认证: 通过建立可靠的组织架构同步机制,为后续实现飞书扫码登录(OAuth 2.0)打下坚实基础。当用户数据在飞书和本地系统保持一致时,才能基于飞书身份实现无缝的单点登录体验。
自动化运维: 利用.NET后台服务(如BackgroundService)实现全自动化的同步流程,替代人工操作,降低运维成本,提高数据准确性。
数据一致性: 确保企业内部所有.NET应用的组织数据与飞书源保持实时一致,为决策提供准确的数据支撑。
二、.NET技术选型
飞书开放平台配置
首先需要在飞书开放平台创建"企业自建应用":
- 登录飞书开放平台
- 创建企业自建应用,获取
AppId和AppSecret - 申请必要的权限:
contact:contact:readonly(核心权限,用于读取联系人信息)contact:user.employee_id:readonly(用于获取员工ID)contact:department:readonly(用于读取部门信息)
.NET项目设置与技术栈
.NET版本: 推荐 .NET 8 (LTS长期支持版本),充分利用最新的性能优化和语言特性。
Mud.Feishu飞书服务SDK: 这是本次实践的核心组件。Mud.Feishu是一个现代化的.NET库,专门用于简化与飞书API的集成。相比原生SDK,它具有以下优势:
| 对比维度 | 原生SDK调用 | Mud.Feishu组件 |
|---|---|---|
| 开发效率 | 需要手动构造HTTP请求、处理响应 | 只需调用简洁的接口方法,一行代码完成操作 |
| 类型安全 | 手动处理JSON序列化,容易出现类型错误 | 提供完整的强类型支持,编译时发现错误 |
| 令牌管理 | 需要手动获取、刷新和管理访问令牌 | 自动处理令牌获取和刷新机制 |
| 异常处理 | 需要手动处理各种网络异常和业务异常 | 提供统一的异常处理机制 |
安装Mud.Feishu:
dotnet add package Mud.Feishu --version 1.0.0
技术栈完整配置:
// appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Feishu": {
"AppId": "your_app_id",
"AppSecret": "your_app_secret",
"BaseUrl": "https://open.feishu.cn"
},
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=OrganizationSync;Trusted_Connection=true;"
}
}
在 Program.cs 中注册服务:
using Mud.Feishu;
var builder = WebApplication.CreateBuilder(args);
// 注册飞书 API 服务
builder.Services.AddFeishuApiService(builder.Configuration);
// 注册数据库上下文
builder.Services.AddDbContext<OrganizationDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// 注册同步服务
builder.Services.AddScoped<IFeishuSyncService, FeishuSyncService>();
builder.Services.AddHostedService<FeishuFullSyncService>();
var app = builder.Build();
三、核心架构与同步模式设计
领域模型设计
设计本地数据库表结构,确保与飞书数据的有效映射:
// 部门实体
public class Department
{
public int Id { get; set; }
public string FeishuDepartmentId { get; set; } // 飞书部门ID,用于关联
public string Name { get; set; }
public string? ParentFeishuDepartmentId { get; set; }
public int SortOrder { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
// 导航属性
public virtual Department? ParentDepartment { get; set; }
public virtual ICollection<Department> SubDepartments { get; set; } = new List<Department>();
public virtual ICollection<User> Users { get; set; } = new List<User>();
}
// 用户实体
public class User
{
public int Id { get; set; }
public string FeishuUserId { get; set; } // 飞书用户ID,用于关联
public string Name { get; set; }
public string? Email { get; set; }
public string? Mobile { get; set; }
public string? EmployeeNumber { get; set; }
public int? DepartmentId { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
// 导航属性
public virtual Department? Department { get; set; }
}
EF Core DbContext配置:
public class OrganizationDbContext : DbContext
{
public OrganizationDbContext(DbContextOptions<OrganizationDbContext> options)
: base(options)
{
}
public DbSet<Department> Departments { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 为飞书ID创建唯一索引
modelBuilder.Entity<Department>()
.HasIndex(d => d.FeishuDepartmentId)
.IsUnique();
modelBuilder.Entity<User>()
.HasIndex(u => u.FeishuUserId)
.IsUnique();
// 配置部门层级关系
modelBuilder.Entity<Department>()
.HasOne(d => d.ParentDepartment)
.WithMany(d => d.SubDepartments)
.HasForeignKey(d => d.ParentFeishuDepartmentId)
.HasPrincipalKey(d => d.FeishuDepartmentId);
}
}
同步模式设计
模式一:全量同步(使用 BackgroundService)
适用于系统初始化或夜间批量同步的场景。通过递归方式获取完整的组织架构:
public class FeishuFullSyncService : BackgroundService
{
private readonly ILogger<FeishuFullSyncService> _logger;
private readonly IFeishuSyncService _syncService;
private readonly IServiceScopeFactory _scopeFactory;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
using var scope = _scopeFactory.CreateScope();
_logger.LogInformation("开始执行全量同步任务");
await _syncService.FullSyncDepartmentsAsync();
await _syncService.FullSyncUsersAsync();
_logger.LogInformation("全量同步任务完成");
// 每天凌晨2点执行
var nextRun = DateTime.Today.AddDays(1).AddHours(2);
var delay = nextRun - DateTime.Now;
await Task.Delay(delay, stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "全量同步任务执行失败");
await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
}
}
}
}
模式二:增量/事件同步(创建ASP.NET Core Web API 控制器)
适用于实时性要求高的场景,通过飞书事件订阅实现:
[ApiController]
[Route("api/[controller]")]
public class FeishuEventController : ControllerBase
{
private readonly ILogger<FeishuEventController> _logger;
private readonly IFeishuSyncService _syncService;
[HttpPost("webhook")]
public async Task<IActionResult> HandleEvent([FromBody] FeishuEventRequest request)
{
try
{
// 验证事件签名
if (!ValidateSignature(request))
return Unauthorized();
// 根据事件类型处理
switch (request.Header.EventType)
{
case "contact.user.updated":
await _syncService.SyncUserAsync(request.Event.UserId);
break;
case "contact.department.updated":
await _syncService.SyncDepartmentAsync(request.Event.DepartmentId);
break;
// 其他事件类型...
}
return Ok(new { code = 0, msg: "success" });
}
catch (Exception ex)
{
_logger.LogError(ex, "处理飞书事件失败");
return BadRequest(new { code = -1, msg: ex.Message });
}
}
private bool ValidateSignature(FeishuEventRequest request)
{
// 实现飞书事件签名验证逻辑
// 参考:https://open.feishu.cn/document/server-docs/event-subscription-guides/event-verification
return true;
}
}
混合模式(推荐): 启动时全量同步 + 运行时事件同步 + 每日定时全量同步兜底。这种模式既保证了数据的一致性,又具备良好的实时性。
四、分步实现指南
步骤一:构建飞书API客户端
创建 FeishuApiService 类,封装飞书API调用:
public interface IFeishuApiService
{
Task<List<FeishuDepartment>> GetDepartmentsAsync();
Task<List<FeishuUser>> GetUsersByDepartmentAsync(string departmentId);
Task<FeishuUser?> GetUserByIdAsync(string userId);
}
public class FeishuApiService : IFeishuApiService
{
private readonly IFeishuV3DepartmentsApi _departmentsApi;
private readonly IFeishuV3UserApi _userApi;
private readonly ILogger<FeishuApiService> _logger;
public FeishuApiService(
IFeishuV3DepartmentsApi departmentsApi,
IFeishuV3UserApi userApi,
ILogger<FeishuApiService> logger)
{
_departmentsApi = departmentsApi;
_userApi = userApi;
_logger = logger;
}
public async Task<List<FeishuDepartment>> GetDepartmentsAsync()
{
try
{
var result = await _departmentsApi.GetDepartmentByIdAsync("0"); // 获取根部门
if (result.Code == 0 && result.Data != null)
{
var departments = new List<FeishuDepartment>();
await ProcessDepartmentTree(result.Data, departments);
return departments;
}
throw new FeishuException($"获取部门列表失败: {result.Msg}");
}
catch (Exception ex)
{
_logger.LogError(ex, "获取部门列表异常");
throw;
}
}
public async Task<List<FeishuUser>> GetUsersByDepartmentAsync(string departmentId)
{
try
{
var users = new List<FeishuUser>();
var pageToken = "";
var pageSize = 50;
do
{
var result = await _userApi.GetUserByDepartmentIdAsync(
departmentId: departmentId,
page_size: pageSize,
page_token: string.IsNullOrEmpty(pageToken) ? null : pageToken);
if (result.Code == 0 && result.Data?.Items != null)
{
users.AddRange(result.Data.Items.Select(item => MapToFeishuUser(item)));
pageToken = result.Data.PageToken ?? "";
}
else
{
throw new FeishuException($"获取部门用户失败: {result.Msg}");
}
} while (!string.IsNullOrEmpty(pageToken));
return users;
}
catch (Exception ex)
{
_logger.LogError(ex, "获取部门用户异常,DepartmentId: {DepartmentId}", departmentId);
throw;
}
}
private async Task ProcessDepartmentTree(DepartmentData dept, List<FeishuDepartment> departments)
{
var feishuDept = MapToFeishuDepartment(dept);
departments.Add(feishuDept);
// 递归获取子部门
if (dept.SubDepartments != null)
{
foreach (var subDept in dept.SubDepartments)
{
await ProcessDepartmentTree(subDept, departments);
}
}
}
private FeishuDepartment MapToFeishuDepartment(DepartmentData dept) => new()
{
DepartmentId = dept.DepartmentId,
Name = dept.Name,
ParentDepartmentId = dept.ParentDepartmentId,
SortOrder = dept.Order
};
private FeishuUser MapToFeishuUser(UserData user) => new()
{
UserId = user.UserId,
Name = user.Name,
Email = user.Email,
Mobile = user.Mobile,
EmployeeNumber = user.EmployeeNumber,
DepartmentIds = user.DepartmentIds?.ToList() ?? new List<string>(),
IsActive = user.Status?.IsActive ?? false
};
}
步骤二:实现数据映射与处理
创建DTO类用于数据转换,并使用AutoMapper进行映射:
// 飞书数据传输对象
public class FeishuDepartment
{
public string DepartmentId { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string? ParentDepartmentId { get; set; }
public int SortOrder { get; set; }
}
public class FeishuUser
{
public string UserId { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string? Email { get; set; }
public string? Mobile { get; set; }
public string? EmployeeNumber { get; set; }
public List<string> DepartmentIds { get; set; } = new();
public bool IsActive { get; set; }
}
// AutoMapper配置
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<FeishuDepartment, Department>()
.ForMember(dest => dest.FeishuDepartmentId, opt => opt.MapFrom(src => src.DepartmentId))
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.CreatedAt, opt => opt.MapFrom(src => DateTime.UtcNow))
.ForMember(dest => dest.UpdatedAt, opt => opt.MapFrom(src => DateTime.UtcNow))
.ForMember(dest => dest.ParentDepartment, opt => opt.Ignore())
.ForMember(dest => dest.SubDepartments, opt => opt.Ignore())
.ForMember(dest => dest.Users, opt => opt.Ignore());
CreateMap<FeishuUser, User>()
.ForMember(dest => dest.FeishuUserId, opt => opt.MapFrom(src => src.UserId))
.ForMember(dest => dest.Id, opt => opt.Ignore())
.ForMember(dest => dest.CreatedAt, opt => opt.MapFrom(src => DateTime.UtcNow))
.ForMember(dest => dest.UpdatedAt, opt => opt.MapFrom(src => DateTime.UtcNow))
.ForMember(dest => dest.Department, opt => opt.Ignore());
}
}
步骤三:实现全量同步服务
创建同步服务接口和实现:
public interface IFeishuSyncService
{
Task FullSyncDepartmentsAsync();
Task FullSyncUsersAsync();
Task SyncDepartmentAsync(string departmentId);
Task SyncUserAsync(string userId);
}
public class FeishuSyncService : IFeishuSyncService
{
private readonly IFeishuApiService _feishuApiService;
private readonly OrganizationDbContext _dbContext;
private readonly IMapper _mapper;
private readonly ILogger<FeishuSyncService> _logger;
public async Task FullSyncDepartmentsAsync()
{
_logger.LogInformation("开始全量同步部门");
try
{
// 获取飞书部门列表
var feishuDepartments = await _feishuApiService.GetDepartmentsAsync();
// 获取本地部门列表
var localDepartments = await _dbContext.Departments
.Where(d => d.IsActive)
.ToDictionaryAsync(d => d.FeishuDepartmentId);
var departmentsToAdd = new List<Department>();
var departmentsToUpdate = new List<Department>();
var departmentIdsToDeactivate = new HashSet<string>(localDepartments.Keys);
foreach (var feishuDept in feishuDepartments)
{
departmentIdsToDeactivate.Remove(feishuDept.DepartmentId);
if (localDepartments.TryGetValue(feishuDept.DepartmentId, out var localDept))
{
// 更新现有部门
_mapper.Map(feishuDept, localDept);
localDept.UpdatedAt = DateTime.UtcNow;
departmentsToUpdate.Add(localDept);
}
else
{
// 新增部门
var newDept = _mapper.Map<Department>(feishuDept);
newDept.IsActive = true;
departmentsToAdd.Add(newDept);
}
}
// 执行数据库操作
if (departmentsToAdd.Any())
{
_dbContext.Departments.AddRange(departmentsToAdd);
_logger.LogInformation("新增部门数量: {Count}", departmentsToAdd.Count);
}
if (departmentsToUpdate.Any())
{
_dbContext.Departments.UpdateRange(departmentsToUpdate);
_logger.LogInformation("更新部门数量: {Count}", departmentsToUpdate.Count);
}
// 逻辑删除不存在的部门
if (departmentIdsToDeactivate.Any())
{
var departmentsToDeactivate = await _dbContext.Departments
.Where(d => departmentIdsToDeactivate.Contains(d.FeishuDepartmentId))
.ToListAsync();
foreach (var dept in departmentsToDeactivate)
{
dept.IsActive = false;
dept.UpdatedAt = DateTime.UtcNow;
}
_logger.LogInformation("停用部门数量: {Count}", departmentsToDeactivate.Count);
}
await _dbContext.SaveChangesAsync();
_logger.LogInformation("部门全量同步完成");
}
catch (Exception ex)
{
_logger.LogError(ex, "部门全量同步失败");
throw;
}
}
public async Task FullSyncUsersAsync()
{
_logger.LogInformation("开始全量同步用户");
try
{
// 获取所有部门
var departments = await _dbContext.Departments
.Where(d => d.IsActive)
.ToListAsync();
var feishuUsers = new List<FeishuUser>();
// 遍历每个部门获取用户
foreach (var dept in departments)
{
try
{
var deptUsers = await _feishuApiService.GetUsersByDepartmentAsync(dept.FeishuDepartmentId);
feishuUsers.AddRange(deptUsers);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取部门用户失败,DepartmentId: {DepartmentId}", dept.FeishuDepartmentId);
// 继续处理其他部门
}
}
// 去重(用户可能属于多个部门)
var uniqueUsers = feishuUsers.GroupBy(u => u.UserId).Select(g => g.First()).ToList();
// 获取本地用户列表
var localUsers = await _dbContext.Users
.Where(u => u.IsActive)
.ToDictionaryAsync(u => u.FeishuUserId);
var usersToAdd = new List<User>();
var usersToUpdate = new List<User>();
var userIdsToDeactivate = new HashSet<string>(localUsers.Keys);
foreach (var feishuUser in uniqueUsers)
{
userIdsToDeactivate.Remove(feishuUser.UserId);
if (localUsers.TryGetValue(feishuUser.UserId, out var localUser))
{
// 更新现有用户
_mapper.Map(feishuUser, localUser);
localUser.UpdatedAt = DateTime.UtcNow;
// 设置主部门(取第一个有效部门)
var primaryDept = departments.FirstOrDefault(d => feishuUser.DepartmentIds.Contains(d.FeishuDepartmentId));
if (primaryDept != null)
{
localUser.DepartmentId = primaryDept.Id;
}
usersToUpdate.Add(localUser);
}
else
{
// 新增用户
var newUser = _mapper.Map<User>(feishuUser);
newUser.IsActive = true;
// 设置主部门
var primaryDept = departments.FirstOrDefault(d => feishuUser.DepartmentIds.Contains(d.FeishuDepartmentId));
if (primaryDept != null)
{
newUser.DepartmentId = primaryDept.Id;
}
usersToAdd.Add(newUser);
}
}
// 执行数据库操作
if (usersToAdd.Any())
{
_dbContext.Users.AddRange(usersToAdd);
_logger.LogInformation("新增用户数量: {Count}", usersToAdd.Count);
}
if (usersToUpdate.Any())
{
_dbContext.Users.UpdateRange(usersToUpdate);
_logger.LogInformation("更新用户数量: {Count}", usersToUpdate.Count);
}
// 逻辑删除不存在的用户
if (userIdsToDeactivate.Any())
{
var usersToDeactivate = await _dbContext.Users
.Where(u => userIdsToDeactivate.Contains(u.FeishuUserId))
.ToListAsync();
foreach (var user in usersToDeactivate)
{
user.IsActive = false;
user.UpdatedAt = DateTime.UtcNow;
}
_logger.LogInformation("停用用户数量: {Count}", usersToDeactivate.Count);
}
await _dbContext.SaveChangesAsync();
_logger.LogInformation("用户全量同步完成");
}
catch (Exception ex)
{
_logger.LogError(ex, "用户全量同步失败");
throw;
}
}
public async Task SyncDepartmentAsync(string departmentId)
{
// 实现单个部门的增量同步
// 类似于全量同步的逻辑,但只处理指定部门
throw new NotImplementedException();
}
public async Task SyncUserAsync(string userId)
{
// 实现单个用户的增量同步
// 类似于全量同步的逻辑,但只处理指定用户
throw new NotImplementedException();
}
}
步骤四:实现增量事件同步
创建事件处理器来处理飞书的实时事件:
public class FeishuEventHandler
{
private readonly IFeishuSyncService _syncService;
private readonly ILogger<FeishuEventHandler> _logger;
public async Task HandleUserUpdatedEvent(FeishuEventPayload payload)
{
try
{
var userId = payload.UserId;
_logger.LogInformation("处理用户更新事件,UserId: {UserId}", userId);
await _syncService.SyncUserAsync(userId);
_logger.LogInformation("用户更新事件处理完成,UserId: {UserId}", userId);
}
catch (Exception ex)
{
_logger.LogError(ex, "处理用户更新事件失败,UserId: {UserId}", payload.UserId);
throw;
}
}
public async Task HandleDepartmentUpdatedEvent(FeishuEventPayload payload)
{
try
{
var departmentId = payload.DepartmentId;
_logger.LogInformation("处理部门更新事件,DepartmentId: {DepartmentId}", departmentId);
await _syncService.SyncDepartmentAsync(departmentId);
_logger.LogInformation("部门更新事件处理完成,DepartmentId: {DepartmentId}", departmentId);
}
catch (Exception ex)
{
_logger.LogError(ex, "处理部门更新事件失败,DepartmentId: {DepartmentId}", payload.DepartmentId);
throw;
}
}
}
步骤五:处理边界情况与异常
使用Polly库实现重试和熔断策略:
public class ResilientFeishuApiService : IFeishuApiService
{
private readonly IFeishuApiService _innerService;
private readonly IAsyncPolicy _retryPolicy;
private readonly IAsyncPolicy _circuitBreakerPolicy;
public ResilientFeishuApiService(IFeishuApiService innerService)
{
_innerService = innerService;
_retryPolicy = Policy
.Handle<FeishuException>(ex => ex.ErrorCode >= 500) // 服务器错误重试
.Or<HttpRequestException>() // 网络错误重试
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)),
onRetry: (outcome, timespan, retryAttempt, context) =>
{
Console.WriteLine($"重试第 {retryAttempt} 次,延迟 {timespan.TotalSeconds} 秒");
});
_circuitBreakerPolicy = Policy
.Handle<FeishuException>()
.CircuitBreakerAsync(
exceptionsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromMinutes(1),
onBreak: (ex, breakDelay) =>
{
Console.WriteLine($"熔断器开启,延迟 {breakDelay.TotalMinutes} 分钟");
},
onReset: () =>
{
Console.WriteLine("熔断器重置");
});
}
public async Task<List<FeishuDepartment>> GetDepartmentsAsync()
{
return await _retryPolicy.ExecuteAsync(() =>
_circuitBreakerPolicy.ExecuteAsync(() => _innerService.GetDepartmentsAsync()));
}
// 其他方法类似实现...
}
五、进阶功能与.NET最佳实践
依赖注入与配置
使用选项模式管理配置,实现配置的强类型化和验证:
public class FeishuSyncOptions
{
public int PageSize { get; set; } = 50;
public TimeSpan SyncInterval { get; set; } = TimeSpan.FromHours(24);
public int MaxRetryAttempts { get; set; } = 3;
public bool EnableEventSync { get; set; } = true;
}
// 在Program.cs中注册
builder.Services.Configure<FeishuSyncOptions>(builder.Configuration.GetSection("FeishuSync"));
builder.Services.AddScoped<IFeishuSyncService, FeishuSyncService>();
日志与监控
集成结构化日志和监控:
public class FeishuSyncService : IFeishuSyncService
{
private readonly ILogger<FeishuSyncService> _logger;
private readonly IMetrics _metrics;
public async Task FullSyncDepartmentsAsync()
{
using var activity = Activity.StartActivity("feishu_sync_departments_full");
var stopwatch = Stopwatch.StartNew();
try
{
_logger.LogInformation("开始全量同步部门,ActivityId: {ActivityId}", Activity.Current?.Id);
// 同步逻辑...
stopwatch.Stop();
_metrics.Counter("feishu_sync_departments_success").Add(1);
_metrics.Histogram("feishu_sync_departments_duration").Record(stopwatch.ElapsedMilliseconds);
_logger.LogInformation("部门全量同步完成,耗时: {ElapsedMs}ms", stopwatch.ElapsedMilliseconds);
}
catch (Exception ex)
{
_metrics.Counter("feishu_sync_departments_failed").Add(1);
_logger.LogError(ex, "部门全量同步失败");
throw;
}
}
}
实现飞书扫码登录(SSO)
基于已有的同步数据,实现飞书OAuth登录:
public class FeishuAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly IFeishuV3AuthenticationApi _authApi;
private readonly IUserService _userService;
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey("Authorization"))
return AuthenticateResult.Fail("Missing Authorization Header");
try
{
var token = Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
// 获取用户信息
var userInfo = await _authApi.GetUserInfoAsync(token);
if (userInfo.Code != 0)
return AuthenticateResult.Fail("Invalid token");
// 在本地数据库中查找用户
var user = await _userService.GetByFeishuUserIdAsync(userInfo.Data.UserId);
if (user == null || !user.IsActive)
return AuthenticateResult.Fail("User not found or inactive");
// 创建身份票据
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Name),
new Claim(ClaimTypes.Email, user.Email ?? ""),
new Claim("feishu_user_id", user.FeishuUserId)
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
catch (Exception ex)
{
return AuthenticateResult.Fail($"Authentication failed: {ex.Message}");
}
}
}
部署与运维
Docker化部署:
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["OrganizationSync/OrganizationSync.csproj", "OrganizationSync/"]
RUN dotnet restore "OrganizationSync/OrganizationSync.csproj"
COPY . .
WORKDIR "/src/OrganizationSync"
RUN dotnet build "OrganizationSync.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "OrganizationSync.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "OrganizationSync.dll"]
健康检查:
public class FeishuSyncHealthCheck : IHealthCheck
{
private readonly IFeishuApiService _feishuApiService;
private readonly OrganizationDbContext _dbContext;
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
try
{
// 检查飞书API连接
var departments = await _feishuApiService.GetDepartmentsAsync();
// 检查数据库连接
var userCount = await _dbContext.Users.CountAsync(cancellationToken);
var data = new Dictionary<string, object>
{
{ "department_count", departments.Count },
{ "local_user_count", userCount },
{ "last_sync", DateTime.UtcNow }
};
return HealthCheckResult.Healthy("飞书同步服务运行正常", data);
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("飞书同步服务异常", ex);
}
}
}
六、总结
通过本次实践,我们成功构建了一个基于.NET 8+和飞书API的企业级组织架构同步服务。整个解决方案采用了现代化的.NET技术栈:
- Mud.Feishu SDK:提供了类型安全的飞书API调用,大幅简化了开发工作
- BackgroundService:实现了可靠的后台定时同步
- Entity Framework Core:提供了高效的数据持久化和关系映射
- System.Text.Json:确保了高性能的JSON序列化
- ASP.NET Core:构建了事件接收和SSO登录的Web API
后续改进方向
基于当前的同步基础,后续还可以进一步实现:
- 飞书消息推送集成
- 审批流程对接
- 数据分析和报表
- 多租户支持
这个实践充分展示了.NET生态系统在企业集成场景中的强大能力,通过合理的技术选型和架构设计,能够构建出高性能、高可靠性的企业级应用。
Mud.Feishu项目源码: 可以参考 Mud.Feishu 项目 获取更多详细的实现代码和最佳实践。
相关资源:

浙公网安备 33010602011771号