# 由壹点统一通知中心接入操作手册 > 交付对象:第一次接入“由壹点”统一钉钉通知、不了解通知中心内部实现的业务项目负责人、后端开发和测试人员。 > > 目标:按照本文从申请开通到生产验收逐步执行,不需要复制通知中心项目,也不需要持有钉钉应用凭证。 ## 先看这里:你现在能不能开始接入 业务项目真正调用通知中心前,必须已经从通知中心维护人处取得以下三项配置: ```dotenv YYD_NOTIFICATION_CENTER_URL=https://sc.jiansun.vip/notification-center YYD_NOTIFICATION_PROJECT_CODE=通知中心正式分配的项目代码 YYD_NOTIFICATION_PROJECT_SECRET=通知中心正式生成的项目独立密钥 ``` 按下面的情况继续: | 你现在的情况 | 下一步 | | --- | --- | | 三项配置都没有 | 只把项目名称告诉通知中心维护人,系统会自动生成项目代码和密钥 | | 只有通知中心地址 | 仍未开通,等待通知中心维护人登记项目并发放代码和密钥 | | 已有地址、项目代码和项目密钥 | 从本文第七节开始配置业务后端 | | 业务系统只有员工花名 | 发送时直接提交准确花名,通知中心会查询 `gongxiangp2` 花名册 | | 还不知道通知发给谁 | 发送前准备准确花名或钉钉 `userId` | | 只想给一个人发送联调消息 | 发送时填写该员工的准确花名或钉钉 `userId`;这不是接入配置 | 请勿把“海棠”或“海棠的钉钉 `userId`”之类的收件人信息,与上述三项接入配置混为一谈。前三项用于证明“哪个项目在调用”;`recipientAlias` 或 `recipientUserId` 用于说明“这一次通知发给谁”。 ## 一、通知中心是什么 统一通知中心是已经独立部署的后端服务。业务项目通过 HTTPS 接口提交通知,通知中心再使用统一的“由壹点”钉钉机器人向员工发送单聊。 ```text 业务项目后端 → 由壹点统一通知中心 → 钉钉机器人 → 收件人员工 ``` 业务项目需要做的事情: - 申请自己的项目身份和项目密钥; - 在自己的后端增加一个很小的通知客户端; - 在业务事件发生时提供收件人、标题、正文和跳转地址; - 保存通知中心返回的投递 ID,必要时查询投递状态。 业务项目不需要做的事情: - 不复制、合并或部署通知中心整个仓库; - 不连接通知中心数据库; - 不申请或保存钉钉 `AppKey`、`AppSecret`、`RobotCode`; - 不直接调用钉钉机器人接口; - 不把项目密钥放到前端。 ## 二、双方分别负责什么 | 角色 | 必须完成的工作 | | --- | --- | | 业务项目负责人 | 提供项目名称 | | 业务项目开发 | 在业务后端保存三项配置,发送收件人、标题、正文和可选跳转信息 | | 通知中心维护人 | 创建项目,安全交付通知中心地址、项目代码和项目密钥 | | 测试或验收人员 | 使用真实员工验证发送、防重、状态和按钮跳转,不接触项目密钥 | ## 三、开始开发前只需确认发送内容 1. 哪个业务事件触发通知,例如“审批单创建”或“任务即将到期”。 2. 通知发给谁:准确花名或钉钉 `userId` 二选一。 3. 标题和正文写什么。 4. 是否需要按钮;需要时提交按钮文字和跳转地址。 5. 为同一业务事件生成稳定的幂等键,避免重复通知。 花名无需提前登记到项目。通知中心会在发送时查询 `gongxiangp2` 已有花名册;找不到对应钉钉 `userId` 时不会推送,并返回明确错误。 ## 四、正确的接入顺序 必须按顺序执行,不要跳过项目开通直接编写生产调用: ```text 第 1 步:业务项目提供项目名称 ↓ 第 2 步:通知中心自动生成项目代码和项目密钥 ↓ 第 3 步:维护人安全交付 URL、PROJECT_CODE、PROJECT_SECRET ↓ 第 4 步:业务后端配置三项环境变量并接入客户端 ↓ 第 5 步:使用一名真实员工完成首次发送和状态查询 ``` 业务项目不能自行宣布开通完成。没有收到正式项目代码和项目密钥时,说明通知中心还没有为该项目建立调用身份。 ## 五、开通项目 业务项目只需发送一行信息: ```text 项目名称:产品开发平台 ``` 通知中心管理面板会自动生成项目代码和项目密钥。项目代码如有特殊需要,可以在高级设置中自定义;普通接入无需填写。 通知中心维护人完成登记后,应通过安全渠道分别交付: ```dotenv 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` | 当前业务记录页面;可为空,非空时原样透传 | `http://10.0.0.8:8080/tasks/53` | ### 6.3 两种收件人方式怎么选择 钉钉 `userId` 是员工在当前企业通讯录中的稳定标识,不是姓名,也不是手机号、邮箱、工号或业务数据库主键。 能够取得 `userId` 的项目,按以下优先顺序取得: 1. 已有员工账号绑定:读取当前收件人账号保存的 `dingtalkUserId` 或等价字段。 2. 已有企业通讯录同步:从项目已经同步的钉钉员工记录中读取 `userId`。 3. 仅做首次联调:由负责人确认一名真实员工,并通过公司现有的权威人员映射取得该员工 `userId`。 4. 完全没有员工与钉钉身份对应关系:改用准确花名,或者先补齐账号绑定或通讯录同步。 如果项目只能提供花名,使用 `recipientAlias`: - 花名由通知中心调用素材平台内部目录精确解析到钉钉 `userId`; - 不做模糊猜测,不按姓名、手机号、邮箱或业务 ID 猜人; - 找不到对应 `userId` 时返回 `RECIPIENT_ALIAS_NOT_FOUND`,不会发送通知; - 目录暂时不可用时返回 `503 RECIPIENT_ALIAS_DIRECTORY_UNAVAILABLE`,同样不会入队; - 同一请求只能传 `recipientUserId` 或 `recipientAlias` 其中一个。 如果正式业务永远只通知固定负责人,可以把其花名或 `userId` 保存在业务项目后端;如果收件人随业务记录变化,就从业务记录读取对应花名或 `userId`,不能把联调人员写死在代码里。 通知中心只做精确花名解析,不会根据任意姓名模糊猜人,也不会把某个测试人员设置成所有项目的默认收件人。 ## 七、在业务项目中保存配置 三项配置只能保存在业务后端的环境变量或正式密钥管理服务中: ```dotenv 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 及以上项目可以复制: - [`examples/node/notification-client.mjs`](../examples/node/notification-client.mjs) - [`examples/node/notification-client.test.mjs`](../examples/node/notification-client.test.mjs) 初始化客户端: ```js 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` 发送一条通知: ```js 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`: ```js 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}`, }) ``` 查询投递状态: ```js const latest = await notificationClient.getDelivery(deliveryId) console.log(latest.status, latest.lastErrorCode) ``` 在通知中心仓库内,可以运行以下命令验证示例客户端的签名和请求格式: ```bash npm run test:example ``` ### 8.2 其他开发语言按统一协议实现 发送接口: ```text POST /v1/notifications ``` 每次请求必须包含以下请求头: ```text 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 小写十六进制字符串。 签名原文: ```text timestamp\nnonce\nHTTP_METHOD\nPATH\nSHA256(rawBody) ``` 发送通知时的示意算法: ```text 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,但业务幂等键保持不变。 ## 九、发送内容和字段规则 请求正文: ```json { "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" } ``` 花名方式只替换收件人字段: ```json { "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 或纯文本业务正文,输入最长 4800 字符;通知中心在前面加固定项目抬头,总显示长度见下文 | | `actionTitle` | 否 | 最长 20 字符,建议写明确动作,例如“立即审批”;有跳转地址但未传时默认“查看详情” | | `actionUrl` | 否 | 可为空;非空时按原样透传,支持 HTTP、HTTPS、内网 IP/域名和端口 | 通知中心会根据项目配置统一生成: ```text 【项目名称】业务标题 ``` 例如业务项目提交“资产报废待审批”,用户最终看到的是“【固定资产】资产报废待审批”。 ### 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)。 ## 十、幂等防重和多人通知 推荐的幂等键格式: ```text {事件类型}:{业务记录ID}:{recipientUserId或recipientAlias}:{消息版本} ``` 示例: ```text asset_disposal_created:53:16408293139985728:v1 ``` 规则: - 同一业务事件重复执行时,必须复用相同幂等键。 - 相同 `projectCode + idempotencyKey` 且内容相同,只返回原投递记录,不会重复通知。 - 相同幂等键但内容不同会返回 `409 IDEMPOTENCY_CONFLICT`,必须修复业务逻辑,不能随机换键绕过。 - 确实需要发送一条内容更新后的新通知时,经过业务确认后提升消息版本,例如从 `v1` 改为 `v2`。 当前接口一次发送给一名员工。向多人通知时逐人调用,并把每个人的钉钉 `userId` 或准确花名放入幂等键: ```js 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` | 通知中心内部异常 | 保留原幂等键,短暂退避后有限重试并告警 | 查询接口: ```text GET /v1/notifications/{deliveryId} ``` 投递状态: | 状态 | 含义 | 业务项目应该做什么 | | --- | --- | --- | | `pending` | 已受理,等待发送 | 等待,不要重新创建通知 | | `sending` | 正在发送 | 等待,不要重新创建通知 | | `retrying` | 通知中心正在有限重试 | 等待,由通知中心处理 | | `sent` | 钉钉已接受发送请求 | 可继续查询已读状态 | | `unread` | 已投递但尚未阅读 | 按业务需要展示或记录 | | `read` | 收件人已经阅读 | 终态 | | `failed` | 已确定失败,或结果不确定而停止自动重试 | 记录错误并通知负责人处理 | 完整错误码和重试边界见[错误与状态规范](./错误与状态规范.md)。 ## 十二、业务端重试和日志要求 重试原则: 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. 验证票据过期、重复使用和跨项目使用都会失败。 通知中心票据只能证明钉钉身份,不能替代业务项目自己的权限判断。免登的完整协议见[接入指南](./接入指南.md#8-钉钉-h5-免登是可选能力)。 ## 十四、联调与上线验收 ### 14.1 开发自测 - [ ] 三项配置只存在于后端,仓库和前端产物中没有真实密钥。 - [ ] 服务端时间已同步,请求签名路径不包含 `/notification-center` 反向代理前缀。 - [ ] 使用本人真实钉钉 `userId` 能创建通知并取得投递 `id`。 - [ ] 如使用花名,准确花名能解析并发送,找不到对应 `userId` 时会被明确拒绝。 - [ ] 能使用投递 `id` 查询状态。 - [ ] 钉钉聊天中的可见正文顶部自动包含正确项目名称和业务标题,没有重复前缀或双标题。 - [ ] 提供 `actionUrl` 时收到 ActionCard,Markdown 正文和按钮文字正确,点击进入正确业务记录;如启用免登且目标满足现有 HTTPS 登录流程,会自动包装到统一入口。 - [ ] 若选择不提供 `actionUrl`,收到普通 Markdown 消息且没有底部按钮。 - [ ] 重复执行同一业务事件不会产生第二条通知。 - [ ] 无效钉钉 `userId` 最终进入明确失败状态。 ### 14.2 生产验收 生产上线不能只验证健康接口,至少完成: - [ ] 项目负责人收到一条真实业务通知。 - [ ] 另一名真实员工收到一条真实业务通知。 - [ ] 两人看到的项目名称、业务内容和按钮都正确。 - [ ] 重复提交同一请求没有重复消息。 - [ ] 能从业务日志关联“业务事件 → 幂等键 → 投递 ID → 最终状态”。 - [ ] 日志、错误页、浏览器请求和监控平台均未出现项目密钥。 - [ ] 已明确通知失败由谁处理,以及如何联系通知中心维护人。 - [ ] 如启用免登,已额外验证员工身份、权限、票据过期和一次性消费。 验收完成后,双方保存以下记录: ```text 项目名称: projectCode: 业务项目版本或 Git 提交: 通知中心版本或 Git 提交: 生产验证时间: 验证通知的业务事件 ID: 验证通知的投递 ID: 发送结果: 点击跳转结果: 通知中心维护人: 遗留问题: ``` ## 十五、常见接入错误 | 错误做法 | 正确做法 | | --- | --- | | 前端直接调用通知中心 | 只允许业务后端调用 | | 从其他项目复制项目密钥 | 每个项目由通知中心单独生成密钥 | | 把任意姓名直接当花名发送 | 只使用素材平台内部目录能够精确解析的准确花名 | | 同时发送 `recipientUserId` 和 `recipientAlias` | 两种收件人方式只能选择一种 | | 标题自己添加“【项目名】” | 只提交业务标题,由通知中心统一拼接 | | 正文再手写同一条项目抬头 | `content` 只提供业务正文,通知中心会在可见正文顶部加入抬头 | | 只传纯文本却希望出现加粗字段和引用 | 在 `content` 中使用 Markdown,按[消息规范](./消息规范.md)的示例编排 | | 不传 `actionUrl` 却希望出现 ActionCard 按钮 | 提供实际业务页面地址,按钮文字用 `actionTitle`;无地址则是无按钮的 Markdown 消息 | | 修改样式后沿用旧测试事件并期待旧消息改变 | 新的样式测试使用新测试事件和新幂等键;同一业务事件的重试仍保持原幂等键 | | 签名路径包含公网 `/notification-center` 前缀 | 签名内部路径 `/v1/notifications` | | 先序列化签名,发送时再次序列化成不同正文 | 对实际发送的同一份 raw body 签名 | | 每次重试随机生成新的幂等键 | 重试沿用原幂等键,只更换 nonce | | 把 HTTP `202` 当成已送达 | 保存投递 ID 并查询最终状态 | | 多人共用一个幂等键 | 每名收件人使用独立幂等键 | | 收到超时就无限重发 | 有限重试;结果不确定时先人工核验 | | 只测试自己,不测试第二名员工 | 生产至少完成双人真实验收 | ## 十六、完成标准 只有同时满足以下条件,才算正式接入完成: 1. 项目已经在通知中心创建,并使用自己的项目代码和项目密钥。 2. 通知由业务后端发出,前端和 Git 仓库中没有任何密钥。 3. 业务项目保存投递 ID,正确处理幂等、状态查询和有限重试。 4. 通知内容符合[消息规范](./消息规范.md)。 5. 已完成一次真实生产通知、跳转和防重验收。 6. 已记录上线版本、验证证据和故障处理方式。 更细的逐项门禁可继续使用[新项目接入清单](./新项目接入清单.md)。