用n8n+JavaScript打造智能天气机器人:从钉钉通知到异常预警全实现

最近在折腾一个有意思的项目:如何让团队每天早上不再被动地查看手机上的天气App,而是让天气信息主动、智能地“找上门”来,并且在出现恶劣天气时能第一时间发出预警。听起来像是需要写一堆后端服务和定时任务?其实,借助一个强大的开源工具,再配合一些JavaScript脚本,我们完全可以像搭积木一样,构建出一个既稳定又灵活的智能天气机器人。这个工具就是n8n。

对于需要将多个系统串联起来、实现自动化流程的中级开发者而言,n8n提供了一个绝佳的画布。它远不止是一个简单的“如果-那么”触发器。通过其内置的Function节点,我们可以注入自定义的JavaScript逻辑,处理复杂的API响应、设计健壮的错误重试机制,甚至实现简单的决策树。本文将带你从零开始,实现一个超越基础通知的工程化天气机器人。我们将重点关注如何在真实、不完美的网络环境和API服务下,构建一个可靠、可维护的自动化解决方案,最终将信息精准推送到钉钉工作群。

1. 项目蓝图与核心架构设计

在动手拖拽节点之前,花点时间思考整体架构是避免后期混乱的关键。一个健壮的智能天气机器人,不应该只是一个简单的“定时获取-发送”管道。我们需要考虑以下几个核心问题:

  • 数据源稳定性:免费的天气API可能有调用频率限制,偶尔会超时或返回错误格式的数据。
  • 信息价值密度:发送“上海,晴,25℃”是基础功能。我们能否附加穿衣建议、紫外线指数、未来几小时降水概率?
  • 异常处理与预警:当气温超过35℃、或预报有暴雨、大风时,系统能否立即触发告警,而不是等到下一次定时任务?
  • 流程可观测性:工作流运行失败时,我们如何快速定位是API问题、网络问题,还是消息格式问题?

基于这些思考,我设计了如下核心架构流程:

[定时触发器] -> [获取天气API数据] -> [数据清洗与校验] -> [核心逻辑判断]
       |                                          |
       |                                          v
       |                              [是异常天气?] -> [是] -> [发送预警通知]
       |                                          |
       |                                          + -> [否] -> [生成日常简报]
       |                                                       |
       +------------------------------------------------------> [发送至钉钉]

这个流程的骨架在n8n中可以通过节点连线直观呈现。但其中的血肉——数据校验、逻辑判断、消息生成——则需要我们使用JavaScript来填充。接下来,我们将进入n8n的实际操作界面,从搭建基础工作流开始。

2. 搭建基础工作流与Function节点初探

首先,确保你有一个运行中的n8n实例。你可以通过Docker快速部署:

docker run -it --rm \
  --name n8n \
  -p 5678:5678 \
  -v ~/.n8n:/home/node/.n8n \
  n8nio/n8n

访问 http://localhost:5678 即可进入控制台。我们创建一个新的工作流。

2.1 设置可靠的触发器

第一个节点我们选择 Schedule Trigger。这是工作流的心脏起搏器。对于日常天气简报,我们可以设置为每天早晨7点触发。但为了测试方便,可以先设置为每分钟触发一次。

在节点的配置中,关键字段是 Rule。使用Cron表达式来定义时间规则。例如:

  • 0 7 * * * 表示每天UTC时间7点整运行。注意时区问题,你可以在n8n的设置或节点高级选项中调整时区。
  • */5 * * * * 表示每5分钟运行一次,适合调试。

提示:在生产环境中,建议触发频率不要高于天气API提供商的免费套餐限制,通常每小时1-2次是安全的。

2.2 获取天气数据并处理不确定性

接下来,添加一个 HTTP Request 节点。这里我们以和风天气(HeWeather,需注册获取Key)或Open-Meteo(免费无需Key)为例。

关键配置步骤:

  1. URL:填入API端点,例如 https://api.open-meteo.com/v1/forecast。
  2. Method:选择 GET。
  3. Query Parameters:这是传递参数的地方。我们需要添加:
    • latitude: 纬度(如上海的31.23)
    • longitude: 经度(如上海的121.47)
    • current_weather: true (获取当前天气)
    • hourly: temperature_2m,precipitation_probability (获取未来小时数据)
    • timezone: auto
  4. Authentication:选择 Generic Credential,在Type中选择 Query Parameter,Name填 apikey(如果使用需要Key的API)。

