5 Commits e944c0bf90 ... e9bfe51690

Author SHA1 Message Date
  niujiuru e9bfe51690 优化修改sshd模块代码,解决自测过程中发现的问题和bug 2 weeks ago
  niujiuru 025abdb618 优化修改sshd客户端代码, 使其支持管道、重定向等高级功能, 包装成shell命令 2 weeks ago
  niujiuru 3ce0693ebe 默认关闭大华相机的触发模式, 若打开, 会和自动曝光设置有逻辑冲突 2 weeks ago
  niujiuru 562d6f4a3e 优化sshd客户端的欢迎界面 2 weeks ago
  niujiuru 35671228bf 优化sshd模块,远程执行指令时记录历史日志,便于溯源 2 weeks ago

+ 1 - 1
dh_takephoto/takephoto.c

@@ -11,7 +11,7 @@ static unsigned int device_type =  interfaceTypeGige | interfaceTypeUsb3;
 static const char * const manufacturer = "Huaray Technology";
 
 // 触发模式
-static const char * const setTriggerMode = "On"; /// 默认: 打开,  可关闭: "Off"
+static const char * const setTriggerMode = "Off"; /// 默认: "Off",  可选: "On"
 
 // 曝光取图
 #define AE_WAIT_MAX_MS     180000 // 最大曝光稳定等待的时间, 默认3分钟, 单位: ms

+ 1 - 0
go.mod

