diff --git a/src/framework/utils/telnet/telnet.go b/src/framework/utils/telnet/telnet.go index bcafd082..33579e94 100644 --- a/src/framework/utils/telnet/telnet.go +++ b/src/framework/utils/telnet/telnet.go @@ -50,10 +50,12 @@ func (c *ConnTelnet) NewClient() (*ConnTelnet, error) { // fmt.Fprintln(client, c.User) // fmt.Fprintln(client, c.Password) - c.Client = &client - // 调整窗口大小 (120 列 x 128 行) - requestPty(c.Client, 120, 128) + // 需要确保接收方理解并正确处理发送窗口大小设置命令 + client.Write([]byte{255, 251, 31}) + client.Write([]byte{255, 250, 31, byte(120 >> 8), byte(120 & 0xFF), byte(128 >> 8), byte(128 & 0xFF), 255, 240}) + + c.Client = &client // 排空连接登录的信息 c.RunCMD("") @@ -111,20 +113,9 @@ func (c *ConnTelnet) NewClientSession(cols, rows int) (*TelnetClientSession, err if c.Client == nil { return nil, fmt.Errorf("telnet client not connected") } - requestPty(c.Client, cols, rows) - return &TelnetClientSession{ + s := &TelnetClientSession{ Client: *c.Client, - }, nil -} - -// requestPty 调整终端窗口大小 -func requestPty(client *net.Conn, cols, rows int) error { - if client == nil { - return fmt.Errorf("telnet client not connected") } - conn := *client - // 需要确保接收方理解并正确处理发送窗口大小设置命令 - conn.Write([]byte{255, 251, 31}) - conn.Write([]byte{255, 250, 31, byte(cols >> 8), byte(cols & 0xFF), byte(rows >> 8), byte(rows & 0xFF), 255, 240}) - return nil + s.WindowChange(cols, rows) + return s, nil } diff --git a/src/framework/utils/telnet/telnet_session.go b/src/framework/utils/telnet/telnet_session.go index 9289eebf..4d0cc59f 100644 --- a/src/framework/utils/telnet/telnet_session.go +++ b/src/framework/utils/telnet/telnet_session.go @@ -19,6 +19,17 @@ func (s *TelnetClientSession) Close() { } } +// WindowChange informs the remote host about a terminal window dimension change to h rows and w columns. +func (s *TelnetClientSession) WindowChange(h, w int) error { + if s.Client == nil { + return fmt.Errorf("client is nil to content write failed") + } + // 需要确保接收方理解并正确处理发送窗口大小设置命令 + s.Client.Write([]byte{255, 251, 31}) + s.Client.Write([]byte{255, 250, 31, byte(w >> 8), byte(w & 0xFF), byte(h >> 8), byte(h & 0xFF), 255, 240}) + return nil +} + // Write 写入命令 不带回车(\n)也会执行根据客户端情况 func (s *TelnetClientSession) Write(cmd string) (int, error) { if s.Client == nil { diff --git a/src/modules/ws/controller/ws.go b/src/modules/ws/controller/ws.go index 1d289e6e..4ff3def6 100644 --- a/src/modules/ws/controller/ws.go +++ b/src/modules/ws/controller/ws.go @@ -3,7 +3,6 @@ package controller import ( "encoding/json" "fmt" - "strconv" "strings" "time" @@ -88,7 +87,7 @@ func (s *WSController) WS(c *gin.Context) { // Test 测试 // -// GET /test?clientId=&groupID= +// GET /test?clientId=xxx&groupID=xxx func (s *WSController) Test(c *gin.Context) { language := ctx.AcceptLanguage(c) @@ -125,6 +124,21 @@ func (s *WSController) Test(c *gin.Context) { // GET /ssh?hostId=1&cols=80&rows=40 func (s *WSController) SSH(c *gin.Context) { language := ctx.AcceptLanguage(c) + var query struct { + HostId string `form:"hostId" binding:"required"` // 连接主机ID + Cols int `form:"cols"` // 终端单行字符数 + Rows int `form:"rows"` // 终端显示行数 + } + if err := c.ShouldBindQuery(&query); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + if query.Cols < 80 || query.Cols > 400 { + query.Cols = 80 + } + if query.Rows < 40 || query.Rows > 1200 { + query.Rows = 40 + } // 登录用户信息 loginUser, err := ctx.LoginUser(c) @@ -133,14 +147,8 @@ func (s *WSController) SSH(c *gin.Context) { return } - // 连接主机ID - hostId := c.Query("hostId") - if hostId == "" { - c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) - return - } - neHost := s.neHostService.SelectById(hostId) - if neHost.HostID != hostId || neHost.HostType != "ssh" { + neHost := s.neHostService.SelectById(query.HostId) + if neHost.HostID != query.HostId || neHost.HostType != "ssh" { // 没有可访问主机信息数据! c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.noData"))) return @@ -163,19 +171,8 @@ func (s *WSController) SSH(c *gin.Context) { } defer client.Close() - // 终端单行字符数 - cols, err := strconv.Atoi(c.Query("cols")) - if err != nil { - cols = 80 - } - // 终端显示行数 - rows, err := strconv.Atoi(c.Query("rows")) - if err != nil { - rows = 40 - } - // 创建SSH客户端会话 - clientSession, err := client.NewClientSession(cols, rows) + clientSession, err := client.NewClientSession(query.Cols, query.Rows) if err != nil { // 连接主机失败,请检查连接参数后重试 c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.errByHostInfo"))) @@ -204,7 +201,7 @@ func (s *WSController) SSH(c *gin.Context) { if len(outputByte) > 0 { outputStr := string(outputByte) msgByte, _ := json.Marshal(result.Ok(map[string]any{ - "requestId": fmt.Sprintf("ssh_%s_%d", hostId, ms.UnixMilli()), + "requestId": fmt.Sprintf("ssh_%s_%d", neHost.HostID, ms.UnixMilli()), "data": outputStr, })) wsClient.MsgChan <- msgByte @@ -229,6 +226,21 @@ func (s *WSController) SSH(c *gin.Context) { // GET /telnet?hostId=1 func (s *WSController) Telnet(c *gin.Context) { language := ctx.AcceptLanguage(c) + var query struct { + HostId string `form:"hostId" binding:"required"` // 连接主机ID + Cols int `form:"cols"` // 终端单行字符数 + Rows int `form:"rows"` // 终端显示行数 + } + if err := c.ShouldBindQuery(&query); err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + if query.Cols < 120 || query.Cols > 400 { + query.Cols = 120 + } + if query.Rows < 128 || query.Rows > 1200 { + query.Rows = 128 + } // 登录用户信息 loginUser, err := ctx.LoginUser(c) @@ -237,14 +249,8 @@ func (s *WSController) Telnet(c *gin.Context) { return } - // 连接主机ID - hostId := c.Query("hostId") - if hostId == "" { - c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) - return - } - neHost := s.neHostService.SelectById(hostId) - if neHost.HostID != hostId || neHost.HostType != "telnet" { + neHost := s.neHostService.SelectById(query.HostId) + if neHost.HostID != query.HostId || neHost.HostType != "telnet" { // 没有可访问主机信息数据! c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.noData"))) return @@ -260,20 +266,8 @@ func (s *WSController) Telnet(c *gin.Context) { return } defer client.Close() - - // 终端单行字符数 - cols, err := strconv.Atoi(c.DefaultQuery("cols", "120")) - if err != nil { - cols = 120 - } - // 终端显示行数 - rows, err := strconv.Atoi(c.DefaultQuery("rows", "128")) - if err != nil { - rows = 128 - } - // 创建Telnet客户端会话 - clientSession, err := client.NewClientSession(cols, rows) + clientSession, err := client.NewClientSession(query.Cols, query.Rows) if err != nil { // 连接主机失败,请检查连接参数后重试 c.JSON(200, result.ErrMsg(i18n.TKey(language, "neHost.errByHostInfo"))) @@ -290,7 +284,11 @@ func (s *WSController) Telnet(c *gin.Context) { wsClient := s.wsService.ClientCreate(loginUser.UserID, nil, wsConn, clientSession) go s.wsService.ClientWriteListen(wsClient) - go s.wsService.ClientReadListen(wsClient, service.ReceiveShell) + go s.wsService.ClientReadListen(wsClient, service.ReceiveTelnet) + + // 等待1秒,排空首次消息 + time.Sleep(1 * time.Second) + _ = clientSession.Read() // 实时读取Telnet消息直接输出 msTicker := time.NewTicker(100 * time.Millisecond) @@ -302,7 +300,7 @@ func (s *WSController) Telnet(c *gin.Context) { if len(outputByte) > 0 { outputStr := strings.TrimRight(string(outputByte), "\x00") msgByte, _ := json.Marshal(result.Ok(map[string]any{ - "requestId": fmt.Sprintf("telnet_%s_%d", hostId, ms.UnixMilli()), + "requestId": fmt.Sprintf("telnet_%s_%d", neHost.HostID, ms.UnixMilli()), "data": outputStr, })) wsClient.MsgChan <- msgByte @@ -337,13 +335,6 @@ func (s *WSController) ShellView(c *gin.Context) { c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) return } - // 登录用户信息 - loginUser, err := ctx.LoginUser(c) - if err != nil { - c.JSON(401, result.CodeMsg(401, i18n.TKey(language, err.Error()))) - return - } - if query.Cols < 120 || query.Cols > 400 { query.Cols = 120 } @@ -351,6 +342,13 @@ func (s *WSController) ShellView(c *gin.Context) { query.Rows = 40 } + // 登录用户信息 + loginUser, err := ctx.LoginUser(c) + if err != nil { + c.JSON(401, result.CodeMsg(401, i18n.TKey(language, err.Error()))) + return + } + // 网元主机的SSH客户端 sshClient, err := s.neInfoService.NeRunSSHClient(query.NeType, query.NeId) if err != nil { @@ -377,6 +375,10 @@ func (s *WSController) ShellView(c *gin.Context) { go s.wsService.ClientWriteListen(wsClient) go s.wsService.ClientReadListen(wsClient, service.ReceiveShellView) + // 等待1秒,排空首次消息 + time.Sleep(1 * time.Second) + _ = clientSession.Read() + // 实时读取SSH消息直接输出 msTicker := time.NewTicker(100 * time.Millisecond) defer msTicker.Stop() diff --git a/src/modules/ws/service/ws.impl.go b/src/modules/ws/service/ws.impl.go index fb781767..da16419e 100644 --- a/src/modules/ws/service/ws.impl.go +++ b/src/modules/ws/service/ws.impl.go @@ -181,7 +181,7 @@ func (s *WSImpl) ClientReadListen(wsClient *model.WSClient, receiveType int) { if messageType == websocket.TextMessage { var reqMsg model.WSRequest if err := json.Unmarshal(msg, &reqMsg); err != nil { - msgByte, _ := json.Marshal(result.ErrMsg("message format not supported")) + msgByte, _ := json.Marshal(result.ErrMsg("message format json error")) wsClient.MsgChan <- msgByte continue } @@ -193,6 +193,8 @@ func (s *WSImpl) ClientReadListen(wsClient *model.WSClient, receiveType int) { go NewWSReceiveImpl.Shell(wsClient, reqMsg) case ReceiveShellView: go NewWSReceiveImpl.ShellView(wsClient, reqMsg) + case ReceiveTelnet: + go NewWSReceiveImpl.Telnet(wsClient, reqMsg) } } } diff --git a/src/modules/ws/service/ws_receive.go b/src/modules/ws/service/ws_receive.go index 083399be..0372c39c 100644 --- a/src/modules/ws/service/ws_receive.go +++ b/src/modules/ws/service/ws_receive.go @@ -6,6 +6,7 @@ const ( ReceiveCommont = iota // Commont 接收通用业务处理 ReceiveShell // Shell 接收终端交互业务处理 ReceiveShellView // ShellView 接收查看文件终端交互业务处理 + ReceiveTelnet // Telnet 接收终端交互业务处理 ) // IWSReceive WebSocket消息接收处理 服务层接口 @@ -18,4 +19,7 @@ type IWSReceive interface { // ShellView 接收查看文件终端交互业务处理 ShellView(client *model.WSClient, reqMsg model.WSRequest) + + // Telnet 接收终端交互业务处理 + Telnet(client *model.WSClient, reqMsg model.WSRequest) }