版本封存

This commit is contained in:
zeaslity
2025-12-06 11:26:05 +08:00
parent 13949e1ba8
commit c0ae5e30c4
57 changed files with 2443 additions and 1428 deletions

View File

@@ -0,0 +1,47 @@
package main
import (
"os"
"sync"
"cmii-uav-watchdog-common/models"
)
var PodEnv = models.EnvInfo{}
func init() {
PodEnv = GetEnvInfo()
}
// GetEnvInfo 获取环境信息
// 单例模式, 如果已经初始化过, 则直接返回
func GetEnvInfo() models.EnvInfo {
var once sync.Once
if PodEnv.K8S_NAMESPACE != "" {
return PodEnv
}
once.Do(func() {
PodEnv = models.EnvInfo{
K8S_NAMESPACE: os.Getenv("K8S_NAMESPACE"),
APPLICATION_NAME: os.Getenv("APPLICATION_NAME"),
CUST_JAVA_OPTS: os.Getenv("CUST_JAVA_OPTS"),
BIZ_CONFIG_GROUP: os.Getenv("BIZ_CONFIG_GROUP"),
SYS_CONFIG_GROUP: os.Getenv("SYS_CONFIG_GROUP"),
IMAGE_NAME: os.Getenv("IMAGE_NAME"),
JAVA_VERSION: os.Getenv("JAVA_VERSION"),
GIT_COMMIT: os.Getenv("GIT_COMMIT"),
GIT_BRANCH: os.Getenv("GIT_BRANCH"),
NODE_NAME: os.Getenv("NODE_NAME"),
NODE_IP: os.Getenv("NODE_IP"),
POD_NAME: os.Getenv("POD_NAME"),
LIMIT_CPU: os.Getenv("LIMIT_CPU"),
LIMIT_MEMORY: os.Getenv("LIMIT_MEMORY"),
REQUEST_CPU: os.Getenv("REQUEST_CPU"),
REQUEST_MEMORY: os.Getenv("REQUEST_MEMORY"),
}
})
return PodEnv
}

View File

@@ -1 +1,70 @@
package cmd
package main
// import (
// "cmii-uav-watchdog-agent/host_info"
// "cmii-uav-watchdog-common/wdd_log"
// "net/http"
// "os"
// "os/signal"
// "syscall"
// "github.com/gin-gonic/gin"
// )
// func StartHostInfoGin() {
// // 创建一个默认的 Gin 路由
// var r = gin.Default() // 定义一个 GET 路由
// r.GET("/ping", func(c *gin.Context) {
// c.JSON(http.StatusOK, gin.H{"message": "pong"})
// })
// // 定义一个 POST 路由
// r.POST("/echo", func(c *gin.Context) {
// var json map[string]interface{}
// if err := c.ShouldBindJSON(&json); err == nil {
// c.JSON(http.StatusOK, json)
// } else {
// c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
// }
// })
// r.GET("/cpu", func(c *gin.Context) {
// cpuInfo := host_info.GetCPUInfo() // 直接返回 CPU 信息
// c.JSON(http.StatusOK, cpuInfo)
// })
// r.GET("/memory", func(c *gin.Context) {
// memInfo := host_info.GetMemoryInfo() // 直接返回内存信息
// c.JSON(http.StatusOK, memInfo)
// })
// r.GET("/disk", func(c *gin.Context) {
// diskInfo := host_info.GetDiskInfo() // 直接返回磁盘信息
// c.JSON(http.StatusOK, diskInfo)
// })
// r.GET("/motherboard", func(c *gin.Context) {
// mbInfo := host_info.GetMotherboardInfo() // 直接返回主板信息
// c.JSON(http.StatusOK, mbInfo)
// })
// r.GET("/network", func(c *gin.Context) {
// networkInterfaces := host_info.GetNetworkInterfaces()
// c.JSON(http.StatusOK, networkInterfaces)
// })
// r.GET("/all", func(c *gin.Context) {
// allInfo := host_info.GetAllInfo()
// c.JSON(http.StatusOK, allInfo)
// })
// //r.GET("/phy", func(c *gin.Context) {
// // allInfo, _ := services.GetPVForLV()
// // c.JSON(http.StatusOK, allInfo)
// //})
// // 启动服务,监听在 8080 端口
// r.Run(":8098")
// // 等待终止信号
// sigs := make(chan os.Signal, 1)
// signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
// <-sigs
// wdd_log.Info("正在关闭服务...")
// }

View File

