Skip to content
Bitzsoft.Integrationsbitzsoft.integrations

Concept

VideoConference 视频会议

视频会议域完整手册——IVideoConferenceService 统一接口、5 家供应商鉴权差异(OAuth2/Graph/app-token)、令牌缓存与失效自愈、参会人管理与录制查询。

Last updated

VideoConference 域统一了 5 家视频会议供应商的会议创建、查询、更新、取消、参会人管理和录制查询能力。它遵循标准三层包模式。所有供应商共享同一个 IVideoConferenceService 接口,但鉴权机制差异显著:腾讯会议/Zoom 用 OAuth2 Client Credentials,Microsoft Teams 用 Azure AD Client Credentials 调 Graph,飞书用 tenant_access_token Bearer,钉钉用 access_token query 参数。

域结构

Bitzsoft.Integrations.VideoConference ← 抽象层
├── Bitzsoft.Integrations.VideoConference.TencentMeeting ← 腾讯会议(OAuth2 Client Credentials)
├── Bitzsoft.Integrations.VideoConference.Zoom ← Zoom(Server-to-Server OAuth)
├── Bitzsoft.Integrations.VideoConference.MicrosoftTeams ← Microsoft Teams(Graph onlineMeetings)
├── Bitzsoft.Integrations.VideoConference.Feishu ← 飞书视频会议(tenant_access_token)
├── Bitzsoft.Integrations.VideoConference.DingTalk ← 钉钉视频会议(access_token query)
└── Bitzsoft.Integrations.VideoConference.All ← 聚合包

统一接口(9 方法)

IVideoConferenceService 围绕”会议(Meeting)“这一核心概念组织,共 9 个成员:

public interface IVideoConferenceService
{
string ProviderName { get; }
// 会议生命周期
Task<MeetingInfo> CreateMeetingAsync(CreateMeetingRequest request, CancellationToken cancellationToken = default);
Task<MeetingInfo> GetMeetingAsync(string meetingId, CancellationToken cancellationToken = default);
Task<VideoConferenceResult> UpdateMeetingAsync(UpdateMeetingRequest request, CancellationToken cancellationToken = default);
Task<VideoConferenceResult> CancelMeetingAsync(string meetingId, CancellationToken cancellationToken = default);
Task<List<MeetingInfo>> ListMeetingsAsync(ListMeetingsRequest request, CancellationToken cancellationToken = default);
// 参会人
Task<MeetingInviteResult> InviteAttendeesAsync(InviteAttendeesRequest request, CancellationToken cancellationToken = default);
Task<MeetingInviteResult> RemoveAttendeesAsync(RemoveAttendeesRequest request, CancellationToken cancellationToken = default);
// 录制
Task<List<MeetingRecording>> ListRecordingsAsync(string meetingId, CancellationToken cancellationToken = default);
}

OutboundCall 的返回值约定一致:查询类方法(Create / Get / List / ListRecordings)抛异常表达失败,操作类方法(Update / Cancel / Invite / Remove)返回结果对象——VideoConferenceResult { Success, ErrorMessage?, RequestId? }MeetingInviteResult { Success, ProcessedCount, ErrorMessage? }

鉴权模型

5 家供应商使用 4 种不同的鉴权机制,这是本域最大的实现差异:

供应商鉴权机制Token 注入Token 刷新
腾讯会议OAuth2 Client Credentials(Client Id + Secret + AppId)Bearer 头POST /v1/open-token
ZoomServer-to-Server OAuth(Account Id + Client Id + Secret)Bearer 头POST /oauth/token(HTTP Basic Auth)
Microsoft TeamsAzure AD Client Credentials(Tenant + Client Id + Secret)Bearer 头POST /{tenant}/oauth2/v2.0/token(form-urlencoded)
飞书App Id + App Secret 换 tenant_access_tokenBearer 头POST /open-apis/auth/v3/tenant_access_token/internal
钉钉App Key + App Secret 换 access_tokenquery 参数 ?access_token=GET /gettoken

TokenManager 并发安全

