mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-02 23:40:24 +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))
|
||
}
|