OpenCode 实践课:4 分钟用 Node.js 写出一个命令行 WebSocket 调试工具

项目介绍

今天用 OpenCode 写一个实用的命令行 WebSocket 调试工具 ws-cli。它就像一个命令行的 Postman,可以连接任意 WebSocket 服务、发送消息、查看返回数据,还支持 JSON 格式化输出和心跳延迟检测。对于需要调试 WebSocket 接口的后端开发者来说,这个小工具比浏览器控制台方便得多。

  • 技术栈:Node.js + ws
  • 代码量:约 90 行
  • 核心功能:连接 WebSocket、发送消息、JSON 格式化、心跳检测

准备工作

确保环境安装了 Node.js(v14+),编辑器或终端里启动 OpenCode 即可开始。全程用自然语言对话,不用手动写一行代码。

实践过程

第一步:搭出最简原型

我把最基础的需求告诉 OpenCode——能连上 WebSocket 并打印收到的消息。

我对 OpenCode 说:

> 帮我用 Node.js 写一个命令行 WebSocket 客户端 ws-cli.js。接收一个 ws 地址作为参数,连接成功后打印 'Connected',收到消息就打印出来,断开时退出进程。使用 ws 这个 npm 包。

OpenCode 做了什么:

它先判断项目还没有 package.json,自动执行 npm init -y,然后安装 ws 依赖。接着直接生成了可运行的代码:

#!/usr/bin/env node
const WebSocket = require('ws');

const url = process.argv[2];
if (!url) {
  console.log('Usage: ws-cli <url>');
  process.exit(1);
}

console.log(`Connecting to ${url}...`);
const ws = new WebSocket(url);

ws.on('open', () => console.log('Connected'));
ws.on('message', (data) => console.log(`Received: ${data}`));
ws.on('close', () => { console.log('Disconnected'); process.exit(0); });
ws.on('error', (err) => console.error(`Error: ${err.message}`));

不到 20 行,一个能用的 WebSocket 客户端就成型了。跑一下测试:

node ws-cli.js ws://echo.websocket.org
# Connecting to ws://echo.websocket.org...
# Connected

关键要点: OpenCode 不仅写了代码,还自动处理了项目初始化(npm init、安装依赖)。你不需要告诉它"先建个 package.json",它自己判断项目状态来补全缺失的步骤。

第二步:丰富命令行参数

能连能收只是基础,我想要一个更灵活的参数系统。

我对 OpenCode 说:

> 给 ws-cli.js 加上命令行参数解析,支持以下选项:
> - -m "消息" 连接后自动发送这条消息
> - -j 发送时把内容包装成 {"text": "..."} 的 JSON 格式
> - -p 收到的 JSON 消息自动格式化缩进输出
> - -h 显示帮助信息(列出所有参数和示例)

OpenCode 做了什么:

它在文件顶部加了一个 parseArgs 函数,用清晰的 switch-case 解析每个参数,返回一个选项对象。然后在主逻辑里根据 opts 来决定行为——有 -m 就自动发送,有 -j 就 JSON 包装。代码从 20 行扩展到了 50 多行,但结构依然清晰。

关键要点: 你不需要描述"怎么实现"(比如"写一个 parseArgs 函数,用 switch 遍历 argv"),只需要描述"要什么效果"。OpenCode 会自己选择最合适的实现方式。

第三步:加入心跳检测

WebSocket 调试时经常要确认连接是否存活,加个心跳。

我对 OpenCode 说:

> 加一个 --ping 选项。启用后每隔 3 秒自动发一次 WebSocket ping 帧,收到 pong 后打印延迟毫秒数。断开连接时要自动清除定时器。

OpenCode 做了什么:

它在 open 回调中加了 setInterval,每次 ws.ping() 时记录时间戳,监听一次性 pong 事件计算差值。在 closeerror 事件里用 clearInterval 清理定时器,不会出现内存泄漏:

if (opts.pingMode) {
  pingInterval = setInterval(() => {
    const start = Date.now();
    ws.ping();
    ws.once('pong', () => console.log(`💓 Pong: ${Date.now() - start}ms`));
  }, 3000);
}

关键要点: 我说了"断开时要清除定时器",OpenCode 就在 closeerror 两个地方都加了清理代码。细节交给 AI,你只负责提需求。

第四步:打磨用户体验

功能都齐了,最后打磨一下交互——图标、交互式输入、错误提示。

我对 OpenCode 说:

> 优化 ws-cli.js 的用户体验:
> 1. 不同状态的输出用 emoji 区分——连接用 🔗,成功用 ✅,收到消息用 📩,发送用 📤,错误用 ❌
> 2. 如果没有指定 -m 参数,进入交互模式,从 stdin 读取用户输入逐行发送
> 3. 连接错误时给出更友好的提示,不要只打印 stack trace