点击 Execute Node 进行测试。你应该能看到一个包含当前天气和未来预报的JSON响应。

然而,网络世界从不完美。这个HTTP节点可能会失败。因此,我们需要配置错误处理机制。在HTTP Request节点的设置中,找到 Error Handling 部分:

  • Retry On Fail: 勾选。设置 Max Attempts 为3,Timeout 为5000毫秒。这样,在第一次请求失败后,n8n会自动重试最多3次。
  • Continue On Fail: 通常不勾选,因为我们希望流程在获取数据失败时停止,而不是传递错误数据下去。

但这还不够。有时API会返回HTTP 200成功状态码,但内容却是错误信息(如 {“code”:”500”, “msg”:”Internal Error”})。这时,我们需要下一个节点——Function节点来进行数据清洗和校验。

2.3 使用JavaScript进行数据校验与格式化

添加一个 Function 节点。这个节点允许我们编写自定义JavaScript代码来处理上游节点传来的数据。

假设上游HTTP节点返回的数据结构如下:

{
  "current_weather": {
    "temperature": 22.5,
    "windspeed": 10.3,
    "weathercode": 3
  },
  "hourly": {
    "time": ["2023-10-01T00:00", ...],
    "temperature_2m": [22.1, 21.8, ...],
    "precipitation_probability": [10, 5, ...]
  }
}

我们的Function节点代码需要做以下几件事:

// 从上一个节点获取数据项(items)
const inputData = items[0].json;

// 1. 基础校验:检查必需字段是否存在
if (!inputData.current_weather || !inputData.hourly) {
  // 如果数据不完整,抛出一个错误,这将导致工作流在此失败并记录日志
  throw new Error('从天气API获取的数据结构不完整,缺少关键字段。');
}

const current = inputData.current_weather;
const hourly = inputData.hourly;

// 2. 数据增强:将天气代码转换为可读描述
const weatherCodeMap = {
  0: '晴朗',
  1: '大部晴朗',
  2: '局部多云',
  3: '阴天',
  // ... 可根据API文档补充更多
};
const weatherDescription = weatherCodeMap[current.weathercode] || `未知代码(${current.weathercode})`;

// 3. 提取关键预警信息:计算未来6小时内最高降水概率
const nextSixHoursPrecip = hourly.precipitation_probability.slice(0, 6);
const maxPrecipProb = Math.max(...nextSixHoursPrecip);

// 4. 判断是否需要预警(逻辑可以根据需求调整)
const isHighTempAlert = current.temperature > 35; // 高温预警
const isRainAlert = maxPrecipProb > 70; // 高降水概率预警

// 5. 构建一个包含所有加工后数据的对象,传递给下游节点
const enrichedWeatherData = {
  location: '上海',
  currentTemp: current.temperature,
  currentWindSpeed: current.windspeed,
  currentWeather: weatherDescription,
  maxPrecipitationProbNext6h: maxPrecipProb,
  alerts: {
    highTemperature: isHighTempAlert,
    heavyRain: isRainAlert
  },
  rawHourlyData: hourly // 保留原始数据,供后续节点可能使用
};

// 将处理后的数据作为新项返回
return [{
  json: enrichedWeatherData
}];

这段代码是工作流的“大脑”。它不仅仅传递数据,还进行了校验、转换和初步的逻辑判断,输出了一个结构清晰、富含业务语义的对象。下游节点将基于这个对象来决定是发送日常简报还是紧急预警。

3. 实现条件分支与智能消息路由

经过Function节点的处理,我们的数据已经包含了预警标志 (alerts.highTemperature 和 alerts.heavyRain)。现在,我们需要根据这些标志来决定工作流的走向。这里会用到 IF 节点。

添加一个 IF 节点。它的配置是基于表达式(Expression)进行条件判断。

  • 条件设置:选择 Add Condition -> Expression。
  • 表达式:这里我们需要判断是否有任何预警被触发。表达式可以写为: {{ $json.alerts.highTemperature || $json.alerts.heavyRain }} 这个表达式会检查 alerts 对象下的两个布尔值,只要有一个为 true,整个表达式结果即为 true。

