YouYiDianNotificationCenter/docs/统一通知中心接入交接说明.md
2026-08-28 22:24:32 +08:00

25 KiB
Raw Blame History

由壹点统一通知中心接入操作手册

交付对象:第一次接入“由壹点”统一钉钉通知、不了解通知中心内部实现的业务项目负责人、后端开发和测试人员。

目标:按照本文从申请开通到生产验收逐步执行,不需要复制通知中心项目,也不需要持有钉钉应用凭证。

先看这里:你现在能不能开始接入

业务项目真正调用通知中心前,必须已经从通知中心维护人处取得以下三项配置:

YYD_NOTIFICATION_CENTER_URL=https://sc.jiansun.vip/notification-center
YYD_NOTIFICATION_PROJECT_CODE=通知中心正式分配的项目代码
YYD_NOTIFICATION_PROJECT_SECRET=通知中心正式生成的项目独立密钥

按下面的情况继续:

你现在的情况 下一步
三项配置都没有 先填写本文第五节的接入申请,不要自己猜项目代码或生成密钥
只有通知中心地址 仍未开通,等待通知中心维护人登记项目并发放代码和密钥
已有地址、项目代码和项目密钥 从本文第七节开始配置业务后端
业务系统只有员工花名 在接入申请中提交需使用的花名,由通知中心维护人登记花名映射
还不知道通知发给谁 先按本文第六节选择 userId 或已登记花名的发送方式
只想给一个人发送联调消息 准备该员工的真实钉钉 userId,或确认其花名已经登记;这不是接入密钥

请勿把“海棠”或“海棠的钉钉 userId”之类的收件人信息,与上述三项接入配置混为一谈。前三项用于证明“哪个项目在调用”;recipientAlias 或 recipientUserId 用于说明“这一次通知发给谁”。

一、通知中心是什么

统一通知中心是已经独立部署的后端服务。业务项目通过 HTTPS 接口提交通知,通知中心再使用统一的“由壹点”钉钉机器人向员工发送单聊。

业务项目后端
  → 由壹点统一通知中心
  → 钉钉机器人
  → 收件人员工

业务项目需要做的事情:

  • 申请自己的项目身份和项目密钥;
  • 在自己的后端增加一个很小的通知客户端;
  • 在业务事件发生时提供收件人、标题、正文和跳转地址;
  • 保存通知中心返回的投递 ID,必要时查询投递状态。

业务项目不需要做的事情:

  • 不复制、合并或部署通知中心整个仓库;
  • 不连接通知中心数据库;
  • 不申请或保存钉钉 AppKey、AppSecret、RobotCode;
  • 不直接调用钉钉机器人接口;
  • 不把项目密钥放到前端。

二、双方分别负责什么

角色 必须完成的工作
业务项目负责人 说明通知场景、收件人规则、跳转域名、预计发送量,指定开发和验收人员
业务项目开发 在业务后端保存配置,接入客户端,提供正确的收件人 userId,实现发送和必要的状态查询
通知中心维护人 审核申请、登记项目、生成独立密钥、配置跳转域名白名单、安全交付三项配置
测试或验收人员 使用真实员工验证发送、防重、状态和按钮跳转,不接触项目密钥

三、开始开发前先确认五件事

  1. 哪个业务事件触发通知,例如“审批单创建”或“任务即将到期”。
  2. 通知发给谁,业务系统能够提供钉钉 userId,还是只能提供花名。
  3. 用户点击通知后进入哪个生产 HTTPS 域名。
  4. 同一事件是否可能重复执行,以及如何使用稳定的幂等键防止重复通知。
  5. 点击通知后是否必须自动建立业务登录态。普通通知默认不启用免登。

如果业务系统只能提供花名,必须在接入申请中列出花名,由通知中心维护人将其登记到当前项目。未登记的姓名、手机号、邮箱和业务系统内部 ID 都不能直接作为通知收件人。

四、正确的接入顺序

必须按顺序执行,不要跳过项目开通直接编写生产调用:

第 1 步:业务项目填写接入申请
        ↓
