2026-08-29 20:51:03 +08:00

166 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 由壹点通知中心接入指南
本文面向需要接入“由壹点”统一钉钉单聊通知的业务项目。业务项目只调用通知中心,不直接持有钉钉应用密钥,也不直接调用钉钉机器人接口。
## 1. 接入边界
- 通知中心负责:统一机器人身份、项目隔离、签名认证、幂等防重、异步投递、有限重试、已读状态查询,以及在启用免登时按既有 HTTPS 规则包装可用的跳转地址。
- 业务项目负责:确定通知触发时机、提供准确的钉钉 `userId` 或准确花名、生成稳定的幂等键、提交业务内容、保存通知中心返回的投递 ID。
- 钉钉 `AppKey`、`AppSecret`、`RobotCode` 只能保存在通知中心。
- 业务项目只能保存自己独有的项目 HMAC 密钥;禁止把密钥提交到 Git、发送到前端或写入浏览器环境变量。
## 2. 接入前只需提供项目名称
业务项目只需告诉通知中心一个用户能看懂的项目名称,例如“固定资产”。项目代码和项目密钥由通知中心自动生成;不需要提前提交负责人、使用场景、通知量、跳转域名、收件人方式或花名清单。
通知中心完成创建后,通过安全渠道提供:
- `YYD_NOTIFICATION_PROJECT_CODE`
- `YYD_NOTIFICATION_PROJECT_SECRET`
- `YYD_NOTIFICATION_CENTER_URL`
## 3. 业务项目环境变量
```dotenv
YYD_NOTIFICATION_CENTER_URL=https://sc.jiansun.vip/notification-center
YYD_NOTIFICATION_PROJECT_CODE=fixed_asset
YYD_NOTIFICATION_PROJECT_SECRET=由通知中心单独分配的项目密钥
```
这些变量只允许出现在业务后端的生产环境中。前端代码不得直接调用通知中心。
## 4. 发送接口
接口:`POST /v1/notifications`
```json
{
"idempotencyKey": "disposal_applied:53:approver-user-id:v1",
"recipientUserId": "钉钉userId",
"title": "资产报废待审批",
"content": "**申请时间:** 2026年7月16日 14:30\n\n**资产:** 摄影灯 A-102\n\n**申请人:** 张三",
"actionTitle": "立即审批",
"actionUrl": "https://asset.jiansun.vip/workflows/disposals/53"
}
```
只能提供花名时,将 `recipientUserId` 替换为准确 `recipientAlias`:
```json
{
"idempotencyKey": "disposal_applied:53:海棠:v1",
"recipientAlias": "海棠",
"title": "资产报废待审批",
"content": "**申请时间:** 2026年7月16日 14:30\n\n**资产:** 摄影灯 A-102",
"actionTitle": "立即审批",
"actionUrl": "https://asset.jiansun.vip/workflows/disposals/53"
}
```
字段约束:
| 字段 | 规则 |
| --- | --- |
| `idempotencyKey` | 必填,最长 160 字符;同一业务事件、同一收件人必须稳定不变 |
| `recipientUserId` | 与 `recipientAlias` 二选一;企业通讯录中的钉钉 `userId` |
| `recipientAlias` | 与 `recipientUserId` 二选一;通知中心会调用素材平台内部目录精确解析,最长 50 字符 |
| `title` | 必填,最长 100 字符;只写业务主题,不要添加 `【项目名】`,拼接项目名后的最终标题不能超过 120 字符 |
| `content` | 必填,Markdown 或纯文本都可以;正文最长 4800 字符,通知中心不会再自动插入标题或固定说明 |
| `actionTitle` | 可选,最长 20 字符;有 `actionUrl` 但未传时默认“查看详情” |
| `actionUrl` | 可选;非空时按业务项目提交值使用,通知中心不限制协议、公网/内网地址或项目域名白名单 |
通知中心会自动生成最终标题:
```text
【{项目名称}】{业务标题}
```
例如业务项目提交“资产报废待审批”,用户最终看到的是:
```text
【固定资产】资产报废待审批
```
通知中心不会再改写正文首行。业务项目提交什么正文,钉钉卡片就发送什么正文。
## 5. 请求签名
每次请求必须携带:
- `x-yyd-project-code`
- `x-yyd-timestamp`:Unix 秒
- `x-yyd-nonce`:16~100 位,每次请求重新生成
- `x-yyd-signature`:HMAC-SHA256 十六进制字符串
签名原文:
```text
timestamp\nnonce\nHTTP_METHOD\nPATH\nSHA256(rawBody)
```
注意:
- `PATH` 是通知中心内部接口路径,如 `/v1/notifications`,不包含公网反向代理前缀 `/notification-center`,也不包含域名和查询参数。
- 计算签名的 `rawBody` 必须与实际发送的请求正文逐字节一致。
- 服务器时间与通知中心相差不能超过五分钟。
- nonce 不能复用,即使上一次请求失败也要生成新的 nonce。
可直接复用的实现见 [`examples/node/notification-client.mjs`](../examples/node/notification-client.mjs)。
## 6. 幂等和多人通知
当前接口一次只接收一名收件人。向多人发送时,业务项目应逐人创建通知,每个人使用独立但稳定的幂等键。花名方式把下面的 `recipientUserId` 替换成准确花名:
```text
{事件类型}:{业务记录ID}:{recipientUserId}:{消息版本}
```
例如:
```text
survey_created:73a3f3e9:15891598262598724:v1
survey_created:73a3f3e9:16408293139985728:v1
```
相同 `projectCode + idempotencyKey`:
- 请求内容相同:返回原投递记录,不重复发送。
- 请求内容不同:返回 `409 IDEMPOTENCY_CONFLICT`,业务项目必须修正幂等键设计,不能通过随机换键绕过。
## 7. 接收返回值和查询状态
创建成功返回 HTTP `202`,表示通知中心已接受任务,不代表钉钉已经送达。业务项目应保存返回的 `id`,必要时调用:
```text
GET /v1/notifications/{id}
```
查询请求也必须签名,GET 请求正文为空字符串。状态含义和处理方式见 [错误与状态规范](./错误与状态规范.md)。
## 8. 钉钉 H5 免登是可选能力
普通通知接入不需要实现免登。只有用户点击卡片后必须自动进入业务登录态时,才申请开启项目 `login` 配置。
开启后业务项目还必须:
1. 提供精确的 HTTPS 回调地址和允许回跳的站内路径前缀。
2. 后端接收一次性票据,并通过签名接口兑换钉钉 `userId`。
3. 在创建本地 Session 前重新核验账号绑定、在职状态和业务权限。
通知中心票据只能证明钉钉身份,不能替代业务项目自身的授权判断。
## 9. 最小上线验证
接入完成后至少验证:
1. 本人收到单聊通知。
2. 另一名真实员工收到单聊通知。
3. 最终标题包含正确的项目来源。
4. 标题包含正确的项目来源,正文保持业务项目提交内容。
5. 使用相同幂等键重复请求不会重复发送。
6. 无效 `userId` 能得到明确失败状态。
7. 创建接口返回 `202` 后,最终状态能进入 `sent`、`unread` 或 `read`。
8. 日志、错误页和前端响应中均没有项目密钥。
完整清单见 [新项目接入清单](./新项目接入清单.md)。