ActPi向阳技术栈
← 返回文章列表

从天气数据到行动提醒:一个降雨预警系统的设计与实现

文章目录
  1. 先定义“要做什么决定”
  2. 系统架构:核心判断只保留一份
  3. 地点精度与隐私边界
  4. 把降水量翻译成行动等级
  5. 雷达图是辅助信号,不是答案
  6. 让附加功能失败时,核心功能仍可用
  7. 消息通知本质上是一个状态机
  8. Netlify 上的部署方式
  9. 开发中踩过的坑
  10. 地图初始化顺序
  11. 把轮询频率误认为数据频率
  12. 混淆查询时间与数据时间
  13. 让一个超时拖垮整个页面
  14. 仍然存在的边界
  15. 结语

天气应用通常擅长告诉我们“今天有雨”,生活中的问题却更具体:雨大约什么时候到、是否来得及处理户外物品、网页没有打开时能否主动提醒?

我把这个需求做成了一个轻量的降雨预警网站:rain.actpi.com。它聚合分钟级降水、实况天气、逐小时预报、官方预警和雷达回波,再把原始数据转换为一条容易执行的建议。

本文不展开真实监测地点、坐标、通知地址和线上调度时段,只讨论可以复用的设计方法与工程经验。

先定义“要做什么决定”

如果只是展示温度和天气图标,调用一次天气接口就够了。但降雨提醒真正要回答的是:

  • 未来一段时间内是否会开始降雨;
  • 从收到提醒到完成处理还剩多少时间;
  • 降水只是短暂飘雨,还是已经达到需要行动的程度;
  • 附近雷达回波是否正在靠近;
  • 页面无人查看时,后台能否持续监测且避免重复通知。

因此,系统的核心不是“天气数据看板”,而是一个持续更新的决策器。输入是多个时间尺度的气象数据,输出则应尽量简单:立即处理、继续观察或暂时安全。

这也带来一个重要原则:先定义行动窗口,再选择数据源和阈值。否则页面很容易堆满数据,却仍然无法帮助用户做决定。

系统架构:核心判断只保留一份

线上版本由静态页面、Netlify Functions、定时任务和状态存储组成:

浏览器
  ├─ 天气接口 ── 分钟级降水、实况、逐小时预报、官方预警
  └─ 雷达接口 ── 雷达元数据与地图瓦片

定时监测任务
  ├─ 调用同一套风险分析逻辑
  ├─ 保存上一次提醒状态
  └─ 通过已配置的消息通道发送通知

浏览器不直接保存第三方服务凭据,而是请求站点自己的 Serverless API。函数负责签名、超时控制、响应校验和字段整理,前端只接收展示所需的结构化结果。

风险判断被提取成共享模块,网页查询和后台监测都调用它。这样可以避免最麻烦的一类问题:页面认为风险很低,定时任务却因为另一套规则发送了紧急提醒。

地点精度与隐私边界

降雨具有明显的局地差异,使用城市或行政区级天气往往过于宽泛。系统允许用坐标指定监测点,再根据数据供应商支持的精度映射到相应天气网格。

这里需要区分两个概念:

  • 用户选择的地点:用于地图定位和交互;
  • 供应商实际查询网格:由接口支持的坐标精度决定。

页面会说明数据对应的是附近网格,而不是把结果包装成“某栋建筑上空的精确预报”。坐标在进入接口前还要检查数值范围,避免异常参数被转发给上游服务。

自定义地点只保存在浏览器本地存储中,不同步到公共服务端。仓库和文章同样不记录家庭、学校、单位等真实地点,也不公开通知密钥、Webhook 或生产环境变量值。

把降水量翻译成行动等级

“未来有雨”并不等于“现在必须行动”。为了减少无意义提醒,我把判断拆为三个层次:

  1. 观察窗口:在未来若干个分钟级时段中寻找最早降雨;
  2. 处置余量:用预计开始时间减去处理所需时间;
  3. 风险等级:综合峰值、累计量和持续时长判断影响。

一个简化模型可以写成:

若窗口内没有有效降水:暂时安全
若只有零星、短暂降水:继续观察
若单时段强度或连续累计超过阈值:建议尽快处理

阈值不是气象行业标准,而是针对具体使用场景的经验参数。户外物品的材质、遮挡、风向和容错程度不同,结果也会不同。因此页面应同时显示最早降雨时间、窗口累计量、峰值和剩余时间,让用户能够理解建议是如何得出的。

比“预测得绝对准确”更现实的目标,是让规则透明、参数可调整,并在每次获得新数据后重新计算。

雷达图是辅助信号,不是答案

分钟级预报适合做数值判断,雷达图则擅长回答另一个问题:附近哪里正在下雨,回波大致往哪个方向移动?

系统在 Leaflet 地图上叠加最近几帧雷达瓦片,并实现了一个保持简单的回波外推:

  1. 从连续雷达帧中抽样有效回波像素;
  2. 计算每帧回波的加权中心;
  3. 根据相邻帧位移估算移动方向和速度;
  4. 将最新回波按该速度向前平移;
  5. 判断平移后的回波是否接近监测点。