@@ -5,6 +5,7 @@ go 1.24.2
 require (
 	github.com/alexflint/go-filemutex v1.3.0
 	github.com/beevik/ntp v1.5.0
+	github.com/denisbrodbeck/machineid v1.0.1
 	github.com/eclipse/paho.mqtt.golang v1.5.1
 	github.com/google/uuid v1.6.0
 	github.com/jlaffaye/ftp v0.2.0

+ 2 - 0
go.sum

@@ -5,6 +5,8 @@ github.com/beevik/ntp v1.5.0/go.mod h1:mJEhBrwT76w9D+IfOEGvuzyuudiW9E52U2BaTrMOY
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ=
+github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
 github.com/eclipse/paho.mqtt.golang v1.5.1 h1:/VSOv3oDLlpqR2Epjn1Q7b2bSTplJIeV2ISgCl2W7nE=
 github.com/eclipse/paho.mqtt.golang v1.5.1/go.mod h1:1/yJCneuyOoCOzKSsOTUc0AJfpsItBGWvYpBLimhArU=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=

BIN
package/rtu_linux_modules_1.0.0.1.tar.gz


BIN
package/yfkj_ssh_client_v1.0.0.1.tar.gz


+ 24 - 3
sshd/client/client.go

@@ -16,6 +16,7 @@ import (
 	"syscall"
 	"time"
 
+	"github.com/denisbrodbeck/machineid"
 	"github.com/google/uuid"
 	"github.com/peterh/liner"
 )
@@ -68,6 +69,11 @@ func main() {
 		cwd:      "/",
 	}
 
+	id, err := machineid.ID()
+	if err == nil {
+		coupler.machineID = id
+	}
+
 	if err := coupler.init2(); err != nil {
 		fmt.Printf("[%s] 错误: %v\n!!", MODULE_NAME, err)
 		return
@@ -92,7 +98,7 @@ func term(pingState *atomic.Bool) {
 
 	interruptLoop(&executing, &interrupted) // Ctrl+C卍
 
-	printWelcome()
+	printWelcome(pingState)
 
 	line := liner.NewLiner()
 	defer line.Close()
@@ -131,6 +137,13 @@ func term(pingState *atomic.Bool) {
 		return matches
 	})
 
+	executing.Store(true)
+	result, err := coupler.exec("pwd")
+	executing.Store(false)
+	if err == nil && result.Cwd != "" { /// 在进入前对齐路径
+		coupler.cwd = result.Cwd
+	}
+
 	for {
 		if !pingState.Load() {
 			fmt.Printf("[%s] 目标设备连接丢失!!\n", MODULE_NAME)
@@ -246,7 +259,7 @@ func heartbeatLoop(pingState *atomic.Bool) {
 	}()
 }
 
-func printWelcome() {
+func printWelcome(pingState *atomic.Bool) {
 	welcome := `
  _   _ _ _       _     _ _ _     
 | \ | (_) |     | |   (_) | |    
@@ -257,10 +270,18 @@ func printWelcome() {
 
 ══════════════════════════════════
        云飞科技RTU远程运维终端
-══════════════════════════════════
 提示: 输入'quit'命令, 退出终端
+══════════════════════════════════
 `
 	fmt.Println(welcome)
+	fmt.Printf("客户端ID: %s\n", coupler.clientID)
+	state := ""
+	if pingState.Load() {
+		state = "已连接 ✅"
+	} else {
+		state = "未连接 ❌"
+	}
+	fmt.Printf("设备状态: %s\n\n", state)
 }
 
 func clearScreen() {

+ 1 - 0
sshd/client/coupler.go

@@ -28,6 +28,7 @@ type MQTTCoupler struct {
 
 	client      mqtt.Client
 	clientID    string
+	machineID   string
 	isConnected atomic.Bool /// 标记是否已连接MQTT的Broker服务
 
 	imei     string // 设备唯一标识

+ 3 - 2
sshd/client/invoker.go

@@ -59,8 +59,9 @@ func (c *MQTTCoupler) exec(
 	}{
 		ClientID: c.clientID,
 		ExecuteParams: shell.ExecuteParams{
-			Cmd:     cmd,
-			Timeout: int(shell.DefaultTimeout / time.Second),
+			HostFingerprint: c.machineID, // 调用者主机指纹
+			Cmd:             cmd,
+			Timeout:         int(shell.DefaultTimeout / time.Second),
 		},
 	}
 

+ 35 - 1
sshd/sshd.go

@@ -3,6 +3,7 @@ package sshd
 import (
 	"context"
 	"errors"
+	"fmt"
 	"os"
 	"strings"
 	"sync"
@@ -291,11 +292,44 @@ func (c *MQTTCoupler) execOneCmd(msg mqtt.Message) {
 			goto retp
 		}
 
+		cmd := params.Cmd
+		if strings.ContainsAny(cmd, "&") {
+			ce.mu.Unlock()
+			resp = jsonrpc2.BuildError(req, -32003, "prohibit the startup of background tasks")
+			goto retp
+		}
+
+		if strings.ContainsAny(cmd, "|>;") || strings.Contains(cmd, "\n") {
+			safeCmd := strings.ReplaceAll(cmd, "'", "'\\''")
+			cmd = fmt.Sprintf("sh -c '%s'", safeCmd) // 包装成 shell 命令, 支持管道等高级功能
+			params.Cmd = cmd
+		}
+
+		hostID := params.HostFingerprint
+		if hostID == "" {
+			hostID = "unknown"
+		}
+
 		ce.state = execRunning
 		ce.mu.Unlock()
 
+		start := time.Now()
+		if true { //////// 记录执行日志-执行前
+			baseapp.Logger.Infof("[%s][▷ EXEC] host=%s, cmd=%q, timeout=%ds",
+				MODULE_NAME, hostID, params.Cmd, params.Timeout)
+		}
+
 		result, err := ce.executor.Exec(params) // 本地执行用户指令
 
+		cost := time.Since(start)
+		if err != nil { // 记录失败日志-执行后
+			baseapp.Logger.Warnf("[%s][✖ EXEC] host=%s, cmd=%q, err=%v, cost=%v",
+				MODULE_NAME, hostID, params.Cmd, err, cost)
+		} else { ///////// 记录成功日志-执行后
+			baseapp.Logger.Infof("[%s][✔ EXEC] host=%s, cmd=%q, cost=%v",
+				MODULE_NAME, hostID, params.Cmd, cost)
+		}
+
 		ce.mu.Lock()
 		if ce.state != execClosing {
 			ce.state = execIdle
@@ -312,7 +346,7 @@ func (c *MQTTCoupler) execOneCmd(msg mqtt.Message) {
 		ce.mu.Unlock()
 
 		if !running {
-			resp = jsonrpc2.BuildError(req, -32003, "no running command")
+			resp = jsonrpc2.BuildError(req, -32004, "no running command")
 			goto retp
 		}
 

+ 9 - 8
utils/shell/execute_types.go

@@ -9,7 +9,7 @@ import (
 )
 
 const (
-	DefaultTimeout = 5 * time.Second
+	DefaultTimeout = 5 * time.Second ////// 执行命令的默认超时时间, 单位: 秒
 )
 
 var (
@@ -18,14 +18,15 @@ var (
 )
 
 type ExecuteParams struct {
-	Cmd     string `json:"cmd"`               // 命令
-	Timeout int    `json:"timeout,omitempty"` // 超时(秒)
-	Dir     string `json:"-"`                 // 工作目录
+	HostFingerprint string `json:"host_fingerprint,omitempty"` // 主机指纹
+	Cmd             string `json:"cmd"`                        // 用户指令
+	Timeout         int    `json:"timeout,omitempty"`          // 超时(秒)
+	Dir             string `json:"-"`                          // 工作目录
 }
 
 type ExecuteResult struct {
-	Stdout   string `json:"stdout"`    ///////// 标准输出
-	Stderr   string `json:"stderr"`    ///////// 错误输出
-	ExitCode int    `json:"exit_code"` ///////// 退出状态码: 0表示成功, 非0表示失败
-	Cwd      string `json:"cwd"`       ///////// 当前目录
+	Stdout   string `json:"stdout"`    ////////////////////////// 标准输出
+	Stderr   string `json:"stderr"`    ////////////////////////// 错误输出
+	ExitCode int    `json:"exit_code"` ///////// 退出状态码: 0成功, 非0失败
+	Cwd      string `json:"cwd"`       ////////////////////////// 当前目录
 }

+ 1 - 1
utils/shell/executor.go

@@ -16,7 +16,7 @@ import (
 type Executor struct {
 	cwd     string        // 当前目录
 	prevCwd string        // 上次目录
-	pg      *processGroup // --进程组
+	pg      *processGroup // 执行命令的进程组
 }
 
 type PathError struct {