第 2 步:通知中心维护人审核名称、代码和跳转域名
        ↓
第 3 步:通知中心维护人登记项目并生成独立密钥
        ↓
第 4 步:维护人安全交付 URL、PROJECT_CODE、PROJECT_SECRET
        ↓
第 5 步:业务后端配置三项环境变量并接入客户端
        ↓
第 6 步:使用一名真实员工完成首次发送和状态查询
        ↓
第 7 步:生产环境完成两名真实员工验收

业务项目不能自行宣布开通完成。没有收到正式项目代码和项目密钥时,说明通知中心还没有为该项目建立调用身份。

五、填写并提交接入申请

复制下面的模板,填写后交给通知中心维护人。业务项目不需要也不能自己提供项目密钥。

【统一通知中心接入申请】

项目中文名称:
建议 projectCode:
业务负责人及联系方式:
技术负责人及联系方式:

通知场景:
触发条件:
收件人规则:
收件人标识方式:钉钉 userId / 花名
钉钉 userId 的数据来源(能够提供时填写):
需要通知中心登记的花名清单(仅花名方式填写):
预计每日通知量:
预计峰值通知量:

生产跳转域名(只写域名,不写协议和路径):
测试跳转域名(如有):
是否需要点击后免登:否 / 是

计划联调日期:
计划上线日期:

不会填写时可参考以下完整示例:

【统一通知中心接入申请】

项目中文名称:产品开发平台
建议 projectCode:product_development
业务负责人及联系方式:张三 / 钉钉
技术负责人及联系方式:李四 / 钉钉

通知场景:产品任务即将到期
触发条件:任务距截止时间不足 24 小时且仍未完成
收件人规则:任务负责人
收件人标识方式:花名
钉钉 userId 的数据来源:无
需要通知中心登记的花名清单:海棠、无邪
预计每日通知量:30 条
预计峰值通知量:每分钟 5 条

生产跳转域名:product.example.com
测试跳转域名:product-test.example.com
是否需要点击后免登:否

计划联调日期:2026年9月1日
计划上线日期:2026年9月5日

登记字段要求:

字段 要求 示例
projectCode 2~40 位小写字母、数字、下划线或短横线;创建后不再修改 fixed_asset
项目名称 用户能看懂的简短名称,会显示在通知标题中 固定资产
跳转域名 标准 HTTPS 域名,不含协议、端口和路径;不使用通配符 asset.example.com
通知场景 写清触发条件、接收人和需要采取的动作 资产报废申请创建后通知审批人
收件人方式 优先使用钉钉 userId;只能提供花名时,提交需要登记的准确花名清单 花名:海棠、无邪
预计发送量 至少说明日常量和峰值 日均 50 条,峰值每分钟 10 条

通知中心维护人完成登记后,应通过安全渠道分别交付:

YYD_NOTIFICATION_CENTER_URL=https://sc.jiansun.vip/notification-center
YYD_NOTIFICATION_PROJECT_CODE=项目代码
YYD_NOTIFICATION_PROJECT_SECRET=项目独有密钥

收到三项配置后的检查:

  • URL 应为 https://sc.jiansun.vip/notification-center,末尾不要自行增加接口路径;
  • 项目代码必须与通知中心维护人登记的值完全一致,区分下划线和短横线;
  • 项目密钥应通过一对一安全渠道或密钥管理服务交付;
  • 不要把密钥转发到群聊、工单、截图、共享文档或 Git;
  • 没收到项目密钥时不要从其他项目复制,也不要使用文档示例值。

如果通知中心维护人还没有完成项目登记,应停在这里等待开通。业务项目可以先准备代码结构,但不能完成真实签名联调。

六、分清接入配置和每次发送的数据

6.1 三项静态接入配置

以下三项通常在一个环境中配置一次,由通知中心维护人交付:

配置 作用 能否多个项目共用
YYD_NOTIFICATION_CENTER_URL 通知中心公共访问地址 地址可以共用
YYD_NOTIFICATION_PROJECT_CODE 标识当前调用来自哪个业务项目 不能冒用其他项目
YYD_NOTIFICATION_PROJECT_SECRET 为每次请求生成签名 绝对不能共用

