通过 RPC 推送告警到企业微信
1. 场景说明
ThinkLink(TKL)平台的 RPC 支持调用外部 HTTP 接口(见 RPC 模型 §1.3.2 类型四),可以把设备告警、运行状态、自定义事件实时推送到企业微信群机器人。当设备触发告警条件时,相关人员可以在企业微信中第一时间收到通知。
本文档介绍如何在 RPC 中向企业微信推送消息,包含三种接入方式(按推荐顺序):
| 方式 | 适用场景 | 鉴权(存放位置) |
|---|---|---|
| 群机器人(§2–§3) | 把消息推送到固定的一个微信群 | Webhook URL(组织参数 wecom_webhook_url) |
| 智能机器人(§6) | 一个机器人需要同时进多个群,或要使用模板卡片、回调等高级能力 | Webhook URL(组织参数) |
| 自建应用(§7) | 定向推送给指定人 / 部门 / 标签(不依赖群聊) | corpid + corpsecret + agentid(组织参数) |
2. 准备工作
2.1 创建企业微信群机器人
- 在企业微信群中点击 群设置 → 群机器人 → 添加机器人
- 设置机器人名称和头像后,复制生成的 Webhook 地址
- Webhook 地址形如:URL 中的
https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxkey参数就是机器人的访问凭证。
2.2 妥善保管 Webhook 密钥(必读)
⚠️ 安全提示:Webhook URL 中的
key等同于群机器人的发送权限凭证,泄漏后任何人都可以向群中发消息。
请遵守以下规则:
- 不要把密钥放在设备的
server_attrs、资产属性、触发模型等任何调用方可读的位置;这些字段会随设备/资产一起被有权限的运维查看,密钥外溢面会扩大 - 推荐做法:把整个 webhook URL 配置到 系统管理 → 服务器配置 → 组织参数(org_params),RPC 脚本通过
org_params.wecom_webhook_url读取。原因:- 组织参数页仅组织管理员可见——普通运维、调用方都看不到密钥
- 换 key 时只改服务器配置一处,所有引用该 RPC 的触发模型、定时任务自动生效,无需重新发布 RPC 脚本
- 同一组织内多个机器人(生产/测试、告警/日报)可分别用不同的 key 名称,互不干扰
- 见服务器配置 §1.6 组织参数
- 建议至少 每季度更换一次 Webhook 密钥(在群机器人设置中点击"重置 webhook 地址",然后只更新组织参数的 value 即可)
- 严格控制群成员范围,避免外部人员通过加入群获取 webhook
- 一旦发现密钥可能泄漏,立即在群机器人设置中重置,回到组织参数页面更新 value,并检查群消息日志是否有异常推送
3. 在 RPC 中调用企业微信
3.1 设计原则
把企业微信告警 RPC 当作一个"通知服务"来设计:
- 密钥封装在内:webhook URL 配置到组织参数(
org_params.wecom_webhook_url),仅组织管理员可见 - 接口暴露在外:调用方通过
params传入消息内容、告警等级、@ 人员等业务字段 - 多机器人时:在组织参数里维护多个 key(例如
wecom_webhook_alarm/wecom_webhook_daily),脚本通过params.channel路由到不同的 org_params key
3.2 准备工作:把 webhook 写到组织参数
进入 系统管理 → 服务器配置 → 组织参数,新增一条:
| Key | Value | Remark |
|---|---|---|
wecom_webhook_url | https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxxxx-... | 企业微信告警机器人 webhook,每季度轮换 |
保存后所有 RPC 立即生效,无需重启服务。
3.3 RPC 脚本:发送文本消息
function rpc_script({ device, params, alarms, logger, org_params }) {
let webhook = org_params?.wecom_webhook_url;
if (!webhook) {
logger.error("wecom_webhook_url not configured in org_params");
return null;
}
let content = params.content || `设备 ${device.name}(${device.eui}) 告警通知`;
return [
{
sleepTimeMs: 0,
type: "axios",
dnMsg: {
method: "POST",
url: webhook,
headers: { "Content-Type": "application/json" },
data: {
msgtype: "text",
text: { content: content }
},
timeout: 5000
}
}
];
}3.4 RPC 脚本:告警通知(推荐,与 ALARM RPC 配套)
本脚本与平台内置的 ALARM RPC 接收相同的入参,由同一个触发模型同时调用,实现"平台告警记录 + 企业微信推送"双通道。
关键设计点:
- action = "new":推送一条"设备告警"消息
- action = "clear":先通过
alarms[name]判断该告警当前是否活跃 —— 只有活跃告警被清除时才推送"告警恢复"消息;条件长期正常时不会每次上行都刷屏 - 入参与 ALARM RPC 对齐:
name/action/title/level/desc字段名和含义完全一致,触发脚本里同一份参数可以同时喂给两个 RPC - webhook 来自 org_params:脚本不再硬编码 key
function rpc_script({ device, params, alarms, logger, org_params }) {
let webhook = org_params?.wecom_webhook_url;
if (!webhook) {
logger.error("wecom_webhook_url not configured in org_params");
return null;
}
let name = params.name;
let action = params.action;
let title = params.title || name || "";
let level = params.level || "mid";
let desc = params.desc || "";
if (action === "clear") {
// 仅当该 alarm_name 当前处于活跃状态时才推送恢复通知,
// 避免条件持续正常的情况下,每次上行都触发一条 clear 消息
if (!alarms || alarms[name] === undefined) {
return null;
}
} else if (action !== "new") {
// 未知 action(如 "no"):不推送
return null;
}
let levelColor = {
urgent: "warning",
high: "warning",
mid: "comment",
low: "info"
}[level] || "info";
let header = action === "new" ? `### 设备告警` : `### 告警恢复`;
let content = [
header,
`> **设备名称**:${device.name}`,
`> **设备 EUI**:\`${device.eui}\``,
`> **告警事件**:\`${name}\``,
`> **告警标题**:${title}`,
`> **告警等级**:<font color="${levelColor}">${level.toUpperCase()}</font>`,
`> **${action === "new" ? "告警内容" : "恢复说明"}**:${desc || "无"}`,
`> **时间**:${new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })}`
].join("\n");
return [
{
sleepTimeMs: 0,
type: "axios",
dnMsg: {
method: "POST",
url: webhook,
headers: { "Content-Type": "application/json" },
data: {
msgtype: "markdown",
markdown: { content: content }
},
timeout: 5000
}
}
];
}💡
alarms是 RPC 入参之一,是以alarm_name为键的活跃告警映射表。键存在表示对应告警当前活跃,键不存在(undefined)表示未产生或已清除。详见告警管理 §1.6.2。
4. 配置 RPC 参数
在 RPC 模型管理界面,为该 RPC 添加以下参数字段(与 ALARM RPC 完全对齐,便于触发脚本复用同一份 params):
| 字段标识 | 类型 | 别名 | 说明 |
|---|---|---|---|
name | string | 告警事件名称 | 唯一标识,必须与 ALARM RPC 的 name 一致,用于 alarms[name] 去重判断 |
action | string | 动作 | new 产生告警 / clear 恢复(仅当原告警活跃时才推送) |
title | string | 告警标题 | 推送消息中的标题字段 |
level | string | 告警等级 | low / mid / high / urgent |
desc | string | 告警描述 | new 时为告警详情,clear 时可填恢复说明 |
文本模式(§3.3)的 RPC 是另一种通用调用形态,参数为
content,不属于本告警链路,可作为独立 RPC 留作非告警类通知(日报、对账等)使用。
5. 集成到告警流程
推荐做法:在同一个触发模型中同时调用 alarm 和 wecom_notify,两者共享同一份参数。alarm 负责在平台中创建/清除告警记录,wecom_notify 负责把消息推送到企业微信群。
function trigger_script(device, thingModelId) {
const ACTION = { no: "no", new: "new", clear: "clear" };
const LEVEL = { low: "low", mid: "mid", high: "high", urgent: "urgent" };
let tdata = device?.telemetry_data?.[thingModelId];
if (tdata === undefined) { return null; }
let name = "overheat_alarm";
let title = "过热告警: [" + device.name + "]";
let level = LEVEL.high;
let desc = "";
let action = ACTION.clear; // 默认:清除(条件未满足)
let group = device?.server_attrs?.group ?? {};
if (tdata.temperature !== undefined && tdata.temperature >= device.server_attrs?.alarm_temp) {
desc = "[" + device.name + "] 温度 " + tdata.temperature + "℃,超过阈值 " + device.server_attrs.alarm_temp + "℃";
action = ACTION.new; // 条件满足:产生告警
}
// 一份参数同时喂给 alarm 和 wecom_notify
let alarmParams = {
_eui: device.eui,
action: action,
name: name,
title: title,
level: level,
desc: desc,
group: group
};
return {
delayms: 0,
abort_previous_timer: true,
actions: [
{ method: "alarm", params: alarmParams },
{ method: "wecom_notify", params: alarmParams }
]
};
}⚠️ 若实测发现
clear时wecom_notify收到的alarms[name]总是undefined(说明平台先执行alarm清除了记录、再执行wecom_notify时已读不到),可将 actions 顺序调整为wecom_notify在前、alarm在后;或在触发脚本中自行用device.server_attrs记录"上一次是否处于告警态"作为判定依据。
6. 使用智能机器人推送
智能机器人与群机器人都走 Webhook,但管理形态完全不同:
| 群机器人 | 智能机器人 | |
|---|---|---|
| 创建入口 | 群内 → 群设置 → 群机器人 → 添加 | 企业微信管理后台 → 应用管理 → 智能机器人 |
| 权限要求 | 群成员即可 | 需企业管理员 |
| 服务范围 | 仅所在的一个群 | 可加入多个群,统一管理 |
| 支持消息 | text / markdown / image / news / file / template_card | 同左 + 模板卡片按钮交互回调 |
| 接收用户消息 | ❌ | ✅(可配置回调 URL) |
6.1 创建智能机器人
- 进入 企业微信管理后台 → 应用管理 → 智能机器人 → 添加机器人
- 配置名称、头像、可见范围
- 把机器人加入需要接收告警的群(一个机器人可加入多个群)
- 在机器人详情中复制 Webhook 地址,格式与群机器人一致:
https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=<智能机器人唯一KEY>
6.2 RPC 脚本
智能机器人的发送接口与群机器人完全一致——只需在组织参数里新增/替换 wecom_webhook_url 为智能机器人的 webhook,§3.4 的脚本代码无需改动,同一份 RPC 即可同时推送到该机器人加入的所有群。
若需要同时保留群机器人和智能机器人,可在组织参数里分别配置 wecom_webhook_group 和 wecom_webhook_smart,再让调用方通过 params.channel 路由:
let webhook = params.channel === "smart"
? org_params?.wecom_webhook_smart
: org_params?.wecom_webhook_group;6.3 安全提醒
- 与群机器人一样,key 只写在组织参数里(见 §2.2)
- 智能机器人 key 的泄漏面比群机器人更大——它可能同时在多个群中,一旦泄漏所有群都会被骚扰;建议管理员每季度在管理后台重置一次,并同步更新组织参数 value
7. 使用自建应用推送
当告警需要发给特定的人、部门或标签(而不是固定的群),用自建应用消息接口更合适。
7.1 准备
- 企业微信管理后台 → 应用管理 → 自建 → 创建应用
- 记录三个关键字段:
corpid:在"我的企业"页面获取agentid:应用详情页中的"AgentId"corpsecret:应用详情页中的"Secret"
- 在应用的"可见范围"中勾选可以接收消息的成员
⚠️
corpsecret一旦泄漏,攻击者可冒充该应用向可见范围内任意成员发任意消息。务必按 §2.2 的方式存放在组织参数中,不要放在server_attrs、触发模型或调用方可读的任何位置。
把以下三个参数写到 系统管理 → 服务器配置 → 组织参数:
| Key | Value 示例 | Remark |
|---|---|---|
wecom_corp_id | ww1234567890abcdef | 企业微信 corpid |
wecom_corp_secret | xxxxxxxxxxxxxxxxxxxxxxxxxxxx | 自建应用 secret,每季度轮换 |
wecom_agent_id | 1000002 | 自建应用 AgentId |
7.2 调用流程
发送一条消息需要两步 HTTP 调用:
- 拿 access_token:
GET https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=...&corpsecret=... - 发送消息:
POST https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=...
由于第二步依赖第一步的响应,必须在 RPC 脚本里用 await axios() 先把 token 拿到,再返回发送动作(参考 RPC 模型 §1.3.1 axios 入参)。
7.3 RPC 脚本
async function rpc_script({ device, params, alarms, logger, org_params }) {
let corpId = org_params?.wecom_corp_id;
let corpSecret = org_params?.wecom_corp_secret;
let agentId = org_params?.wecom_agent_id;
if (!corpId || !corpSecret || !agentId) {
logger.error("wecom_corp_id / wecom_corp_secret / wecom_agent_id not configured in org_params");
return null;
}
let name = params.name;
let action = params.action;
let title = params.title || name || "";
let level = params.level || "mid";
let desc = params.desc || "";
if (action === "clear") {
if (!alarms || alarms[name] === undefined) return null;
} else if (action !== "new") {
return null;
}
// 1) 先拿 access_token
let tokenResp = await axios({
method: "GET",
url: "https://qyapi.weixin.qq.com/cgi-bin/gettoken",
params: { corpid: corpId, corpsecret: corpSecret },
timeout: 5000
});
let token = tokenResp.data?.access_token;
if (!token) {
logger.error("get wecom access_token failed", { resp: tokenResp.data });
return null;
}
let header = action === "new" ? "## 设备告警" : "## 告警恢复";
let content = [
header,
`**设备**:${device.name} (\`${device.eui}\`)`,
`**告警**:${title}`,
`**等级**:${level.toUpperCase()}`,
`**${action === "new" ? "说明" : "恢复"}**:${desc || "无"}`,
`**时间**:${new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" })}`
].join("\n\n");
// 2) 返回发送动作(响应不需要进一步处理时,用 axios 类型动作交给平台异步执行)
return [{
sleepTimeMs: 0,
type: "axios",
dnMsg: {
method: "POST",
url: `https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${token}`,
headers: { "Content-Type": "application/json" },
data: {
touser: params.touser || "@all",
toparty: params.toparty,
totag: params.totag,
msgtype: "markdown",
agentid: Number(agentId),
markdown: { content: content }
},
timeout: 5000
}
}];
}7.4 接收人参数
touser / toparty / totag 由调用方在 params 里传入,至少要指定其一:
| 字段 | 类型 | 说明 |
|---|---|---|
touser | string | 用户 ID 列表,多人用 | 分隔;@all 表示该应用可见范围内所有人 |
toparty | string | 部门 ID 列表,多个用 | 分隔 |
totag | string | 标签 ID 列表,多个用 | 分隔 |
7.5 access_token 的缓存策略
access_token 有效期 7200 秒,且企业微信对 /gettoken 接口有频次限制。两种策略二选一:
- 每次现取(上面脚本采用):实现最简单,告警量低时(每天几十次以内)够用
- 缓存复用:把 token 和过期时间通过
type: "modifyAttrs"写到设备server_attrs,下次先读device.server_attrs.wecom_token判断是否过期,过期才重取。告警量大时启用,可大幅减少/gettoken调用
8. 故障排查
| 现象 | 可能原因 |
|---|---|
| 群中收不到消息 | Webhook key 错误;机器人被移出群;服务器到 qyapi.weixin.qq.com 网络不��� |
RPC 日志显示 errcode != 0 | 消息格式不符合企业微信规范;@人时手机号格式不正确 |
| 偶尔丢消息 | 触发企业微信频率限制(单 webhook 每分钟 20 条) |
自建应用 errcode = 40014 | access_token 已过期或无效,检查是否启用了缓存但未做过期处理 |
自建应用 errcode = 60011 | touser / toparty / totag 中的成员不在应用可见范围 |
自建应用 errcode = 45033 | 触发了 /gettoken 调用频次限制,应启用 §7.5 缓存策略 |
9. 注意事项
- 单条消息正文长度建议控制在 2048 字符以内
- 消息中不要携带客户身份证、手机号、Token 等敏感业务数据;只放定位问题所需的关键信息
- 同一个机器人服务多个用途时,建议在消息开头加上来源标识(如
[生产环境]、[测试环境]) - 重要告警建议同时配置多个通知通道(如企业微信 + 邮件),避免单一通道异常导致漏报