docs: document notification card styling and integration

This commit is contained in:
WuYongJie 2026-08-31 09:42:28 +08:00
parent 34c57340c6
commit 7d5d446a8b
6 changed files with 107 additions and 33 deletions

View File

@ -9,7 +9,7 @@
- [统一通知中心接入操作手册](docs/统一通知中心接入交接说明.md):可直接交给其他项目负责人的完整接入流程、代码入口与验收标准。 - [统一通知中心接入操作手册](docs/统一通知中心接入交接说明.md):可直接交给其他项目负责人的完整接入流程、代码入口与验收标准。
- [通知中心新项目开通操作手册](docs/通知中心新项目开通操作手册.md):仅供通知中心维护人登记项目、生成密钥、安全交付和验收使用。 - [通知中心新项目开通操作手册](docs/通知中心新项目开通操作手册.md):仅供通知中心维护人登记项目、生成密钥、安全交付和验收使用。
- [接入指南](docs/接入指南.md):项目登记、环境变量、签名、发送、多人通知和上线验证。 - [接入指南](docs/接入指南.md):项目登记、环境变量、签名、发送、多人通知和上线验证。
- [消息规范](docs/消息规范.md):标题、正文、状态和按钮的统一写法。 - [消息规范](docs/消息规范.md):固定项目抬头、ActionCard / Markdown 模板选择、正文排版、按钮跳转及完整请求示例。
- [错误与状态规范](docs/错误与状态规范.md):HTTP 返回、投递状态、错误码和重试边界。 - [错误与状态规范](docs/错误与状态规范.md):HTTP 返回、投递状态、错误码和重试边界。
- [新项目接入清单](docs/新项目接入清单.md):从申请到生产验证的逐项门禁。 - [新项目接入清单](docs/新项目接入清单.md):从申请到生产验证的逐项门禁。
- [Node.js 客户端示例](examples/node/README.md):可以直接复制到业务后端的无依赖实现。 - [Node.js 客户端示例](examples/node/README.md):可以直接复制到业务后端的无依赖实现。
@ -90,7 +90,7 @@
- 业务 `/v1/*`:64 KB - 业务 `/v1/*`:64 KB
- 管理 `/admin/*`:256 KB - 管理 `/admin/*`:256 KB
普通业务接入接口 `POST /v1/notifications`、`GET /v1/notifications/:id`、`GET /v1/notifications` 与原签名协议保持兼容。消息字段仍是 `title / content / actionTitle / actionUrl` 四件套:通知中心只统一给标题加上 `【项目名称】` 来源前缀,正文、按钮文案和跳转地址都保持业务项目提交值。 普通业务接入接口 `POST /v1/notifications`、`GET /v1/notifications/:id`、`GET /v1/notifications` 与原签名协议保持兼容。消息字段仍是 `title / content / actionTitle / actionUrl` 四件套:通知中心生成 `【项目名称】业务标题`,并将其作为 Markdown 标题固定显示在钉钉可见正文顶部;业务项目决定其后的正文排版、按钮文案和跳转目标。有 `actionUrl` 时使用原生 ActionCard,无地址时使用无按钮的普通 Markdown 消息。样式示例和总显示长度说明见[消息规范](docs/消息规范.md)。
生产环境建议只把容器端口绑定到 `127.0.0.1`,再由 Nginx 提供独立 HTTPS 域名。素材库和固定资产可能位于不同主机,因此不能把业务项目配置成通知中心的容器内网地址。 生产环境建议只把容器端口绑定到 `127.0.0.1`,再由 Nginx 提供独立 HTTPS 域名。素材库和固定资产可能位于不同主机,因此不能把业务项目配置成通知中心的容器内网地址。

View File

