当Spring的SSE来敲门:一篇让你笑中带泪的实战指南

561 阅读5分钟

一、SSE是什么?—— “单相思”技术界的扛把子

SSE(Server-Sent Events),字面翻译是“服务器发送事件”,但你可以理解为——服务器单方面对客户端的“疯狂输出”

  • 特点:基于HTTP协议,服务器单向推送数据,客户端只能含泪接受(或者假装没收到)。
  • 适用场景
    • 老板的实时工作通知(“这个需求今晚必须上线!”)
    • 股市韭菜的实时行情播报(“您的股票已跌穿地心!”)
    • 女朋友的微信消息轰炸(“你怎么不回我???”)

SSE vs WebSocket

  • SSE:像电台广播,你只能听,不能打电话互动(单向)。
  • WebSocket:像微信语音,双方随时互喷(双向)。
    总结:SSE是“舔狗模式”,WebSocket是“双向奔赴”。

二、Spring Boot集成SSE——3分钟速成

1. 依赖?不存在的!

Spring Boot早已内置SSE支持,只需一个spring-boot-starter-web,连“谢谢”都不用说。

2. 服务端代码:SseEmitter的“深情告白”
@GetMapping(path = "/sse-demo", produces = MediaType.TEXT_EVENT_STREAM_VALUE)  
public SseEmitter pushStockPrice() {  
    SseEmitter emitter = new SseEmitter(60_000L); // 设置60秒超时(超时后自动分手)  
    
    // 开个线程定时发送数据(避免主线程被拖垮)  
    Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {  
        try {  
            String data = "股票价格:" + (Math.random() * 100);  
            emitter.send(data);  
        } catch (IOException e) {  
            emitter.completeWithError(e); // 发送失败?直接拉黑!  
        }  
    }, 0, 1, TimeUnit.SECONDS);  
    
    return emitter;  
}  

关键点

  • produces = TEXT_EVENT_STREAM_VALUE:告诉浏览器这是SSE流。
  • SseEmitter:Spring的“快递小哥”,负责把数据打包送给客户端。
3. 前端代码:EventSource的“痴心等待”
const eventSource = new EventSource('/sse-demo');  
eventSource.onmessage = (e) => {  
    console.log("收到消息:" + e.data);  
    // 如果是股票价格,建议此处添加“摔键盘”逻辑  
};  

注意

  • 数据格式必须遵守SSE规范:data: xxx\n\n(少了换行?服务器直接装死)。
  • 默认自动重连(像极了吵架后主动求和的你)。

三、实战案例——从“摸鱼倒计时”到“老板监控系统”

案例1:摸鱼倒计时(终极打工人必备)

需求:实时显示离下班还有多久,精确到秒。
代码逻辑

emitter.send("距离下班还有:" + calculateTimeRemaining() + "秒!");  

效果:前端页面每秒刷新一次,打工人从此告别焦虑。

案例2:老板监控系统(慎用!)

需求:实时监控员工电脑屏幕(开玩笑的,别当真)。
伪代码

// 每隔5秒截屏一次,Base64编码后推送  
emitter.send(ScreenshotUtils.captureScreen());  

风险:可能触发劳动仲裁。

案例3:在线求夸机器人(治愈系SSE)

需求:用户点击按钮,服务器推送一句随机彩虹屁。
代码

String[] compliments = {"你的代码像诗一样!", "你的发际线很安全!", "你简直是团队的CTO!"};  
emitter.send(compliments[new Random().nextInt(3)]);  

效果:程序员幸福感提升200%。


四、SSE原理——HTTP的“长跑运动员”

底层机制
  1. 长连接:客户端发起请求后,服务器不关闭连接,持续发送数据(Content-Type: text/event-stream)。
  2. 数据格式
    event: 下班提醒  
    data: 还有30分钟!  
    id: 666  
    retry: 5000  
    
    • event:自定义事件类型(比如“摸鱼事件”)。
    • id:事件ID(用于断线重连时续播)。
    • retry:断线后重试时间(单位:毫秒)。
Spring的魔法
  • SseEmitter背后是异步Servlet机制,非阻塞处理请求。
  • 数据通过HTTP流(Chunked Transfer Encoding)分块传输。

**五、避坑指南

  1. 中文乱码

    • 症状:前端收到“淇℃伅”等乱码。
    • 解药:设置响应编码为UTF-8(见代码):
      emitter.send(SseEmitter.event().data("你好", MediaType.TEXT_PLAIN));  
      
  2. 连接泄漏

    • 症状:客户端关闭页面,服务器还在疯狂发送数据。
    • 解药:监听onCompletiononTimeout,及时回收Emitter:
      emitter.onCompletion(() -> log.info("连接已关闭!"));  
      emitter.onTimeout(() -> log.info("连接超时!"));  
      
  3. 跨域问题

    • 症状:前端报错“No 'Access-Control-Allow-Origin' header”。
    • 解药:Spring Security中配置CORS,或添加@CrossOrigin注解。
  4. IE浏览器不支持

    • 症状:IE用户表示“这是什么鬼”。
    • 解药:使用EventSource的Polyfill库,或优雅降级为轮询。

六、最佳实践——SSE的“优雅姿势”

  1. 超时设置
    • 太长浪费资源,太短容易分手,建议30秒-5分钟。
  2. 数据压缩
    • 如果推送JSON大数据,开启GZIP压缩(节省带宽,老板省钱)。
  3. 心跳机制
    • 每隔一段时间发送空注释(:\n\n),防止代理服务器掐断连接。
  4. 鉴权加持
    • 敏感数据?在URL中添加Token,或结合Spring Security拦截非法请求。

七、面试考点——SSE的灵魂八连问

  1. SSE和WebSocket最大的区别是什么?

    • 答:SSE是单向HTTP推送,WebSocket是双向TCP协议。
  2. 如何实现SSE的断线重连?

    • 答:浏览器自动重试(根据retry字段),服务端用id追踪事件位置。
  3. SSE能上传文件吗?

    • 答:不能!SSE是单向的,上传文件得用HTTP POST或WebSocket。
  4. SseEmitter底层依赖什么技术?

    • 答:Servlet 3.0+的异步处理机制,比如DeferredResult
  5. SSE支持哪些浏览器?

    • 答:除了IE,其他现代浏览器都支持(IE:你礼貌吗?)。
  6. 如何推送JSON数据?

    • 答:emitter.send(SseEmitter.event().data(myObject, MediaType.APPLICATION_JSON))
  7. SSE和长轮询(Long Polling)的区别?

    • 答:SSE是长连接,一次连接持续推送;长轮询是反复短连接。
  8. 高并发下SSE会有什么问题?

    • 答:大量长连接占用线程和内存,需结合响应式编程(如WebFlux)优化。

八、总结——SSE的“人生哲学”

SSE是实时通信界的“经济适用男”:

  • 优点:简单、省资源、兼容HTTP生态。
  • 缺点:单向、IE不支持、不能撩回去。

适用场景口诀
低频单向用SSE,高频互动WebSocket,
IE用户请绕道,老板需求别乱搞。

终极忠告

  • 爱TA就及时回应(处理超时),
  • 不爱别拖泥带水(关闭连接)。