基于Node.js与Telegraf构建支持双历法的Telegram天气机器人

news2026/5/12 18:50:18
1. 项目概述一个功能完备的Telegram天气机器人最近在做一个需要集成天气信息的小项目顺手就把之前写的一个Telegram天气机器人翻新重构了一遍。这个机器人不只是简单地查询温度它融合了实时天气、24小时预报并且特别加入了波斯历Jalali Calendar的支持对于需要同时使用公历和波斯历的用户来说非常实用。整个项目用TypeScript构建跑在Node.js环境上通过OpenWeather API获取数据代码结构清晰部署也简单。如果你手头有Telegram Bot和OpenWeather的API密钥十分钟内就能让它跑起来为你服务。这个机器人适合那些想学习如何构建一个真实、有交互的Telegram Bot的开发者也适合需要为社群或自己提供一个便捷天气查询工具的朋友。它涵盖了Bot开发中常见的几个关键点命令处理、外部API调用、环境变量管理、定时任务以及多语言或者说多历法支持。接下来我会详细拆解这个项目的设计思路、实现细节并分享在开发过程中踩过的坑和总结的经验。2. 项目整体设计与核心思路拆解2.1 技术栈选型与理由这个项目选择的技术栈是Node.js TypeScript Telegraf。这里我解释一下为什么这么选这也是很多新手在启动项目时容易纠结的地方。首先Node.js是构建轻量级、高I/O应用的首选。Telegram Bot本质上是一个持续运行、处理大量异步消息的服务Node.js的非阻塞I/O模型非常适合这种场景能够轻松应对并发请求而不会因为某个用户的查询阻塞整个服务。版本要求22.14.0是为了确保使用一些较新的ES特性如顶层await和稳定的性能。其次TypeScript是大型或长期维护项目的“安全带”。Bot的逻辑虽然不复杂但涉及命令解析、API响应处理、数据格式化等多个环节。使用TypeScript可以在编码阶段就捕获大量的潜在类型错误比如将字符串当数字用或者访问一个可能为undefined的API响应字段。这能极大减少运行时错误提升代码的可维护性。对于初学者来说一开始接触TypeScript可能有点门槛但它的类型提示本身就是最好的文档长期来看利远大于弊。最后框架选择了Telegraf。它是目前Node.js生态中最流行、最强大的Telegram Bot框架。相比直接使用原始的Telegram Bot APITelegraf提供了优雅的中间件系统、便捷的命令监听器bot.command和强大的上下文Context管理。它抽象了底层的HTTP请求细节让我们可以更专注于业务逻辑。例如用一行代码bot.command(start, ...)就能处理/start命令非常直观。2.2 核心功能架构解析这个机器人的功能可以划分为四个核心模块它们共同协作来完成服务。2.2.1 用户交互与命令调度模块这是机器人的“大脑”负责与用户直接对话。它监听用户发送的所有消息和命令。当收到/weather 北京时它需要解析出命令weather和参数北京。这个模块基于Telegraf构建利用其路由机制将不同的命令分发给对应的处理函数。同时它还要管理用户的会话状态比如记住用户设置的默认城市。这里的设计关键在于清晰的路由和错误处理。对于无法识别的命令或格式错误的输入要给用户友好、明确的提示而不是让机器人沉默或崩溃。2.2.2 天气数据获取与处理模块这是机器人的“信息源”。它不生产数据而是数据的搬运工和加工者。核心工作是调用第三方天气服务——OpenWeather API。这里有几个技术要点第一是API密钥的安全管理绝对不能硬编码在代码里必须通过环境变量注入。第二是网络请求的健壮性外部API可能会超时、返回错误码或无效数据我们的代码必须包含重试逻辑和全面的错误处理。第三是数据解析与转换OpenWeather返回的JSON数据非常详尽我们需要从中提取出对用户有用的信息如温度、体感温度、湿度、风速、天气图标、预报列表并转换成易于阅读的文本或格式化消息。2.2.3 消息格式化与多历法支持模块这是机器人的“化妆师”。原始数据是冰冷的数字这个模块负责把它们变成温暖、易读的消息。温度后面加上°C风速注明方向用️、️这样的表情符号增加可读性。最具特色的部分是双历法支持。除了显示标准的公历Gregorian日期和时间还会同时显示波斯历Jalali的日期。这需要集成一个像jalaali-js这样的库将UTC时间戳或Date对象转换为波斯历的年、月、日。格式化时需要精心设计消息模板让两种历法信息清晰、美观地并列展示不显得杂乱。2.2.4 定时任务与通知模块这是机器人的“贴心小秘书”。/setdefault命令允许用户设置一个默认城市。基于这个功能我们可以实现每日天气推送。这需要引入一个定时任务库例如node-cron。该模块会在每天早上的特定时间比如早上8点遍历所有设置过默认城市的用户这里需要一个简单的持久化存储如一个JSON文件或轻量级数据库然后代表机器人主动向这些用户的CHAT_ID发送该城市的当日天气简报。这个功能将机器人从被动的查询工具变成了主动的服务提供者大大提升了用户体验和粘性。3. 环境配置与项目初始化实操3.1 前置资源申请与准备在写第一行代码之前我们需要先拿到两把“钥匙”Telegram Bot Token 和 OpenWeather API Key。3.1.1 创建你的Telegram机器人打开Telegram搜索BotFather这个官方机器人。向它发送/newbot命令。按照提示依次输入你机器人的显示名称如“Karim的天气小助手”和用户名必须以bot结尾如karim_weather_bot。创建成功后BotFather会返回一串类似1234567890:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw的字符串。这就是你的BOT_TOKEN务必妥善保管它等同于你机器人的密码。任何人拿到它都可以控制你的机器人。为了测试你还需要获取你自己的CHAT_ID。一个简单的方法是先给你的机器人发一条消息然后访问这个URLhttps://api.telegram.org/botYOUR_BOT_TOKEN/getUpdates。将YOUR_BOT_TOKEN替换成你刚拿到的那串Token。在返回的JSON数据中找到message.chat.id字段的值那就是你的CHAT_ID。注意BotFather提供的Token具有最高权限。切勿将它提交到GitHub等公开代码仓库。我们稍后会将其放入.env文件并确保该文件被.gitignore忽略。3.1.2 获取OpenWeather API密钥访问 OpenWeather官网 并注册账号。登录后在用户面板中找到 “API Keys” 选项卡。点击 “Create Key”给你的密钥起个名字例如“MyTelegramBot”。生成后你会看到一长串字母数字组合的密钥。这就是你的OPENWEATHER_API_KEY。OpenWeather的免费套餐Free tier通常足够个人使用它提供了当前天气、5天3小时预报等接口但有每分钟调用次数的限制通常60次/分钟。对于一个小型天气机器人来说只要不做频繁的批量查询完全够用。3.2 本地开发环境搭建拿到密钥后我们就可以开始搭建本地开发环境了。# 1. 克隆项目代码到本地 git clone https://github.com/imkarimkarim/Telegram-Weather-Bot.git cd Telegram-Weather-Bot # 2. 安装项目依赖 npm install执行npm install后package.json中定义的依赖如telegraf,dotenv,node-cron,jalaali-js,axios等会被下载到node_modules文件夹。3.2.1 环境变量配置文件.env的创建与管理这是项目安全配置的核心步骤。在项目根目录下创建一个名为.env的文件。# .env 文件内容示例 BOT_TOKEN1234567890:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw OPENWEATHER_API_KEYyour_32_char_openweather_api_key_here CHAT_ID987654321请务必将示例值替换成你刚才申请到的真实密钥和Chat ID。重要实操心得我强烈建议在.env文件旁边创建一个.env.example文件。这个文件包含所有需要的环境变量名但不包含真实值。将其提交到Git仓库。这样其他协作者克隆项目后可以复制.env.example为.env并填入自己的值既保证了安全又明确了配置项。# .env.example BOT_TOKENyour_telegram_bot_token_here OPENWEATHER_API_KEYyour_openweather_api_key_here CHAT_IDyour_telegram_chat_id_here同时确保.gitignore文件中包含.env防止误提交。3.2.2 项目结构与初始运行安装完依赖并配置好.env后我们可以先尝试运行一下。# 开发模式运行如果配置了 npm run dev npm run dev # 或者编译TypeScript后运行 npm run build npm start如果一切配置正确你的终端应该不会报错并且你的Telegram机器人会变成“在线”状态。此时在Telegram中打开与机器人的对话发送/start你应该能收到欢迎信息。4. 核心功能实现细节与代码剖析4.1 机器人初始化与命令监听框架机器人的入口文件例如index.ts或bot.ts负责一切初始化工作。我们使用dotenv库在程序启动最初加载.env文件中的配置。import { Telegraf } from telegraf; import dotenv from dotenv; // 加载环境变量 dotenv.config(); // 从环境变量中读取Token这里进行非空断言实际生产环境应做更严谨的校验 const bot new Telegraf(process.env.BOT_TOKEN!); // 定义命令处理 bot.command(start, (ctx) { const welcomeMessage 欢迎使用天气机器人️\n\n我可以为您查询全球城市的天气信息并支持公历/波斯历双历显示。\n\n使用 /help 查看所有可用命令。; ctx.reply(welcomeMessage); }); bot.command(help, (ctx) { const helpText 可用命令列表 /start - 启动机器人并显示欢迎信息 /weather 城市名 - 查询指定城市的当前天气与24小时预报例如/weather 北京 /setdefault 城市名 - 设置您的默认查询城市 /reset - 将默认城市重置为初始设置Astaneh-ye Ashrafiyeh /help - 显示此帮助信息 ; ctx.reply(helpText); }); // 启动机器人开始轮询Telegram服务器获取更新 bot.launch().then(() { console.log( 天气机器人已成功启动); }); // 优雅地处理进程终止信号 process.once(SIGINT, () bot.stop(SIGINT)); process.once(SIGTERM, () bot.stop(SIGTERM));这段代码搭建起了机器人的骨架。Telegraf实例是核心bot.command方法用于注册命令监听器。ctx(上下文) 对象包含了当前消息的所有信息发送者、聊天ID、消息内容等以及回复消息的方法如ctx.reply。4.2 天气数据获取服务的封装我们将与OpenWeather API的交互封装成一个独立的服务模块如weatherService.ts以提高代码的可复用性和可测试性。import axios from axios; import { OpenWeatherCurrent, OpenWeatherForecast } from ./types; // 自定义类型定义 const API_KEY process.env.OPENWEATHER_API_KEY; const BASE_URL https://api.openweathermap.org/data/2.5; export class WeatherService { /** * 获取指定城市的当前天气 * param city 城市名称支持中文、英文 * returns 格式化后的当前天气数据 */ static async getCurrentWeather(city: string): Promiseany { try { const response await axios.get(${BASE_URL}/weather, { params: { q: city, appid: API_KEY, units: metric, // 使用公制单位获取摄氏度温度 lang: zh_cn // 获取中文描述的天气 } }); // 这里可以对response.data进行清洗和转换返回我们需要的部分 return this.formatCurrentWeather(response.data); } catch (error: any) { console.error(获取当前天气失败 (城市: ${city}):, error.message); // 根据不同的错误类型如城市不存在、API密钥无效、网络错误抛出更具体的错误 if (error.response?.status 404) { throw new Error(未找到城市 ${city}请检查名称是否正确。); } else if (error.response?.status 401) { throw new Error(OpenWeather API 密钥无效或已过期。); } else { throw new Error(获取天气数据时发生网络或服务器错误请稍后重试。); } } } /** * 获取指定城市的5天3小时预报并从中提取未来24小时的数据 * param city 城市名称 * returns 未来24小时的天气预报列表 */ static async get24hForecast(city: string): Promiseany[] { try { const response await axios.get(${BASE_URL}/forecast, { params: { q: city, appid: API_KEY, units: metric, lang: zh_cn, cnt: 8 // 5天预报每3小时一个点取8个点正好是24小时 } }); // 处理预报数据提取并格式化未来24小时的信息 return this.formatForecastData(response.data.list); } catch (error) { console.error(获取预报失败 (城市: ${city}):, error); throw error; // 将错误抛给上层处理 } } private static formatCurrentWeather(data: OpenWeatherCurrent): any { // 实现数据格式化逻辑例如 return { city: data.name, country: data.sys.country, temp: Math.round(data.main.temp), // 四舍五入取整 feels_like: Math.round(data.main.feels_like), humidity: data.main.humidity, description: data.weather[0].description, icon: data.weather[0].icon, // 用于后续匹配表情符号 windSpeed: data.wind.speed, // ... 其他所需字段 }; } private static formatForecastData(list: any[]): any[] { // 实现预报数据格式化逻辑可能包括时间转换、数据筛选等 return list.map(item ({ /* 格式化后的预报项 */ })); } }这个服务类使用了axios进行HTTP请求并进行了基本的错误处理。将API密钥、请求参数和格式化逻辑集中在这里使得业务逻辑命令处理更加清晰。4.3 双历法日期时间格式化实现双历法显示是这个机器人的特色功能。我们需要一个函数接收一个标准的JavaScriptDate对象同时输出公历和波斯历的日期字符串。import { toJalaali } from jalaali-js; /** * 格式化日期时间同时显示公历和波斯历 * param date 日期对象 * returns 格式化后的日期时间字符串 */ export function formatDualCalendar(date: Date): string { // 1. 格式化公历日期 const gregorianDateStr date.toLocaleDateString(zh-CN, { year: numeric, month: long, day: numeric, weekday: short }); const gregorianTimeStr date.toLocaleTimeString(zh-CN, { hour: 2-digit, minute: 2-digit }); // 2. 转换为波斯历 const jalaaliDate toJalaali( date.getFullYear(), date.getMonth() 1, // JS月份是0-11 date.getDate() ); // 波斯历月份名称可自定义或使用库提供的 const jalaaliMonths [فروردین, اردیبهشت, خرداد, تیر, مرداد, شهریور, مهر, آبان, آذر, دی, بهمن, اسفند]; const jalaaliDateStr ${jalaaliDate.jy}年 ${jalaaliMonths[jalaaliDate.jm - 1]} ${jalaaliDate.jd}日; // 3. 组合输出 return 公历${gregorianDateStr} ${gregorianTimeStr}\n 波斯历${jalaaliDateStr}; }这个函数首先利用JavaScript内置的toLocaleDateString方法生成格式优美的中文公历日期。然后使用jalaali-js库的toJalaali函数将公历日期转换为波斯历的年、月、日。最后将两者组合成一条易于阅读的消息。你可以根据需要调整日期和时间的格式。4.4 /weather 命令的完整处理流程现在我们将天气服务、日期格式化和消息生成串联起来实现/weather命令。import { WeatherService } from ./services/weatherService; import { formatDualCalendar } from ./utils/calendarFormatter; import { getWeatherEmoji } from ./utils/emojiMapper; // 一个将天气图标代码映射为表情符号的函数 bot.command(weather, async (ctx) { // 1. 解析命令参数 const messageText ctx.message.text; const args messageText.split( ).slice(1); // 去掉 /weather const cityName args.join( ); // 2. 参数校验 if (!cityName.trim()) { return ctx.reply(⚠️ 请指定要查询的城市名称。例如/weather 德黑兰); } // 3. 显示“正在查询”提示提升用户体验 const waitingMsg await ctx.reply(正在查询 ${cityName} 的天气信息请稍候...); try { // 4. 并发获取当前天气和24小时预报 const [currentWeather, hourlyForecast] await Promise.all([ WeatherService.getCurrentWeather(cityName), WeatherService.get24hForecast(cityName) ]); // 5. 生成最终消息 const currentTime new Date(); const weatherEmoji getWeatherEmoji(currentWeather.icon); let forecastText 未来24小时预报\n; hourlyForecast.forEach((item, index) { const time new Date(item.dt * 1000); // OpenWeather的时间戳是秒 const hourStr time.getHours().toString().padStart(2, 0) :00; forecastText ${hourStr} | ${item.temp}°C | ${item.description}\n; }); const finalMessage ${weatherEmoji} *${currentWeather.city}, ${currentWeather.country}* ${formatDualCalendar(currentTime)} ️ 当前温度${currentWeather.temp}°C (体感 ${currentWeather.feels_like}°C) 湿度${currentWeather.humidity}% ️ 风速${currentWeather.windSpeed} m/s 天气状况${currentWeather.description} ${forecastText} .trim(); // 6. 发送最终消息并删除“正在查询”提示 await ctx.deleteMessage(waitingMsg.message_id); await ctx.replyWithMarkdown(finalMessage); } catch (error: any) { // 7. 错误处理 console.error(处理 /weather 命令时出错:, error); await ctx.deleteMessage(waitingMsg.message_id); // 出错也删除等待提示 await ctx.reply(❌ 抱歉查询失败${error.message}); } });这个处理流程体现了良好的用户体验和健壮性参数检查、加载提示、并发请求以提高速度、丰富的消息格式化、完整的错误捕获与用户反馈。4.5 用户默认城市管理与定时推送为了实现/setdefault和每日推送我们需要一个简单的持久化存储来记录用户的偏好。对于小型项目使用一个JSON文件是简单有效的选择。// userPreferences.ts - 用户偏好管理模块 import fs from fs/promises; import path from path; interface UserPreference { chatId: number; defaultCity: string; subscribed: boolean; } const PREF_FILE path.join(__dirname, ../data/user_prefs.json); export class UserPreferenceManager { static async getUserPref(chatId: number): PromiseUserPreference | null { const prefs await this.readPrefsFile(); return prefs.find(p p.chatId chatId) || null; } static async setDefaultCity(chatId: number, city: string): Promisevoid { let prefs await this.readPrefsFile(); const existingIndex prefs.findIndex(p p.chatId chatId); if (existingIndex 0) { prefs[existingIndex].defaultCity city; prefs[existingIndex].subscribed true; // 设置默认城市即视为订阅推送 } else { prefs.push({ chatId, defaultCity: city, subscribed: true }); } await this.writePrefsFile(prefs); } static async getAllSubscribedUsers(): PromiseUserPreference[] { const prefs await this.readPrefsFile(); return prefs.filter(p p.subscribed); } private static async readPrefsFile(): PromiseUserPreference[] { try { const data await fs.readFile(PREF_FILE, utf-8); return JSON.parse(data); } catch (error) { // 如果文件不存在返回空数组 return []; } } private static async writePrefsFile(prefs: UserPreference[]): Promisevoid { await fs.mkdir(path.dirname(PREF_FILE), { recursive: true }); // 确保目录存在 await fs.writeFile(PREF_FILE, JSON.stringify(prefs, null, 2), utf-8); } }有了这个管理器/setdefault命令的处理就很简单了bot.command(setdefault, async (ctx) { const args ctx.message.text.split( ).slice(1); const cityName args.join( ); if (!cityName) { return ctx.reply(⚠️ 请指定要设置为默认的城市。例如/setdefault 伊斯法罕); } try { // 可选先验证城市是否有效 await WeatherService.getCurrentWeather(cityName); await UserPreferenceManager.setDefaultCity(ctx.chat.id, cityName); await ctx.reply(✅ 已成功将您的默认城市设置为${cityName}\n您将在每天上午8点收到该城市的天气推送。); } catch (error: any) { await ctx.reply(❌ 设置失败${error.message}); } });最后实现定时推送任务。我们在主启动文件里添加import cron from node-cron; import { UserPreferenceManager } from ./userPreferences; import { WeatherService } from ./services/weatherService; // 每天上午8点服务器时间执行 cron.schedule(0 8 * * *, async () { console.log(开始执行每日天气推送任务...); try { const subscribedUsers await UserPreferenceManager.getAllSubscribedUsers(); for (const user of subscribedUsers) { try { const weather await WeatherService.getCurrentWeather(user.defaultCity); const message 早安\n您关注的 ${user.defaultCity} 今日天气${weather.temp}°C${weather.description}; await bot.telegram.sendMessage(user.chatId, message); // 避免请求过快稍作延迟 await new Promise(resolve setTimeout(resolve, 100)); } catch (error) { console.error(向用户 ${user.chatId} 推送失败:, error); // 可以选择记录错误但不要因为一个用户失败而中断整个任务 } } console.log(每日天气推送任务完成。); } catch (error) { console.error(执行定时任务时发生全局错误:, error); } });5. 部署上线与生产环境考量本地开发测试无误后我们需要将机器人部署到一个7x24小时运行的服务器上。5.1 服务器环境准备与进程管理你可以选择任何云服务器如AWS EC2, DigitalOcean Droplet, 阿里云ECS等。核心步骤包括安装Node.js环境使用nvm管理Node版本是最佳实践。curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash source ~/.bashrc nvm install 22 # 安装项目要求的Node.js版本 nvm use 22部署代码将项目代码不包括node_modules和.env上传到服务器可以通过Git克隆。git clone your-repo-url cd Telegram-Weather-Bot安装依赖并构建npm ci --onlyproduction # 使用ci命令安装package-lock.json中确切的版本更稳定 npm run build配置生产环境变量在服务器上创建.env文件填入生产环境的BOT_TOKEN和OPENWEATHER_API_KEY。CHAT_ID可以留空或填入管理员的ID用于接收错误通知。使用进程管理器不能让Node进程在前台直接运行一旦断开SSH连接进程就终止了。我们需要一个进程管理器来保持其运行并在崩溃时自动重启。PM2是Node.js生态中最流行的选择。npm install -g pm2 pm2 start dist/index.js --name weather-bot # 假设编译后的入口文件是dist/index.js pm2 save # 保存当前进程列表 pm2 startup # 设置PM2开机自启根据提示执行生成的命令使用pm2 monit可以查看实时日志和资源占用pm2 logs weather-bot查看特定应用日志。5.2 日志记录与错误监控在生产环境中完善的日志至关重要。结构化日志不要只用console.log。可以使用winston或pino这样的日志库它们支持不同日志级别info, warn, error、输出到文件、按日期分割文件等功能。错误追踪在try...catch块中不仅要给用户返回友好提示还要将详细的错误堆栈记录到日志文件或发送到错误监控平台如Sentry方便事后排查。心跳检测可以设置一个简单的健康检查接口或定时任务定期向自己发送一条测试消息确保机器人还在正常运行。5.3 性能优化与API限制管理缓存策略天气数据变化不频繁可以对查询结果进行短期缓存例如5-10分钟。这能显著减少对OpenWeather API的调用次数避免触及免费套餐的速率限制同时提升机器人响应速度。可以使用node-cache或lru-cache实现内存缓存。请求队列与限流如果你的机器人用户量激增需要考虑对向外部的API请求如OpenWeather进行队列管理或限流防止短时间内发起过多请求导致IP被临时封禁。数据库升级当用户量增长到数百上千时JSON文件作为存储将变得低效且不安全。应考虑迁移到轻量级数据库如SQLite简单或PostgreSQL功能更全用于存储用户偏好和查询历史。6. 常见问题排查与实战经验在开发和维护这个机器人的过程中我遇到了不少典型问题这里总结一下希望能帮你避开这些坑。6.1 机器人无响应或命令无效问题现象可能原因排查步骤与解决方案发送/start无任何回复1. Bot Token 错误或无效。2. 机器人未启动。3. 服务器网络无法访问Telegram API。1. 检查.env文件中的BOT_TOKEN是否与BotFather提供的一致且未过期。2. 在服务器上运行pm2 status或 ps aux部分命令有效部分无效1. 命令处理函数存在语法错误导致崩溃。2. 命令监听器未正确注册。1. 检查服务器日志寻找未捕获的异常Uncaught Exception。2. 确认所有bot.command(...)调用都在bot.launch()之前。Telegraf的中间件和命令监听器需要先注册。机器人响应极慢1. OpenWeather API 响应慢或超时。2. 服务器性能不足或网络延迟高。3. 代码中存在同步阻塞操作。1. 在代码中为axios请求添加超时设置如timeout: 10000。2. 考虑引入缓存减少重复API调用。3. 使用async/await确保异步操作不被阻塞。6.2 天气数据获取失败或异常问题现象可能原因排查步骤与解决方案提示“未找到城市”1. 城市名称拼写错误或包含特殊字符。2. OpenWeather 的城市数据库中没有该城市。1. 建议用户使用标准的城市名如“Beijing”或“北京”避免使用别名或缩写。2. 可以尝试在代码中增加容错例如先尝试中文名查询失败后再尝试拼音或英文名。也可以集成一个城市搜索提示功能。天气数据明显错误如温度-100°C1. OpenWeather API 返回了异常数据。2. 数据解析或格式化代码有bug。1. 在WeatherService的格式化函数中添加数据有效性校验。例如检查温度值是否在一个合理的范围内如-50°C到60°C。2. 打印出原始的API响应 (console.log(data))对比OpenWeather官方文档检查字段映射是否正确。频繁收到“API密钥无效”错误1. API Key 在.env中配置错误。2. OpenWeather 免费套餐调用次数超限。3. Key 被盗用或主动重置。1. 双重检查.env文件中的OPENWEATHER_API_KEY值确保没有多余的空格或换行。2. 登录OpenWeather账户查看API调用统计。免费套餐通常为60次/分钟如果超限需优化代码加缓存或升级套餐。3. 在OpenWeather面板上撤销旧Key生成一个新Key替换。6.3 部署与运维相关故障问题现象可能原因排查步骤与解决方案PM2进程频繁重启1. 应用程序存在内存泄漏内存使用超过PM2默认限制。2. 代码中有未处理的Promise拒绝Unhandled Promise Rejection。1. 使用pm2 monit观察内存曲线。如果持续增长需要使用Node.js内存分析工具如heapdump查找泄漏点。2. 在项目入口处全局捕获未处理的Promise拒绝process.on(unhandledRejection, (reason, promise) { console.error(未处理的Promise拒绝:, reason); });定时推送任务没有执行1. 服务器时间时区设置不正确。2. Cron表达式写错。3.node-cron的定时任务在PM2的fork模式下可能有问题。1. 使用date命令检查服务器时间。确保时区正确如Asia/Shanghai。可以使用timedatectl set-timezone Asia/Shanghai设置。2. 检查cron.schedule(0 8 * * *, ...)表达式确认是每天8点。3. 尝试将PM2的运行模式从fork改为cluster或在Cron任务内部添加更详细的日志来追踪其是否被触发。用户数据默认城市丢失1.user_prefs.json文件权限问题导致无法写入。2. 多进程实例同时写入导致文件损坏如果未来扩展。1. 检查运行Node进程的用户对data/目录是否有写权限 (ls -la)。2.重要经验对于简单的文件存储在写入时使用fs.writeFile的同步版本或确保单进程写入。更好的长期方案是迁移到数据库数据库引擎本身处理了并发写入。6.4 个人实战心得与技巧环境变量是“金科玉律”永远不要将密钥硬编码。.env文件是本地开发的标配在生产环境如Docker、云服务器可以使用环境变量注入、密钥管理服务如AWS Secrets Manager或配置文件但需确保访问权限严格。错误处理要“内外兼修”对用户展示友好、简洁的错误信息如“服务暂时不可用请稍后再试”同时在后台日志记录完整的错误堆栈和上下文如请求的城市、用户ID这是快速定位线上问题的关键。为用户体验添加“润滑剂”在调用外部API如查询天气时使用ctx.reply(‘查询中...’)先给用户一个即时反馈操作完成后再编辑或删除这条消息并发送最终结果。这个简单的交互能极大提升用户感知的响应速度。TypeScript是你的“最佳搭档”在定义OpenWeather API返回数据的接口interface时你可能会发现官方文档的字段非常多。一个技巧是先快速定义一个包含你已知关键字段的接口然后通过console.log打印出一次完整的API响应再根据实际返回的数据结构来补充和完善你的TypeScript接口定义。这样比完全照搬文档更高效、更准确。从小处着手为扩展留余地这个项目从简单的查询开始逐步添加了预报、双历法、定时推送。在代码结构上从一开始就将天气服务、用户管理、工具函数分模块编写这使得后续添加新功能比如空气质量指数、天气预警变得非常容易只需要在相应的模块中添加方法并在命令处理器中调用即可。良好的架构是项目能够持续演进的基石。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2606904.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…