@ -65,7 +65,7 @@ YYD_NOTIFICATION_PROJECT_SECRET=由通知中心单独分配的项目密钥
| `recipientUserId` | 与 `recipientAlias` 二选一;企业通讯录中的钉钉 `userId` | | `recipientUserId` | 与 `recipientAlias` 二选一;企业通讯录中的钉钉 `userId` |
| `recipientAlias` | 与 `recipientUserId` 二选一;通知中心会调用素材平台内部目录精确解析,最长 50 字符 | | `recipientAlias` | 与 `recipientUserId` 二选一;通知中心会调用素材平台内部目录精确解析,最长 50 字符 |
| `title` | 必填,最长 100 字符;只写业务主题,不要添加 `【项目名】`,拼接项目名后的最终标题不能超过 120 字符 | | `title` | 必填,最长 100 字符;只写业务主题,不要添加 `【项目名】`,拼接项目名后的最终标题不能超过 120 字符 |
| `content` | 必填,Markdown 或纯文本都可以;正文最长 4800 字符,通知中心不会再自动插入标题或固定说明 | | `content` | 必填,Markdown 或纯文本都可以;输入最长 4800 字符,发送时在前面加上固定项目抬头;总显示长度见下文 |
| `actionTitle` | 可选,最长 20 字符;有 `actionUrl` 但未传时默认“查看详情” | | `actionTitle` | 可选,最长 20 字符;有 `actionUrl` 但未传时默认“查看详情” |
| `actionUrl` | 可选;非空时按业务项目提交值使用,通知中心不限制协议、公网/内网地址或项目域名白名单 | | `actionUrl` | 可选;非空时按业务项目提交值使用,通知中心不限制协议、公网/内网地址或项目域名白名单 |
@ -81,7 +81,13 @@ YYD_NOTIFICATION_PROJECT_SECRET=由通知中心单独分配的项目密钥
【固定资产】资产报废待审批 【固定资产】资产报废待审批
``` ```
通知中心不会再改写正文首行。业务项目提交什么正文,钉钉卡片就发送什么正文。 发送给钉钉的可见正文固定为 `### 【项目名称】业务标题`、两个换行,再接业务项目的 `content`。业务项目不需要在正文重复抬头,也不要把抬头加入待签名的请求正文;固定抬头由通知中心在发送时生成。
有非空 `actionUrl` 时使用钉钉原生 `sampleActionCard`,底部显示 `actionTitle` 按钮;不传或留空时使用 `sampleMarkdown`,没有按钮,卡片外框和宽度可能不同。仅填写 `actionTitle` 不会生成按钮。
要复现“固定资产待审批”那种完整卡片,按上面的请求提供 Markdown 正文和跳转地址;正文可用 `**字段名:**` 加粗、空行分段、`>` 显示处理提示。业务方不需要提交钉钉的 `msgKey` 或 `msgParam`。
当前发送实现将抬头与正文合并后的钉钉 `text` 截取前 4800 字符,超长正文尾部可能被截断。完整格式、可复制示例和按钮行为见[消息规范](./消息规范.md)。
## 5. 请求签名 ## 5. 请求签名
@ -155,8 +161,8 @@ GET /v1/notifications/{id}
1. 本人收到单聊通知。 1. 本人收到单聊通知。
2. 另一名真实员工收到单聊通知。 2. 另一名真实员工收到单聊通知。
3. 最终标题包含正确的项目来源。 3. 钉钉聊天中的可见正文顶部显示正确的项目名称和业务标题,且没有重复抬头。
4. 标题包含正确的项目来源,正文保持业务项目提交内容。 4. 有跳转地址时收到 ActionCard,正文排版、按钮文字和点击目标正确;无跳转地址时收到无按钮的 Markdown 消息。
5. 使用相同幂等键重复请求不会重复发送。 5. 使用相同幂等键重复请求不会重复发送。
6. 无效 `userId` 能得到明确失败状态。 6. 无效 `userId` 能得到明确失败状态。
7. 创建接口返回 `202` 后,最终状态能进入 `sent`、`unread` 或 `read`。 7. 创建接口返回 `202` 后,最终状态能进入 `sent`、`unread` 或 `read`。

View File

