五种卡片,没有第六种:钉钉 ActionCard 深链通知体系全景与落地

一天之内,智慧园区平台的钉钉通知从”一种告警卡片”扩成了”五张卡片”的体系:告警卡深链到 H5 办理页,人脸审批卡带上申请事由,视频申请审批卡深链到 PC 列表。上线前把全景彻底捋了一遍——线上代码里所有会发钉钉卡片的场景一共五种,再无其他。这篇记录体系的设计、深链的取舍、旁路发送的架构,以及那份”明天照着测就行”的排障手册。

五种卡片全景

# 卡片 触发动作 发给谁 落在哪端
1 告警事件卡 告警事件命中规则,规则通知通道含钉钉 规则配置的处理人 手机钉钉 → H5
2 人脸审批任务卡 H5 发起更换人脸申请,审批任务到达 审批人 手机钉钉 → H5
3 实时监控申请审批卡 PC 提交实时监控申请,审批任务到达 审批人 电脑钉钉 → PC
4 回放申请审批卡 PC 提交回放申请,审批任务到达 审批人 电脑钉钉 → PC
5 回放审批通过卡 回放流程全部走完 申请人 钉钉 → PC

上级提过”只要有站内信就发钉钉卡”——目前并没有实现,以上五种就是全部。范围要不要扩,等确认后再定(方案已备好:全系统站内信都经 Feign 收口到 smart-service 一处写入,钩在收口点即可全覆盖,不必改十几个发送点)。

这里有一个对测试极有价值的认知:审批类卡片(2/3/4)和站内信是同一个审批任务的两个通道。所以”收到了站内信但没收到钉钉卡”本身就是最强的定位线索——说明 BPM 流程没问题,问题只在钉钉通道这一段:配置、绑定或 iot。

五种卡片全景
图 1:五种卡片,三种深链形态——H5 办理页、H5 详情页、PC 列表页。

深链设计:每一种去向都有理由

告警卡分两档。 告警已生成工单 → 跳 myOrder/warningDetail?...&type=DEAL 办理形态,页面上有处理按钮;无工单(手工确认规则)→ 跳告警中心 AlarmDetail 确认页。为什么不能学站内信一律跳 warningDetail?因为无工单时 handleDetail(告警id) 查不到工单,status 为 undefined,”处理”按钮的显示条件永不成立——用户会落进一个半残页。站内信那个跳法本身就是个隐患,卡片没有照抄。时序上两个入口都先 startFlowSafely 回填 workOrderId 再推送,保证发卡时工单 id 一定有值。

深链:按钮直达目标页
图 2:深链的价值在于”直达”——跳过首页和消息列表,按钮落点就是那条待办本身。

人脸卡深链 H5 办理页,参数语义与站内信 bizJson 完全一致(id/detailId/taskId/type=DEAL),H5 免登闭环零改动——路由守卫存目标路径,login 页检测到 DingTalk UA 自动免登,再 replace 回目标。卡片还捎带了一个业务增强:H5 发起时新增申请事由选择(新入职录入/原照片模糊需更换/设备无法识别),随申请进流程快照并显示在卡片上。

视频申请卡深链 PC,而不是 H5——H5 根本没有这两类工单的处理页,统一导流 PC。PC 深链有三档登录态体验:浏览器有登录态 → 直落列表页;无登录态 → 登录页带 redirect=原路径,登录成功回跳列表页(依赖前端一个登录回跳的修复);手机钉钉点开体验仍差——内嵌浏览器无登录态、管理端表格小屏不可用,定位就是给坐在电脑前的审批人用,PC 端做钉钉免登工程量大,明确不做。

配置与降级:不配 ≠ 报错

四个 Nacos 键控制整个体系,缺省行为全部安全

服务 不配的效果
warning alarming.notify.h5-base-url 卡片照发,按钮退 iot 兜底链(行为同改动前)
bpm bpm.notify.h5-base-url 人脸卡整卡不发,站内信照常
bpm bpm.notify.pc-base-url 视频申请卡整卡不发
security security.notify.pc-base-url 卡片照发,按钮退兜底