OpenCode 做了什么:

它把之前的纯文本输出替换成了 emoji,在没有 -m 时调用 process.stdin.on('data', ...) 进入交互模式,error 回调里只打印 err.message 而非完整堆栈。最终代码正好约 90 行。

完整代码

#!/usr/bin/env node

const WebSocket = require('ws');

function parseArgs(args) {
  if (args.length < 1 || args[0] === '-h' || args[0] === '--help') {
    console.log('WebSocket CLI 调试工具\n');
    console.log('用法: ws-cli <url> [选项]\n');
    console.log('选项:');
    console.log('  -m, --msg <text>   连接后发送的消息');
    console.log('  -j, --json         以 JSON 格式发送');
    console.log('  -p, --pretty       格式化接收到的 JSON');
    console.log('  --ping             心跳检测 (3s 间隔)');
    console.log('  -h, --help         显示帮助\n');
    console.log('示例:');
    console.log('  ws-cli ws://localhost:8080');
    console.log('  ws-cli wss://echo.websocket.org --ping');
    console.log('  ws-cli ws://localhost:8080 -m hello --json');
    process.exit(0);
  }

  return {
    url: args[0],
    message: null,
    jsonMode: false,
    prettyMode: false,
    pingMode: false,
    _parse() {
      for (let i = 1; i < args.length; i++) {
        switch (args[i]) {
          case '-m': case '--msg': this.message = args[++i]; break;
          case '-j': case '--json': this.jsonMode = true; break;
          case '-p': case '--pretty': this.prettyMode = true; break;
          case '--ping': this.pingMode = true; break;
        }
      }
    }
  };
}

const opts = parseArgs(process.argv.slice(2));
opts._parse();

console.log(`🔗 正在连接 ${opts.url}...`);
const ws = new WebSocket(opts.url);
let pingInterval = null;

ws.on('open', () => {
  console.log('✅ 已连接');

  if (opts.pingMode) {
    pingInterval = setInterval(() => {
      const start = Date.now();
      ws.ping();
      ws.once('pong', () => console.log(`💓 Pong: ${Date.now() - start}ms`));
    }, 3000);
    console.log('💓 心跳检测已启动 (3s)');
  }

  if (opts.message) {
    const payload = opts.jsonMode
      ? JSON.stringify({ text: opts.message })
      : opts.message;
    console.log(`📤 发送: ${payload}`);
    ws.send(payload);
  } else {
    console.log('💬 输入消息按回车发送 (Ctrl+C 退出):');
    process.stdin.on('data', (data) => {
      const msg = data.toString().trim();
      if (msg) {
        const payload = opts.jsonMode
          ? JSON.stringify({ text: msg })
          : msg;
        ws.send(payload);
      }
    });
  }
});

ws.on('message', (data) => {
  const raw = data.toString();
  try {
    const parsed = JSON.parse(raw);
    console.log(`📩 收到: ${
      opts.prettyMode ? JSON.stringify(parsed, null, 2) : raw
    }`);
  } catch {
    console.log(`📩 收到: ${raw}`);
  }
});

ws.on('close', (code) => {
  if (pingInterval) clearInterval(pingInterval);
  console.log(`❌ 断开连接 (code: ${code})`);
  process.exit(0);
});

ws.on('error', (err) => {
  if (pingInterval) clearInterval(pingInterval);
  console.error(`❌ 错误: ${err.message}`);
  process.exit(1);
});

依赖安装后即可使用:

npm install ws
node ws-cli.js ws://localhost:8080 --ping
node ws-cli.js ws://localhost:8080 -m "hello" --json --pretty

小结

从"帮我写一个 WebSocket 客户端"到功能完备的调试工具,实际编码环节不到 4 分钟。真正花时间的不是写代码,而是想清楚"我要什么功能"——这恰好是 AI 时代开发者应该聚焦的地方。

几点体会:

描述需求比描述实现更重要。 你不需要告诉 OpenCode"用 switch 解析参数"或"用 setInterval 做心跳",你只需要说"支持这些参数""每 3 秒发一次 ping"。OpenCode 自己选择实现路径。

渐进式迭代效率最高。 先搞一个能跑的最简版本(第一步),然后逐层加功能(第二/三/四步)。每次只提一个明确的需求,OpenCode 在已有代码基础上改,可预期、好验证。

AI 把"想法→代码"的摩擦降到了零。 哪怕你不熟悉 WebSocket 协议细节或 Node.js 事件模型,只要你能描述需求,就能拿到可运行的代码。

如果你还没试过用 AI 编程助手写工具,建议装一个 OpenCode,从一个小需求开始试。4 分钟让你体验什么叫"说话就是写代码"。