mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-06 01:10:26 +08:00
215 lines
4.7 KiB
Go
215 lines
4.7 KiB
Go
|
|
package mcm
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"bytes"
|
|||
|
|
"fmt"
|
|||
|
|
"mayfly-go/pkg/errorx"
|
|||
|
|
"strings"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"github.com/veops/go-ansiterm"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// 命令过滤函数,若返回error,则不执行该命令
|
|||
|
|
type CmdFilterFunc func(cmd string) error
|
|||
|
|
|
|||
|
|
const (
|
|||
|
|
CR = 0x0d // 单个字节 13 通常表示发送一个 CR(Carriage Return,回车)字符 \r
|
|||
|
|
EOT = 0x03 // 通过向标准输入发送单个字节 3 通常表示发送一个 EOT(End of Transmission)信号。EOT 是一种控制字符,在通信中用于指示数据传输的结束。发送 EOT 信号可以被用来终止当前的交互或数据传输
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
type ExecutedCmd struct {
|
|||
|
|
Cmd string `json:"cmd"` // 执行的命令
|
|||
|
|
Time int64 `json:"time"` // 执行时间戳
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TerminalHandler 终端处理器
|
|||
|
|
type TerminalHandler struct {
|
|||
|
|
Filters []CmdFilterFunc
|
|||
|
|
ExecutedCmds []*ExecutedCmd // 已执行的命令
|
|||
|
|
|
|||
|
|
Parser *Parser
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// PreWriteHandle 写入数据至终端前的处理,可进行过滤等操作
|
|||
|
|
func (tf *TerminalHandler) PreWriteHandle(p []byte) error {
|
|||
|
|
tf.Parser.AppendInputData(p)
|
|||
|
|
|
|||
|
|
// 不是回车命令,则表示命令未结束
|
|||
|
|
if bytes.LastIndex(p, []byte{CR}) != 0 {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// time.Sleep(time.Millisecond * 30)
|
|||
|
|
command := tf.Parser.GetCmd()
|
|||
|
|
// 重置终端输入输出
|
|||
|
|
tf.Parser.Reset()
|
|||
|
|
|
|||
|
|
if command == "" {
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 执行命令过滤器
|
|||
|
|
for _, filter := range tf.Filters {
|
|||
|
|
if err := filter(command); err != nil {
|
|||
|
|
msg := fmt.Sprintf("\r\n%s%s", tf.Parser.Ps1, GetErrorContent(err.Error()))
|
|||
|
|
return errorx.NewBiz(msg)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 记录执行命令
|
|||
|
|
tf.ExecutedCmds = append(tf.ExecutedCmds, &ExecutedCmd{
|
|||
|
|
Cmd: command,
|
|||
|
|
Time: time.Now().Unix(),
|
|||
|
|
})
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// HandleRead 处理从终端读取的数据进行操作
|
|||
|
|
func (tf *TerminalHandler) HandleRead(data []byte) error {
|
|||
|
|
tf.Parser.AppendOutData(data)
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type Parser struct {
|
|||
|
|
Output *ansiterm.ByteStream
|
|||
|
|
InputData []byte
|
|||
|
|
OutputData []byte
|
|||
|
|
Ps1 string
|
|||
|
|
|
|||
|
|
vimState bool
|
|||
|
|
commandState bool
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func NewParser(width, height int) *Parser {
|
|||
|
|
return &Parser{
|
|||
|
|
Output: NewParserByteStream(width, height),
|
|||
|
|
vimState: false,
|
|||
|
|
commandState: true,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func NewParserByteStream(width, height int) *ansiterm.ByteStream {
|
|||
|
|
screen := ansiterm.NewScreen(width, height)
|
|||
|
|
stream := ansiterm.InitByteStream(screen, false)
|
|||
|
|
stream.Attach(screen)
|
|||
|
|
return stream
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var (
|
|||
|
|
enterMarks = [][]byte{
|
|||
|
|
[]byte("\x1b[?1049h"),
|
|||
|
|
[]byte("\x1b[?1048h"),
|
|||
|
|
[]byte("\x1b[?1047h"),
|
|||
|
|
[]byte("\x1b[?47h"),
|
|||
|
|
[]byte("\x1b[?25l"),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
exitMarks = [][]byte{
|
|||
|
|
[]byte("\x1b[?1049l"),
|
|||
|
|
[]byte("\x1b[?1048l"),
|
|||
|
|
[]byte("\x1b[?1047l"),
|
|||
|
|
[]byte("\x1b[?47l"),
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
screenMarks = [][]byte{
|
|||
|
|
{0x1b, 0x5b, 0x4b, 0x0d, 0x0a},
|
|||
|
|
{0x1b, 0x5b, 0x34, 0x6c},
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
func (p *Parser) AppendInputData(data []byte) {
|
|||
|
|
if len(p.InputData) == 0 {
|
|||
|
|
// 如 "root@cloud-s0ervh-hh87:~# " 获取前一段用户名等提示内容
|
|||
|
|
p.Ps1 = p.GetOutput()
|
|||
|
|
}
|
|||
|
|
p.InputData = append(p.InputData, data...)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (p *Parser) AppendOutData(data []byte) {
|
|||
|
|
// 非编辑等状态,则追加输出内容
|
|||
|
|
if !p.State(data) {
|
|||
|
|
p.OutputData = append(p.OutputData, data...)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetCmd 获取执行的命令
|
|||
|
|
func (p *Parser) GetCmd() string {
|
|||
|
|
// "root@cloud-s0ervh-hh87:~# ls"
|
|||
|
|
s := p.GetOutput()
|
|||
|
|
// Ps1 = "root@cloud-s0ervh-hh87:~# "
|
|||
|
|
return strings.TrimPrefix(s, p.Ps1)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (p *Parser) Reset() {
|
|||
|
|
p.Output.Listener.Reset()
|
|||
|
|
p.OutputData = nil
|
|||
|
|
p.InputData = nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (p *Parser) GetOutput() string {
|
|||
|
|
p.Output.Feed(p.OutputData)
|
|||
|
|
|
|||
|
|
res := parseOutput(p.Output.Listener.Display())
|
|||
|
|
if len(res) == 0 {
|
|||
|
|
return ""
|
|||
|
|
}
|
|||
|
|
return res[len(res)-1]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func parseOutput(data []string) (output []string) {
|
|||
|
|
for _, line := range data {
|
|||
|
|
if strings.TrimSpace(line) != "" {
|
|||
|
|
output = append(output, line)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return output
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (p *Parser) State(b []byte) bool {
|
|||
|
|
if !p.vimState && IsEditEnterMode(b) {
|
|||
|
|
if !isNewScreen(b) {
|
|||
|
|
p.vimState = true
|
|||
|
|
p.commandState = false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if p.vimState && IsEditExitMode(b) {
|
|||
|
|
// 重置终端输入输出
|
|||
|
|
p.Reset()
|
|||
|
|
p.vimState = false
|
|||
|
|
p.commandState = true
|
|||
|
|
}
|
|||
|
|
return p.vimState
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func isNewScreen(p []byte) bool {
|
|||
|
|
return matchMark(p, screenMarks)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func IsEditEnterMode(p []byte) bool {
|
|||
|
|
return matchMark(p, enterMarks)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func IsEditExitMode(p []byte) bool {
|
|||
|
|
return matchMark(p, exitMarks)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func matchMark(p []byte, marks [][]byte) bool {
|
|||
|
|
for _, item := range marks {
|
|||
|
|
if bytes.Contains(p, item) {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetErrorContent 包装返回终端错误消息
|
|||
|
|
func GetErrorContent(msg string) string {
|
|||
|
|
return fmt.Sprintf("\033[1;31m%s\033[0m", msg)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetErrorContentRn 包装返回终端错误消息, 并自动回车换行
|
|||
|
|
func GetErrorContentRn(msg string) string {
|
|||
|
|
return fmt.Sprintf("\r\n%s", GetErrorContent(msg))
|
|||
|
|
}
|