三条铁律,测试前默念:**@Value 不热刷新,bpm 的两个键改了必须重启 bpm,没重启就全部静默不发且日志毫无痕迹;bpm-service.yml新建 dataId(bpm 此前没有专属配置),光改 warning/security 的旧文件没用;只有 businessType 9/4/5 发钉钉**,其他类型走到审批节点也不发——guard 拦在发送前,这是设计,不是 bug。

旁路发送架构:通知不能拖死审批

钉钉发送的全部路径遵循同一个范式:

1
2
3
4
5
6
7
flowchart LR
A["审批任务到达<br/>makePush"] --> B{"同步 guard<br/>类型 9/4/5?<br/>配置已配?<br/>接收人非空?"}
B -->|任一不满足| C["连事务回调都不注册<br/>站内信照常"]
B -->|通过| D["afterCommit<br/>注册事务回调"]
D --> E["有界线程池<br/>core1/max2/queue50<br/>拒绝=丢弃+日志"]
E --> F["iot sendActionCard<br/>超 100 人自动分批"]
F --> G["钉钉工作通知"]

几个关键决策:guard 前置到注册之前,无关审批类型的空任务连线程池都不进,防止挤满有界队列;事务提交后才发送,避免”卡片发了、事务回滚了”的假通知;有界池 + 丢弃策略,钉钉再慢也拖不垮审批主流程;iot 单次发送上限 100 人,超限自动分批,单批失败不影响其余。未绑定 ding_user_id 的接收人静默跳过(日志记”跳过”),多人时绑了的发、没绑的跳过,receiverCount 比审批人数少不是 bug。

旁路发送:主路让行,支路送达
图 3:审批主流程是主路,钉钉通知走支路——支路堵了,主路的车照跑。

卡片文案也做了结构化:无模板规则按 bizType 走三档默认文案(消防/AI/通用兜底),数据全取自告警记录零配置;级别数字码统一翻译中文;钉钉 markdown 的单 \n 不换行,渲染时把 \n 转成行尾两空格的硬换行——只转钉钉通道,短信和 WebSocket 弹窗仍用原始内容,防止尾随空格混进短信。这一版是用户点破的:别被”配置化”惯性带偏,级别、类型、位置都是库里的字段,后端自动拼即可,不要让前端配模板。

排障手册:一句口诀走天下

没收到卡,先 grep 服务日志”钉钉”二字,三种结果对应三条完全不同的路:

日志里看到 结论 去查
完全没有”钉钉”字样 没走到发送逻辑 配置没重启?dataId 没建?键名抄错?businessType 不对?部署包是旧的?
“跳过:接收人均未绑定钉钉 userid” 绑定问题 按站内信表查 receiver,再查 sys_user.ding_user_id(不要用 user_name 模糊猜)
“发送失败 errcode=” iot→钉钉侧问题 errcode 对照钉钉开放平台文档,改我们代码没用

还要注意调用链关系:bpm/warning/security 打”发送成功”只代表它把请求交给了 iot,iot 的 sendActionCard success 才代表钉钉服务端真正受理,两边日志时间戳对得上才算全链路通。

排障分诊:按日志关键字走三条路
图 4:无日志、绑定跳过、errcode——三种日志形态对应三条排查路径。

自测顺序:从易到难

上线后按卡 3/4 → 卡 5 → 卡 2 → 卡 1 的顺序测。视频申请卡最简单(纯界面操作零 SQL),先验证”bpm 配置 + 重启”这条最可能出问题的链路;告警卡留最后,它依赖规则配置、真实事件或接口注入,变量最多。开测前还有五分钟自检:Nacos 四个键、服务重启时间、库迁移是否执行、测试人 ding_user_id 绑定、PC 基座地址可达——这一步不做,后面全白测

收尾

这套体系最重要的设计取向是”旁路”二字:钉钉通知永远不影响审批主流程,配置缺失永远退到安全行为,发送失败永远只是日志里的一行 warn。五种卡片各司其职,边界清清楚楚——包括”没有第六种”这个边界本身。等上级确认范围后,站内信全量推钉钉的通用卡随时可以在这个范式上长出来。