6.2 每次发送通知都要提供的业务数据

以下内容不是接入密钥,而是每条通知的具体内容:

数据 从哪里取得 示例
recipientUserId 业务项目的员工账号绑定或权威通讯录映射 海棠对应的钉钉 userId
recipientAlias 通知中心已为当前项目登记的准确花名 海棠
idempotencyKey 业务项目根据事件、记录、收件人和版本生成 task_due:53:user-id:v1
title 当前业务事件 产品任务即将到期
content 当前业务记录 截止时间、任务名称、负责人
actionUrl 当前业务记录的 HTTPS 页面 https://product.example.com/tasks/53

6.3 两种收件人方式怎么选择

钉钉 userId 是员工在当前企业通讯录中的稳定标识,不是姓名,也不是手机号、邮箱、工号或业务数据库主键。

能够取得 userId 的项目,按以下优先顺序取得:

  1. 已有员工账号绑定:读取当前收件人账号保存的 dingtalkUserId 或等价字段。
  2. 已有企业通讯录同步:从项目已经同步的钉钉员工记录中读取 userId。
  3. 仅做首次联调:由负责人确认一名真实员工,并通过公司现有的权威人员映射取得该员工 userId。
  4. 完全没有员工与钉钉身份对应关系:改用已登记花名,或者先补齐账号绑定或通讯录同步。

如果项目只能提供花名,使用 recipientAlias:

  • 花名必须由通知中心维护人预先登记,并绑定到经过确认的钉钉 userId;
  • 每个项目维护自己的可用花名,不会读取其他项目的映射;
  • 花名会进行空格、大小写和全半角归一化,但不会模糊猜测;
  • 未登记花名返回 RECIPIENT_ALIAS_NOT_FOUND,不会发送通知;
  • 同一项目不能登记两个归一化后相同的花名;
  • 同一请求只能传 recipientUserId 或 recipientAlias 其中一个。

如果正式业务永远只通知固定负责人,可以登记其花名或把 userId 保存在业务项目后端;如果收件人随业务记录变化,就从业务记录读取对应的已登记花名或 userId,不能把联调人员写死在代码里。

通知中心只解析当前项目已经登记的准确花名,不会根据任意姓名模糊猜人,也不会把某个测试人员设置成所有项目的默认收件人。

七、在业务项目中保存配置

三项配置只能保存在业务后端的环境变量或正式密钥管理服务中:

YYD_NOTIFICATION_CENTER_URL=https://sc.jiansun.vip/notification-center
YYD_NOTIFICATION_PROJECT_CODE=your_project_code
YYD_NOTIFICATION_PROJECT_SECRET=通过安全渠道取得的项目密钥

必须同时满足:

  • .env 已被 Git 忽略,仓库中只提交不含真实值的 .env.example。
  • 配置只注入后端进程,不能发送给浏览器或移动端。
  • 不使用 NEXT_PUBLIC_、VITE_ 等会公开到前端的变量名。
  • 日志、异常上报和调试页面不输出密钥、签名或完整请求头。
  • 测试和生产建议使用不同的项目身份和密钥;禁止多个项目共用一个密钥。

配置完成后,后端启动时应检查三项值都存在;缺少任意一项就明确报错并停止通知调用,不要静默跳过或改用其他项目的配置。

八、业务后端接入

8.1 Node.js 项目推荐直接复用现有客户端

Node.js 18 及以上项目可以复制:

初始化客户端:

import { YouYiDianNotificationClient } from './notification-client.mjs'

const notificationClient = new YouYiDianNotificationClient({
  baseUrl: process.env.YYD_NOTIFICATION_CENTER_URL,
  projectCode: process.env.YYD_NOTIFICATION_PROJECT_CODE,
  projectSecret: process.env.YYD_NOTIFICATION_PROJECT_SECRET,
})

使用钉钉 userId 发送一条通知:

const recipientUserId = employee.dingtalkUserId
if (!recipientUserId) throw new Error('该员工尚未绑定钉钉 userId,不能发送通知')