IF节点会根据表达式结果,将数据流导向两个不同的分支:true(有预警)和 false(无预警,日常情况)。

3.1 预警分支:构建紧急通知消息

如果进入 true 分支,意味着出现了需要立即关注的天气情况。我们添加一个 Function 节点来专门生成预警消息。

const data = items[0].json;
const alertMessages = [];

if (data.alerts.highTemperature) {
  alertMessages.push(`🔥 **高温预警**:当前气温 ${data.currentTemp}°C,请注意防暑降温,避免户外长时间活动。`);
}
if (data.alerts.heavyRain) {
  alertMessages.push(`🌧️ **强降水预警**:未来6小时内降水概率高达 ${data.maxPrecipitationProbNext6h}%,建议携带雨具,注意出行安全。`);
}

const finalAlertMessage = `## ⚠️ 天气异常预警通知\n\n` +
                         `**地点**:${data.location}\n` +
                         `**时间**:${new Date().toLocaleString('zh-CN')}\n` +
                         `**当前天气**:${data.currentWeather},温度 ${data.currentTemp}°C,风速 ${data.currentWindSpeed} km/h\n\n` +
                         `**预警详情**:\n` +
                         alertMessages.join('\n') + `\n\n请相关同事注意防范。`;

// 输出一个专门用于预警的消息对象
return [{
  json: {
    dingtalkMessage: {
      msgtype: 'markdown',
      markdown: {
        title: '天气异常预警',
        text: finalAlertMessage
      },
      at: {
        isAtAll: true // @所有人,可根据需要调整
      }
    },
    isAlert: true // 打上标记,便于后续可能的统一处理
  }
}];

3.2 日常分支:生成友好天气简报

如果进入 false 分支,则是风平浪静的日常。我们添加另一个 Function 节点来生成更友好、信息丰富的简报。

const data = items[0].json;

// 从原始数据中提取未来几小时温度趋势
const hourlyTemps = data.rawHourlyData.temperature_2m.slice(0, 12); // 取未来12小时
const tempTrend = hourlyTemps.map((t, i) => `${i+1}时后: ${t}°C`).join(' | ');

// 生成一些趣味建议
let suggestion = '';
if (data.currentTemp > 28) {
  suggestion = '天气较热,建议穿着短袖、薄裙等清凉夏季服装。';
} else if (data.currentTemp < 10) {
  suggestion = '天气较凉,建议穿外套、毛衣等春秋过渡装。';
} else {
  suggestion = '气温适宜,可穿着衬衫、薄外套等舒适衣物。';
}

const dailyMessage = `## 🌤️ 早安天气简报\n\n` +
                    `**${data.location}** · ${new Date().toLocaleDateString('zh-CN', { weekday: 'long', month: 'long', day: 'numeric' })}\n\n` +
                    `**现在**:${data.currentWeather},${data.currentTemp}°C,风速 ${data.currentWindSpeed} km/h\n` +
                    `**体感**:${suggestion}\n\n` +
                    `**今日趋势**:${tempTrend}\n\n` +
                    `祝您有愉快的一天!☕`;

return [{
  json: {
    dingtalkMessage: {
      msgtype: 'markdown',
      markdown: {
        title: '每日天气',
        text: dailyMessage
      }
      // 日常通知不需要@所有人
    },
    isAlert: false
  }
}];

至此,我们有了两条并行的消息生产流水线,它们产出结构相似但内容不同的消息对象。接下来,需要将它们汇聚并发送出去。

4. 连接钉钉与消息发送优化

无论来自哪个分支,消息最终都要发送到钉钉群。我们使用 DingTalk 节点(如果官方节点库没有,可以使用 Webhook 节点模拟)。这里以Webhook节点为例,因为它更通用。

4.1 配置钉钉机器人Webhook

首先,在钉钉群中添加一个自定义机器人,获取其Webhook地址。格式类似:https://oapi.dingtalk.com/robot/send?access_token=XXXXXX。

