本文将带你从 普通请求式接口 改造为 Server-Sent Events (SSE) 长连接推送,并实现 每分钟主动推送和风天气实时天气 数据,实现大屏、监控平台、移动端的实时天气刷新。


1. 背景需求

原本系统的天气接口如下:

@GetMapping("/now")
public JSONObject now(@RequestParam String location) {
    return client.getWeatherNow(location);
}

这是一个典型的 短连接 + 请求式 API。每次前端想要最新天气,都必须主动发起 HTTP 请求。

缺点:

  • ⛔ 无法实时更新

  • ⛔ 大屏需要不断轮询,浪费资源

  • ⛔ 并发多时服务器压力大

为了解决这些问题,我们改造为:

⭐ 基于 SSE(Server-Sent Events)的天气实时推送服务

  • 前端只需发起一次连接

  • 服务端每 60 秒主动推送一次天气

  • 支持自动重连

  • 适合大屏场景、监控终端、物联网设备


2. SSE 原理快速说明

SSE(Server-Sent Events)是 HTML5 原生支持的 服务器单向推送 技术。

特点:

  • 服务端 → 客户端 单向消息

  • 前端自动重连(EventSource 内置)

  • 基于 HTTP,兼容性好

  • 天生适合大屏推送

与 WebSocket 对比:

能力 SSE WebSocket
单向推送
双向通信
心跳机制 内置 需自定义
前端易开发 非常简单 略复杂
适用场景 大屏、监控推送 游戏、聊天

因此天气推送用 SSE 是最佳选择。


3. 最终效果演示

前端只需:

const evt = new EventSource("/weather/sse/now?location=101280601");

evt.onmessage = (e) => {
    console.log("实时天气:", JSON.parse(e.data));
};

服务端每分钟自动返回:

{
  "temp": "25",
  "text": "晴",
  "updateTime": "2025-11-21 16:30:02"
}

4. Spring Boot SSE 服务端改造完整代码

4.1 推送 Controller

@RestController
@RequestMapping("/weather/sse")
public class WeatherSseController {

    private final QWeatherClient client;
    private final Map<String, SseEmitter> emitterMap = new ConcurrentHashMap<>();

    public WeatherSseController(QWeatherClient client) {
        this.client = client;
    }

    @GetMapping("/now")
    public SseEmitter now(@RequestParam String location) {
       SseEmitter emitter = new SseEmitter(0L);

        // 保存上一次成功的天气数据
        final JSONObject[] lastData = new JSONObject[1];

        executor.scheduleAtFixedRate(() -> {
            try {
                JSONObject data = weatherService.getWeatherSimple(location);

                if (data != null) {
                    lastData[0] = data; // 保存最新成功数据
                } else if (lastData[0] != null) {
                    // API 返回空,保持上次数据
                    data = lastData[0];
                } else {
                    // 第一次就失败,返回默认提示
                    JSONObject fallback = new JSONObject();
                    fallback.put("temp", "--");
                    fallback.put("text", "天气数据不可用");
                    fallback.put("windDir", "--");
                    fallback.put("windScale", "--");
                    fallback.put("updateTime", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                    data = fallback;
                }

                emitter.send(SseEmitter.event()
                        .name("weather")
                        .data(data.toJSONString()));

            } catch (IOException e) {
                // SSE 连接被关闭或异常
                log.error("SSE 推送异常: " + e.getMessage());
            } catch (Exception e) {
                log.error("获取天气数据异常: " + e.getMessage());
            }
        }, 0, 1, TimeUnit.MINUTES);

        emitter.onCompletion(() -> log.error("SSE 已完成"));
        emitter.onTimeout(() -> log.error("SSE 超时"));
        emitter.onError((e) -> log.error("SSE 连接出错: " + e.getMessage()));

        return emitter;
    }

  
}

⭐ 关键点说明:

  • SseEmitter(0L):永不超时

  • 使用 ScheduledExecutorService 每分钟推送一次

  • 自动关闭连接,避免资源泄漏

  • name("weather") 前端可按事件名监听


5. QWeather 和风天气 Client 实现

@Component
public class QWeatherClient {

    @Value("${qweather.key}")
    private String apiKey;

    @Value("${qweather.host}")
    private String apiHost;

    private final RestTemplate restTemplate = new RestTemplate();

    public JSONObject getWeatherNow(String location) {
        String url = String.format(
                "%s/v7/weather/now?location=%s&key=%s",
                apiHost, location, apiKey
        );

        String resp = restTemplate.getForObject(url, String.class);
        return JSON.parseObject(resp);
    }
}

6. application.yml 配置

qweather:
  key: 你的key
  host: https://api.qweather.com

7. 前端监听 SSE(可直接用在大屏)

const source = new EventSource(`/weather/sse/now?location=101280601`);

// 接收消息
source.addEventListener("weather", function (event) {
    const data = JSON.parse(event.data);
    console.log("最新天气", data);
});

// 自动重连
source.onerror = () => {
    console.log("连接断开,客户端会自动重连...");
};

前端不需要手写重连逻辑,EventSource 会在网络断开后自动 reconnect。


8. 项目实战中的典型应用场景

✔ 大屏城市天气监控
✔ 企业气象监控系统
✔ 气象数据驱动的自动化业务(如下雨时自动关闭户外设备)
✔ IoT 设备屏幕周期天气更新
✔ 小程序与 Web 可共用接口


9. 对比轮询的性能优势

指标 轮询 SSE
请求次数 每分钟 N 次用户 × 仅 1 次长期连接
网络消耗 极低
延迟 依赖轮询间隔 实时
并发表现 优秀

SSE 特别适合天气这种稳定低频数据推送。


10. 总结

本文完整实现:

  • 基于 SSE 的天气实时推送服务端

  • 和风天气 API 的实时查询

  • 任务调度每分钟推送

  • 前端 EventSource 实时更新

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