const delivery = await notificationClient.send({
  idempotencyKey: `asset_disposal:${disposal.id}:${recipientUserId}:v1`,
  recipientUserId,
  title: '资产报废待审批',
  content: [
    `**申请时间:** ${appliedAt}`,
    '',
    `**资产:** ${assetName}`,
    '',
    `**申请人:** ${applicantName}`,
    '',
    '> 请核对资产及配件明细后处理。',
  ].join('\n'),
  actionTitle: '立即审批',
  actionUrl: `https://asset.example.com/workflows/disposals/${disposal.id}`,
})

// 保存到业务记录,后续可查询实际投递状态。
await saveNotificationDeliveryId(disposal.id, recipientUserId, delivery.id)

业务项目只能提供花名时,改传已经登记的 recipientAlias,不要同时传 recipientUserId:

const recipientAlias = task.ownerAlias

const delivery = await notificationClient.send({
  idempotencyKey: `task_due:${task.id}:${recipientAlias}:v1`,
  recipientAlias,
  title: '产品任务即将到期',
  content: `**截止时间:** ${deadline}\n\n**任务:** ${task.title}`,
  actionTitle: '查看任务',
  actionUrl: `https://product.example.com/tasks/${task.id}`,
})

查询投递状态:

const latest = await notificationClient.getDelivery(deliveryId)
console.log(latest.status, latest.lastErrorCode)

在通知中心仓库内,可以运行以下命令验证示例客户端的签名和请求格式:

npm run test:example

8.2 其他开发语言按统一协议实现

发送接口:

POST /v1/notifications

每次请求必须包含以下请求头:

x-yyd-project-code
x-yyd-timestamp
x-yyd-nonce
x-yyd-signature
  • x-yyd-timestamp:当前 Unix 秒,业务服务器与通知中心的时间差不能超过五分钟。
  • x-yyd-nonce:每次 HTTP 请求重新生成的 16~100 位随机字符串。
  • x-yyd-signature:使用项目密钥计算的 HMAC-SHA256 小写十六进制字符串。

签名原文:

timestamp\nnonce\nHTTP_METHOD\nPATH\nSHA256(rawBody)

发送通知时的示意算法:

rawBody = JSON 序列化后的实际请求正文
bodyHash = SHA256(rawBody)
canonical = timestamp + "\n" + nonce + "\nPOST\n/v1/notifications\n" + bodyHash
signature = HMAC_SHA256_HEX(projectSecret, canonical)

必须注意:

  • 计算签名的 rawBody 必须与网络中真正发送的正文逐字节一致。
  • PATH 使用内部接口路径 /v1/notifications,不包含域名、查询参数和公网反向代理前缀 /notification-center。
  • GET 查询请求的正文是空字符串,仍需对空正文的 SHA-256 值签名。
  • 每次重试使用新的 timestamp 和 nonce,但业务幂等键保持不变。

九、发送内容和字段规则

请求正文:

{
  "idempotencyKey": "asset_disposal:53:ding-user-id:v1",
  "recipientUserId": "员工的钉钉userId",
  "title": "资产报废待审批",
  "content": "**申请时间:** 2026年8月5日 14:30\n\n**资产:** 摄影灯 A-102\n\n**申请人:** 张三",
  "actionTitle": "立即审批",
  "actionUrl": "https://asset.example.com/workflows/disposals/53"
}

花名方式只替换收件人字段:

{
  "idempotencyKey": "asset_disposal:53:海棠:v1",
  "recipientAlias": "海棠",
  "title": "资产报废待审批",
  "content": "**申请时间:** 2026年8月5日 14:30\n\n**资产:** 摄影灯 A-102",
  "actionTitle": "立即审批",
  "actionUrl": "https://asset.example.com/workflows/disposals/53"
}
字段 必填 规则
idempotencyKey 是 最长 160 字符;同一业务事件和同一收件人必须稳定不变
recipientUserId 二选一 企业通讯录中的钉钉 userId;与 recipientAlias 只能传一个
recipientAlias 二选一 当前项目已登记的准确花名,最长 50 字符;与 recipientUserId 只能传一个
title 是 只写业务主题,最长 100 字符;不要自己添加项目名或“由壹点”前缀
content 是 Markdown 业务正文;不要自己写首行标题;字段之间保留空行
actionTitle 否 最长 20 字符,建议写明确动作,例如“立即审批”
actionUrl 是 标准 443 端口的完整 HTTPS 地址,域名必须已登记在项目白名单中

