部署n8n

n8n是开源自动化工作流,可以拿来做一些神奇的事情(自己发掘)。

开始部署(Docker)

其实官方仓库的README已经给了,就2条指令(
拉不下来镜像的自己找加速源或者梯子(

# 创建一个卷给n8n用,不然会有权限问题
docker volume create n8n_data
# 创建n8n的容器
docker run -it --rm --name n8n -p 5678:5678 -v n8n_data:/home/node/.n8n docker.n8n.io/n8nio/n8n
# 其实完全可以写成如下的命令
# -it基础之上加了个d,表示后台运行
# 添加了重启策略
# 使用环境变量指定了公网访问的地址(不然用的时候会变成http://localhost:5678,每次都得改)
docker run -itd --restart always --name n8n -e WEBHOOK_URL=https://example.com -p 5678:5678 -v n8n_data:/home/node/.n8n n8nio/n8n

完成以上步骤之后就可以在浏览器中打开n8n了,下面我再给一个Nginx反代的配置

# HTTP -> HTTPS
server {
    listen 80;
    server_name n8n.example.org;
    return      301 https://$server_name$request_uri;
}
# HTTPS
server {
    listen 443 ssl;
    server_name n8n.example.org;
    # SSL证书,根据实际情况写
    ssl_certificate /etc/letsencrypt/live/example.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.org/privkey.pem;
    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Rea $remote_addr;
        proxy_set_header X-Forwarded-Host $http_host:$server_port;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Nginx-Proxy true;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        # Websocket需要,不然会有问题
        proxy_set_header Connection "upgrade";
        proxy_set_header Upgrade $http_upgrade;
        # 容器暴露的地址
        proxy_pass http://127.0.0.1:5678;
        # 上传的最大文件尺寸
        client_max_body_size 200m;
    }
}

部署UptimeFlare

这个是托管在Cloudflare(确实赛博菩萨,这才是真神) Worker的状态监控服务

这里吧,还有图(实际上是懒得写了)

配置n8n工作流

这里以OneBot V11协议为例(协议端自己探索,比如napcat)

  1. 使用WebHook作为起点,其配置如下:
    1.1 为了传递更多数据,HTTP请求方式写:POST
    1.2 PATH随意,默认是实例的编号(不要和别的冲突,其实UUID也行)
    1.3 Authentication 认证方式看情况写,但是不能不写,为了安全
    1.4 响应根据情况选,我选择的是Using 'Response to Webhook' Node
  2. 第二个节点使用Respond to Webhook(什么都不用配置)
  3. 第三个节点使用Code,这里需要写一点代码,因为要处理消息
    // 取出请求标头
    const headers = $input.first().json.headers;
    // 取出请求体
    const body = $input.first().json.body;
    // 计算时间(uptime flare回传的是秒级别的时间戳,所以需要乘1000)
    const timeIncidentStart = new Date(body.timeIncidentStart * 1000);
    // 这里存最终的消息和参数
    const res = [];
    // 要发送的消息
    const msg = `🌐服务: ${body.monitorName}
    状态: ${body.isUp ? '✅运行' : '❌故障'}
    🕒时间: 
    ${timeIncidentStart.toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' }).replace(/\//g, '-')}
    💬原因: ${body.reason}`;
    // 从请求标头获取发送目标(参照下面的结构定义)
    headers.target.split(',').forEach((i) => {
    // isPrivate表示是否私聊
    // target表示发送的目标(私聊就是QQ号,群聊就是群号)
    res.push({ isPrivate: i.startsWith('private_'), target: i.replace(/[a-z_]/g, ''), msg: msg })
    })
    // 存入主体
    $input.first().json.body = res;
    return $input.first().json.body;
  4. 添加if节点(用于区分群聊和私聊,因为5的节点一个节点只能发送同一种类型的消息)
    4.1 Conditions 中使用表达式形式,并直接在fx中写{{ $json.isPrivate }}然后后面is true
  5. 安装n8n-nodes-onebot节点
  6. 配置OneBot节点,发送群消息和私聊消息写法一样:User Name or ID或Group Name or ID写为{{ $json.target }},然后在Message栏中写{{ $json.msg }}

结构定义

  1. 必填请求标头
标头
target private_11223344,public_66778899
authorization 认证令牌
  1. 请求体
    {
    "monitorName": "US - United States Server (AS979) - 154040",
    "isUp": true,
    "timeIncidentStart": 1741860850,
    "timeNow": 1741861248,
    "reason": "OK"
    }

配置UptimeFlare状态变更回调

在uptime.config.ts的callbacks里面的onStatusChange中写如下代码

    onStatusChange: async (
        env: any,
        monitor: any,
        isUp: boolean,
        timeIncidentStart: number,
        timeNow: number,
        reason: string
    ) => {
        // This callback will be called when there's a status change for any monitor
        // Write any Typescript code here
        const payload = {
            monitorName: monitor.name,
            isUp: isUp,
            timeIncidentStart: timeIncidentStart,
            timeNow: timeNow,
            reason: reason,
        };
        try {
            const response = await fetch('这里是Webhook的端点', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': '这里写令牌',
                    'Target': '这里写通知目标'
                },
                body: JSON.stringify(payload),
            });
            console.log(response);
        } catch (error) {
            console.error(error);
        }
        // This will not follow the grace period settings and will be called immediately when the status changes
        // You need to handle the grace period manually if you want to implement it
    },

摸🐟从未停止,努力从未开始。