温嘉琪 / BUILDING SOMETHING FUN

一个研究:n8n上为什么没有websocket?

背景:一个看似简单的需求

我在开发一个基于n8n的CV经历完善系统,整个流程很清晰:

  1. 用户输入工作经历
  2. AI分析并完善成STAR格式的面试故事
  3. 生成文本和语音版本
  4. 保存到数据库并发送到Telegram

看起来很简单对吧?直到我遇到了讯飞的语音合成API…

第一个困惑:为什么n8n没有WebSocket节点?

当我查看讯飞TTS API文档时,发现它只提供WebSocket接口:

wss://cbm01.cn-huabei-1.xf-yun.com/v1/private/mcd9m97e6

我天真地以为在n8n中加个WebSocket节点就搞定了,结果发现:

  • n8n官方没有WebSocket客户端节点
  • 只有WebSocket触发器(作为服务端)
  • 社区有个n8n-nodes-websocket-standalone节点

技术方案的演进过程

方案1:使用社区WebSocket节点

我最初的想法是直接用社区节点连接讯飞API:

// 在Code节点中调用WebSocket
const ws = new WebSocket('wss://xunfei-url');
// 然后发现...这在n8n的浏览器环境中根本跑不通

问题

  • n8n的Code节点运行在浏览器环境
  • 无法使用Node.js的ws
  • 无法导入crypto模块做鉴权

方案2:在n8n后端处理TTS

我想让n8n直接调用讯飞API,生成音频后通过WebSocket返回给前端:

narrative_story → [n8n调用讯飞] → audio_base64 → WebSocket → 前端播放

实现复杂度

  • 需要在n8n中实现WebSocket客户端
  • 处理讯飞的复杂鉴权算法
  • 管理音频流的接收和拼接
  • Base64编码/解码处理

方案3:前端处理TTS

然后我想,为什么不让前端去调用TTS?

narrative_story → WebSocket → 前端收到文本 → [前端调用讯飞] → 音频播放

但这里有个根本问题:前端无法直接调用讯飞WebSocket API

原因

  • CORS跨域限制
  • API密钥安全问题
  • 浏览器无法完成复杂的HMAC-SHA256签名

深入理解:WebSocket到底是什么?

在这个过程中,我意识到我对WebSocket的理解还不够深入。

HTTP vs WebSocket的本质区别

HTTP就像发邮件

客户端: "你好,我要数据"
服务器: "给你数据"
[连接结束]

WebSocket就像打电话

客户端: "你好,我要建立通话"
服务器: "好的,电话接通了"
[保持通话状态]
双方可以随时说话...

WebSocket的"升级"机制

WebSocket不是全新协议,而是从HTTP"升级"而来:

GET /websocket HTTP/1.1
Host: example.com
Upgrade: websocket          ← 关键:请求升级
Connection: Upgrade         ← 关键:连接要升级
Sec-WebSocket-Key: xxx

HTTP/1.1 101 Switching Protocols  ← 服务器同意升级
Upgrade: websocket
Connection: Upgrade

这就是为什么代理服务器经常搞不定WebSocket——它们可能会丢掉这些特殊的升级头。

为什么n8n没有原生WebSocket支持?

通过研究,我发现了几个关键原因:

1. 设计理念冲突

n8n是工作流引擎

触发器 → 步骤1 → 步骤2 → 步骤3 → 结果

这是单向的、有明确开始和结束的流程。

WebSocket是持续连接

     发送消息
应用 ←----------→ 外部服务
     接收消息

这是双向的、持续的对话。

2. 状态管理复杂

// n8n节点是无状态的
function processNode(input) {
    // 处理完就结束了
    return output;
}

// WebSocket需要持续状态
const ws = new WebSocket('url');
ws.onmessage = (msg) => {
    // 消息什么时候来?如何触发工作流?
};
// 连接要一直保持,但节点执行完就结束了

3. 基础设施复杂性

通过一篇关于n8n WebSocket问题的博客,我了解到:

  • 不同的Ingress控制器需要不同配置
  • 社区版nginx-ingress vs 官方NGINX Inc.的配置完全不同
  • WebSocket连接失败很难调试
# 需要这样的特殊配置
nginx.org/proxy-read-timeout: "3600"
nginx.org/websocket-services: "service-name"

讯飞为什么选择WebSocket?

流式语音合成的优势

传统HTTP方式

用户: "合成这段话" → 等待30秒 → 收到完整音频文件

WebSocket流式方式

用户: "合成这段话"
服务器: "第一段音频好了" → 立即开始播放
服务器: "第二段音频好了" → 无缝续播
服务器: "完成"

优势

  • 大幅降低首次播放延迟
  • 支持长文本分段处理
  • 实时反馈合成进度
  • 更好的用户体验

最终的技术选择

经过深入分析,我发现我的真实需求其实是:

故事生成 → TTS音频 → 发送到Telegram

WebSocket在这里没有意义!因为:

  • 音频直接发给Telegram,不需要实时推送给前端
  • 整个流程是后端处理,前端只需要知道"完成"即可

推荐方案:外部代理服务

n8n → HTTP请求 → TTS代理服务 → 讯飞WebSocket → 返回音频 → n8n继续

TTS代理服务示例

// tts-proxy.js
app.post('/synthesize', async (req, res) => {
    const { text, voice } = req.body;

    // 这里处理讯飞WebSocket的复杂逻辑
    const audioBase64 = await callXunfeiWebSocket(text, voice);

    res.json({
        audio_base64: audioBase64,
        format: 'mp3'
    });
});

n8n中的HTTP Request节点

{
    "method": "POST",
    "url": "http://tts-proxy:3000/synthesize",
    "body": {
        "text": "{{ $json.narrative_story }}",
        "voice": "x5_lingfeiyi_flow"
    }
}

经验总结

技术层面

  1. 不要为了技术而技术
    • WebSocket很酷,但不一定适合你的场景
    • 选择最简单能解决问题的方案
  2. 理解工具的设计理念
    • n8n是为批处理工作流设计的
    • 不要强迫它做不擅长的事情
  3. 外部代理是好选择
    • 各司其职:n8n专注工作流,代理专注协议处理
    • 更容易测试和调试

架构层面

简单的架构 > 复杂的架构
能工作的方案 > 完美的方案

最终我选择了:

n8n工作流 → HTTP代理 → WebSocket处理 → 返回结果

而不是:

n8n工作流 → 复杂的WebSocket处理 → 各种异常情况

写在最后

这次技术探索让我明白:

  • 深入理解技术原理很重要
  • 但更重要的是理解什么时候不用某个技术
  • 最好的解决方案往往是最简单的那个

如果你也在n8n中遇到WebSocket相关的需求,希望这篇文章能帮你少走弯路。

记住:技术是为业务服务的,不是为了炫技而存在的


这篇文章记录了我在开发CV故事生成系统时遇到的WebSocket集成挑战,以及最终的解决方案。如果你有类似的经历或更好的解决方案,欢迎交流讨论。

相关资源