通知中心会根据项目配置统一生成:

【项目名称】业务标题

例如业务项目提交“资产报废待审批”,用户最终看到的是“【固定资产】资产报废待审批”。完整文案规则见消息规范。

十、幂等防重和多人通知

推荐的幂等键格式:

{事件类型}:{业务记录ID}:{recipientUserId或recipientAlias}:{消息版本}

示例:

asset_disposal_created:53:16408293139985728:v1

规则:

  • 同一业务事件重复执行时,必须复用相同幂等键。
  • 相同 projectCode + idempotencyKey 且内容相同,只返回原投递记录,不会重复通知。
  • 相同幂等键但内容不同会返回 409 IDEMPOTENCY_CONFLICT,必须修复业务逻辑,不能随机换键绕过。
  • 确实需要发送一条内容更新后的新通知时,经过业务确认后提升消息版本,例如从 v1 改为 v2。

当前接口一次发送给一名员工。向多人通知时逐人调用,并把每个人的钉钉 userId 或已登记花名放入幂等键:

for (const recipient of recipients) {
  await notificationClient.send({
    ...message,
    recipientUserId: recipient.dingtalkUserId,
    idempotencyKey: `survey_created:${survey.id}:${recipient.dingtalkUserId}:v1`,
  })
}

批量发送时应设置合理的并发上限,记录每名收件人的结果;不能因为其中一人失败就反复重发已经受理的其他通知。

十一、正确理解返回值和投递状态

创建接口常见返回:

HTTP 状态 含义 业务项目处理
202 新通知已进入队列 保存返回的投递 id;不能当作已经送达
200 相同幂等请求已经存在 按成功受理处理,继续使用返回的原投递 id
401 项目、时间或签名错误 停止自动重试,检查配置、服务器时间和签名实现
409 nonce 重复或幂等内容冲突 nonce 重复时换新 nonce;内容冲突必须修复业务逻辑
422 字段、长度或跳转地址不合法 修正请求内容后再提交
500 通知中心内部异常 保留原幂等键,短暂退避后有限重试并告警

查询接口:

GET /v1/notifications/{deliveryId}

投递状态:

状态 含义 业务项目应该做什么
pending 已受理,等待发送 等待,不要重新创建通知
sending 正在发送 等待,不要重新创建通知
retrying 通知中心正在有限重试 等待,由通知中心处理
sent 钉钉已接受发送请求 可继续查询已读状态
unread 已投递但尚未阅读 按业务需要展示或记录
read 收件人已经阅读 终态
failed 已确定失败,或结果不确定而停止自动重试 记录错误并通知负责人处理

完整错误码和重试边界见错误与状态规范。

十二、业务端重试和日志要求

重试原则:

  1. 网络连接失败或 HTTP 500 时可以短暂退避并有限重试。
  2. 每次 HTTP 重试生成新的 timestamp 和 nonce。
  3. 所有重试必须复用原 idempotencyKey。
  4. 收到 HTTP 200 / 202 后不再创建新通知,改为查询原投递 ID。
  5. HTTP 401 / 409 / 422 默认不自动重试,先修正配置或业务数据。
  6. DELIVERY_OUTCOME_UNKNOWN 必须先到钉钉侧人工核验,再决定是否补发,避免员工收到重复通知。

业务日志建议记录:

  • 业务事件 ID;
  • idempotencyKey;
  • 通知中心返回的投递 id;
  • 收件人钉钉 userId;
  • HTTP 状态、投递状态和 lastErrorCode;
  • 请求耗时和发生时间。

禁止记录:

  • 项目密钥;
  • x-yyd-signature;
  • 钉钉应用密钥和机器人凭证;
  • 免登码和一次性登录票据;
  • 消息中不必要的个人敏感信息。