@ -24,18 +24,19 @@ YYD_NOTIFICATION_PROJECT_SECRET=通知中心生成的项目密钥
- [ ] `recipientUserId` 与 `recipientAlias` 二选一。 - [ ] `recipientUserId` 与 `recipientAlias` 二选一。
- [ ] 花名由通知中心从 `gongxiangp2` 花名册解析;找不到对应钉钉 `userId` 时不会推送。 - [ ] 花名由通知中心从 `gongxiangp2` 花名册解析;找不到对应钉钉 `userId` 时不会推送。
- [ ] 业务项目提交标题和正文;通知中心只给标题增加 `【项目名称】` 前缀。 - [ ] 业务项目只提交业务标题和正文,不重复抬头;通知中心在可见正文顶部固定显示 `【项目名称】业务标题`。
- [ ] 按钮文字和跳转地址均为可选,由业务项目决定。 - [ ] 按钮文字和跳转地址均为可选,由业务项目决定;需要 ActionCard 时提供非空 `actionUrl`,无地址时发送无按钮的普通 Markdown 消息。
- [ ] 需要加粗、分段、引用等样式时,在 `content` 中使用 Markdown,参照[消息规范](./消息规范.md)的完整示例。
- [ ] 跳转地址按业务项目提交值使用,不限制 HTTPS、公网域名或项目域名白名单。 - [ ] 跳转地址按业务项目提交值使用,不限制 HTTPS、公网域名或项目域名白名单。
- [ ] 保存通知中心返回的投递 ID;`202` 只表示已接收,不表示已经送达。 - [ ] 保存通知中心返回的投递 ID;`202` 只表示已接收,不表示已经送达。
## 4. 上线验证 ## 4. 上线验证
- [ ] 正常收件人能够收到通知,标题中的项目名称正确。 - [ ] 正常收件人能够收到通知,聊天卡片可见抬头中的项目名称正确,正文格式和所选消息模板一致。
- [ ] 花名不存在时返回明确错误,且没有产生投递记录。 - [ ] 花名不存在时返回明确错误,且没有产生投递记录。
- [ ] 相同项目和幂等键不会产生重复通知。 - [ ] 相同项目和幂等键不会产生重复通知。
- [ ] 一个项目不能查询另一个项目的投递记录。 - [ ] 一个项目不能查询另一个项目的投递记录。
- [ ] HTTP、HTTPS 或内网跳转地址按业务项目提交内容进入通知。 - [ ] 需要跳转时,ActionCard 的按钮文字正确,点击能进入业务项目指定的 HTTP、HTTPS 或内网页面。
- [ ] 发送失败时能查询到明确状态和错误原因。 - [ ] 发送失败时能查询到明确状态和错误原因。
以上通过即可完成接入,不增加其它前置表单或审批门槛。 以上通过即可完成接入,不增加其它前置表单或审批门槛。

View File