在n8n中添加一个 HTTP Request 节点,用于发送消息。

  • URL:填入你的钉钉机器人Webhook地址。
  • Method:POST
  • Headers:添加 Content-Type: application/json
  • Body:选择 JSON,然后填入表达式 {{ $json.dingtalkMessage }}。这样,无论是预警消息还是日常简报,其dingtalkMessage属性都会被作为请求体发送。

4.2 处理发送失败与重试

消息发送也可能失败(网络波动、钉钉限流等)。除了在HTTP Request节点设置重试,我们还可以增加一个“守门员”节点来确保关键预警不丢失。

在预警分支的Function节点和发送节点之间,可以插入一个 Wait 节点(设置等待几秒)和一个 IF 节点进行简易的“发送状态检查”(这需要更复杂的设计,例如将发送状态写入临时存储)。一个更简单的工程化实践是:利用n8n的错误触发机制。

你可以创建两个独立的工作流:

  1. 主工作流:负责获取、处理、判断天气。如果判断为预警,它不仅生成消息,还通过 Webhook 节点触发第二个工作流。
  2. 预警专用工作流:由Webhook触发,专门负责发送预警消息,并配置更激进的重试策略(如重试5次,每次间隔1分钟)。这样可以将预警发送的逻辑与主业务逻辑解耦,提高可靠性。

4.3 消息格式与@功能优化

钉钉Markdown支持有限但有用的语法。我们已经在上面的Function节点中使用了Markdown格式。对于@功能:

  • at.isAtAll: true 会@所有人,慎用。
  • 可以指定具体手机号:at.atMobiles: ["138xxxx8888"]。你可以尝试从n8n的数据库或其他服务中动态获取需要被@的成员列表,注入到消息对象中。

一个更清晰的发送节点配置参数表示例如下:

参数项值/表达式说明
URLhttps://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN钉钉机器人Webhook
MethodPOST
HeaderContent-Type: application/json
Body{{ $json.dingtalkMessage }}直接使用上游构建好的消息对象
OptionsIgnore SSL Issues根据你的证书情况可选
Error HandlingRetry On Fail (Max: 3)配置发送失败重试

5. 工程化扩展:监控、日志与故障排查

一个投入生产使用的自动化流程,必须具备可观测性。n8n在这方面提供了良好的内置支持。

5.1 利用执行历史(Executions)进行调试

工作流每次运行都会在 Executions 页面留下完整记录。你可以看到:

  • 每个节点的输入和输出数据。
  • 每个节点的执行状态(成功、失败、错误信息)。
  • 整个工作流的执行时间线。

这是排查问题的第一现场。例如,如果发送到钉钉失败,你可以查看HTTP Request节点的输出,确认是网络错误、认证错误还是消息格式错误。

5.2 添加日志节点(Debug Node)

在关键节点之后,特别是Function节点之后,可以添加 Debug 节点。它不会影响数据流,但会将当前节点的输入数据完整地输出到执行日志中。这对于开发阶段验证数据处理逻辑是否正确极其有用。

5.3 实现简单的状态监控

你可以创建一个辅助工作流,定期检查主天气机器人的“心跳”。例如:

  1. 在主工作流的最后,添加一个 HTTP Request 节点,调用一个可以记录最后成功运行时间的服务(甚至可以是一个简单的Google Sheet或Airtable)。
  2. 创建另一个监控工作流,定时(比如每小时)检查这个“最后成功时间”。如果超过24小时没有更新,则通过钉钉或其他渠道发送告警:“天气机器人可能已停止工作”。

5.4 凭证(Credentials)的安全管理

在HTTP Request节点中使用的API Key,务必通过n8n的 Credentials 功能来管理。不要将密钥硬编码在节点参数中。在Credential中选择“Create New”,然后选择对应的认证类型(如Query Auth),将密钥填入并命名保存。之后在所有需要该API的工作流中,都可以安全地引用这个凭证名称。这样既方便轮换密钥,也避免了密钥泄露在配置中。

整个项目搭建下来,你会发现,n8n配合JavaScript提供的灵活性,足以应对许多中小型自动化场景。它降低了集成多个服务的门槛,但又通过代码节点保留了处理复杂逻辑的能力。这个天气机器人只是一个起点,你可以基于此模式,轻松扩展出监控服务器状态、同步跨平台数据、处理表单审批等各类自动化流程,真正把重复性的数字劳动交给机器。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