Spring Boot 中基于 WebClient 的 SSE 流式接口实战

—— 从 Feign 到 WebClient 的一次真实踩坑记录

一、背景:为什么我要做 SSE?

在最近的一个项目中,我负责接入一个 AI 问答服务
一开始的接口形态非常常规:

@PostMapping("/health_manager") public RespBean<HealthManagerQueryDataVO> sendQuery(...) 

客户端发请求,服务端等 AI 全部生成完内容,再一次性返回。

问题很快就暴露了:

  • AI 返回慢(10 秒甚至更久)
  • 用户页面“卡死”,体验极差
  • 其实 AI 是“边生成边返回”的,但我们完全浪费了这个能力

于是,目标就很明确了:

把原有同步接口,改造成支持 SSE(Server-Sent Events)的流式接口

二、什么是 SSE?为什么适合 AI 问答?

1️⃣ SSE 是什么?

SSE(Server-Sent Events)是一种 服务器主动推送 的 HTTP 通信方式:

  • 基于 HTTP
  • 单向(服务端 → 客户端)
  • 长连接
  • 文本流(text/event-stream

返回的数据长这样:

data: 你好 data: 我是 data: AI 

客户端可以一边接收,一边渲染


2️⃣ 为什么 SSE 特别适合 AI 场景?

技术适配度
HTTP 普通接口❌ 等全部生成
WebSocket❌ 太重
SSE✅ 天生流式

AI 的输出特征是:

  • token 级 / 句子级生成
  • 可边生成边消费
  • 用户随时可能中断

👉 SSE 几乎是最优解


三、第一个坑:Feign 不支持 SSE

项目里原本调用 AI 服务用的是 Feign

@FeignClient("mb-ai") RespBean sendQuery(...) 

一开始我尝试“硬改”,但很快发现:

Feign 本质是一次性 HTTP 调用,它不支持流式消费响应体

哪怕 AI 服务是 SSE,Feign 也会:

  • 等完整响应
  • 再反序列化
  • 流式直接失效

结论很明确:

❌ Feign 不能用于 SSE
✅ SSE 必须用 WebClient / HttpClient

四、正确姿势:WebClient + SseEmitter

1️⃣ Controller 层:返回 SseEmitter

SSE 接口和普通接口最大的不同是:
返回值不再是业务对象,而是一个“连接本身”

@PostMapping( value = "/health_manager/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE ) public SseEmitter healthManagerStream( @RequestBody HealthManagerQueryDTO request) { SseEmitter emitter = new SseEmitter(0L); // 不超时 aiService.streamQuery(request, emitter); return emitter; } 

关键点:

  • produces = text/event-stream
  • 返回 SseEmitter
  • 业务逻辑交给 Service

2️⃣ Service 层:WebClient 真正消费 AI 流

webClient.post() .uri("/health_manager") .contentType(MediaType.APPLICATION_JSON) .accept(MediaType.TEXT_EVENT_STREAM) .bodyValue(request) .retrieve() .bodyToFlux(String.class) .subscribe( data -> emitter.send(data), error -> emitter.completeWithError(error), emitter::complete ); 

这段代码的含义是:

  • AI 每吐一段数据
  • 我就 emitter.send()
  • 前端立刻收到

真正实现了“边生成、边返回、边渲染”


五、第二个大坑:UnknownHostException: mb-ai

代码写完,一跑,直接报错:

java.net.UnknownHostException: mb-ai 

第一反应:

“不对啊,Feign 一直是能调用 mb-ai 的”

原因分析

  • Feign:自动走注册中心(Nacos / Eureka)
  • WebClient:只认 DNS
.baseUrl("http://mb-ai") 

在 WebClient 看来:

mb-ai 就是一个普通域名
但 DNS 根本不认识它

六、正确解法:WebClient 接入服务发现

1️⃣ 引入 LoadBalancer

<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> 

2️⃣ 给 WebClient.Builder 加 @LoadBalanced

@Configuration public class WebClientConfig { @Bean @LoadBalanced public WebClient.Builder webClientBuilder() { return WebClient.builder(); } } 

3️⃣ baseUrl 继续用服务名

.baseUrl("http://mb-ai") 

此时调用链变成:

WebClient → LoadBalancer → Nacos → 真实 IP:PORT 

UnknownHostException 到此彻底解决


七、最终依赖组合(最小可用)

<!-- WebClient / SSE --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> <!-- 服务发现 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-loadbalancer</artifactId> </dependency> <!-- Nacos(项目里一般已有) --> spring-cloud-starter-alibaba-nacos-discovery 
⚠️ 不会把项目变成 WebFlux
只是“在 MVC 项目里用 WebClient”

八、架构上的最终形态(我现在的做法)

Feign └── 普通同步接口(兼容老系统) WebClient └── SSE 流式接口(AI 问答) 

接口层设计成:

POST /health_manager // 非流式 POST /health_manager/stream // SSE 

前端可以按需选择。


九、一些实战踩坑总结

❌ Feign 强行做 SSE

→ 行不通

❌ WebClient 不加 LoadBalanced

→ 必炸 UnknownHostException

❌ 忘了 produces

→ 前端收不到流

❌ AI 实际没返回 text/event-stream

→ 你这边再对也没用


十、写在最后

这次改造最大的收获不是“把 SSE 跑通了”,而是更清楚地理解了:

  • Feign 和 WebClient 的边界
  • 同步接口和流式接口在架构层面的本质差异
  • AI 场景对交互模型的倒逼

如果你现在也在做:

  • AI 问答
  • 长文本生成
  • 实时推送

那么,SSE 几乎是绕不开的一步

Read more

Flutter 三方库 mcp_server 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、透明、基于 Model Context Protocol 的工业级 AI 插件服务端与上下文通信引擎

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 mcp_server 的鸿蒙化适配指南 - 在鸿蒙系统上构建极致、透明、基于 Model Context Protocol 的工业级 AI 插件服务端与上下文通信引擎 在鸿蒙(OpenHarmony)系统的端云一体化应用、人工智能辅助系统、或者是需要让大模型(LLM)由于由于能够直接物理感知并由于控制鸿蒙本地硬件、资源与工具的场景中,如何实现毫秒级的由于由于由于由跨协议通信?mcp_server 为开发者提供了一套工业级的、针对 Model Context Protocol 进行深度封装的服务端方案。本文将深入实战其在鸿蒙 AI 插件逻辑层中的应用。 前言 什么是 MCP?它是一个将“AI 模型上下文(Context)”与“由于由于由本地由于工具执行(Tools

技术拆解:P2P组网如何一键远程AI

技术拆解:P2P组网如何一键远程AI

文章目录 * **远程访问AI服务的核心是什么?** * **从暴露服务到连接设备** * **核心组件与交互解析** * **安全架构深度剖析** * **一键安装脚本的技术实现** * **# Windows** * **#macOS** * **#Linux** * **与AI工作流的结合实践** 远程访问AI服务的核心是什么? 你自己在电脑或者服务器上装了AI服务,比如大语言模型、Stable Diffusion这些,但是有个头疼的事儿:外面的人或者你在别的地方,怎么既安全又方便地连上这些本地的服务?以前的办法要么得有公网IP,还得敲一堆命令行用SSH隧道,要么就是直接开端口映射,等于把服务直接晾在公网上,太不安全了。 今天咱们就好好说说一种靠P2P虚拟组网的办法,还拿个叫节点小宝的工具举例子,看看它怎么做到不用改啥东西,点一下就装好,还能建个加密的通道,实现那种“服务藏得好好的,想连就能直接连上”的安全远程访问方式。 从暴露服务到连接设备 核心思路转变在于:不再尝试将内网服务端口暴露到公网(一个危险的攻击面),而是将外部访问设

从零开始:在腾讯云服务器上部署 OpenClaw AI 助手(2)—— 浏览器自动化功能配置

从零开始:在腾讯云服务器上部署 OpenClaw AI 助手(2)—— 浏览器自动化功能配置 让 AI 助手拥有"眼睛"和"双手",实现网页自动化操控 前言 在上一篇博客中,我们成功在腾讯云服务器上部署了 OpenClaw AI 助手,实现了基本的对话功能。但那时的 AI 就像一个"只会说话的大脑"——能理解你的问题,却无法真正操作电脑。 这篇博客将记录如何为 OpenClaw 配置浏览器自动化功能,让 AI 助手真正拥有"眼睛"(看网页)和"双手"(操作网页),变成一个能够自动打开网页、填写表单、

在国内环境部署 OpenClaw:从零到跑通的个人 AI 助手搭建指南

在国内环境部署 OpenClaw:从零到跑通的个人 AI 助手搭建指南 OpenClaw 是一个开源的个人 AI 助手框架,可以连接 WhatsApp、Telegram、Slack、Discord、飞书等 20+ 消息渠道。本文记录了在国内网络环境下部署 OpenClaw 的完整流程,包括网络适配、模型配置、渠道接入等实战经验。 什么是 OpenClaw? OpenClaw 是一个 local-first 的个人 AI 助手平台。它的核心是一个 Gateway 服务,运行在你自己的设备上,通过 WebSocket 管理会话、消息路由和工具调用。 核心特性: * 🏠 本地运行,数据不经过第三方 * 📱 支持 20+ 消息渠道(飞书、Telegram、Discord、Slack、微信等)