6.9 KiB
由壹点通知中心
由壹点内部项目统一钉钉机器人单聊出口。只有本服务保存钉钉应用密钥和 RobotCode;素材库、固定资产、产品开发平台等业务系统使用各自的项目密钥调用本服务。
业务项目接入
新项目不要从本 README 中零散复制接口代码,请按以下规范接入:
- 统一通知中心接入操作手册:可直接交给其他项目负责人的完整接入流程、代码入口与验收标准。
- 通知中心新项目开通操作手册:仅供通知中心维护人登记项目、生成密钥、安全交付和验收使用。
- 接入指南:项目登记、环境变量、签名、发送、多人通知和上线验证。
- 消息规范:标题、正文、状态和按钮的统一写法。
- 错误与状态规范:HTTP 返回、投递状态、错误码和重试边界。
- 新项目接入清单:从申请到生产验证的逐项门禁。
- Node.js 客户端示例:可以直接复制到业务后端的无依赖实现。
接入的核心边界:业务项目只保存自己的项目代码和 HMAC 密钥;钉钉 AppKey、AppSecret、RobotCode 始终只由通知中心持有。
收件人支持两种方式:业务项目可以直接提交钉钉 userId,也可以提交当前项目已由通知中心登记的准确花名。花名会在中心解析为 userId;未登记或冲突时拒绝发送,不做模糊猜测。
安全边界
- 每个项目使用独立 HMAC 密钥。
- 请求签名包含时间、nonce、HTTP 方法、路径和正文摘要。
- nonce 五分钟内只能使用一次。
- 每个项目只能跳转到配置的 HTTPS 域名。
projectCode + idempotencyKey唯一,防止业务重试造成重复通知。- 项目无法读取其它项目的投递记录。
- 钉钉
AppSecret只保存在通知中心;业务系统只用自己的项目 HMAC 密钥兑换登录票据。 - 登录票据随机生成、只存 SHA-256 摘要、最长 60 秒、绑定项目和回跳域名并且只能原子消费一次。
- 免登码和登录票据都只通过 POST 传递,不进入 URL;服务日志不记录二者和任何密钥。
本地启动
- 复制
.env.example为.env并填写真实配置;不要提交.env。 Windows 下项目目录包含空格,DATABASE_URL请保留为完整绝对路径。 - 执行
npm install。 - 执行
npm run prisma:generate。 - 执行
npm run prisma:migrate。 - 执行
npm run dev。
健康检查:GET /health。
npm test 不读取本地 .env:测试入口会创建唯一临时 SQLite,先执行正式 Prisma 迁移,再串行运行测试,并在成功或失败后清理数据库、journal、WAL 和 SHM 文件。
生产环境建议只把容器端口绑定到 127.0.0.1,再由 Nginx 提供独立 HTTPS 域名。素材库和固定资产可能位于不同主机,因此不能把业务项目配置成通知中心的容器内网地址。
当前生产部署脚本位于:
scripts/deploy-material-host.sh:只管理通知中心独立容器、配置和独立 FRP 服务;scripts/deploy-public-nginx.sh:备份并扩展素材库专用 FRP 端口白名单,然后为素材库公网域名增加/notification-center/反向代理;脚本会验证主站、S3 和通知中心,失败时回滚。
脚本不包含任何密钥。统一机器人凭证从素材库生产环境安全复制到通知中心独立 .env,项目 HMAC 密钥在服务器生成并以 0600 权限保存。
发布脚本会先一致性备份通知中心 SQLite、环境配置和源码,再从本次解压的源码强制构建镜像并 force-recreate,不会因为服务器上存在旧 latest 镜像而跳过新代码。素材库生产 NAS 无法稳定访问 Docker Hub,正式发布前应预载 node:22-alpine 基础镜像并确认 npm 依赖源可用;构建失败会在重建容器前终止,旧服务继续运行。
签名算法
请求头:
x-yyd-project-codex-yyd-timestamp:Unix 秒x-yyd-nonce:每次请求生成新的随机字符串x-yyd-signature:HMAC-SHA256 十六进制字符串
待签名文本:
timestamp\nnonce\nHTTP_METHOD\nPATH\nSHA256(rawBody)
发送接口:POST /v1/notifications。查询接口:GET /v1/notifications/:id。
通知中心只接受任务并异步发送,业务系统通过查询接口获取 pending / sending / sent / unread / read / retrying / failed 状态。
钉钉 H5 免登中转
项目的 login 配置是可选项;不配置时仍按原方式直接跳转,素材库等现有调用不受影响。启用后,通知中心会把业务传入的项目 HTTPS 地址转换为内部 redirectPath,并把卡片动作包装到统一入口:
GET /v1/auth/h5-entry?projectCode=fixed_asset&redirectPath=%2Fworkflows%2Fdisposals%2F53
入口页面向钉钉 JSAPI 同时传入公开的 clientId(即 AppKey)和 corpId 获取免登码,再以 JSON POST 调用:
POST /v1/auth/h5-code
{"projectCode":"fixed_asset","redirectPath":"/workflows/disposals/53","code":"..."}
中心换取钉钉 userId 后返回 {ticket, formPostUrl, expiresIn},页面立即通过隐藏表单把 ticket POST 到项目配置的精确 callbackUrl。业务后端收到票据后,使用与通知接口相同的 HMAC 头调用:
POST /v1/auth/tickets/redeem
{"ticket":"..."}
成功响应只包含 {dingtalkUserId, redirectPath}。未知、过期、已消费或跨项目票据统一返回 401 TICKET_INVALID_OR_EXPIRED,不泄露票据是否存在。业务系统必须在创建 Session 前重新核验本地账号、员工绑定和在职状态。
项目配置示例:
{
"code": "fixed_asset",
"name": "固定资产",
"secret": "独立随机项目密钥",
"allowedHosts": ["asset.jiansun.vip"],
"login": {
"enabled": true,
"callbackUrl": "https://asset.jiansun.vip/api/dingtalk/broker-login",
"allowedRedirectPathPrefixes": ["/workflows/disposals", "/dashboard", "/inventory"]
}
}
新增环境变量:
DINGTALK_CORP_ID:统一钉钉应用所属企业 CorpId。YYD_NOTIFICATION_PUBLIC_BASE_URL:通知中心公网 HTTPS 基地址,生产为反向代理后的/notification-center地址。DINGTALK_LOGIN_TICKET_TTL_SECONDS:30~60 秒,默认 60 秒。
新建任务最多投递 5 次,明确的限流等未受理结果按 1、5、15、30 分钟退避。发送调用若在请求已发出后发生网络断开、超时、HTTP 408/5xx、钉钉成功响应缺少 processQueryKey,或进程在发送中途崩溃并遗留过期租约,会以 DELIVERY_OUTCOME_UNKNOWN 结束并停止自动重试,避免不确定结果造成重复单聊。历史任务保留创建时的最大尝试次数,不通过迁移篡改。