SA 会议系统:第三方日历集成 — 架构与流程
文档状态:V1.0 编写日期:2026-07-16 定位:系统架构、数据流、运行时时序图,面向后端开发。
1. 系统架构
核心原则:
- AI Agent / 前端只调会议系统自己的 API,不感知第三方
- 写操作(创建/取消日程)通过 CalendarAdapter 同步到第三方
- 读操作(查询忙闲)直接查本地数据库,不调第三方 API
- 用户在第三方侧的操作通过事件订阅推送到我方,保持数据一致
2. 配置阶段:导入会议室
管理员通过三步向导将第三方平台的会议室基础资料全量导入到会议系统中。
导入的内容:
- 会议室基础信息:名称、容量、设备列表、平台路径、provider_room_id
- 设备数据字典:从所有房间的 device 字段提取去重,自动生成 identifier
不导入忙闲数据。忙闲通过运行时的事件订阅实时同步。
3. 运行时:创建会议
失败处理:飞书侧创建失败 → 我方会议记录标记 sync_status=failed,保留 fail_reason,不删除我方记录。
4. 运行时:取消会议
5. 运行时:查询可用会议室
不涉及第三方调用。 忙闲数据通过事件订阅已同步到本地数据库。
AI 不传 room_id,只传条件。会议 App 负责筛选匹配。
6. 运行时:事件订阅(用户直接在飞书侧操作)
我方需要提供:
- 公网可访问的 HTTPS 回调 URL
- 在飞书开放平台配置事件订阅,选择
calendar.calendar_event.changed_v4 - 验证飞书推送的
X-Lark-Request-Timestamp和X-Lark-Request-Nonce
数据一致性保障:
- 事件订阅为主数据源,保证 99% 以上的实时性
- 降级方案:定时任务(如每 30 分钟)调 freebusy API 做增量对账
- 首次接入时调 freebusy 拉取近期历史数据做 baseline
7. CalendarAdapter 接口
interface CalendarAdapter {
/** 在第三方日历中创建日程并绑定会议室 */
createEvent(
params: {
providerRoomId: string, // 飞书 resource_id / 钉钉 roomId
summary: string,
start: string, // ISO 8601
end: string,
description?: string,
}
): Promise<{ eventId: string, url?: string }>;
/** 取消第三方日历中的日程 */
cancelEvent(providerEventId: string): Promise<void>;
/** 首次接入时,全量拉取近期忙闲数据做初始化 */
initialSync(
rooms: { providerRoomId: string }[],
startTime: string,
endTime: string
): Promise<{
providerRoomId: string,
busySlots: { start: string, end: string }[]
}[]>;
}内置实现:FeishuAdapter、DingTalkAdapter
8. 数据模型
calendar_providers
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| name | String | 平台别名,如"飞书-生产" |
| provider_type | Enum | feishu / dingtalk |
| auth_config | JSON (加密) | 认证凭据 |
| is_enabled | Bool | 是否启用 |
| created_at | DateTime |
rooms(会议室表,变更)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| name | String | 会议室名称 |
| capacity | Int | 容纳人数 |
| room_type | String | 会议室类型 |
| location_code | String | → 物联平台空间树节点 ID |
| device_ids | String[] | 关联的设备字典 ID 列表 |
| source_platform | Enum | feishu / dingtalk |
| provider_room_id | String | 第三方 room_id |
| provider_room_name | String | 第三方原始名称 |
| is_enabled | Bool | |
| created_at | DateTime |
meetings(会议记录,增加字段)
| 字段 | 类型 | 说明 |
|---|---|---|
| provider_event_id | String | 第三方 event_id(成功写入后回填) |
| sync_status | Enum | pending / synced / failed |
| sync_fail_reason | String | 同步失败原因 |
import_logs(导入日志)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | UUID | 主键 |
| provider_id | UUID | 关联平台 |
| rooms_count | Int | 导入会议室数 |
| devices_count | Int | 新建设备字典数 |
| result | Enum | success / failed |
| created_at | DateTime |
9. 安全
| 要求 | 说明 |
|---|---|
| 凭据加密 | auth_config 在数据库内 AES-256 加密,仅 Adapter 运行时解密 |
| Token 缓存 | access_token 内存缓存,过期前 5 分钟自动续期 |
| 错误隔离 | 第三方异常不影响我方系统正常预定 |
| 事件回调验证 | 验证飞书推送的签名头,防止伪造回调 |
10. 飞书适配器
认证
POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal
Body: { "app_id": "...", "app_secret": "..." }
→ { "tenant_access_token": "...", "expire": 7200 }创建日程
POST https://open.feishu.cn/open-apis/calendar/v4/calendars/{calendar_id}/events
Body: {
"summary": "研发周会",
"start": { "date": null, "datetime": "2026-06-05T10:00:00", "timezone": "Asia/Shanghai" },
"end": { "date": null, "datetime": "2026-06-05T11:00:00", "timezone": "Asia/Shanghai" },
"attendees": [{ "type": "resource", "room_id": "omm_xxx123" }]
}事件订阅
- 事件:
calendar.calendar_event.changed_v4 - 配置:在飞书开放平台 → 事件订阅 → 添加回调 URL
- 我方需提供公网 HTTPS 回调端点
/feishu/event/callback - 推送示例:
{
"event": {
"calendar_id": "feishu_cal_xxx",
"event_id": "feishu_evt_xxx",
"change_type": "created"
}
}权限
| Scope | 用途 |
|---|---|
| calendar:calendar.event:create | 创建日程 |
| calendar:calendar.event:read | 确认创建结果 |
| calendar:calendar.event:delete | 取消会议 |
| calendar:calendar.event:subscribe | 事件订阅 |
11. 钉钉适配器
认证
GET https://oapi.dingtalk.com/gettoken?appkey=xxx&appsecret=xxx
→ { "access_token": "...", "expire_in": 7200 }注意事项
- 钉钉日历 API 版本较多,以官方最新文档为准
- 会议室资源通过钉钉"智能会议室"模块获取
- 钉钉的会议室资源 ID 格式为
rmxxx - 事件订阅需在钉钉开放平台配置 HTTP 回调
12. 工作量估算
| 模块 | 人天 | 说明 |
|---|---|---|
| 数据模型变更 | 1 | rooms 表扩展 + import_logs 表 |
| 飞书 Adapter | 2 | 创建/取消日程 + 初始同步 |
| 钉钉 Adapter | 2 | 同上 |
| 事件订阅服务 | 2 | 回调接收 + 忙闲状态同步 |
| 配置页面(前端 UI) | 2 | 三步向导 |
| 会议预约集成(后端) | 2 | 创建/取消会议时调 Adapter |
| 合计 | 11 人天 |
13. 待确认风险与缺口
13.1 钉钉:查询会议室列表需要 userId
钉钉的会议室列表接口为 GET /v1.0/calendar/users/{userId}/meetingRooms,比飞书多一个用户维度参数。
处理方式:AI 智能体在调用会议 App API 时,可将当前操作用户的 ID 传递给会议 App。会议 App 内部自行判断是否需要转换为钉钉的 userId / unionId。对于非用户场景(如定时任务、系统自动操作),可使用具有会议室管理权限的服务账号。
13.2 钉钉:查询忙闲有数量限制
根据项目前期调研(docs/03_接口文档/钉钉_会议API_兼容性分析报告.md),钉钉 freebusy 查询存在以下限制:
- 单次最多查询 5 个会议室(超过需分批)
- 查询时间范围最长 90 天
- 标准版 API 调用量限制 1 万次/月
影响:如果会议室数量大于 5,查询可用时需要分批并发。如果事件订阅不可用(见下条),依赖 freebusy 轮询时会受到 API 调用量限制。
以上限制来自前期调研文档。建议在开发前以钉钉官方最新文档为准,必要时实际调用验证。
13.3 钉钉:事件订阅能力待验证
飞书明确支持 calendar.calendar_event.changed_v4 事件,但钉钉是否支持日程变更的主动推送尚未确认。
如果钉钉支持事件订阅:架构与飞书一致,忙闲靠推送同步,架构图成立。 如果钉钉不支持事件订阅:需改为定时轮询 freebusy API(受限于 5 间/次 + 1 万次/月),架构图中的"事件订阅"箭头需替换为轮询机制。
开发前需要在钉钉开放平台确认日历相关的事件列表。
13.4 未来扩展第三方平台
CalendarAdapter 接口本身是抽象设计,新增平台只需实现 createEvent、cancelEvent、initialSync 三个方法,代码层面可扩展。
但配置页面(三步向导)有以下硬编码需要同步修改:
- 步骤一:平台选项是固定的(飞书/钉钉),新增需改前端
- 步骤三:会议室列表的设备列、平台路径列是飞书特化的;新平台可能有自己的特有字段
建议:如果未来对接 3 个以上平台,考虑将平台类型做成配置化(读取 Adapter 注册列表自动渲染表单),但目前两个平台的情况下硬编码足够。