这个方法便于解释,也足够用来提示“回波正在靠近”。但它无法预测雨区的新生、增强、分裂或消散,更不是数值天气预报。因此雷达外推只作为辅助信号,不能覆盖分钟级降水和官方预警的结论。

让附加功能失败时,核心功能仍可用

天气站点依赖多条外部链路:实况、逐小时预报、预警、雷达元数据、地图底图和通知服务都可能独立失败。可靠性设计的关键,是让故障局部化。

系统将分钟级降水作为核心数据,其余请求并行执行并独立处理结果:

  • 实况接口超时,不影响降雨风险卡片;
  • 雷达服务不可用,仍保留天气判断和地图底图;
  • 底图加载失败,页面给出明确提示,而不是无限显示“加载中”;
  • 通知通道失败,会记录错误并允许其他已配置通道继续尝试。

对于可能返回代理错误页的接口,也不能直接假设响应一定是 JSON。更稳妥的处理顺序是:先读取文本,检查 HTTP 状态,再显式解析 JSON,最后验证关键字段。

const response = await fetch(url, { signal });
const body = await response.text();

if (!response.ok) {
  throw new Error(`upstream request failed: ${response.status}`);
}

let data;
try {
  data = JSON.parse(body);
} catch {
  throw new Error('upstream did not return valid JSON');
}

缓存策略也按数据类型区分:分钟级降水保持较短缓存,变化较慢的实况和逐小时数据可以适当延长。接口超时时,如果已有一份仍具参考价值的缓存,页面会标注数据年龄,而不是直接清空所有内容。

消息通知本质上是一个状态机

定时任务可以频繁检查,但不能每次发现同一片雨区就发送一条消息。为此,状态存储至少要记录:

  • 上一轮是否处于风险状态;
  • 风险首次出现和最近一次提醒的时间;
  • 上一轮风险摘要;
  • 当前风险是否已经解除。

通知逻辑可以简化为:

安全 → 风险:发送提醒
风险 → 风险:仅在等级显著上升或冷却期结束后提醒
风险 → 安全:更新状态,不发送紧急消息
安全 → 安全:不操作

这种边沿触发比“每次轮询都发送”更符合人的使用习惯。多个通知通道可以并存,但访问令牌和 Webhook 只能放在 Netlify 的服务端环境变量中,绝不能写进前端脚本或提交到仓库。

Netlify 上的部署方式

项目采用静态前端加 Serverless Functions 的结构。核心目录大致如下:

web/                    # 静态页面与交互逻辑
netlify/functions/      # 天气、雷达和定时监测入口
netlify/lib/            # 共享分析与通知模块
netlify.toml            # 构建、路由和调度配置

前端通过站内路径访问天气和雷达接口,由 Netlify 重写到对应函数。生产环境再按配置的频率和时段运行监测任务,并使用持久化存储保存去重状态。

部署中最值得坚持的几条规则是:

  • 仓库只提交环境变量名和示例值,不提交真实凭据;
  • 预览部署默认不发送真实通知;
  • 查询函数与定时任务共享判断模块;
  • 修改环境变量后重新部署,并进行一次受控的通知测试;
  • 对上游调用设置超时、缓存和清晰的降级路径。

本地调试时,应先使用模拟数据或测试通知完成规则验证,再执行单次真实查询;确认结果无误后,最后开启周期监测。这样既能节省接口额度,也能避免调试阶段打扰实际接收者。

开发中踩过的坑

地图初始化顺序

Leaflet 要先设置地图中心和缩放级别,再添加图层。否则可能出现 Set map center and zoom first。地图容器尺寸发生变化后,还需要重新计算布局,否则瓦片可能只渲染一部分。

把轮询频率误认为数据频率

客户端每分钟请求一次,并不代表上游每分钟都会生成新预报。轮询周期应参考数据源的实际更新时间,并结合缓存与接口额度设置。更频繁地获取同一批数据,只会增加请求量。

混淆查询时间与数据时间

“页面什么时候刷新”和“供应商什么时候更新数据”是两个不同时间。页面同时展示两者后,用户才能判断看到的是缓存、延迟数据还是最新结果。

让一个超时拖垮整个页面

早期实现把多个接口串联起来,只要云量或雷达请求超时,核心降雨判断也无法显示。改成并行查询、独立超时和模块化降级后,页面在外部服务波动时仍然可用。

仍然存在的边界

这个系统适合个人场景中的辅助决策,但它仍有清晰边界:

  • 公共天气网格不能代替现场雨量计;
  • 简单回波外推无法描述对流系统的快速生消;
  • 第三方天气、地图和消息服务都可能限流或中断;
  • 本地保存的地点不会自动同步到其他设备;
  • 经验阈值需要根据实际误报和漏报持续调整。

涉及人身安全、灾害避险或生产调度时,应以气象主管部门发布的预警和专业系统为准,不能只依赖这个轻量工具。

结语

这个项目最有价值的部分,不是把更多天气字段搬到网页上,而是完成了从数据到行动的转换:

什么时候可能下雨?现在是否需要处理?如果我没打开页面,系统能否及时提醒?

当数据源、判断规则、通知状态和失败边界都被明确下来,一个看似简单的生活需求就变成了可解释、可维护的工程系统。天气仍然充满不确定性,但好的工具可以把这种不确定性压缩成更清晰的下一步。