优化uptime-webhook的告警内容修改
本文最后更新于 2025-12-31,文章内容可能已经过时。
优化后效果

- 不同状态的告警以不同颜色显示,方便区分;
- 优化为中文通知文案,简单易懂,符合中国宝宝体质;
- 完美适配飞书通知体,支持@需要提醒的用户,这样就可以屏蔽通知群里其他信息,也不会错过重要通知(因为会@你);
- 支持配置点击提示消息自动跳转到网站查看。
想实现和我一样的效果,就和我一起继续吧。当然如果你觉得这篇文章对你有帮助,欢迎帮忙点赞、转发、评论、打赏,你的支持就是我更新的动力!
简介
UptimeKuma 是一个开源的自托管监控工具,详情可见 Github: github.com/louislam/uptime-kuma 下文简称 UK。

它可以帮助我们定时监控目标域名或中间件服务的运行状态,同时提供 SSL 证书到期提示的能力。本站的监测站网络状态便是基于 UK 容器来进行部署的。
他的功能也很多,支持把网站的异常状态通过邮件、钉钉、飞书等方式发送,尤其是飞书、钉钉这种国内常用办公软件。但是 UK 的通知默认是英文的,且不提供通知样式的修改,有些丑陋。本文基于飞书的通知美化改造,抛砖引玉给大家提供一种新的思路。比如你可以用同样的思路修改,webhook、钉钉、企微等的通知内容。
1. 监控配置与安装(UK)
1.1 UK安装与配置(docker 安装)
关于 UK 的安装及配置,网上有很多教程,鱼鱼就不在赘述了。需要的朋友可以参考四个步骤使用UptimeKuma搭建状态监测站 - ElysiumStack|不会摄影的设计师不是优秀的旅行家
2. 配置通知
配置好监控站后,点击 编辑->设置通知 进行通知配置。


下拉列表中选择飞书

配置名称、WebHook URL 、开启并应用到所有监控,测试成功后保存

- 飞书的
webhook url获取参考:在群组中使用机器人 - 进入下面前,可手动停止/恢复下不重要的配置了监控服务,用来测试下监控及通知已经可以正常工作。
3. 告警通知优化
至此你已经可以监控网站状态,并进行飞书通知了。下面开始优化通知效果。
3.1 前置准备
- 将
/app/server/拷贝到/app/data/server下
docker exec -it uptime-kuma bash
cd /app
cp server/ ./data/ -r
exit
3.2 重启重新添加映射后的 docker。
docker stop uptime-kuma
docker run -d --restart=always -p 3003:3001 -v /data/uptime-kuma:/app/data -v /data/uptime-kuma/server:/app/server --name uptime-kuma louislam/uptime-kuma
3 .3 修改内容后,替换 /data/uptime-kuma/server/notification-providers 下的 feushu.js 文件。
需要修改的地方:
1 . 第 62 行 card_link 中的: url: " https://status.freuan.top/status/web" ,替换为你需要点击通知跳转的地址。

2. 第 82 行 const atTag = '<at id="aabbccddxxxx"></at>'; 中的 aabbccddxxxx,替换为你自己的 id。

- 获取自己的飞书 user_id 参考:如何获取用户的 User ID - 开发指南 - 开发文档 - 飞书开放平台
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { DOWN, UP } = require("../../src/util");
class Feishu extends NotificationProvider {
name = "Feishu";
/**
* @inheritdoc
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
const okMsg = "Sent Successfully.";
try {
let config = this.getAxiosConfigWithProxy({});
// 处理测试消息(不包含 heartbeatJSON 的情况)
if (heartbeatJSON == null) {
let testdata = {
msg_type: "text",
content: {
text: msg,
},
};
await axios.post(notification.feishuWebHookUrl, testdata, config);
return okMsg;
}
// ==========================================
// 2. 构建飞书卡片数据
// ==========================================
const isDown = heartbeatJSON["status"] === DOWN;
const statusColor = isDown ? "red" : "green";
const statusText = isDown ? "故障" : "正常";
// 获取处理后的文本内容(包含 @人 和 错误详情逻辑)
const contentText = this.getContent(heartbeatJSON);
let data = {
msg_type: "interactive",
card: {
config: {
wide_screen_mode: true
},
header: {
title: {
tag: "plain_text",
content: `${monitorJSON["name"]}`
},
template: statusColor
},
elements: [
{
tag: "div",
text: {
tag: "lark_md",
content: contentText
}
}
],
// 添加状态页跳转链接
card_link: {
url: "https://status.freuan.top/status/web"
}
}
};
await axios.post(notification.feishuWebHookUrl, data, config);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
/**
* 生成卡片内容,包含 @人 和 错误详情判断逻辑
* @param {object} heartbeatJSON
* @returns {string}
*/
getContent(heartbeatJSON) {
const atTag = '<at id="aabbccddxxxx"></at>';
// ==========================================
// 1. 核心时区修复:强制将输入时间视为 UTC
// ==========================================
if (heartbeatJSON && heartbeatJSON.time) {
let timeStr = heartbeatJSON.time;
// 关键修复:如果时间字符串末尾没有 Z (UTC标识) 或 + (偏移量),手动加上 Z
// 这强制 JS 将 "2025-12-29 04:28:17" 解析为 "2025-12-29 04:28:17 UTC"
if (!timeStr.endsWith('Z') && !timeStr.includes('+')) {
timeStr += 'Z';
}
const originalDate = new Date(timeStr);
const options = {
timeZone: 'Asia/Shanghai',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
};
// 格式化为 "2023-10-27 10:00:00"
heartbeatJSON.time = originalDate.toLocaleString('zh-CN', options).replace(/\//g, '-');
}
const time = heartbeatJSON["time"];
const isDown = heartbeatJSON["status"] === DOWN;
let content = "";
if (isDown) {
content += `${atTag}⚠️ **服务状态:故障**\n`;
content += `**检测时间**:${time}\n`;
// 错误详情逻辑判断
let errMsg = heartbeatJSON["msg"] || "未知错误";
if (errMsg.includes("status code")) {
content += "**错误详情**:服务不可用。";
} else if (errMsg.includes("timeout")) {
content += "**错误详情**:连接超时";
} else if (errMsg.includes("refused")) {
content += "**错误详情**:连接被拒绝";
} else {
content += `**错误详情**:${errMsg}`;
}
} else if (heartbeatJSON["status"] === UP) {
content += `${atTag}✅ **服务状态:正常**\n`;
content += `**检测时间**:${time}\n`;
content += "您的服务已恢复正常访问。";
} else {
content += `${atTag}📋 **状态更新**`;
}
return content;
}
}
module.exports = Feishu;
- 感谢你赐予我前进的力量
赞赏者名单
因为你们的支持让我意识到写文章的价值🙏
本文是原创文章,采用 CC BY-NC-ND 4.0 协议,完整转载请注明来自 纪梦鱼
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果
音乐天地