每个供应商的 Internal/*TokenManager 是一个注册为 Singleton 的令牌缓存管理器,采用双重检查锁模式:

  1. 快速路径:先无锁检查 _cached,命中且未过期直接返回。
  2. 双重检查:获取 SemaphoreSlim 后再次检查缓存,防止并发刷新。
  3. 提前刷新effectiveExpire = Math.Max(expire - 300, 60),提前 5 分钟刷新,下限 60 秒。
  4. 失效自愈:HttpClient 检测到令牌失效码时调 Invalidate() 清缓存,下次调用自动刷新。
public async Task<string> GetTokenAsync(CancellationToken ct)
{
if (TryGetValid(out var token)) // ① 快速路径无锁
return token;
await _lock.WaitAsync(ct);
try
{
if (TryGetValid(out token)) // ② 进锁后 double-check
return token;
return await RefreshAsync(ct); // ③ 换 token
}
finally { _lock.Release(); }
}

双 HttpClient 隔离

每个供应商注册两个 HttpClient:业务 HttpClient(Typed Client,按请求注入令牌)和令牌刷新 HttpClient(命名 Client,不经业务管道,避免循环依赖)。

// 令牌刷新专用(不经业务管道,避免令牌注入循环)
services.AddHttpClient(XxxConstants.TokenHttpClientName)
.AddRequestLogging(XxxConstants.TokenHttpClientName);
// 业务请求(按请求注入令牌)
services.AddHttpClient<XxxHttpClient>()
.AddRequestLogging<XxxHttpClient>();

错误处理

业务码校验

各供应商的成功判断方式不同:

供应商成功判断失败码字段
腾讯会议code == "0"(无 code 视为成功)code / message
ZoomHTTP 2xx 直接返回(无外层 code)HTTP 非 2xx + {code, message}
Microsoft TeamsHTTP 2xx 直接返回HTTP 非 2xx + {error: {code, message}}
飞书code == "0"code / msg
钉钉errcode == "0"(无 errcode 视为成功)errcode / errmsg

令牌失效自愈

HttpClient 检测到令牌失效类错误时,清除 TokenManager 缓存,下次调用自动刷新:

供应商失效码
腾讯会议9012(无权限)· 9013(过期)· 9014(无效)
Zoom / TeamsHTTP 401 Unauthorized
飞书99991661 · 99991663 · 99991664
钉钉40014 · 42001 · 48002

供应商清单

供应商Provider IDAPI 基地址StabilityDI 方法
腾讯会议tencent-meetingapi.meeting.qq.comPreviewAddTencentMeetingVideoConference()
Zoomzoomapi.zoom.usPreviewAddZoomVideoConference()
Microsoft Teamsmicrosoft-teamsgraph.microsoft.comPreviewAddMicrosoftTeamsVideoConference()
飞书视频会议feishuopen.feishu.cnPreviewAddFeishuVideoConference()
钉钉视频会议dingtalkoapi.dingtalk.comPreviewAddDingTalkVideoConference()

所有供应商标记为 Preview——基本流程走通,边界场景可能未覆盖。

时间戳差异

不同供应商的时间表示方式不同,ModelMapper 负责统一为 DateTimeOffset(UTC):

供应商时间格式示例
腾讯会议 / 飞书Unix 秒级时间戳1700000000
钉钉Unix 毫秒级时间戳1700000000000
Zoom / TeamsISO 86012023-11-14T22:13:20Z

注册

单厂商

// ① 腾讯会议(OAuth2 Client Credentials)
builder.Services.AddTencentMeetingVideoConference(builder.Configuration.GetSection("VideoConference:TencentMeeting"));
// ② Zoom(Server-to-Server OAuth)
builder.Services.AddZoomVideoConference(builder.Configuration.GetSection("VideoConference:Zoom"));
// ③ Microsoft Teams(Azure AD Client Credentials → Graph)
builder.Services.AddMicrosoftTeamsVideoConference(builder.Configuration.GetSection("VideoConference:MicrosoftTeams"));
// ④ 飞书视频会议(tenant_access_token)
builder.Services.AddFeishuVideoConference(builder.Configuration.GetSection("VideoConference:Feishu"));
// ⑤ 钉钉视频会议(access_token query)
builder.Services.AddDingTalkVideoConference(builder.Configuration.GetSection("VideoConference:DingTalk"));

全量聚合

builder.Services.AddBitzsoftVideoConferenceAll(builder.Configuration, "VideoConference");

按配置节存在性自动注册,appsettings.json 只需写出启用的供应商:

{
"VideoConference": {
"TencentMeeting": {
"ClientId": "...",
"ClientSecret": "...",
"SdkAppId": "..."
},
"Zoom": {
"AccountId": "...",
"ClientId": "...",
"ClientSecret": "..."
},
"MicrosoftTeams": {
"TenantId": "...",
"ClientId": "...",
"ClientSecret": "...",
"DefaultUserPrincipalName": "organizer@contoso.com"
}
}
}

消费

创建会议

public class MeetingManager(IIntegrationProviderResolver<IVideoConferenceService> providers)
{
public async Task<MeetingInfo> ScheduleAsync(string vendor, string subject, DateTimeOffset start, DateTimeOffset end)
{
var meeting = providers.GetRequired(vendor); // "tencent-meeting" / "zoom" / ...
return await meeting.CreateMeetingAsync(new CreateMeetingRequest
{
Subject = subject,
StartTime = start,
EndTime = end,
Attendees = [new() { Email = "alice@example.com", Name = "Alice" }],
WaitingRoomEnabled = true,
});
}
}

参会人管理

// 邀请
var inviteResult = await meeting.InviteAttendeesAsync(new InviteAttendeesRequest
{
MeetingId = info.MeetingId,
Attendees = [new() { Email = "bob@example.com" }],
});
// inviteResult.Success / inviteResult.ProcessedCount
// 移除(腾讯/飞书 DELETE invitees,Zoom PUT registrants/status + action=cancel)
var removeResult = await meeting.RemoveAttendeesAsync(new RemoveAttendeesRequest
{
MeetingId = info.MeetingId,
Attendees = [new() { UserId = "registrant-id" }],
});

录制查询

public async Task<List<MeetingRecording>> GetRecordingsAsync(IVideoConferenceService meeting, string meetingId)
=> await meeting.ListRecordingsAsync(meetingId);

相关

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%