10 KiB
P1-04.8.1 国内企业日历:钉钉与企业微信交付报告
日期:2026-08-24
状态:已实现、已迁移、已完成自动化与 Chrome 登录态 UAT;真实厂商凭据联调待预发布环境完成
1. 阶段结论
在主要面向中国大陆企业的合同治理产品中,只提供 Google Calendar 和 Microsoft 365 会形成明显的采购与可用性缺口。P1-04.8.1 已增加两类国内企业日历源:
dingtalk:钉钉企业内部应用,使用 AppKey/AppSecret 获取短期访问令牌,以用户 UnionID 和 Calendar ID 查询日程,并保存厂商syncToken做增量同步;wecom:企业微信自建应用,使用 CorpID/CorpSecret 获取短期访问令牌,以cal_id查询日程。由于该接口没有等价增量游标,平台执行完整快照、生成稳定摘要并在本地计算差异。
两者均接入现有租户 SLA 工作日历、同步运行证据、定时治理 Worker、失败退避和租户隔离体系。未引入一套平行的“国内版日历子系统”,因此产品操作模型和运维边界保持一致。
2. 产品流程与体验
管理员在“治理配置 → 企业同步日历”中可以选择:日期清单、ICS、钉钉、企业微信、Google 或 Microsoft。
2.1 钉钉
表单要求:日历名称、Calendar ID、日历所属用户 UnionID、AppKey、AppSecret、手动/定时方式、间隔和是否启用增量游标。
创建流程:
- 先创建状态为
pending的租户日历源; - 服务端向钉钉固定令牌端点校验 AppKey/AppSecret,失败时不保存凭据;
- 校验成功后加密保存 AppSecret、access token,并将连接状态改为
connected; - 初次同步按时间窗口建立全量快照,后续携带加密保存的
syncToken; - 厂商返回游标失效类 400/410 时清除旧游标并重建全量快照,形成新的运行证据;
- 撤销连接会停用源并清除凭据、令牌、游标和当前物化事件,历史同步运行证据仍保留。
2.2 企业微信
表单要求:日历名称、cal_id、CorpID、CorpSecret、手动/定时方式和间隔。页面明确显示“完整日程快照 + 平台本地差异”,不展示增量游标开关,定时选项使用“定时同步”而不是“定时增量”。
该接口只允许读取由当前自建应用创建的日历,不能读取企业微信中的任意个人/共享日历;界面已明确提示这一产品限制。服务端分页拉取日程,以空 schedule_list 作为结束条件,排除 status 非正常的取消日程,只将正常的全天事件纳入 SLA 节假日物化,并按事件 ID、日期、标题和删除状态生成 SHA-256 快照摘要。每轮是 full 运行,但物化层仍计算新增、删除和未变化数量,因此不会把供应商不提供的能力包装成伪增量。
2.3 失败恢复
凭据校验失败时,已创建源保持 pending,不会假装连接成功,也不会静默删除管理员刚填写的源。管理员修正后可使用卡片上的“使用下方凭据重连”;按钮只有在下方选择同一提供方且凭据完整时才启用,防止钉钉/企微凭据误投。
3. 架构与安全实现
3.1 适配器边界
新增国内连接器模块,复用现有 CalendarIncrementalResult、远程事件物化和治理 Worker。所有外部 URL 固定在代码中:
- 钉钉:
api.dingtalk.com; - 企业微信:
qyapi.weixin.qq.com。
用户不能配置令牌端点或 API 主机;HTTP 重定向关闭,单页和总分页数设置安全上限,外部错误转换为有限长度的业务错误,不回传请求体或密钥。
3.2 凭据保护
- AppSecret/CorpSecret、短期 access token 和同步游标使用现有专用 Fernet 密钥加密;
- AppKey/CorpID 作为应用标识明文保存,secret 从不出现在查询响应;
- API 只返回
credential_configured、connection_method、连接状态和令牌过期时间; - 连接、重连和撤销只允许 Playbook 管理员;
httpx/httpcoreINFO 日志被提升到 WARNING,防止企业微信必须放在查询参数中的 CorpSecret 或 access token 被完整 URL 日志泄漏;- 当前本地 UAT 使用独立的非生产开发密钥;生产必须改用 Secret Manager、KMS/Vault 或只读 secret file,并在接入真实凭据前完成轮换与恢复演练。
3.3 数据与迁移
Alembic 版本:20260824_0060。
playbook_calendar_sources 新增:
principal_id:钉钉用户 UnionID;enterprise_client_id:AppKey 或 CorpID;enterprise_client_secret_ciphertext:加密后的应用 secret;enterprise_credentials_updated_at:凭据更新时间及索引。
现有 OAuth access-token、过期时间、同步游标、租约、失败次数和同步运行表继续复用。运行时 schema-check 已覆盖上述关键字段。
3.4 API 与前端
新增:
POST /api/v1/enterprise-playbook/governance/calendar-sources/{source_id}/enterprise-credentials/connect;POST /api/v1/enterprise-playbook/governance/calendar-sources/{source_id}/enterprise-credentials/revoke;- Contract Web 两个同源代理路由;
- 管理端连接状态、国内凭据表单、失败重连、撤销入口和供应商真实同步语义提示。
OpenAPI 已更新到 105 个路径、158 个 schema,0 个悬空 schema 引用;secret 字段标记为 writeOnly。
4. 验证结果
- 后端收集 352 项:全量回归 348 项通过、4 项按既有环境条件跳过、0 项失败;最后的企业微信官方语义修正后,受影响专项再次 15/15 通过;
- 国内日历适配器与 Playbook operations 专项:15/15 通过;
- 钉钉断言:令牌请求结构、固定主机、访问令牌头、
syncToken推进、全量到增量、加密持久化; - 企业微信断言:
corpid/corpsecret请求结构、cal_id空页终止、取消日程过滤、全天日程解析、完整快照摘要、强制full语义、撤销清理; - PostgreSQL 正向迁移到
20260824_0060 (head); - 全新 SQLite 完成
base → 0060 → 0059 → 0060往返; - 前端单元测试 45/45 通过;
- TypeScript 类型检查通过;
- Next.js 生产构建通过,生成/收集 135 个页面;
- Contract Web 3000、Contract API 8100、AI API 8101 和治理 Worker 均保持运行;
- Contract API 运行时日历集成检查为
ok。
5. Chrome 超级管理员 UAT
工作空间:Playbook 隔离 UAT
账号:uat.legal.20260823@example.test
页面:http://127.0.0.1:3000/admin/playbook?tab=operations
已确认:
- 页面显示
Enterprise governance · P1-04.8.1; - 钉钉入口显示 Calendar ID、UnionID、AppKey、只提交不回显的 AppSecret、增量游标和手动/定时增量;
- 企业微信入口显示
cal_id、CorpID、只提交不回显的 CorpSecret、手动/定时同步、仅支持应用自建日历的限制及完整快照说明; - 必填信息为空时“添加日历源”保持禁用;
- 实际 Chrome 视口无控件重叠、文本截断或不可达按钮;
- 生产构建后刷新仍保持登录态,数据加载完成,浏览器控制台 0 条错误。
本地没有用户企业的真实钉钉/企业微信应用凭据,因此本轮没有向厂商发送真实 secret、没有创建虚假成功连接,也没有对企业日历写入或删除任何数据。外部成功链路由隔离 MockTransport、数据库集成和 Worker 测试覆盖;真实授权范围、应用可见范围和企业安全设置仍必须在预发布租户验证。
6. 市场与架构评估
对中国大陆市场,这项扩展应视为可采购性补齐,而不是可选装饰。企业合同治理中的 SLA 截止日若不能读取客户日常使用的办公协同日历,管理员最终仍会回到手工维护节假日,自动化价值显著下降。钉钉和企业微信覆盖了两类主流企业协同生态,优先级高于继续深化 Google 特有体验。
继续使用“统一日历领域模型 + 提供方适配器 + 事务物化 + 治理 Worker”是合理架构:
- SLA 只依赖标准化全天日期,不把厂商对象泄露到合同/冲突领域;
- 提供方差异被限制在令牌和拉取适配器,避免前后端处处出现厂商条件分支;
- 短期令牌与同步状态和租户治理事实保持事务一致;
- 当前吞吐量不需要拆出独立日历微服务,未来可将调度迁到队列而不改变 API;
- 对企业微信保留真实的全量语义,牺牲一点网络效率换取可解释性和数据正确性,比虚构游标更符合合同治理产品的审计要求。
7. 上线门槛与下一步
正式开放真实租户前必须完成:
- 分别创建最小只读权限的钉钉内部应用和企业微信自建应用,确认日历所有者、应用可见范围、厂商安全策略和
cal_id/UnionID 的获取流程;企业微信需由该应用创建专用 SLA 日历; - 在两个隔离预发布租户验证:初次全量、增加/修改/取消全天事件、钉钉增量、游标失效回退、企微快照差异、令牌过期刷新、撤销、租户隔离;
- 将本地开发密钥替换为生产 secret file/KMS,并演练密钥轮换、备份恢复和旧密文不可用告警;
- 建立提供方错误码分类、连续失败告警、同步延迟 SLO、配额/限流监控和失败运行回放;
- 以真实节假日与调休日历做业务验收,明确“全天事件才进入 SLA”的产品规则,防止普通会议误作节假日;
- 收集目标客户办公生态数据,再决定 P1-04.8.2 是否优先增加飞书,而不是仅凭技术可接入性扩张提供方数量。
8. 设计依据
- 钉钉企业内部应用 access token:https://open.dingtalk.com/document/orgapp-server/obtain-the-access_token-of-an-internal-app
- 钉钉官方生成 Calendar SDK(ListEvents、syncToken、固定请求路径与访问令牌头):https://pkg.go.dev/github.com/alibabacloud-go/dingtalk/calendar_1_0
- 企业微信获取 access token:https://developer.work.weixin.qq.com/document/path/91039
- 企业微信获取日历下的日程列表:https://developer.work.weixin.qq.com/document/path/93648#%E8%8E%B7%E5%8F%96%E6%97%A5%E5%8E%86%E4%B8%8B%E7%9A%84%E6%97%A5%E7%A8%8B%E5%88%97%E8%A1%A8