@ -1,37 +1,87 @@
# 消息规范 # 消息规范
通知中心只统一一件可见样式:在业务标题前增加项目名称,让收件人知道通知来源。 本文面向外部业务项目负责人和开发人员。通知使用钉钉原生消息模板,不是 HTML/CSS 网页;卡片外框、宽度、颜色和按钮外观由钉钉客户端渲染。
通知中心固定展示项目来源和业务标题;业务项目决定正文排版、按钮文字及跳转目标。
## 1. 卡片类型和跳转
| 外部项目提交内容 | 通知中心使用的模板 | 收件人看到的效果 |
| --- | --- | --- |
| `actionUrl` 非空 | `sampleActionCard` | 原生 ActionCard,底部显示一个跳转按钮 |
| `actionUrl` 未传或为空 | `sampleMarkdown` | 普通 Markdown 消息,没有底部按钮,外框和宽度可能与 ActionCard 不同 |
要实现“固定资产待审批”那种带按钮的卡片,提供 Markdown 正文和非空 `actionUrl` 即可;`actionTitle` 建议写明确动作,例如“立即审批”,未填写时默认“查看详情”。只填按钮文字但不填地址,不会生成按钮。
业务项目仍调用通知中心的 `POST /v1/notifications`,无需提交 `msgKey`、`msgParam`、`singleTitle` 或 `singleURL`,也无需直接调用钉钉 API。通知中心会把 `actionTitle` 映射为钉钉的 `singleTitle`,把实际发送的跳转地址映射为 `singleURL`。
按钮目标由业务项目的 `actionUrl` 决定。HTTP、HTTPS、内网 IP/域名和端口均可使用,无需为普通通知登记域名白名单;接收人的设备仍须能够访问该业务地址。只有启用钉钉 H5 免登且目标满足既有 HTTPS 登录规则时,通知中心才会包装到免登入口,最终仍回到指定业务页面。
## 2. 固定项目抬头
业务项目的 `title` 只填写业务主题,不要自行拼接项目名称。通知中心从当前鉴权项目的配置中取得名称,生成:
```text ```text
【项目名称】业务标题 【项目名称】业务标题
``` ```
例如外部项目提交: 发送给钉钉时,不只设置独立的 `title` 字段,还会在可见正文顶部加入 Markdown 标题,两种消息模板都如此:
```markdown
### 【项目名称】业务标题
业务项目提交的 content
```
请勿在 `content` 中再重复同一条项目抬头或业务标题,以免出现双标题。除此之外,通知中心不添加固定时间、业务字段顺序或其它正文模板。
## 3. 完整 ActionCard 请求示例
以下为发送接口的 JSON 正文示例。花名、业务记录和地址均为示例值,接入时替换为本项目实际内容;接口鉴权与签名见[接入指南](./接入指南.md#5-请求签名)。
```json ```json
{ {
"title": "脚本执行失败", "idempotencyKey": "asset_disposal:53:海棠:v1",
"content": "数据同步任务执行失败,请查看日志。", "recipientAlias": "海棠",
"actionTitle": "查看详情", "title": "资产报废待审批",
"actionUrl": "http://192.168.58.20:8080/jobs/123" "content": "**申请时间:** 2026年8月31日 15:58\n\n**资产:** 电脑桌(JS-CPU-0704)\n\n**申请人:** 示例员工\n\n**报废原因:** 资产损坏,申请报废。\n\n> 请核对资产及配件明细后处理。",
"actionTitle": "立即审批",
"actionUrl": "http://asset.example.com/workflows/disposals/53"
} }
``` ```
最终标题为: 假设管理面板中的项目名称是“固定资产”,卡片正文为:
```text ```markdown
【脚本监控】脚本执行失败 ### 【固定资产】资产报废待审批
**申请时间:** 2026年8月31日 15:58
**资产:** 电脑桌(JS-CPU-0704)
**申请人:** 示例员工
**报废原因:** 资产损坏,申请报废。
> 请核对资产及配件明细后处理。
``` ```
正文、按钮文字和跳转地址均由业务项目决定,通知中心不再插入固定标题、时间、字段顺序或排版模板。 底部按钮显示“立即审批”,点击进入 `http://asset.example.com/workflows/disposals/53`。`**字段名:**` 表示加粗,空行用于分段,`>` 用于引用提示;这些只是可复用的示例排版,不要求所有业务项目都使用相同字段。
字段规则: ## 4. 字段与长度
| 字段 | 规则 | | 字段 | 规则 |
| --- | --- | | --- | --- |
| `title` | 必填,业务项目只写业务标题;通知中心自动增加项目名称前缀 | | `title` | 必填,最长 100 字符;拼接项目名称后的最终标题最长 120 字符 |
| `content` | 必填,纯文本或 Markdown 均可;按业务项目内容发送 | | `content` | 必填,输入最长 4800 字符;纯文本或 Markdown 均可,显示在固定项目抬头之后 |
| `actionTitle` | 可选;有跳转地址但未填写时默认“查看详情” | | `actionTitle` | 可选,最长 20 字符;有跳转地址但未填写时默认“查看详情” |
| `actionUrl` | 可选;按业务项目提交值使用,不限制协议、公网/内网地址或项目域名白名单 | | `actionUrl` | 可选;按业务项目提交值使用,不限制协议、公网/内网地址或项目域名白名单 |
业务项目应自行保证内容清楚、按钮可理解、跳转地址适合自己的使用环境。通知中心只负责可靠接收、项目隔离、分发和错误返回。 当前发送实现会将“`### ` + 最终标题 + 两个换行 + `content`”组成的钉钉 `text` 截取前 4800 字符。因此抬头也占显示额度,超长正文尾部可能被截断;业务详情宜放在跳转页面。业务项目仍对自己提交的原始 JSON 签名,无需预先插入抬头,保存的业务正文也不会因此改写。
## 5. 样式验收
- 确认钉钉聊天中的可见正文顶部有正确的 `【项目名称】业务标题`,不是只看消息摘要或接口返回值。
- 如需 ActionCard,使用非空 `actionUrl` 发送,确认正文格式、底部按钮和实际点击目标。
- 如果选择不提供跳转地址,应预期收到没有按钮的普通 Markdown 消息,而非相同外框的 ActionCard。
- 已发出的旧消息不会随代码、项目配置或测试页变化而自动更新。测试新的样式时创建一条新的测试事件并使用新幂等键;重试同一业务事件时仍复用原幂等键,不能借此绕过防重。

View File

@ -339,8 +339,8 @@ signature = HMAC_SHA256_HEX(projectSecret, canonical)
| `recipientUserId` | 二选一 | 企业通讯录中的钉钉 `userId`;与 `recipientAlias` 只能传一个 | | `recipientUserId` | 二选一 | 企业通讯录中的钉钉 `userId`;与 `recipientAlias` 只能传一个 |
| `recipientAlias` | 二选一 | 通知中心会通过素材平台内部目录精确解析的准确花名,最长 50 字符;与 `recipientUserId` 只能传一个 | | `recipientAlias` | 二选一 | 通知中心会通过素材平台内部目录精确解析的准确花名,最长 50 字符;与 `recipientUserId` 只能传一个 |
| `title` | 是 | 只写业务主题,最长 100 字符;不要自己添加项目名或“由壹点”前缀 | | `title` | 是 | 只写业务主题,最长 100 字符;不要自己添加项目名或“由壹点”前缀 |
| `content` | 是 | Markdown 或纯文本业务正文;通知中心不会再自动插入标题或固定说明 | | `content` | 是 | Markdown 或纯文本业务正文,输入最长 4800 字符;通知中心在前面加固定项目抬头,总显示长度见下文 |
| `actionTitle` | 否 | 最长 20 字符,建议写明确动作,例如“立即审批” | | `actionTitle` | 否 | 最长 20 字符,建议写明确动作,例如“立即审批”;有跳转地址但未传时默认“查看详情” |
| `actionUrl` | 否 | 可为空;非空时按原样透传,支持 HTTP、HTTPS、内网 IP/域名和端口 | | `actionUrl` | 否 | 可为空;非空时按原样透传,支持 HTTP、HTTPS、内网 IP/域名和端口 |
通知中心会根据项目配置统一生成: 通知中心会根据项目配置统一生成:
@ -349,7 +349,19 @@ signature = HMAC_SHA256_HEX(projectSecret, canonical)
【项目名称】业务标题 【项目名称】业务标题
``` ```
例如业务项目提交“资产报废待审批”,用户最终看到的是“【固定资产】资产报废待审批”。完整文案规则见[消息规范](./消息规范.md)。 例如业务项目提交“资产报废待审批”,用户最终看到的是“【固定资产】资产报废待审批”。
### 9.1 如何实现带样式和按钮的 ActionCard
- `actionUrl` 非空:通知中心使用钉钉原生 `sampleActionCard`,底部按钮文字为 `actionTitle`,点击进入指定业务页面。
- `actionUrl` 未传或为空:使用 `sampleMarkdown`,没有底部按钮,外观与 ActionCard 不同;仅填写按钮文字不能生成按钮。
- `content` 由业务项目用 Markdown 编排,例如 `**资产:** 电脑桌`、空行分段、`> 请核对明细后处理。`。这不是 HTML/CSS,卡片外框、宽度、颜色和按钮外观由钉钉客户端决定。
- 通知中心会在两种模板的可见正文顶部固定加入 `### 【项目名称】业务标题`,再用两个换行拼接 `content`;业务项目不要在 `title` 或 `content` 中重复项目抬头。
- 普通通知的跳转地址支持 HTTP、HTTPS 和内网地址;只有明确启用免登且目标满足既有 HTTPS 登录规则时,才会包装到免登入口,最终仍回到业务方指定页面。
上面的发送接口 JSON 示例已经具备完整 ActionCard 所需的正文、按钮文字和跳转地址。业务方只需使用本项目的真实值,不需要直接传 `msgKey`、`msgParam`、`singleTitle` 或 `singleURL`,这些由通知中心适配。
当前钉钉 `text` 是“固定抬头 + 两个换行 + 业务正文”,合计截取前 4800 字符,超长正文尾部可能被截断。签名仍使用业务方提交的原始 JSON。完整样式规范和可复制的待审批卡片示例见[消息规范](./消息规范.md)。
## 十、幂等防重和多人通知 ## 十、幂等防重和多人通知
@ -470,8 +482,9 @@ GET /v1/notifications/{deliveryId}
- [ ] 使用本人真实钉钉 `userId` 能创建通知并取得投递 `id`。 - [ ] 使用本人真实钉钉 `userId` 能创建通知并取得投递 `id`。
- [ ] 如使用花名,准确花名能解析并发送,找不到对应 `userId` 时会被明确拒绝。 - [ ] 如使用花名,准确花名能解析并发送,找不到对应 `userId` 时会被明确拒绝。
- [ ] 能使用投递 `id` 查询状态。 - [ ] 能使用投递 `id` 查询状态。
- [ ] 通知标题自动包含正确项目名称,没有重复前缀或双标题。 - [ ] 钉钉聊天中的可见正文顶部自动包含正确项目名称和业务标题,没有重复前缀或双标题。
- [ ] 按钮进入正确业务记录;如启用免登且目标满足现有 HTTPS 登录流程,会自动包装到统一入口。 - [ ] 提供 `actionUrl` 时收到 ActionCard,Markdown 正文和按钮文字正确,点击进入正确业务记录;如启用免登且目标满足现有 HTTPS 登录流程,会自动包装到统一入口。
- [ ] 若选择不提供 `actionUrl`,收到普通 Markdown 消息且没有底部按钮。
- [ ] 重复执行同一业务事件不会产生第二条通知。 - [ ] 重复执行同一业务事件不会产生第二条通知。
- [ ] 无效钉钉 `userId` 最终进入明确失败状态。 - [ ] 无效钉钉 `userId` 最终进入明确失败状态。
@ -513,6 +526,10 @@ projectCode:
| 把任意姓名直接当花名发送 | 只使用素材平台内部目录能够精确解析的准确花名 | | 把任意姓名直接当花名发送 | 只使用素材平台内部目录能够精确解析的准确花名 |
| 同时发送 `recipientUserId` 和 `recipientAlias` | 两种收件人方式只能选择一种 | | 同时发送 `recipientUserId` 和 `recipientAlias` | 两种收件人方式只能选择一种 |
| 标题自己添加“【项目名】” | 只提交业务标题,由通知中心统一拼接 | | 标题自己添加“【项目名】” | 只提交业务标题,由通知中心统一拼接 |
| 正文再手写同一条项目抬头 | `content` 只提供业务正文,通知中心会在可见正文顶部加入抬头 |
| 只传纯文本却希望出现加粗字段和引用 | 在 `content` 中使用 Markdown,按[消息规范](./消息规范.md)的示例编排 |
| 不传 `actionUrl` 却希望出现 ActionCard 按钮 | 提供实际业务页面地址,按钮文字用 `actionTitle`;无地址则是无按钮的 Markdown 消息 |
| 修改样式后沿用旧测试事件并期待旧消息改变 | 新的样式测试使用新测试事件和新幂等键;同一业务事件的重试仍保持原幂等键 |
| 签名路径包含公网 `/notification-center` 前缀 | 签名内部路径 `/v1/notifications` | | 签名路径包含公网 `/notification-center` 前缀 | 签名内部路径 `/v1/notifications` |
| 先序列化签名,发送时再次序列化成不同正文 | 对实际发送的同一份 raw body 签名 | | 先序列化签名,发送时再次序列化成不同正文 | 对实际发送的同一份 raw body 签名 |
| 每次重试随机生成新的幂等键 | 重试沿用原幂等键,只更换 nonce | | 每次重试随机生成新的幂等键 | 重试沿用原幂等键,只更换 nonce |

View File

@ -29,7 +29,7 @@
创建完成后只需确认: 创建完成后只需确认:
1. 项目名称正确,最终通知标题会显示 `【项目名称】业务标题`。 1. 项目名称正确,最终通知的可见正文顶部会显示 `【项目名称】业务标题`。
2. 项目代码没有与现有项目冲突。 2. 项目代码没有与现有项目冲突。
3. 项目密钥只展示给需要接入的业务后端。 3. 项目密钥只展示给需要接入的业务后端。
4. 现有项目的代码和密钥没有改变。 4. 现有项目的代码和密钥没有改变。
@ -164,8 +164,8 @@ YYD_NOTIFICATION_PROJECT_SECRET=实际项目独立密钥
3. 查询接口只能由当前项目查询该投递记录。 3. 查询接口只能由当前项目查询该投递记录。
4. 最终状态进入 `sent`、`unread`、`read` 或给出明确 `failed` 原因。 4. 最终状态进入 `sent`、`unread`、`read` 或给出明确 `failed` 原因。
5. 测试员工实际收到“由壹点”机器人单聊。 5. 测试员工实际收到“由壹点”机器人单聊。
6. 标题包含正确项目名称。 6. 钉钉聊天中的可见抬头包含正确项目名称和业务标题,业务正文没有重复抬头。
7. 按钮进入业务项目提交的正确页面。 7. 提供跳转地址时收到 ActionCard,Markdown 正文格式和底部按钮正确,点击进入业务项目提交的页面;无地址时为无按钮的普通 Markdown 消息。样例见[消息规范](./消息规范.md)。
8. 相同幂等键重复请求不产生第二条消息。 8. 相同幂等键重复请求不产生第二条消息。
9. 如使用花名,花名册中存在对应 userId 时能发送,找不到时明确返回 `RECIPIENT_ALIAS_NOT_FOUND`。 9. 如使用花名,花名册中存在对应 userId 时能发送,找不到时明确返回 `RECIPIENT_ALIAS_NOT_FOUND`。