3个城市的天气预报
高级
这是一个Personal Productivity, Multimodal AI领域的自动化工作流,包含 20 个节点。主要使用 Set, Code, Gmail, Merge, FormTrigger 等节点。 使用AI增强格式将OpenWeatherMap多城市天气预报发送到Gmail
前置要求
- •Google 账号和 Gmail API 凭证
- •可能需要目标 API 的认证凭证
- •OpenAI API Key
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "MQmMLg34LuvMsRan",
"meta": {
"instanceId": "a24c3628d7fa97ff1f4002e54a6f44641e116c70a069e29f6d0359e4681ff811",
"templateId": "806",
"templateCredsSetupCompleted": true
},
"name": "3个城市的天气预报",
"tags": [],
"nodes": [
{
"id": "45a9c8c7-b596-4f48-baf8-09a46e4004df",
"name": "JS - 验证来自LLM的JSON",
"type": "n8n-nodes-base.code",
"position": [
1824,
16
],
"parameters": {
"jsCode": "function extractRaw(o) {\n if (typeof o === 'string') return o;\n const m = o.message || o.messages || o;\n if (typeof m?.content === 'string') return m.content;\n if (Array.isArray(m?.content)) {\n const first = m.content[0];\n if (typeof first?.text?.value === 'string') return first.text.value;\n if (typeof first?.text === 'string') return first.text;\n }\n if (Array.isArray(m)) {\n const c = m[0]?.content;\n if (typeof c === 'string') return c;\n if (Array.isArray(c)) {\n const first = c[0];\n if (typeof first?.text?.value === 'string') return first.text.value;\n if (typeof first?.text === 'string') return first.text;\n }\n }\n return JSON.stringify(o);\n}\nfunction tryParseJSON(s) {\n if (!s || typeof s !== 'string') return null;\n try { return JSON.parse(s); } catch {}\n const start = s.indexOf('{');\n const end = s.lastIndexOf('}');\n if (start >= 0 && end > start) {\n const candidate = s.slice(start, end + 1);\n try { return JSON.parse(candidate); } catch {}\n }\n return null;\n}\n\nconst raw = extractRaw($json);\nconst parsed = tryParseJSON(raw);\n\nif (parsed && parsed.subject && parsed.html) {\n return [{ subject: parsed.subject, html: parsed.html, ok: true }];\n} else {\n return [{ ok: false, reason: 'bad_json', raw }];\n}\n"
},
"typeVersion": 2
},
{
"id": "f837e628-3bac-4d22-afef-32ba3df0c016",
"name": "GMAIL - 发送天气简报",
"type": "n8n-nodes-base.gmail",
"position": [
1712,
352
],
"webhookId": "9043b9cb-2579-49d5-8812-f39b84575dc7",
"parameters": {
"sendTo": "<<recipient@example.com>>",
"message": "={{ $('LLM - AI Weather Briefing Composer').item.json.message.content.html }}",
"options": {},
"subject": "={{ $('LLM - AI Weather Briefing Composer').item.json.message.content.subject }}"
},
"credentials": {
"gmailOAuth2": {
"id": "<< REPLACE_WITH_CREDENTIAL_REF >>",
"name": "Gmail account"
}
},
"typeVersion": 2.1
},
{
"id": "b6c7d8b2-0a69-472d-b508-3453eadeea4b",
"name": "JS - 为LLM打包城市数据",
"type": "n8n-nodes-base.code",
"position": [
1328,
96
],
"parameters": {
"jsCode": "// Pack all incoming items into one payload for the LLM.\nconst cities = items.map(i => ({\n city: i.json.city,\n country: i.json.country,\n summaries: i.json.summaries\n}));\nreturn [{ cities }];\n"
},
"typeVersion": 2
},
{
"id": "e4a3a918-3b48-44ba-a463-3d77786fdc9a",
"name": "JS - 构建每日摘要(5天)",
"type": "n8n-nodes-base.code",
"position": [
1216,
288
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "// We need to get the asked city from Pick Lat/Lon node\n// Since we can't use $items() reliably, we'll store it differently\n\n// The OpenWeatherMap response has city.name which is the API's name\n// But we need the original asked city name\n// Check if we have it in the input, otherwise fall back to API name\nconst apiCityName = $json.city?.name || 'City';\nconst apiCountry = $json.city?.country || '';\nconst tzOffset = $json.city?.timezone || 0; // seconds\nconst data = $json.list || [];\nconst units = 'metric';\n\n// Use the API city name for now - we'll fix this in the next step\nconst city = apiCityName;\nconst country = apiCountry;\n\nfunction toLocalDateStr(dt) {\n // dt is unix seconds; shift by city timezone\n const d = new Date((dt + tzOffset) * 1000);\n return d.toISOString().slice(0,10); // YYYY-MM-DD\n}\n\n// Group by day\nconst days = {};\nfor (const p of data) {\n const day = toLocalDateStr(p.dt);\n if (!days[day]) days[day] = { points: [] };\n days[day].points.push(p);\n}\n\n// Aggregate per day\nconst daySummaries = Object.entries(days).map(([date, obj]) => {\n const pts = obj.points;\n\n const temps = pts.map(p => p.main.temp);\n const min = Math.min(...temps);\n const max = Math.max(...temps);\n\n // wind\n const windMax = Math.max(...pts.map(p => (p.wind?.gust ?? p.wind?.speed ?? 0)));\n\n // rain total (mm)\n const rain = pts.reduce((acc, p) => acc + (p.rain?.['3h'] || 0), 0);\n\n // pop average (precip probability 0..1)\n const popAvg = pts.reduce((a,p)=>a+(p.pop||0),0) / (pts.length || 1);\n\n // humidity average\n const humAvg = pts.reduce((a,p)=>a+(p.main?.humidity||0),0) / (pts.length || 1);\n\n // dominant condition by frequency\n const counts = {};\n for (const p of pts) {\n const desc = (p.weather?.[0]?.main || 'Other');\n counts[desc] = (counts[desc] || 0) + 1;\n }\n const dominant = Object.entries(counts).sort((a,b)=>b[1]-a[1])[0]?.[0] || 'Other';\n\n return {\n date,\n min_temp: Number(min.toFixed(1)),\n max_temp: Number(max.toFixed(1)),\n dominant_condition: dominant,\n rain_mm: Number(rain.toFixed(1)),\n precip_prob_avg: Number((popAvg*100).toFixed(0)),\n wind_max: Number(windMax.toFixed(1)),\n humidity_avg: Number(humAvg.toFixed(0)),\n unit_temp: units === 'metric' ? '°C' : '°F',\n unit_wind: units === 'metric' ? 'm/s' : 'mph'\n };\n}).sort((a,b)=>a.date.localeCompare(b.date));\n\n// keep only next 5 days\nconst next5 = daySummaries.slice(0,5);\n\nreturn {\n city,\n country,\n timezone_offset_seconds: tzOffset,\n summaries: next5\n};"
},
"typeVersion": 2
},
{
"id": "0a824d7f-d3d3-4c8e-b782-36d5c94191ae",
"name": "OpenWeatherMap (OWM) - 5天预报(按城市)",
"type": "n8n-nodes-base.openWeatherMap",
"position": [
1024,
80
],
"parameters": {
"cityName": "={{ $json.asked_city }}",
"operation": "5DayForecast"
},
"credentials": {
"openWeatherMapApi": {
"id": "KwBMaZHr4WNwZ47Y",
"name": "OpenWeatherMap account 2"
}
},
"executeOnce": false,
"typeVersion": 1
},
{
"id": "2ad3a2e8-3605-4203-a113-44fe99c34e66",
"name": "JS - 选择经纬度",
"type": "n8n-nodes-base.code",
"position": [
640,
288
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const asked = $json.city || null;\nconst location = $json.body?.[0] || $json.body;\n\nif (!location || !location.lat || !location.lon) {\n throw new Error('No valid geocoding results for: ' + asked);\n}\n\nreturn {\n asked_city: asked,\n city_from_api: location.name || asked,\n country: location.country || null,\n lat: location.lat,\n lon: location.lon\n};"
},
"typeVersion": 2
},
{
"id": "e6202cc9-8516-4c12-beea-1c3dca68dc8a",
"name": "SET - 将查询城市传递至预报",
"type": "n8n-nodes-base.set",
"position": [
832,
288
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "2c7d6017-02ca-4e76-ab6e-000707eea2ab",
"name": "asked_city",
"type": "string",
"value": "={{ $json.asked_city }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "d2f43ed1-2f4a-476b-8e38-4277ce72e786",
"name": "MERGE - 配对城市",
"type": "n8n-nodes-base.merge",
"position": [
464,
288
],
"parameters": {
"mode": "combine",
"options": {
"includeUnpaired": false
},
"combineBy": "combineByPosition"
},
"typeVersion": 3.2
},
{
"id": "5dd95724-b5fe-4230-87b4-4554f1dc74ea",
"name": "HTTP - OWM地理编码",
"type": "n8n-nodes-base.httpRequest",
"position": [
224,
32
],
"parameters": {
"url": "https://api.openweathermap.org/geo/1.0/direct\n",
"options": {
"response": {
"response": {
"fullResponse": true,
"responseFormat": "json"
}
}
},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "q",
"value": "=={{ encodeURIComponent($json.city) }}"
},
{
"name": "limit",
"value": "1"
},
{
"name": "appid",
"value": "<<YOUR_API_KEY>>"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "d574f191-aed1-472c-80e0-96f22ef14480",
"name": "JS - 标准化城市输入",
"type": "n8n-nodes-base.code",
"position": [
16,
304
],
"parameters": {
"jsCode": "// Collect only city names from the form payload\n// Filter out metadata fields like submittedAt and formMode\nconst cities = [];\n\nfor (const [key, value] of Object.entries($json)) {\n // Exclude known metadata fields\n if (key === 'submittedAt' || key === 'formMode') {\n continue;\n }\n \n // Include all other string values that are not empty\n if (typeof value === 'string' && value.trim()) {\n cities.push(value.trim());\n }\n}\n\n// Limit to 3 cities and create one item per city\nreturn cities.slice(0, 3).map(c => ({ city: c }));"
},
"typeVersion": 2
},
{
"id": "11ec0415-f62f-4e61-8416-7468c235e2b7",
"name": "TRIGGER - 输入城市名称",
"type": "n8n-nodes-base.formTrigger",
"position": [
-160,
304
],
"webhookId": "37fec581-130f-4c8f-9aad-33f0832f2fd3",
"parameters": {
"options": {},
"formTitle": "Weather predictor demo task",
"formFields": {
"values": [
{
"fieldLabel": "Enter City Name 1",
"placeholder": "eg: Hyderabad",
"requiredField": true
},
{
"fieldLabel": "Enter City Name 2",
"placeholder": "eg: Bangalore",
"requiredField": true
},
{
"fieldLabel": "Enter City Name 3",
"placeholder": "Chennai",
"requiredField": true
}
]
},
"formDescription": "It provides the weather prediction of 3 cities and provide general tips and precuations"
},
"typeVersion": 2.3
},
{
"id": "7b1dfae5-be7d-4d76-9f6c-3bf3bc0eb271",
"name": "LLM - AI天气简报生成器",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
1520,
16
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o-mini",
"cachedResultName": "GPT-4O-MINI"
},
"options": {},
"messages": {
"values": [
{
"role": "system",
"content": "=You are an expert meteorologist creating professional weather briefings. Return STRICT JSON with keys: subject, html. Use clear, actionable language. No conversational text outside the JSON.\n\n"
},
{
"content": "=You are an expert meteorologist creating professional weather briefings. Return STRICT JSON with keys: subject, html. Use clear, actionable language. No conversational text outside the JSON.\n```\n\n## **Revised User Prompt:**\n```\nCreate a professional 5-day weather forecast email for EACH city in this JSON:\n{{ JSON.stringify($json.cities) }}\n\nREQUIREMENTS:\n\n📋 Structure:\n- Start with a friendly greeting and brief intro\n- For each city, use H2 with \"🌤️ City — Country\"\n- HTML table with these columns: Date | Temp Range | Conditions | Rain(mm) | Precip(%) | Wind(m/s) | General Tips | Precautions\n- Add a concluding paragraph with travel/planning tips across all cities\n\n🎨 Styling (use inline CSS):\n- Header row: background #4A90E2 (blue), white text, bold, font-size: 14px\n- Borders: 1px solid #DEE2E6 on all cells\n- Padding: 10px in cells\n- Font: Arial, sans-serif, font-size: 13px\n- Center-align all table content\n- Table width: 100%, border-collapse: collapse\n\n🌈 CONDITION-BASED ROW COLORS (apply to entire row based on dominant_condition):\n- \"Clear\": background-color: #FFFACD (light yellow)\n- \"Clouds\": background-color: #ADD8E6 (light blue)\n- \"Rain\": background-color: #FFB6C6 (light red/pink)\n- Extremely sunny (max_temp ≥ 35°C AND condition is \"Clear\"): background-color: #FFBF00 (amber)\n- Default (other conditions): background-color: #FFFFFF (white)\n\n📊 Content Guidelines:\n- Date: Format as \"YYYY-MM-DD (Day)\" - e.g., \"2025-10-19 (Sun)\"\n- Temp Range: Show as \"Min°C / Max°C\" (e.g., \"24° / 32°\")\n- Conditions: Add weather emoji + condition name:\n - Clear: ☀️ Clear\n - Clouds: ☁️ Clouds\n - Rain: 🌧️ Rain\n - Thunderstorm: ⛈️ Thunderstorm\n- Rain: Show mm value\n- Precip%: Show percentage value\n- Wind: Show m/s value\n- General Tips: Brief everyday advice (e.g., \"Stay hydrated\", \"Dress lightly\")\n- Precautions: Specific safety warnings (detailed below)\n\n⚠️ PRECAUTIONS Column Content (show relevant warnings only, or \"None\" if weather is safe):\n- Heavy rain (rain_mm ≥ 10 OR precip_prob_avg ≥ 70): \"⚠️ Heavy rain expected. Carry umbrella, avoid travel if possible. Expect waterlogging.\"\n- Moderate rain (rain_mm ≥ 5 AND < 10 OR precip_prob_avg ≥ 50 AND < 70): \"☔ Rain likely. Carry rain gear, drive carefully.\"\n- Extreme heat (max_temp ≥ 35°C): \"🌡️ HEAT ALERT: Stay indoors during peak hours (12-4 PM). Drink 3+ liters water. Risk of heat stroke.\"\n- High heat (max_temp ≥ 32°C AND < 35°C): \"☀️ Very hot. Limit outdoor activities. Use sunscreen SPF 50+.\"\n- High wind (wind_max ≥ 10 m/s): \"💨 Strong winds expected. Secure loose objects. Avoid outdoor hoardings.\"\n- Thunderstorms (if \"Thunderstorm\" in conditions): \"⛈️ STORM WARNING: Stay indoors. Unplug electronics. Avoid water bodies.\"\n- Multiple conditions: Combine warnings separated by \" | \"\n- Safe weather: \"None\" or \"✅ Safe weather conditions\"\n\n💡 GENERAL TIPS Column Content (positive, practical daily advice):\n- Clear & pleasant (max < 32°C): \"Great for outdoor activities\"\n- Clear & hot (max ≥ 32°C): \"Stay hydrated, use sun protection\"\n- Clouds: \"Comfortable weather, good for sightseeing\"\n- Light rain: \"Keep umbrella handy\"\n- Heavy rain: \"Plan indoor activities\"\n- Windy: \"Secure loose items\"\n\n📝 Email Subject:\nCreate an engaging subject that highlights the most significant weather event:\n- If any day has rain_mm ≥ 10: \"⚠️ Heavy Rain Alert: [City Names] | 5-Day Forecast\"\n- If any day has max_temp ≥ 35°C: \"🌡️ Heat Wave Alert: [City Names] | 5-Day Forecast\"\n- If all days are clear: \"☀️ Sunny Week Ahead: [City Names] | 5-Day Forecast\"\n- Otherwise: \"5-Day Weather Forecast: [City Names]\"\n\n🎯 Conclusion Paragraph:\nInclude:\n- Overall weather pattern across all cities\n- Best days for outdoor activities/travel\n- Most important safety advisory if any extreme weather\n- Friendly closing line\n\nCRITICAL: Apply the condition-based background color to the ENTIRE ROW (all cells in that row), not just the Conditions column.\n\nReturn ONLY valid JSON:\n{\"subject\":\"...\",\"html\":\"...\"}"
}
]
},
"jsonOutput": true
},
"credentials": {
"openAiApi": {
"id": "<< REPLACE_WITH_OPENAI_CREDENTIAL_REF >>",
"name": "OpenAi account"
}
},
"typeVersion": 1.8
},
{
"id": "07e5c472-3d02-4579-a1ef-77bd8ea44bf5",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
-816,
-512
],
"parameters": {
"width": 608,
"height": 1056,
"content": "# 试用说明"
},
"typeVersion": 1
},
{
"id": "39f8510b-c1a3-44b7-97bd-fe491bd72d15",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
176,
-160
],
"parameters": {
"color": 4,
"width": 208,
"height": 656,
"content": "## 地理编码 (OWM)"
},
"typeVersion": 1
},
{
"id": "746f1466-187a-421b-8160-b4a982dd02a8",
"name": "便签2",
"type": "n8n-nodes-base.stickyNote",
"position": [
400,
96
],
"parameters": {
"color": 6,
"width": 560,
"height": 400,
"content": "## 城市与坐标配对"
},
"typeVersion": 1
},
{
"id": "fd98f570-9beb-4cb7-9de3-eb1fb2599a33",
"name": "便签3",
"type": "n8n-nodes-base.stickyNote",
"position": [
976,
-144
],
"parameters": {
"color": 3,
"width": 208,
"height": 640,
"content": "## 预报获取"
},
"typeVersion": 1
},
{
"id": "7864bbf7-eeab-43a6-ab00-7d1786a5f414",
"name": "便签4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1456,
-144
],
"parameters": {
"color": 2,
"width": 576,
"height": 352,
"content": "## LLM AI天气简报"
},
"typeVersion": 1
},
{
"id": "8ac37160-d037-42a3-8900-c70f326c5901",
"name": "便签5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1200,
-144
],
"parameters": {
"color": 4,
"height": 640,
"content": "## 摘要与打包"
},
"typeVersion": 1
},
{
"id": "fe07b361-deae-4f13-b3a7-d85804d5d7a7",
"name": "### 需要帮助?",
"type": "n8n-nodes-base.stickyNote",
"position": [
1456,
224
],
"parameters": {
"color": 6,
"width": 576,
"height": 288,
"content": "## 交付"
},
"typeVersion": 1
},
{
"id": "0d440118-543a-44ca-bdc1-03187c110850",
"name": "## 试试看!",
"type": "n8n-nodes-base.stickyNote",
"position": [
-192,
112
],
"parameters": {
"color": 5,
"width": 352,
"height": 384,
"content": "## 输入与标准化"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "dd0565ff-6b0c-4003-a4a6-fd5579f32295",
"connections": {
"JS - Pick Lat/Lon": {
"main": [
[
{
"node": "SET - Carry Asked City to Forecast",
"type": "main",
"index": 0
}
]
]
},
"MERGE - Pair Cities": {
"main": [
[
{
"node": "JS - Pick Lat/Lon",
"type": "main",
"index": 0
}
]
]
},
"HTTP - OWM Geocoding": {
"main": [
[
{
"node": "MERGE - Pair Cities",
"type": "main",
"index": 0
}
]
]
},
"JS - Bundle Cities for LLM": {
"main": [
[
{
"node": "LLM - AI Weather Briefing Composer",
"type": "main",
"index": 0
}
]
]
},
"JS - Normalize City Inputs": {
"main": [
[
{
"node": "HTTP - OWM Geocoding",
"type": "main",
"index": 0
},
{
"node": "MERGE - Pair Cities",
"type": "main",
"index": 1
}
]
]
},
"TRIGGER - Input City Names": {
"main": [
[
{
"node": "JS - Normalize City Inputs",
"type": "main",
"index": 0
}
]
]
},
"JS - Validate JSON from LLM": {
"main": [
[
{
"node": "GMAIL - Send Weather Briefing",
"type": "main",
"index": 0
}
]
]
},
"LLM - AI Weather Briefing Composer": {
"main": [
[
{
"node": "JS - Validate JSON from LLM",
"type": "main",
"index": 0
}
]
]
},
"SET - Carry Asked City to Forecast": {
"main": [
[
{
"node": "OpenWeatherMap (OWM) - 5 Day Forecast (by City)",
"type": "main",
"index": 0
}
]
]
},
"JS - Build Daily Summaries (5 days)": {
"main": [
[
{
"node": "JS - Bundle Cities for LLM",
"type": "main",
"index": 0
}
]
]
},
"OpenWeatherMap (OWM) - 5 Day Forecast (by City)": {
"main": [
[
{
"node": "JS - Build Daily Summaries (5 days)",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
高级 - 个人效率, 多模态 AI
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
{模板} ATS优化简历和求职信生成器
使用Gemini AI和PDF.co优化简历并生成求职信
If
Set
Code
+10
23 节点Paul Abraham
个人效率
来自多个招聘网站的求职自动化
使用 5 个招聘平台和 AI 简历生成器自动化求职与申请
If
Set
Code
+14
34 节点Gerald Denor
个人效率
智能旅行套餐查找器 - 使用Skyscanner和Booking.com搜索航班和酒店
智能旅行套餐查找器:通过 Skyscanner 和 Booking.com 搜索航班与酒店
Set
Code
Gmail
+5
12 节点Cheng Siong Chin
个人效率
每日WhatsApp摘要与群组级别控制
WhatsApp群组摘要工作流
If
Set
Code
+13
39 节点Luís Philipe Trindade
个人效率
工作日日志记录
AI工时表生成器 - 集成Gmail、日历和GitHub到Google表格
If
Set
Code
+11
31 节点Luka Zivkovic
个人效率
基于AI的潜在客户资格评定与个性化触达(使用Relevance AI)
基于AI的潜在客户资格评定与个性化触达:使用Relevance AI
Set
Code
Gmail
+11
34 节点Diptamoy Barman
内容创作
工作流信息
难度等级
高级
节点数量20
分类2
节点类型9
作者
Sridevi Edupuganti
@edupugantiI help customers experience 10x faster ROI through AI Automation. AI Generalist | Pursuing Generative AI & ML(IIT-G) | Google Certified Prompt Engineer | Ex-VP | Ex-Microsoft | ISB Certified CTO & AI Leader | Azure & AI Strategist | 5X Azure | n8n level2 | Wellness Advocate & Cult Ninja
外部链接
在 n8n.io 查看 →
分享此工作流