You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go基于whatsmeow SSE推送WhatsApp登录二维码 前端解析显示异常

核心问题

你的实现存在两个根本性错误,直接导致数据解析失败:

  • SSE(text/event-stream)是纯文本协议,仅支持传输UTF-8编码的文本帧,直接写入二进制图片流会破坏SSE的帧格式,导致客户端解析报错。
  • 同时用EventSource和fetch请求同一个SSE接口会建立两个独立的HTTP连接,两个连接拿到的是完全隔离的响应流,根本无法实现“同一条连接区分文本和图片”的逻辑。
解决方案

两种实现路径,第一种改造成本最低,第二种性能更优,按需选择即可。

方案一:单SSE连接传输所有数据(推荐,改造成本最低)

完全复用单个SSE连接,所有内容按标准SSE帧格式传输,不需要额外发起其他请求:

服务端调整

  1. 取消直接往SSE流写入二维码二进制的逻辑,生成二维码PNG字节流后做Base64编码,和其他状态事件一样封装为标准SSE帧返回。
  2. 用SSE规范的event字段区分不同事件类型,不要把所有内容都塞到默认消息帧里,帧格式参考如下Go代码:
    // 设置SSE响应头
    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache")
    w.Header().Set("Connection", "keep-alive")
    flusher, _ := w.(http.Flusher)
    
    // 已登录事件
    fmt.Fprint(w, "event: already_logged\ndata: 已登录\n\n")
    flusher.Flush()
    
    // 新二维码事件,data直接拼接为data URI格式
    qrBase64 := base64.StdEncoding.EncodeToString(qrPngBytes)
    fmt.Fprintf(w, "event: new_qr\ndata: data:image/png;base64,%s\n\n", qrBase64)
    flusher.Flush()
    
    // 二维码超时事件
    fmt.Fprint(w, "event: qr_timeout\ndata: 二维码已过期,正在刷新\n\n")
    flusher.Flush()
    
    // 登录成功事件
    fmt.Fprint(w, "event: login_success\ndata: 登录成功\n\n")
    flusher.Flush()
    

前端调整

  1. 删除额外的fetch请求逻辑,仅保留单个EventSource实例,通过监听不同事件名分别处理状态更新和二维码渲染即可,完全不需要做Content-Type判断。
  2. 参考实现代码:
    const statusDom = document.querySelector('#login-status')
    const qrDom = document.querySelector('#qr-img')
    const es = new EventSource('http://你的服务地址:9999/login/stream')
    
    es.addEventListener('already_logged', () => {
      statusDom.textContent = '已处于登录状态,正在跳转'
      es.close()
    })
    
    es.addEventListener('new_qr', (e) => {
      statusDom.textContent = '请使用WhatsApp扫码登录'
      qrDom.src = e.data
    })
    
    es.addEventListener('qr_timeout', () => {
      statusDom.textContent = '二维码已过期,等待刷新'
      qrDom.src = ''
    })
    
    es.addEventListener('login_success', () => {
      statusDom.textContent = '登录成功,正在跳转'
      qrDom.remove()
      es.close()
    })
    
    es.onerror = () => {
      statusDom.textContent = '连接断开,正在尝试重连'
    }
    

方案二:SSE只传状态,二维码走独立接口(性能更优)

如果介意Base64编码带来的约33%体积冗余,可以把状态通知和二维码资源拆分传输:

服务端调整

  1. SSE连接仅推送纯文本状态事件,当生成新二维码时,给二维码分配一个唯一ID(随机字符串、递增版本号均可),把ID放在new code事件的data里返回给前端。
  2. 新增独立的二维码获取接口,例如/login/qrcode/{qrId},接口直接返回对应ID的PNG二进制流,设置Content-Type: image/png,二维码过期后接口直接返回404即可。

前端调整

  1. 同样仅保留单个EventSource实例监听SSE事件,收到new code事件时,取出事件里的二维码ID,直接把img标签的src属性设置为二维码接口地址(例如/login/qrcode/xxx),浏览器会自动发起GET请求加载图片,不需要手写fetch处理二进制流。
  2. 其他状态事件直接更新对应span标签的文本内容即可。
注意事项
  • 不要尝试在SSE流中直接混传二进制:SSE以\n\n作为帧分隔符,二进制PNG数据中大概率会出现匹配的字节序列,会导致客户端把二进制数据拆分为格式错误的文本帧,解析完全混乱。
  • whatsmeow生成的默认尺寸二维码PNG体积在150KB以内,Base64编码后体积约200KB,登录场景下这个开销完全可以接受,优先选方案一可以最快跑通流程。
  • 如果选择方案二,给二维码接口设置1-2分钟的短缓存即可,避免浏览器重复请求同一个二维码时反复回源。

内容的提问来源于stack exchange,提问作者Hasan A Yousef

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 21:27:19