渠道与投递
- Asset
- ADP-DLV-DOC-DOC-0002
- Feature
- dingtalk-direct-notifications
- 领域
- delivery-integrations
- 文档状态
- draft
- 文档 Owner
- DOC
- 事实源
- canonical-docs
- 更新
- 2026/10/14
钉钉连接、凭据与身份
DingTalkApplicationConnection
P0 每个 Workspace 最多一个连接:
corp_id_ciphertext/corp_id_hash;client_id、robot_code、credential_ref_key;generation;mode=disabled|active;- permission、visibility、health 与验证时间。
运行配置:
PLANE_DINGTALK_NOTIFICATIONS_ENABLED=0
PLANE_DINGTALK_SECRET_DIR=/run/secrets/plane/dingtalk
PLANE_NOTIFICATION_IDENTITY_KEY_FILE=/run/secrets/plane/notification-identity.key
credential_ref_key 只允许安全字符;解析后的 realpath 必须位于 PLANE_DINGTALK_SECRET_DIR 内。
AppSecret 使用 server-owned secret reference,数据库/API/UI 不保存或回显原文。身份字段使用独立 key file
加密,不从 Django SECRET_KEY 派生。key rotation 通过 connection generation 和明确 migration/invalidating
流程完成,不能静默用旧 key fallback。
provider host 固定为钉钉官方 API,不接受 host override 或普通用户输入的 Webhook URL。access token 只在 服务端按 connection generation 有界缓存,不写数据库、普通日志或浏览器响应。
connection verify 必须逐项确认:
- 应用凭据可用;
- 具备根据手机号查询用户权限;
- 机器人具备企业内单聊发送权限;
- 目标用户位于应用可见范围。
缺失 credential、权限、机器人能力或 visibility 时 readiness 明确 blocked,不能用旧 credential、群 Webhook、姓名/email 猜测或手工 userId 绕过。
DingTalkBindingRequest
绑定流程固定为:
User enters country_code + mobile
-> Plane normalizes E.164 and looks up the DingTalk userId by mobile
-> Plane sends an eight-digit one-time code by DingTalk direct message
-> pending_code_verification
-> User enters the code in Plane within 10 minutes
-> pending_admin_review
-> Workspace Admin rechecks that the mobile still resolves to the same userId and approves
-> approved_pending_test
-> non-sensitive enablement test message is provider accepted
-> create active DingTalkUserBinding
字段和状态规则:
- 服务端使用
phonenumbers规范化手机号为 E.164; - 手机号只临时加密保存,并保存不可逆 hash 与脱敏 hint;
- 查询结果的 DingTalk
userId加密保存,并记录 identity hash; - 验证码只保存按申请 ID 限域的 keyed hash,8 位数字、10 分钟有效、最多尝试 5 次;同一 Plane 用户或同一号码
在同一工作区每小时最多发起 3 次申请;验证码、完整号码和
userId不进入浏览器响应或普通日志; - 状态为
pending_code_verification、pending_admin_review、approved_pending_test、active、rejected、expired、conflict、unreachable; - 同一 Workspace/User 只允许一个 pending request;本人验证后管理员有 7 天审核,超时自动
expired; - 只有本人输入正确验证码后才允许管理员审核;验证码发送结果不确定时不自动重发,申请仍可用收到的验证码确认;
- Admin 审核时重新查询手机号,必须命中相同 identity hash;变化则
conflict; - approve/reject 使用新的 UUID
Idempotency-Key,同 key/同 digest replay 返回原结果,同 key/不同 digest 拒绝,并保存审计; - approve、reject 或 expire 后清除手机号密文,只保留 hash、hint 和审计元数据;
- 测试消息不含 Project、issue、review 或执行内容;只有 provider accepted 后才创建 active binding。
切换时一次性把尚无验证码证明的旧 pending_admin_review / approved_pending_test 申请标记为 expired,
清除临时手机号和身份密文;已有 active binding 不变。受影响成员需重新申请,不保留旧审核入口。
Admin 页面只展示 Plane 用户、脱敏手机号、验证码确认时间和服务端实时的身份匹配状态;不读取成员详情,
完整手机号和 userId 不返回浏览器。普通用户不能直接激活 binding。用户可以 unlink,Workspace Admin 可以 invalidate;系统不会按
姓名、邮箱、全局 User.mobile_number 或历史手机号自动重新绑定。
DingTalkUserBinding
active binding 至少绑定 Workspace、Plane user、connection generation、corp identity hash 与加密 provider identity,并用唯一约束阻止同一企业 identity 静默绑定多个 active Plane users。connection rotation、 用户离职、visibility 变化或 Admin invalidate 后旧 generation 不再可投递。
第一批投递
五类新增事件只生成 dingtalk_direct delivery,不新增站内视图、不新增邮件或自动切换渠道。
沿用既有单用户企业机器人发送、连接与绑定 generation、claim、sweeper、retry 和脱敏 attempt。
既有指派/提及的站内与邮件行为保持在其当前 owning 链路。
消息内容
每条消息含“通知原因 · Issue 编号”、短标题、关键变化及 Plane 链接。 验收提醒注明专业验收或测试验收;退回提醒指向已保存反馈;阻塞变化区分进入/解除; 回复指向对应评论;日期修改显示前后日期,空值显示未设置或已移除。 不发送完整评论/反馈、内部路径、附件、执行日志、代码 diff、token 或身份信息。 所有内容来自受权限校验的持久化业务事实,不接受任意模板、URL 或 command。
发送与恢复
事务提交后 on_commit 唤醒 routing;现有 sweeper 恢复 pending、deferred 和明确可重试失败。
Provider accepted 只表示服务商接受请求,不表示送达或已读。
结果明确的临时失败沿用现有有界重试;可能已发送而响应丢失、发送中 worker 丢失时进入
outcome_unknown,不能盲目重发。日志、诊断、attempt 均脱敏,不记录消息全文或凭据。
个人设置与免打扰
个人保持钉钉总开关、五类事件开关、当前时区及现有 quiet hours。 事件开关默认开启,但总开关、身份绑定、连接与项目规则仍是前置条件。 五类消息均遵守免打扰,延后至窗口结束后重新检查来源、权限与当前责任;过时消息取消。 不增加紧急绕过、补发历史消息、邮件 fallback 或钉钉内业务按钮。