十三、钉钉 H5 免登是可选项

普通通知只需要“发送 + 查询”接口,不需要接入免登。

只有用户点击卡片后必须自动进入业务登录态时,项目负责人才能申请开启免登。开启后还需要:

  1. 向通知中心登记精确的 HTTPS 回调地址和允许回跳的站内路径前缀。
  2. 业务后端接收一次性登录票据。
  3. 使用本项目的 HMAC 身份调用 /v1/auth/tickets/redeem 兑换钉钉 userId。
  4. 在创建本地 Session 前,重新核验员工绑定、在职状态和业务权限。
  5. 验证票据过期、重复使用和跨项目使用都会失败。

通知中心票据只能证明钉钉身份,不能替代业务项目自己的权限判断。免登的完整协议见接入指南。

十四、联调与上线验收

14.1 开发自测

  • 三项配置只存在于后端,仓库和前端产物中没有真实密钥。
  • 服务端时间已同步,请求签名路径不包含 /notification-center 反向代理前缀。
  • 使用本人真实钉钉 userId 能创建通知并取得投递 id。
  • 如使用花名,已登记花名能解析并发送,未登记花名会被明确拒绝。
  • 能使用投递 id 查询状态。
  • 通知标题自动包含正确项目名称,没有重复前缀或双标题。
  • 按钮进入已登记的 HTTPS 域名和正确业务记录。
  • 重复执行同一业务事件不会产生第二条通知。
  • 无效钉钉 userId 最终进入明确失败状态。

14.2 生产验收

生产上线不能只验证健康接口,至少完成:

  • 项目负责人收到一条真实业务通知。
  • 另一名真实员工收到一条真实业务通知。
  • 两人看到的项目名称、业务内容和按钮都正确。
  • 重复提交同一请求没有重复消息。
  • 能从业务日志关联“业务事件 → 幂等键 → 投递 ID → 最终状态”。
  • 日志、错误页、浏览器请求和监控平台均未出现项目密钥。
  • 已明确通知失败由谁处理,以及如何联系通知中心维护人。
  • 如启用免登,已额外验证员工身份、权限、票据过期和一次性消费。

验收完成后,双方保存以下记录:

项目名称:
projectCode:
业务项目版本或 Git 提交:
通知中心版本或 Git 提交:
生产验证时间:
验证通知的业务事件 ID:
验证通知的投递 ID:
发送结果:
点击跳转结果:
业务负责人:
技术负责人:
通知中心维护人:
遗留问题:

十五、常见接入错误

错误做法 正确做法
前端直接调用通知中心 只允许业务后端调用
从其他项目复制项目密钥 每个项目由通知中心单独生成密钥
把任意姓名直接当花名发送 只使用当前项目已由通知中心登记的准确花名
同时发送 recipientUserId 和 recipientAlias 两种收件人方式只能选择一种
标题自己添加“【项目名】” 只提交业务标题,由通知中心统一拼接
签名路径包含公网 /notification-center 前缀 签名内部路径 /v1/notifications
先序列化签名,发送时再次序列化成不同正文 对实际发送的同一份 raw body 签名
每次重试随机生成新的幂等键 重试沿用原幂等键,只更换 nonce
把 HTTP 202 当成已送达 保存投递 ID 并查询最终状态
多人共用一个幂等键 每名收件人使用独立幂等键
收到超时就无限重发 有限重试;结果不确定时先人工核验
只测试自己,不测试第二名员工 生产至少完成双人真实验收

十六、完成标准

只有同时满足以下条件,才算正式接入完成:

  1. 项目已经在通知中心登记,并使用独立项目密钥和域名白名单。
  2. 通知由业务后端发出,前端和 Git 仓库中没有任何密钥。
  3. 业务项目保存投递 ID,正确处理幂等、状态查询和有限重试。
  4. 通知内容符合消息规范。
  5. 已完成两名真实员工的生产通知、跳转和防重验收。
  6. 已记录双方负责人、上线版本、验证证据和故障处理方式。

更细的逐项门禁可继续使用新项目接入清单。