基于xterm.js + socket.js的Web SSH

该博客介绍了如何利用xterm.js创建一个终端面板,并结合sockjs-client和stompjs来实现WebSocket消息通信。通过示例代码展示了从初始化xterm到建立socket连接,再到处理键盘输入和发送数据的整个过程。当连接成功后,终端会显示'Connection Successful!'并可以接收和显示来自服务器的消息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

xterm.js 作为终端面板,以 socket.js + stompjs 实现消息通信。

效果如下
在这里插入图片描述
代码

<template>
  <div ref="terminalContainerRef"></div>
</template>

<script lang="ts" setup>
  import { ref, onMounted, nextTick, onUnmounted, watch } from "vue";
  import { Terminal } from "xterm";
  import "xterm/css/xterm.css";
  import SockJS from "sockjs-client/dist/sockjs.min.js";
  import Stomp from "stompjs";

  let socket: any = null;
  let term: any = null;
  let stompClient: any = null;
  const terminalContainerRef = ref();

  const props = defineProps({
    params: {
      type: Object,
      default: () => {},
    },
    visible: {
      type: Boolean,
      default: false,
    },
  });

  const socketOptions = {
    url: `api/amo/amo-socket`, // sock连接地址
    subscribes: `/amo/`, // 订阅地址前缀,真实地址为:/amo/${主机id}
    sendUrl: `/amo/webssh/recever`, // 发送消息地址
  };

  watch(
    () => props.visible,
    (val) => {
      if (val) {
        initWebSSH();
      } else {
        disconnect();
      }
    },
    { deep: true },
  );

  onMounted(() => {
    initWebSSH();
  });

  onUnmounted(() => {
    disconnect();
  });

  /**
   * @description 初始化 xterm
   * @param {*}
   * @returns {*}
   */
  const initWebSSH = () => {
    nextTick(() => {
      openTerminal();
    });
  };

  /**
   * @description 断开socket连接
   * @param {*}
   * @returns {*}
   */
  const disconnect = () => {
    // 断开连接
    stompClient.disconnect();
    term.dispose();
    console.log("Disconnected!");
  };

  /**
   * @description 发送socket消息
   * @param {object} data
   * @returns {*}
   */
  const onSendMsg = (data) => {
    stompClient.send(socketOptions.sendUrl, {}, JSON.stringify(data));
  };

  /**
   * @description 建立socket连接
   * @returns {*}
   */
  const createSocket = () => {
    const certificate = props.params.cerIdList?.find((item: any) => item.isDefault == "1");
    let msgObj = {
      operate: "CONNECT", // "CONNECT" 连接  "COMMAND"命令   "CLOSE" 关闭
      hostId: props.params?.hostId,
      certificateId: certificate?.cerId || "",
      command: "",
    };

    // 订阅消息
    const onSubscribes = () => {
      const subscribes = socketOptions.subscribes;
      const link = subscribes + props.params?.hostId;
      stompClient.subscribe(
        link,
        (msg) => {
          let { body } = msg;
          body = JSON.parse(body);
          const { bizCode, data } = body; // 这里的 data 为服务器发送到客户端的信息字段,不是固定的
          console.log(bizCode + "_received", data);
          term.write(data);
        },
        {},
      );
    };

    // 监听键盘输入,并发送给服务器
    const onKeyBoard = () => {
      // 连接成功后监听terminal输入
      term.onData((str) => {
        msgObj.command = str;
        msgObj.operate = "COMMAND";

        onSendMsg(msgObj);
      });
    };

    // 连接
    const token = localStorage.token;
    socket = new SockJS(socketOptions.url, null, {
      // transports: ["websocket"],
      // timeout: 15000,
      headers: {
        Authorization: `Bearer ${token}`,
      }
    });

    stompClient = Stomp.over(socket);
    stompClient.connect(
      {},
      (frame) => {
        onKeyBoard();
        onSendMsg(msgObj);
        // 接收订阅消息
        onSubscribes();
        term.writeln("Connection Successful!");
        term.writeln("Type some keys and commands to play around.");
        term.focus();
      },
      () => {
        term.writeln("");
        term.writeln("Connection Fail!");
        term.writeln("");
        console.log("***连接失败***");
      },
    );
  };

  /**
   * @description socket连接前准备
   * @param {object} term
   * @returns {*}
   */
  const runFakeTerminal = (terms) => {
    if (terms._initialized) {
      return;
    }
    terms._initialized = true;
    //在页面上显示连接中...
    terms.writeln("Connecting...");
  };

  /**
   * @description 实例化 xterm
   * @param {*}
   * @returns {*}
   */
  const openTerminal = () => {
    const terminalDom = terminalContainerRef.value;
    if (!terminalDom) return;

    const terms = new Terminal({
      rows: 40, // 行数,对应高度
      cols: 72, // 列数,对应宽度
      cursorBlink: true, // 光标闪烁
      cursorStyle: "block", // 光标样式  null | 'block' | 'underline' | 'bar'
      scrollback: 10, //终端中的回滚量
      tabStopWidth: 8, //制表宽度
      disableStdin: false, //是否应禁用输入。
      rendererType: "canvas", //渲染类型
      theme: {
        foreground: "yellow", //字体
        background: "#060101", //背景色
        cursor: "help", //设置光标
      },
    });
    terms.open(terminalDom);
    term = terms;
    runFakeTerminal(terms);

    // 建立socket连接
    createSocket();
  };

  defineExpose({
    disconnect,
  });
</script>

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值