@@ -1,16 +1,20 @@
package cmd
package main
import (
"cmii-uav-watchdog-common/wdd_log"
"flag"
"log"
"os"
"os/exec"
"os/signal"
"strings"
"sync"
"syscall"
"time"
)
// 是否是Debug模式
var DebugMode = false
var (
businessProgramType = flag.String("business-program-type", "", "Type of business program (java or python)")
businessProgramPath = flag.String("business-program-path", "", "Path to the business program file")
@@ -19,45 +23,221 @@ var (
mu sync.Mutex
)
// java 启动参数
var (
podName = "unknown"
imageName = "unknown"
imageVersion = "unknown"
workpath = "/cmii"
custJavaOpts = "-Xms200m -Xmx1500m -Djava.awt.headless=true -Dlog4j2.formatMsgNoLookups=true "
envJvmTimezone = "Asia/Shanghai"
k8sNamespace = "default"
applicationName = "app"
nacosRegistry = "helm-nacos:8848"
nacosDiscoveryIp = "127.0.0.1"
nacosDiscoveryPort = "8080"
nacosUsername = "nacos"
nacosPassword = "nacos"
bizConfigGroup = "wdd-biz"
sysConfigGroup = "wdd-sys"
)
// 初始化
func init() {
// 获取环境变量 特殊设置才能开启DEBUG
debugMode := os.Getenv("CMII_DEBUG_MODE")
if debugMode == "WDD_DEBUG" {
DebugMode = true
}
wdd_log.Info("DebugMode 是否开启: %v", DebugMode)
}
// 初始化配置并打印配置信息
func initConfig() {
// 获取环境变量
imageName = getEnvOrDefault("IMAGE_NAME", imageName)
if imageName != "unknown" {
parts := strings.Split(imageName, ":")
if len(parts) > 1 {
imageVersion = parts[len(parts)-1]
}
}
workpath = getEnvOrDefault("WORKPATH", workpath)
custJavaOpts = getEnvOrDefault("CUST_JAVA_OPTS", custJavaOpts)
envJvmTimezone = getEnvOrDefault("ENV_JVM_TIMEZONE", envJvmTimezone)
k8sNamespace = getEnvOrDefault("K8S_NAMESPACE", k8sNamespace)
applicationName = getEnvOrDefault("APPLICATION_NAME", applicationName)
nacosRegistry = getEnvOrDefault("NACOS_REGISTRY", nacosRegistry)
nacosDiscoveryIp = getEnvOrDefault("NACOS_DISCOVERY_IP", nacosDiscoveryIp)
nacosDiscoveryPort = getEnvOrDefault("NACOS_DISCOVERY_PORT", nacosDiscoveryPort)
nacosUsername = getEnvOrDefault("NACOS_USERNAME", nacosUsername)
nacosPassword = getEnvOrDefault("NACOS_PASSWORD", nacosPassword)
// 设置默认配置组
defaultConfigGroup := "default"
if imageVersion != "unknown" {
parts := strings.Split(imageVersion, "-")
if len(parts) > 0 {
defaultConfigGroup = parts[0]
}
}
bizConfigGroup = getEnvOrDefault("BIZ_CONFIG_GROUP", bizConfigGroup)
if bizConfigGroup == "" {
wdd_log.Info("[CONTAINER] As BIZ_CONFIG_GROUP is null, it set default value [%s]", defaultConfigGroup)
bizConfigGroup = defaultConfigGroup
os.Setenv("BIZ_CONFIG_GROUP", bizConfigGroup)
}
sysConfigGroup = getEnvOrDefault("SYS_CONFIG_GROUP", sysConfigGroup)
if sysConfigGroup == "" {
wdd_log.Info("[CONTAINER] As SYS_CONFIG_GROUP is null, it set default value [%s]", defaultConfigGroup)
sysConfigGroup = defaultConfigGroup
os.Setenv("SYS_CONFIG_GROUP", sysConfigGroup)
}
// 打印配置信息
wdd_log.Info("[CONTAINER] %s image is running ...", imageName)
wdd_log.Info("[CONTAINER] IMAGE_VERSION is %s", imageVersion)
wdd_log.Info("[CONTAINER] WORKPATH is %s", workpath)
wdd_log.Info("[CONTAINER] CUST_JAVA_OPTS is %s", custJavaOpts)
wdd_log.Info("[CONTAINER] JVM_TIMEZONE is %s", envJvmTimezone)
wdd_log.Info("[CONTAINER] K8S_NAMESPACE is %s", k8sNamespace)
wdd_log.Info("[CONTAINER] APPLICATION_NAME is %s", applicationName)
wdd_log.Info("[CONTAINER] NACOS_REGISTRY is %s", nacosRegistry)
wdd_log.Info("[CONTAINER] NACOS_DISCOVERY_IP is %s", nacosDiscoveryIp)
wdd_log.Info("[CONTAINER] NACOS_DISCOVERY_PORT is %s", nacosDiscoveryPort)
wdd_log.Info("[CONTAINER] NACOS_USERNAME is %s", nacosUsername)
wdd_log.Info("[CONTAINER] NACOS_PASSWORD is %s", nacosPassword)
wdd_log.Info("[CONTAINER] BIZ_CONFIG_GROUP is %s", bizConfigGroup)
wdd_log.Info("[CONTAINER] SYS_CONFIG_GROUP is %s", sysConfigGroup)
wdd_log.Info("[CONTAINER] starting...")
}
// 获取环境变量,如果为空则返回默认值
func getEnvOrDefault(key, defaultValue string) string {
value := os.Getenv(key)
if value == "" {
return defaultValue
}
return value
}
func startBusinessProcess(programType, programPath string) *exec.Cmd {
var cmd *exec.Cmd
switch programType {
case "java":
cmd = exec.Command("java", "-jar", programPath)
// 初始化配置
initConfig()
// 构建命令参数列表
args := []string{}
// 添加Java选项
if custJavaOpts != "" {
// 分割CUST_JAVA_OPTS中的多个参数
for _, opt := range splitArgs(custJavaOpts) {
if opt != "" {
args = append(args, opt)
}
}
}
// 添加主JAR文件
args = append(args, "-jar", programPath)
// 添加其他参数
args = append(args, []string{
"--user.timezone=" + envJvmTimezone,
"-Dfile.encoding=UTF-8",
"--spring.main.allow-bean-definition-overriding=true",
"--spring.application.name=" + applicationName,
"--spring.cloud.nacos.username=" + nacosUsername,
"--spring.cloud.nacos.password=" + nacosPassword,
"--spring.cloud.nacos.config.server-addr=" + nacosRegistry,
"--spring.cloud.nacos.config.extension-configs[0].data-id=" + applicationName + ".yml",
"--spring.cloud.nacos.config.extension-configs[0].group=" + bizConfigGroup,
"--spring.cloud.nacos.config.extension-configs[0].refresh=true",
"--spring.cloud.nacos.config.shared-configs[0].data-id=cmii-backend-system.yml",
"--spring.cloud.nacos.config.shared-configs[0].group=" + sysConfigGroup,
"--spring.cloud.nacos.config.shared-configs[0].refresh=true",
"--spring.cloud.nacos.discovery.server-addr=" + nacosRegistry,
"--spring.cloud.nacos.discovery.ip=" + nacosDiscoveryIp,
"--spring.cloud.nacos.discovery.port=" + nacosDiscoveryPort,
}...)
wdd_log.Info("[CONTAINER] java args: %v", args)
cmd = exec.Command("java", args...)
case "python":
cmd = exec.Command("python", programPath)
default:
log.Fatalf("Unsupported business program type: %s", programType)
wdd_log.Error("不支持的业务程序类型: %s", programType)
}
return cmd
}
// 分割命令行参数
func splitArgs(s string) []string {
var args []string
var inQuote bool
var current string
var quoteChar rune
for _, r := range s {
switch {
case (r == '"' || r == '\'') && !inQuote:
inQuote = true
quoteChar = r
case r == quoteChar && inQuote:
inQuote = false
quoteChar = 0
case r == ' ' && !inQuote:
if current != "" {
args = append(args, current)
current = ""
}
default:
current += string(r)
}
}
if current != "" {
args = append(args, current)
}
return args
}
func main() {
// 解析命令行参数
flag.Parse()
if *businessProgramType == "" || *businessProgramPath == "" {
log.Fatal("Missing required flags: -business-program-type and -business-program-path must be specified")
wdd_log.Error("缺少必要的参数: -business-program-type -business-program-path 必须指定")
}
// 信号处理
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
// StartHeartbeatDetection(signalChan)
go func() {
for sig := range signalChan {
log.Printf("Received signal: %v", sig)
wdd_log.Info("接收到信号: %v", sig)
mu.Lock()
stopRequested = true
if currentCmd != nil && currentCmd.Process != nil {
// 发送 SIGTERM 给业务进程
if err := currentCmd.Process.Signal(syscall.SIGTERM); err != nil {
log.Printf("Failed to send SIGTERM to process: %v", err)
wdd_log.Error("向进程发送SIGTERM信号失败: %v", err)
}
// 等待 10 秒后强制杀死进程
time.AfterFunc(10*time.Second, func() {
mu.Lock()
defer mu.Unlock()
if currentCmd != nil && currentCmd.Process != nil {
log.Println("Graceful shutdown timeout, sending SIGKILL")
wdd_log.Warn("优雅关闭超时,发送SIGKILL信号")
currentCmd.Process.Kill()
}
})
@@ -66,12 +246,19 @@ func main() {
}
}()
// 授权检测
go func() {
for {
StartHeartbeatDetection(signalChan)
}
}()
// 主循环
for {
mu.Lock()
if stopRequested {
mu.Unlock()
log.Println("Shutting down due to stop request")
wdd_log.Info("收到停止请求,正在关闭")
os.Exit(0)
}
mu.Unlock()
@@ -82,11 +269,16 @@ func main() {
// 启动业务进程
if err := cmd.Start(); err != nil {
log.Printf("Failed to start business process: %v", err)
wdd_log.Error("启动业务进程失败: %v", err)
time.Sleep(5 * time.Second)
continue
}
// 业务进程启动成功
if *businessProgramType == "java" {
wdd_log.Info("[CONTAINER] 程序启动成功!")
}
mu.Lock()
currentCmd = cmd
mu.Unlock()
@@ -98,21 +290,21 @@ func main() {
mu.Unlock()
if err != nil {
log.Printf("Business process exited with error: %v", err)
wdd_log.Error("业务进程异常退出: %v", err)
} else {
log.Println("Business process exited normally")
wdd_log.Info("业务进程正常退出")
}
mu.Lock()
if stopRequested {
mu.Unlock()
log.Println("Shutting down due to stop request")
wdd_log.Info("收到停止请求,正在关闭")
os.Exit(0)
}
mu.Unlock()
// 等待 5 秒后重启
log.Println("Restarting business process in 5 seconds...")
wdd_log.Info("5秒后重启业务进程...")
time.Sleep(5 * time.Second)
}
}

View File

@@ -1,76 +1,95 @@
package cmd
package main
import (
"cmii-uav-watchdog-agent/host_info"
"cmii-uav-watchdog-agent/rpc"
"cmii-uav-watchdog-agent/totp"
"cmii-uav-watchdog-common/models"
"fmt"
"log"
"cmii-uav-watchdog-common/totp_tier_two"
"cmii-uav-watchdog-common/wdd_log"
"os"
"os/signal"
"syscall"
"time"
)
const (
var (
// 最大重试次数
maxRetryCount = 5
maxRetryCount = 12
// 默认心跳检测间隔
defaultHeartbeatInterval = 30 * time.Second
defaultHeartbeatInterval = 2 * time.Hour
// 检测失败后的等待间隔
failWaitInterval = 5 * time.Second
// 环境变量名称
appNameEnv = "APP_NAME"
failWaitInterval = 1 * time.Hour
)
// 启动心跳检测
func StartHeartbeatDetection() {
log.Println("启动心跳检测任务...")
var tierTwoTotpSecret = ""
// StartHeartbeatDetection 启动心跳检测
func StartHeartbeatDetection(signalChan chan os.Signal) {
wdd_log.Info("启动心跳检测任务...")
// variable
var err error
// 如果Debug模式那么使用环境变量赋值
if DebugMode {
// 如果从在环境变量 那么使用环境变量赋值
heartbeatInterval := os.Getenv("WATCHDOG_AGENT_HEARTBEAT_INTERVAL")
if heartbeatInterval != "" {
defaultHeartbeatInterval, err = time.ParseDuration(heartbeatInterval)
if err != nil {
wdd_log.Error("无法解析环境变量: %v", err)
}
wdd_log.Info("已更新心跳检测间隔 => %s", defaultHeartbeatInterval)
}
failWaitIntervalEnv := os.Getenv("WATCHDOG_AGENT_FAIL_WAIT_INTERVAL")
if failWaitIntervalEnv != "" {
failWaitInterval, err = time.ParseDuration(failWaitIntervalEnv)
if err != nil {
wdd_log.Error("无法解析环境变量: %v", err)
}
wdd_log.Info("已更新心跳检测失败等待间隔 => %s", failWaitInterval)
}
//
}
// 创建RPC客户端
client := rpc.NewClient(nil)
heartbeatURL := os.Getenv("WATCHDOG_HEARTBEAT_URL")
client := rpc.NewClient(nil, heartbeatURL)
// 监听终止信号
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
wdd_log.Info("心跳检测URL: %s", client.GetHeartbeatURL())
// 失败计数器
failCount := 0
failCount := 1
// 心跳检测循环
for {
select {
case <-signalChan:
log.Println("收到终止信号,停止心跳检测")
wdd_log.Info("收到终止信号,停止心跳检测")
return
default:
// 尝试发送心跳请求
authorized, err := sendHeartbeat(client)
if err != nil {
log.Printf("心跳检测失败: %v", err)
wdd_log.Error("第 %d 次心跳检测失败: %v", failCount, err)
failCount++
} else if !authorized {
log.Println("未获得授权")
wdd_log.Warn("第 %d 次心跳检测未获得授权", failCount)
failCount++
} else {
// 检测成功,重置失败计数
failCount = 0
log.Println("心跳检测成功,已获得授权")
failCount = 1
wdd_log.Info("第 %d 次心跳检测成功,已获得授权", failCount)
}
// 检查是否达到最大失败次数
if failCount >= maxRetryCount {
log.Printf("心跳检测连续失败 %d 次,发送终止信号", failCount)
// 发送终止信号给start_up.go
process, err := os.FindProcess(os.Getpid())
if err == nil {
process.Signal(syscall.SIGTERM)
}
wdd_log.Fatal("心跳检测连续失败 %d 次,发送终止信号", failCount)
signalChan <- syscall.SIGTERM
return
}
@@ -88,8 +107,9 @@ func StartHeartbeatDetection() {
// 发送心跳请求
func sendHeartbeat(client *rpc.Client) (bool, error) {
// 1. 获取主机信息
hostInfoData := services.GetAllInfo()
hostInfoData := host_info.GetAllInfo()
hostInfo := models.HostInfo{
SystemInfo: hostInfoData.SystemInfo,
CPUInfo: hostInfoData.CPUInfo,
@@ -98,26 +118,21 @@ func sendHeartbeat(client *rpc.Client) (bool, error) {
NetInfo: hostInfoData.NetInfo,
}
// 2. 获取应用名称
appName := os.Getenv(appNameEnv)
if appName == "" {
appName = "unknown-app"
log.Printf("警告: 环境变量 %s 未设置,使用默认值: %s", appNameEnv, appName)
}
// 获取环境信息
envInfo := GetEnvInfo()
// 构建心跳请求
request := &models.HeartbeatRequest{
HostInfo: hostInfo,
Timestamp: time.Now().Unix(),
AppName: appName,
EnvInfo: envInfo,
}
// 3. 如果已有TOTP密钥则生成TOTP验证码
totpSecret := totp.GetTOTPSecret()
if totpSecret != "" {
totpCode, err := totp.GenerateTOTPCode()
if tierTwoTotpSecret != "" {
totpCode, err := totp_tier_two.GenerateTierTwoTOTPCode(tierTwoTotpSecret)
if err != nil {
log.Printf("生成TOTP验证码失败: %v", err)
wdd_log.Error("生成TOTP验证码失败: %v", err)
} else {
request.TOTPCode = totpCode
}
@@ -126,20 +141,24 @@ func sendHeartbeat(client *rpc.Client) (bool, error) {
// 4. 发送心跳请求
response, err := client.SendHeartbeatWithRetry(request, 10*time.Second)
if err != nil {
return false, fmt.Errorf("发送心跳请求失败: %w", err)
return false, err
}
if response == nil {
return false, err
}
// 5. 处理响应
if response.SecondTOTPSecret != "" {
// 存储TOTP密钥
totp.SetTOTPSecret(response.SecondTOTPSecret)
log.Println("已更新TOTP密钥")
tierTwoTotpSecret = response.SecondTOTPSecret
wdd_log.Info("已更新TOTP密钥 => %s", tierTwoTotpSecret)
}
// 6. 如果有TOTP验证码进行验证
if response.TOTPCode != "" && totpSecret != "" {
if !totp.ValidateTOTPCode(response.TOTPCode) {
log.Println("TOTP验证码验证失败")
if response.TOTPCode != "" && tierTwoTotpSecret != "" {
if !totp_tier_two.VerifyTierTwoTOTPCode(response.TOTPCode, tierTwoTotpSecret) {
wdd_log.Warn("TOTP验证码验证失败")
return false, nil
}
}

View File

@@ -2,38 +2,10 @@ module cmii-uav-watchdog-agent
go 1.24
require (
cmii-uav-watchdog-common v0.0.0
github.com/gin-gonic/gin v1.10.0
)
require cmii-uav-watchdog-common v0.0.0
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
require cmii-uav-watchdog-otp v0.0.0 // indirect
replace cmii-uav-watchdog-common => ../cmii-uav-watchdog-common
replace cmii-uav-watchdog-otp => ../cmii-uav-watchdog-otp

View File

@@ -1,89 +0,0 @@
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
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/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@@ -1,163 +1,205 @@
package services
package host_info
import (
"bufio"
"cmii-uav-watchdog-common/models"
"fmt"
"cmii-uav-watchdog-common/wdd_log"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
)
// NewCPUInfo 创建一个默认的 CPUInfo
func NewCPUInfo() models.CPUInfo {
return models.CPUInfo{
ModelName: "unknown",
Cores: 0,
Architecture: "unknown",
UUID: "unknown",
ModelName: "unknown",
Cores: 0,
Architecture: "amd64",
Flags: []string{},
Hypervisor: "unknown",
Virtualization: "unknown",
}
}
const (
exePath = "/proc/self/exe"
elfMagic = 0x7f
elfMagicString = "ELF"
eMachineOffset = 18
)
// parseLine 解析单行 CPU 信息
func parseLine(line string, cpuInfo *models.CPUInfo) {
if strings.HasPrefix(line, "model name") {
cpuInfo.ModelName = strings.TrimSpace(strings.Split(line, ":")[1])
}
}
// getCPUCores 通过 processor 行获取 CPU 核心数
func getCPUCores() (int, error) {
cores := 0
file, err := os.Open("/proc/cpuinfo")
if err != nil {
return cores, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(line, "processor") {
cores++
}
}
if err := scanner.Err(); err != nil {
return cores, err
}
return cores, nil
}
// getCPUUUID 获取 CPU UUID
func getCPUUUID() (string, error) {
data, err := os.ReadFile("/sys/class/dmi/id/product_uuid")
if err != nil {
return "unknown", err
}
return strings.TrimSpace(string(data)), nil
}
func getCPUArchitecture() string {
cpuarch := "unknown"
// 打开当前进程的执行文件
file, err := os.Open(exePath)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Error opening %s: %v\n", exePath, err)
return cpuarch
}
defer file.Close()
// 读取前64个字节以获取 ELF 头部信息
header := make([]byte, 64)
if _, err := file.Read(header); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "Error reading header: %v\n", err)
return cpuarch
}
// 检查 ELF 文件标识
if header[0] != elfMagic || string(header[1:4]) != elfMagicString {
_, _ = fmt.Fprintln(os.Stderr, "File is not an ELF file")
return cpuarch
}
// 获取架构信息
arch := header[eMachineOffset] // e_machine 字段的偏移量
switch arch {
case 0x02: // EM_386
cpuarch = "x86(32-bit)"
case 0x03: // EM_X86_64
cpuarch = "x86_64(64-bit)"
case 0x28: // EM_ARM
cpuarch = "ARM"
case 0x2A: // EM_ARM64
cpuarch = "ARM64"
case 0x08: // EM_MIPS
cpuarch = "MIPS"
default:
cpuarch = "unknown architecture"
}
return cpuarch
}
// GetCPUInfo 获取 CPU 信息
// GetCPUInfo 获取CPU信息
// 返回CPU信息结构体
func GetCPUInfo() models.CPUInfo {
wdd_log.Debug("开始获取CPU信息")
// 首先尝试从/proc/cpuinfo文件中读取信息
cpuInfo, err := getCPUInfoFromProc()
if err == nil {
wdd_log.Debug("成功从/proc/cpuinfo获取CPU信息")
return cpuInfo
}
// 如果从文件获取失败尝试使用lscpu命令
wdd_log.Warn("从/proc/cpuinfo获取CPU信息失败: %s尝试使用lscpu命令", err.Error())
cpuInfo, err = getCPUInfoFromLscpu()
if err == nil {
wdd_log.Debug("成功从lscpu命令获取CPU信息")
return cpuInfo
}
// 如果都失败使用runtime包获取基本信息
wdd_log.Warn("从lscpu获取CPU信息失败: %s使用runtime包获取基本信息", err.Error())
return getFallbackCPUInfo()
}
// getCPUInfoFromProc 从/proc/cpuinfo文件中读取CPU信息
// 返回CPU信息结构体和可能的错误
func getCPUInfoFromProc() (models.CPUInfo, error) {
wdd_log.Debug("尝试从/proc/cpuinfo读取CPU信息")
// 创建默认的CPU信息结构体
cpuInfo := NewCPUInfo()
// 读取 /proc/cpuinfo
// 打开/proc/cpuinfo文件
file, err := os.Open("/proc/cpuinfo")
if err != nil {
fmt.Println("Error opening /proc/cpuinfo:", err)
return cpuInfo // 返回默认值
return cpuInfo, err
}
defer func() {
if err := file.Close(); err != nil {
fmt.Println("Error closing /proc/cpuinfo:", err)
}
}()
defer file.Close()
// 使用scanner读取文件
scanner := bufio.NewScanner(file)
// 处理的CPU核心数量
coreCount := 1
var flags []string
// 逐行读取并解析
for scanner.Scan() {
parseLine(scanner.Text(), &cpuInfo)
}
if err := scanner.Err(); err != nil {
fmt.Println("Error reading /proc/cpuinfo:", err)
return cpuInfo // 返回默认值
line := scanner.Text()
parts := strings.Split(line, ":")
if len(parts) < 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
switch key {
case "processor":
// 计数处理器核心数量
coreCount++
case "model name":
// 获取CPU型号名称
cpuInfo.ModelName = value
// 提取频率信息(如果包含)
if strings.Contains(value, "@") {
freqParts := strings.Split(value, "@")
if len(freqParts) > 1 {
cpuInfo.Frequency = strings.TrimSpace(freqParts[1])
}
}
case "flags":
// 获取CPU标志
flags = strings.Fields(value)
case "cpu cores":
// 获取每个物理CPU的核心数
cores, err := strconv.Atoi(value)
if err == nil && cores > 0 {
cpuInfo.Cores = cores
}
}
}
// 获取 CPU 核心
cores, err := getCPUCores()
// 如果没有从cpu cores字段获取到核心数使用处理器计
if cpuInfo.Cores == 0 && coreCount > 0 {
cpuInfo.Cores = coreCount
}
// 设置CPU标志
cpuInfo.Flags = flags
// 检测虚拟化相关信息
for _, flag := range flags {
if flag == "vmx" {
cpuInfo.Virtualization = "vmx"
} else if flag == "svm" {
cpuInfo.Virtualization = "svm"
} else if flag == "hypervisor" {
cpuInfo.Hypervisor = "present"
}
}
// 设置架构
cpuInfo.Architecture = runtime.GOARCH
return cpuInfo, nil
}
// getCPUInfoFromLscpu 使用lscpu命令获取CPU信息
// 返回CPU信息结构体和可能的错误
func getCPUInfoFromLscpu() (models.CPUInfo, error) {
wdd_log.Debug("尝试使用lscpu命令获取CPU信息")
// 创建默认的CPU信息结构体
cpuInfo := NewCPUInfo()
// 执行lscpu命令
cmd := exec.Command("lscpu")
output, err := cmd.Output()
if err != nil {
fmt.Println("Error getting CPU cores:", err)
} else {
cpuInfo.Cores = cores
return cpuInfo, err
}
// 获取 CPU UUID
uuid, err := getCPUUUID()
if err != nil {
fmt.Println("Error getting CPU UUID:", err)
} else {
cpuInfo.UUID = uuid
// 解析输出
lines := strings.Split(string(output), "\n")
for _, line := range lines {
parts := strings.Split(line, ":")
if len(parts) < 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
switch key {
case "Model name":
cpuInfo.ModelName = value
// 提取频率信息(如果包含)
if strings.Contains(value, "@") {
freqParts := strings.Split(value, "@")
if len(freqParts) > 1 {
cpuInfo.Frequency = strings.TrimSpace(freqParts[1])
}
}
case "Architecture":
cpuInfo.Architecture = value
case "CPU(s)":
cores, err := strconv.Atoi(value)
if err == nil {
cpuInfo.Cores = cores
}
case "Flags":
cpuInfo.Flags = strings.Fields(value)
case "Hypervisor vendor":
cpuInfo.Hypervisor = value
case "Virtualization":
cpuInfo.Virtualization = value
case "CPU MHz":
if cpuInfo.Frequency == "" {
cpuInfo.Frequency = value + " MHz"
}
}
}
cpuInfo.Architecture = getCPUArchitecture()
return cpuInfo, nil
}
// getFallbackCPUInfo 使用runtime包获取基本CPU信息
// 返回CPU信息结构体
func getFallbackCPUInfo() models.CPUInfo {
wdd_log.Debug("使用runtime包获取基本CPU信息")
cpuInfo := NewCPUInfo()
cpuInfo.Cores = runtime.NumCPU()
cpuInfo.Architecture = runtime.GOARCH
return cpuInfo
}
/*
CPU模型名称: Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz
核心数量: 4
架构信息: x86_64
*/

View File

@@ -1,8 +1,8 @@
package services
package host_info
import (
"cmii-uav-watchdog-common/models"
"fmt"
"cmii-uav-watchdog-common/wdd_log"
"strconv"
"strings"
"syscall"
@@ -68,7 +68,7 @@ func GetDiskInfo() []models.DiskInfo {
// Get disk usage information using stat
var stat syscall.Statfs_t
if err := syscall.Statfs(mountPoint, &stat); err != nil {
fmt.Printf("Warning: error getting statfs for %s: %v\n", mountPoint, err)
wdd_log.Warn("获取 %s 的statfs信息失败: %v", mountPoint, err)
continue
}
@@ -85,7 +85,8 @@ func GetDiskInfo() []models.DiskInfo {
// Calculate percentage used
usePercent := "0%"
if total > 0 {
usePercent = fmt.Sprintf("%.1f%%", float64(used)/float64(total)*100)
percentage := float64(used) / float64(total) * 100
usePercent = strconv.FormatFloat(percentage, 'f', 1, 64) + "%"
}
// Determine the physical device and its size

View File

@@ -1,4 +1,4 @@
package services
package host_info
import "cmii-uav-watchdog-common/models"

View File

@@ -1,8 +1,8 @@
package services
package host_info
import (
"cmii-uav-watchdog-common/models"
"fmt"
"cmii-uav-watchdog-common/wdd_log"
"os"
"strings"
)
@@ -19,7 +19,7 @@ var DefaultMotherboardInfo = models.MotherboardInfo{
func readFileWithWarning(path string) ([]byte, error) {
value, err := os.ReadFile(path)
if err != nil {
fmt.Printf("Warning: unable to read file %s: %v\n", path, err)
wdd_log.Warn("无法读取文件 %s: %v", path, err)
return nil, err
}
return value, nil

View File

@@ -1,8 +1,8 @@
package services
package host_info
import (
"cmii-uav-watchdog-common/models"
"fmt"
"cmii-uav-watchdog-common/wdd_log"
"os"
"strconv"
"strings"
@@ -26,7 +26,7 @@ func GetMemoryInfo() models.MemoryInfo {
memInfo := NewMemoryInfo()
data, err := os.ReadFile("/proc/meminfo")
if err != nil {
fmt.Println("Error reading /proc/meminfo:", err)
wdd_log.Error("读取 /proc/meminfo 失败: %v", err)
return memInfo
}
@@ -39,7 +39,7 @@ func GetMemoryInfo() models.MemoryInfo {
key := fields[0]
value, err := strconv.ParseUint(fields[1], 10, 64)
if err != nil {
fmt.Printf("Error parsing value for %s: %v\n", key, err)
wdd_log.Error("解析 %s 的值失败: %v", key, err)
continue
}

View File

@@ -1,8 +1,8 @@
package services
package host_info
import (
"cmii-uav-watchdog-common/models"
"fmt"
"cmii-uav-watchdog-common/wdd_log"
"net"
"strings"
)
@@ -25,7 +25,7 @@ func GetNetworkInterfaces() []models.NetworkInterfaceInfo {
// 获取所有网络接口
ifaces, err := net.Interfaces()
if err != nil {
fmt.Println("Error getting network interfaces:", err)
wdd_log.Error("获取网络接口失败: %v", err)
return []models.NetworkInterfaceInfo{{Name: "unknown", MACAddress: "00:00:00:00:00:00", IPAddresses: []string{}}}
}
@@ -40,7 +40,7 @@ func GetNetworkInterfaces() []models.NetworkInterfaceInfo {
// 获取 MAC 地址
macAddress, err := getMACAddress(iface)
if err != nil {
fmt.Println("Error getting MAC address for", iface.Name, ":", err)
wdd_log.Error("获取网卡 %s 的MAC地址失败: %v", iface.Name, err)
continue
}
@@ -48,7 +48,7 @@ func GetNetworkInterfaces() []models.NetworkInterfaceInfo {
var ipAddresses []string
addrs, err := iface.Addrs()
if err != nil {
fmt.Println("Error getting addresses for", iface.Name, ":", err)
wdd_log.Error("获取网卡 %s 的IP地址失败: %v", iface.Name, err)
continue
}

View File

@@ -1,8 +1,8 @@
package services
package host_info
import (
"cmii-uav-watchdog-common/models"
"log"
"cmii-uav-watchdog-common/wdd_log"
"os"
"strings"
)
@@ -10,16 +10,12 @@ import (
// NewOSInfo 创建并返回一个 OSInfo 实例,设置默认值
func NewOSInfo() models.OSInfo {
return models.OSInfo{
Name: "unknown",
Version: "unknown",
ID: "unknown",
IDLike: "unknown",
VersionID: "unknown",
PrettyName: "unknown",
HomeURL: "unknown",
SupportURL: "unknown",
BugReportURL: "unknown",
PrivacyURL: "unknown",
Name: "unknown",
Version: "unknown",
ID: "unknown",
IDLike: "unknown",
VersionID: "unknown",
PrettyName: "unknown",
}
}
@@ -41,6 +37,14 @@ func GetSystemInfo() models.SystemInfo {
if machineID != "" {
sysInfo.MachineID = machineID
}
// 获取 machine serial
serialID := getMachineSerial()
if serialID != "" {
sysInfo.MachineSerial = serialID
}
// 获取操作系统版本
sysInfo.OS = getOSVersion()
@@ -50,12 +54,26 @@ func GetSystemInfo() models.SystemInfo {
sysInfo.KernelVersion = kernelVersion
}
// 获取节点名称
sysInfo.NodeName = "unknown"
nodeName := os.Getenv("NODE_NAME")
if nodeName != "" {
sysInfo.NodeName = nodeName
}
// 获取节点IP
sysInfo.NodeIP = "unknown"
ip := os.Getenv("NODE_IP")
if ip != "" {
sysInfo.NodeIP = ip
}
return sysInfo
}
func getOSVersion() models.OSInfo {
data, err := os.ReadFile("/etc/os-release")
if err != nil {
log.Printf("Error reading /etc/os-release: %v", err)
wdd_log.Error("读取 /etc/os-release 失败: %v", err)
return NewOSInfo() // 返回默认值
}
@@ -88,25 +106,27 @@ func getOSVersion() models.OSInfo {
osInfo.VersionID = value
case "PRETTY_NAME":
osInfo.PrettyName = value
case "HOME_URL":
osInfo.HomeURL = value
case "SUPPORT_URL":
osInfo.SupportURL = value
case "BUG_REPORT_URL":
osInfo.BugReportURL = value
case "PRIVACY_URL":
osInfo.PrivacyURL = value
}
}
return osInfo
}
// getMachineID 从 /etc/machine-id 文件中获取机器 ID
// getMachineID 从 /sys/devices/virtual/dmi/id/product_uuid 文件中获取机器 ID
func getMachineID() string {
data, err := os.ReadFile("/etc/machine-id")
data, err := os.ReadFile("/sys/devices/virtual/dmi/id/product_uuid")
if err != nil {
log.Printf("Error reading /etc/machine-id: %v", err)
wdd_log.Error("读取 /sys/devices/virtual/dmi/id/product_uuid 失败: %v", err)
return ""
}
return strings.TrimSpace(string(data)) // 去除多余空格
}
// getMachineID 从 /sys/devices/virtual/dmi/id/product_uuid 文件中获取机器 ID
func getMachineSerial() string {
data, err := os.ReadFile("/sys/devices/virtual/dmi/id/product_serial")
if err != nil {
wdd_log.Error("读取 /sys/devices/virtual/dmi/id/product_serial 失败: %v", err)
return ""
}
return strings.TrimSpace(string(data)) // 去除多余空格
@@ -116,8 +136,8 @@ func getMachineID() string {
func getKernelVersion() string {
data, err := os.ReadFile("/proc/version")
if err != nil {
log.Printf("Error reading /proc/version: %v", err)
wdd_log.Error("读取 /proc/version 失败: %v", err)
return ""
}
return string(data)
return strings.TrimSpace(string(data))
}

View File

@@ -1,69 +0,0 @@
package main
import (
"cmii-uav-watchdog-agent/host_info"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"os"
"os/signal"
"syscall"
)
func main() {
// 创建一个默认的 Gin 路由
var r = gin.Default() // 定义一个 GET 路由
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "pong"})
})
// 定义一个 POST 路由
r.POST("/echo", func(c *gin.Context) {
var json map[string]interface{}
if err := c.ShouldBindJSON(&json); err == nil {
c.JSON(http.StatusOK, json)
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
}
})
r.GET("/cpu", func(c *gin.Context) {
cpuInfo := services.GetCPUInfo() // 直接返回 CPU 信息
c.JSON(http.StatusOK, cpuInfo)
})
r.GET("/memory", func(c *gin.Context) {
memInfo := services.GetMemoryInfo() // 直接返回内存信息
c.JSON(http.StatusOK, memInfo)
})
r.GET("/disk", func(c *gin.Context) {
diskInfo := services.GetDiskInfo() // 直接返回磁盘信息
c.JSON(http.StatusOK, diskInfo)
})
r.GET("/motherboard", func(c *gin.Context) {
mbInfo := services.GetMotherboardInfo() // 直接返回主板信息
c.JSON(http.StatusOK, mbInfo)
})
r.GET("/network", func(c *gin.Context) {
networkInterfaces := services.GetNetworkInterfaces()
c.JSON(http.StatusOK, networkInterfaces)
})
r.GET("/all", func(c *gin.Context) {
allInfo := services.GetAllInfo()
c.JSON(http.StatusOK, allInfo)
})
//r.GET("/phy", func(c *gin.Context) {
// allInfo, _ := services.GetPVForLV()
// c.JSON(http.StatusOK, allInfo)
//})
// 启动服务,监听在 8080 端口
r.Run(":8098")
// 等待终止信号
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs
fmt.Println("Shutting down service...")
}

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"io"
"net/http"
"strings"
"time"
"cmii-uav-watchdog-common/models"
@@ -23,7 +24,8 @@ var (
)
const (
DefaultHeartbeatURL = "http://cmii-uav-watchdog/heartbeat"
DefaultHeartbeatURL = "http://cmii-uav-watchdog:8080/api/heartbeat"
// DefaultHeartbeatURL = "http://192.168.35.70:8990/api/heartbeat"
)
// ClientOptions HTTP客户端配置选项
@@ -48,8 +50,9 @@ func DefaultClientOptions() *ClientOptions {
// Client HTTP客户端封装
type Client struct {
httpClient *http.Client
options *ClientOptions
httpClient *http.Client
options *ClientOptions
heartbeatURL string // 心跳请求的URL地址
}
// NewClient 创建一个新的HTTP客户端
@@ -58,7 +61,7 @@ type Client struct {
//
// 返回:
// - *Client: HTTP客户端实例
func NewClient(options *ClientOptions) *Client {
func NewClient(options *ClientOptions, heartbeatURL string) *Client {
if options == nil {
options = DefaultClientOptions()
}
@@ -75,10 +78,22 @@ func NewClient(options *ClientOptions) *Client {
Transport: transport,
}
return &Client{
httpClient: httpClient,
options: options,
// 解析heartbeatURL
heartbeatURL = strings.TrimSpace(heartbeatURL)
if heartbeatURL == "" {
heartbeatURL = DefaultHeartbeatURL
}
return &Client{
httpClient: httpClient,
options: options,
heartbeatURL: heartbeatURL,
}
}
// GetHeartbeatURL 获取心跳请求的URL地址
func (c *Client) GetHeartbeatURL() string {
return c.heartbeatURL
}
// SendHeartbeat 发送心跳请求并处理响应
@@ -114,7 +129,7 @@ func (c *Client) SendHeartbeat(ctx context.Context, request *models.HeartbeatReq
}
// 创建HTTP请求
req, err := http.NewRequestWithContext(ctx, http.MethodPost, DefaultHeartbeatURL, bytes.NewBuffer(requestBody))
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.heartbeatURL, bytes.NewBuffer(requestBody))
if err != nil {
lastError = fmt.Errorf("创建请求失败: %w", err)
continue
@@ -140,9 +155,37 @@ func (c *Client) SendHeartbeat(ctx context.Context, request *models.HeartbeatReq
// 确保响应体被关闭
defer resp.Body.Close()
// 检查HTTP状态码
if resp.StatusCode != http.StatusOK {
lastError = fmt.Errorf("%w: 状态码 %d", ErrInvalidStatusCode, resp.StatusCode)
// 使用switch检查HTTP状态码
switch resp.StatusCode {
case http.StatusOK:
// 状态码为200继续处理响应
case http.StatusBadRequest:
lastError = fmt.Errorf("%w: 状态码 %d请求无效", ErrInvalidStatusCode, resp.StatusCode)
// 读取并丢弃响应体,避免连接泄漏
_, _ = io.Copy(io.Discard, resp.Body)
continue
case http.StatusUnauthorized:
lastError = fmt.Errorf("%w: 状态码 %d未授权", ErrInvalidStatusCode, resp.StatusCode)
// 读取并丢弃响应体,避免连接泄漏
_, _ = io.Copy(io.Discard, resp.Body)
continue
case http.StatusForbidden:
lastError = fmt.Errorf("%w: 状态码 %d禁止访问", ErrInvalidStatusCode, resp.StatusCode)
// 读取并丢弃响应体,避免连接泄漏
_, _ = io.Copy(io.Discard, resp.Body)
continue
case http.StatusNotFound:
lastError = fmt.Errorf("%w: 状态码 %d未找到资源", ErrInvalidStatusCode, resp.StatusCode)
// 读取并丢弃响应体,避免连接泄漏
_, _ = io.Copy(io.Discard, resp.Body)
continue
case http.StatusInternalServerError:
lastError = fmt.Errorf("%w: 状态码 %d服务器内部错误", ErrInvalidStatusCode, resp.StatusCode)
// 读取并丢弃响应体,避免连接泄漏
_, _ = io.Copy(io.Discard, resp.Body)
continue
default:
lastError = fmt.Errorf("%w: 状态码 %d未知错误", ErrInvalidStatusCode, resp.StatusCode)
// 读取并丢弃响应体,避免连接泄漏
_, _ = io.Copy(io.Discard, resp.Body)
continue

View File

@@ -1,105 +0,0 @@
package totp
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base32"
"encoding/binary"
"fmt"
"strings"
"sync"
"time"
)
// TOTPConfig TOTP配置
type TOTPConfig struct {
Secret string // TOTP密钥
Digits int // TOTP验证码长度
TimeStep time.Duration // TOTP时间步长
Algorithm string // TOTP算法
}
var (
defaultConfig = TOTPConfig{
Secret: "",
Digits: 6,
TimeStep: 30 * time.Second,
Algorithm: "SHA1",
}
mu sync.RWMutex
)
// SetTOTPSecret 设置TOTP密钥
func SetTOTPSecret(secret string) {
mu.Lock()
defer mu.Unlock()
defaultConfig.Secret = secret
}
// GetTOTPSecret 获取TOTP密钥
func GetTOTPSecret() string {
mu.RLock()
defer mu.RUnlock()
return defaultConfig.Secret
}
// GenerateTOTPCode 生成TOTP验证码
func GenerateTOTPCode() (string, error) {
mu.RLock()
config := defaultConfig
mu.RUnlock()
if config.Secret == "" {
return "", fmt.Errorf("TOTP密钥未设置")
}
// 确保密钥是Base32编码的
secret := strings.ToUpper(config.Secret)
secret = strings.ReplaceAll(secret, " ", "")
secretBytes, err := base32.StdEncoding.DecodeString(secret)
if err != nil {
return "", fmt.Errorf("解码TOTP密钥失败: %w", err)
}
// 获取当前时间戳并转换为TOTP时间计数器
timeCounter := uint64(time.Now().Unix()) / uint64(config.TimeStep.Seconds())
// 将时间计数器转换为字节数组
timeBytes := make([]byte, 8)
binary.BigEndian.PutUint64(timeBytes, timeCounter)
// 使用HMAC-SHA1计算TOTP值
h := hmac.New(sha1.New, secretBytes)
h.Write(timeBytes)
hash := h.Sum(nil)
// 根据RFC 6238我们使用哈希的最后一个字节的低4位作为偏移
offset := hash[len(hash)-1] & 0x0f
// 从哈希中提取4字节并转换为整数
binary := binary.BigEndian.Uint32(hash[offset : offset+4])
// 屏蔽最高位并获取指定位数的数字
totp := binary & 0x7fffffff % uint32(pow10(config.Digits))
// 将数字格式化为字符串,填充前导零
return fmt.Sprintf("%0*d", config.Digits, totp), nil
}
// ValidateTOTPCode 验证TOTP验证码
func ValidateTOTPCode(code string) bool {
generatedCode, err := GenerateTOTPCode()
if err != nil {
return false
}
return code == generatedCode
}
// pow10 计算10的n次方
func pow10(n int) int {
result := 1
for i := 0; i < n; i++ {
result *= 10
}
return result
}