vue+go实现web端连接Linux终端

vue+go实现web端连接Linux终端

实现效果在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

实现逻辑1——vue

依赖包

"xterm": "^5.3.0","xterm-addon-attach": "^0.9.0","xterm-addon-fit": "^0.8.0"

样式和代码逻辑

<template><a-modalv-model:visible="visible":title="$t(`routers.dom_system_terminal`)":footer="null"@cancel="closeWs"width="80%"destroyOnClose><div><div v-show="showForm" class="form-container"><a-form :labelCol="{ span: 5 }" :wrapperCol="{ span: 15 }"><a-form-item :label="$t('routers.table_address')" v-bind="validateInfos.server"><a-input:maxlength="60"v-model:value="modelRef.server":placeholder="$t('routers.text_please_address')"/></a-form-item><a-form-item :label="$t('routers.dom_username')" v-bind="validateInfos.user"><a-input:maxlength="60"v-model:value="modelRef.user":placeholder="$t('routers.text_username')"/></a-form-item><a-form-item :label="$t('routers.dom_pass')" v-bind="validateInfos.pwd"><a-input-password:maxlength="60"autocomplete="new-password"v-model:value="modelRef.pwd":placeholder="$t('routers.text_password')"/></a-form-item><a-form-item :wrapper-col="{ offset: 5, span: 15 }"><a-button @click="handleOk" type="primary">{{ $t("routers.dom_save") }}</a-button></a-form-item></a-form></div><div v-show="!showForm" style="height: 400px" ref="terminal" /></div></a-modal>
</template>
<script lang="ts">import { defineComponent, reactive, ref, onBeforeUnmount } from "vue";import "xterm/css/xterm.css";import { Terminal } from "xterm";import { FitAddon } from "xterm-addon-fit";import { AttachAddon } from "xterm-addon-attach";import { system } from "@/api";import { useI18n } from "vue-i18n";import { Form } from "ant-design-vue";export default defineComponent({name: "TermModal",setup() {const visible = ref<boolean>(false);const showForm = ref<boolean>(true);const modelRef = reactive({server: "",//带端口号输入user: "",pwd: "",});const { t } = useI18n();const rulesRef = reactive({server: [{required: true,message: t("routers.text_please_address"),},],user: [{required: true,message: t("routers.text_username"),},],pwd: [{required: true,message: t("routers.text_password"),},],});const show = () => {visible.value = true;};const data = reactive<any>({term: null,fitAddon: null,socketUrl: "ws://" + window.location.host + "/ws", //这里正常应该是后端地址,但我这边前后端都是自己做的,打包以后的ip和端口相同socket: "",});const terminal = ref();const initTerm = () => {// 1.xterm终端初始化let height = document.body.clientHeight;let rows: number = Number((height / 15).toFixed(0)); //18是字体高度,根据需要自己修改data.term = new Terminal({rows: rows,});// 2.webSocket初始化data.socket = new WebSocket(data.socketUrl); // 带 token 发起连接// 链接成功后// 3.websocket集成的插件,这里要注意,网上写了很多websocket相关代码.xterm4版本没必要.const attachAddon = new AttachAddon(data.socket);data.fitAddon = new FitAddon(); // 全屏插件attachAddon.activate(data.term);data.fitAddon.activate(data.term);data.term.open(terminal.value);setTimeout(() => {data.fitAddon.fit();}, 5);data.term.focus();data.socket.onclose = () => {//网络波动,ws连接断开data.term && data.term.dispose();showForm.value = true;console.log("close socket");};data.socket.onmessage = (res: any) => {//ssh连接失败返回if (res && res.data && res.data.indexOf("失败") !== -1)setTimeout(() => {closeWs();}, 3000);};window.addEventListener("resize", windowChange);};onBeforeUnmount(() => {closeWs();});const windowChange = () => {data.fitAddon.fit();data.term.scrollToBottom();};const closeWs = () => {resetFields();data.socket && data.socket.close();data.term && data.term.dispose();window.removeEventListener("resize", windowChange);showForm.value = true;};const useForm = Form.useForm;const { validate, validateInfos, resetFields } = useForm(modelRef, rulesRef);const handleOk = () => {validate().then(() => {system.wsInfo({ server: modelRef.server, user: modelRef.user, pwd: modelRef.pwd }).then(() => {showForm.value = false;//连接ws,隐藏表单页}).catch((err: any) => {console.log("error", err);}).finally(() => {initTerm();});}).catch((err: any) => {console.log("error", err);});};return {show,visible,terminal,closeWs,validateInfos,modelRef,resetFields,showForm,handleOk,};},});
</script><style lang="less">.xterm-screen {height: 100%;}
</style>
<style lang="less" scoped>.form-container {background-color: black;padding: 66px 12px 60px 12px;::v-deep(.ant-form-item-label > label) {color: white;}}
</style>

实现逻辑2——go

采用的是goframe框架
依赖包:

github.com/gogf/gf/v2 v2.5.4
github.com/gorilla/websocket v1.5.0 // indirect

main:

package mainimport ("foxess.ems/router""github.com/gogf/gf/v2/frame/g"
)
func main() {s := g.Server()router.Bind(s)s.Run()
}

router:

package router
func Bind(s *ghttp.Server) {s.Group("/", run)
}
func run(g *ghttp.RouterGroup) {g.GET("/system/ws/info", system.WsInfo)g.GET("/ws", system.ConnectWs)
}

system:

package system
import ("fmt""foxess.ems/app/def""github.com/gogf/gf/v2/net/ghttp""github.com/gorilla/websocket""net/http"
)
var wsInfo = &def.ConnectWsArg{}
func WsInfo(r *ghttp.Request) {res := &def.Response{}args := &def.ConnectWsArg{}if e := r.Parse(args); e != nil {res.Errno = 40000} else {wsInfo = argsres.Result = &UploadResultParam{Access: 1,}}r.Response.WriteJson(res)
}
func ConnectWs(r *ghttp.Request) {var upGrader = websocket.Upgrader{ReadBufferSize:  1024,WriteBufferSize: 1024,CheckOrigin: func(r *http.Request) bool {return true},}ws, err := upGrader.Upgrade(r.Response.Writer, r.Request, nil)if err != nil {fmt.Println(err)}//延迟关闭ws连接defer ws.Close()def.SshBridgeHandler(ws, wsInfo)
}

ws文件

package defimport ("bytes""fmt""github.com/gorilla/websocket""golang.org/x/crypto/ssh""io""log""sync""time"
)type wsBufferWriter struct {buffer bytes.Buffermu     sync.Mutex
}
type XtermService struct {stdinPipe   io.WriteClosercomboOutput *wsBufferWritersession     *ssh.SessionwsConn      *websocket.Conn
}// wsBufferWriter接口实现
func (w *wsBufferWriter) Write(p []byte) (n int, err error) {w.mu.Lock()defer w.mu.Unlock()return w.buffer.Write(p)
}func (w *wsBufferWriter) Bytes() []byte {w.mu.Lock()defer w.mu.Unlock()return w.buffer.Bytes()
}func (w *wsBufferWriter) Reset() {w.mu.Lock()defer w.mu.Unlock()w.buffer.Reset()
}type ConnectWsArg struct {Server string `json:"server"`User   string `json:"user"`Pwd    string `json:"pwd"`
}func SshBridgeHandler(ws *websocket.Conn, args *ConnectWsArg) {// 创建 SSH 连接config := &ssh.ClientConfig{User: args.User,Auth: []ssh.AuthMethod{ssh.Password(args.Pwd),},HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 注意:这会忽略对远程主机密钥的检查,不建议在生产环境中使用}client, err := ssh.Dial("tcp", args.Server, config)if err != nil {fmt.Println("Failed to dial: ", err)err := ws.WriteMessage(websocket.TextMessage, []byte("\n第一步:ssh连接失败"+err.Error()))if err != nil {return}return}defer client.Close()// 从SSH连接接收数据并发送到WebSocketsession, err := client.NewSession()if err != nil {err := ws.WriteMessage(websocket.TextMessage, []byte("\n第二步:ssh创建会话失败"+err.Error()))if err != nil {return}return}stdin, err := session.StdinPipe()if err != nil {log.Println(err)return}defer stdin.Close()wsBuffer := new(wsBufferWriter)session.Stdout = wsBuffersession.Stderr = wsBuffermodes := ssh.TerminalModes{ssh.ECHO:          1,ssh.TTY_OP_ISPEED: 14400,ssh.TTY_OP_OSPEED: 14400,}//伪造xterm终端err = session.RequestPty("xterm", 100, 100, modes)if err != nil {err := ws.WriteMessage(websocket.TextMessage, []byte("第三步:会话伪造终端失败"+err.Error()))if err != nil {return}return}err = session.Shell()if err != nil {err := ws.WriteMessage(websocket.TextMessage, []byte("第四步:启动shell终端失败"+err.Error()))if err != nil {return}return}var xterm = &XtermService{stdinPipe:   stdin,comboOutput: wsBuffer,session:     session,wsConn:      ws,}//defer session.Close()quitChan := make(chan bool, 3)//4.以上初始化信息基本结束.下面是携程读写websocket和ssh管道的操作.也就是信息通信xterm.start(quitChan)//session 等待go xterm.Wait(quitChan)<-quitChan_, message, err := ws.ReadMessage()_, err = stdin.Write(message)if err != nil {log.Println(err)return}fmt.Println(string(message))output, err := session.CombinedOutput(string(message))err = ws.WriteMessage(websocket.TextMessage, output)if err != nil {return}}func (s *XtermService) start(quitChan chan bool) {go s.receiveWsMsg(quitChan)go s.sendWsOutput(quitChan)
}// 将客户端信息返回到
func (s *XtermService) sendWsOutput(quitChan chan bool) {wsConn := s.wsConndefer setQuit(quitChan)ticker := time.NewTicker(time.Millisecond * time.Duration(60))defer ticker.Stop()for {select {case <-ticker.C:if s.comboOutput == nil {return}bytes := s.comboOutput.Bytes()if len(bytes) > 0 {wsConn.WriteMessage(websocket.TextMessage, bytes)s.comboOutput.buffer.Reset()}case <-quitChan:return}}
}// 读取ws信息写入ssh客户端中.
func (s *XtermService) receiveWsMsg(quitChan chan bool) {wsConn := s.wsConndefer setQuit(quitChan) //告诉其他携程退出for {select {case <-quitChan:returndefault://1.websocket 读取信息_, data, err := wsConn.ReadMessage()fmt.Println("===readMessage===", string(data))if err != nil {fmt.Println("receiveWsMsg=>读取ws信息失败", err)return}//2.读取到的数据写入ssh 管道内.s.stdinPipe.Write(data)}}
}func (s *XtermService) Wait(quitChan chan bool) {if err := s.session.Wait(); err != nil {setQuit(quitChan)}
}func setQuit(quitChan chan bool) {quitChan <- true
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/35216.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FileNotFoundError: Cannot find DGL C++ graphbolt library at ...

FileNotFoundError: Cannot find DGL C graphbolt library at ...-CSDN博客https://blog.csdn.net/weixin_44017989/article/details/137658749

k8s手撕架构图+详解

“如果您在解决类似问题时也遇到了困难&#xff0c;希望我的经验分享对您有所帮助。如果您有任何疑问或者想分享您的经历&#xff0c;欢迎在评论区留言&#xff0c;我们可以一起探讨解决方案。祝您在编程路上顺利前行&#xff0c;不断突破技术的难关&#xff0c;感谢您的阅读&a…

【LeetCode】一、数组相关(双指针算法 + 置换)

文章目录 1、算法复杂度1.1 时间复杂度1.2 空间复杂度 2、数组3、leetcode485&#xff1a;最大连续1的个数4、leetcode283&#xff1a;移动05、leetcode27&#xff1a;移除元素 1、算法复杂度 1.1 时间复杂度 算法的执行时间与输入值之间的关系&#xff08;看代码实际总行数的…

NineData和华为云在一起!提供一站式智能数据库DevOps平台

以GuassDB数据库为底座 NineData和华为云一起 为企业提供 一站式智能数据库DevOps平台 帮助开发者 高效、安全地完成 数据库SQL审核 访问控制、敏感数据保护等 日常数据库相关开发任务 NineData 智能数据管理平台 NineData 作为新一代的云原生智能数据管理平台&#xf…

HSRP热备份路由协议(VRRP虚拟路由冗余协议)配置以及实现负载均衡

1、相关原理 在网络中&#xff0c;如果一台作为默认网关的三层交换机或者路由器损坏&#xff0c;所有使用该网关为下一跳的主机通信必然中断&#xff0c;即使配置多个默认网关&#xff0c;在不重启终端的情况下&#xff0c;也不能彻底换到新网关。Cisco提出了HSRP热备份路由协…

运算符重载详解(完全版)

1.运算符重载 C为了增强代码的可读性引入了运算符重载&#xff0c;运算符重载是具有特殊函数名的函数&#xff0c;也具有其返回值类型&#xff0c;函数名字和参数列表&#xff0c;其返回值类型与参数列表都与普通的函数类似 函数名&#xff1a;关键字operator后面接需要重载的…

Interview preparation--Elasticsearch写入原理与调优

ES的写入过程 ES支持的写操作 create&#xff1a; create操作不同于put操作&#xff0c;put操作的时候如果当前put的数据存在则会被覆盖&#xff0c;如果put操作的时候加上操作类型create&#xff0c;如果数据存在则会返回失败&#xff0c;比如&#xff1a;PUT /pruduct/_cre…

Ubuntu20.04安装python2和python3及版本配置

Ubuntu20.04安装python2和python3及版本配置_ubuntu 20.04 python3-CSDN博客https://blog.csdn.net/pangc2014/article/details/117407413 >>>ubuntu 安装源码python2_mob649e8161c39d的技术博客_51CTO博客https://blog.51cto.com/u_16175489/7327966

大厂薪资福利篇第四弹:字节跳动

欢迎来到绝命Coding&#xff01; 今天继续更新大家最关心的 大厂薪资福利系列&#xff01; 往期分享&#xff1a; 福利开水喝不完&#xff1f;大厂薪资福利篇&#xff01;美团 职场文化发源地&#xff1f;大厂薪资福利篇&#xff01;阿里巴巴 给这么多&#xff01;还能带宠物上…

MFC案例:自制工具条(Toolbar)按钮的小程序

程序目标&#xff1a;在基于对话框的MFC项目中&#xff0c;自制三个 Toolbar 按钮&#xff08;用颜色区分&#xff0c;分别为红、绿、蓝&#xff09;&#xff1b;程序运行时&#xff0c;单击红色按钮显示一个红色的填充椭圆&#xff1b;再单击绿色按钮则进行清屏&#xff1b;最…

[hive] posexplode生成从去年一月一号,到本月的月时间表

生成从去年一月一号&#xff0c;到本月的月时间表 posexplode用法&#xff1a; lateral view 表别名 as 序号列名,数组中的元素的名 1、生成序列 SELECT time_stamp_fist_day_of_last_year,--去年第一天的时间戳numfrom(SELECTsplit(repeat_o,,) o_array,time_stamp_fist_da…

用英文介绍纽约:NEW YORK, USA‘s MEGACITY

NEW YORK, USA’s MEGACITY | America’s Largest City Link: https://www.youtube.com/watch?vdzjQ-akB3BI&listPLmSQiOQJmbZ7TU39cyx7gizM9i8nOuZXy&index24 The story of New York City, America’s megalopolis. Summary Paragraph 1: The Historical Developm…

Chrome插件:​Vue.js Devtools 高效地开发和调试

在现代前端开发中&#xff0c;Vue.js因其灵活性和性能优势&#xff0c;受到越来越多开发者的青睐。然而&#xff0c;随着项目规模的扩大&#xff0c;调试和优化变得愈发复杂。幸运的是&#xff0c;Vue.js Devtools的出现&#xff0c;为开发者提供了一套强大的工具集&#xff0c…

vue大屏适配方案

前言 开发过大屏的铁汁们应该知道&#xff0c;前期最头疼的就是大屏适配&#xff0c;由于大屏项目需要在市面上不是很常见的显示器上进行展示&#xff0c;所以要根据不同的尺寸进行适配&#xff0c;今天我将为大家分享的我使用的大屏适配方案&#xff0c;话不多说&#xff0c;直…

Matlab|风光及负荷多场景随机生成与缩减

目录 1 主要内容 计算模型 场景生成与聚类方法应用 2 部分程序 3 程序结果 4 下载链接 1 主要内容 该程序方法复现了《融合多场景分析的交直流混合微电网多时间尺度随机优化调度策略》3.1节基于多场景技术的随机性建模部分&#xff0c;该部分是随机优化调度的重要组成部分…

轻松掌握:工科生如何高效阅读国际期刊和撰写论文(下)

⭐️我叫忆_恒心&#xff0c;一名喜欢书写博客的研究生&#x1f468;‍&#x1f393;。 如果觉得本文能帮到您&#xff0c;麻烦点个赞&#x1f44d;呗&#xff01; 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧&#xff0c;喜欢的小伙伴给个三连支…

透明屏幕的魅力:为何它如此受欢迎

在科技日新月异的今天&#xff0c;透明屏幕技术以其独特的魅力和广泛的应用前景&#xff0c;逐渐成为了科技领域的一颗璀璨明星。从智能手机、平板电脑到大型显示屏&#xff0c;透明屏幕技术以其前所未有的视觉体验和实用性&#xff0c;赢得了广大消费者的喜爱。 一、透明屏幕的…

docker 镜像突然拉取不了,教你解决

最近我们可以发现&#xff0c;在Linux系统里拉取不了镜像了&#xff0c;翻墙也拉取不了&#xff0c;这时候我们可以有一个新的docker 镜像同步网站来解决这个问题 一、首先打开镜像官网&#xff08;需要翻墙&#xff09; https://hub.docker.com/ 然后搜索一个镜像 搜索最新…

Nginx实战:简单登录验证配置(基于openssl)

本文提供的是基于openssl创建的密码文件,对nginx指定的location访问。进行登录验证的配置方式。 1、验证页面配置 我的nginx实验环境是直接yum安装的,如果是自己编译安装的那么对应目录就是自己安装配置的目录。 先在/usr/share/nginx/html下创建一个usertest.html,里面添加…

Spring Cache常见问题解决

目录 一 报错:Null key returned for cache operation 二 报错&#xff1a;类型转换异常 三 取出的数据为null 一 报错:Null key returned for cache operation 这里报错有两种情况&#xff1a; 第一&#xff0c;如果你在新增的方法上使用Cacheable注解&#xff0c;那么肯定是…