Compare commits
10 Commits
25dbc87a73
...
13949e1ba8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13949e1ba8 | ||
|
|
9f050506df | ||
|
|
6001907e8a | ||
|
|
58b470e95f | ||
|
|
6abb488622 | ||
|
|
da0ec7e81a | ||
|
|
55abec4a72 | ||
|
|
f7ccf7d461 | ||
|
|
4f8a8a6ff2 | ||
|
|
34147b2f69 |
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
6
.idea/misc.xml
generated
6
.idea/misc.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_23" default="true" project-jdk-name="graalvm-jdk-23" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@@ -2,7 +2,6 @@
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/cmii-uav-watchdog-common.iml" filepath="$PROJECT_DIR$/cmii-uav-watchdog-common.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/cmii-uav-watchdog-project.iml" filepath="$PROJECT_DIR$/.idea/cmii-uav-watchdog-project.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
|
||||
26
README.md
26
README.md
@@ -0,0 +1,26 @@
|
||||
|
||||
# 测试方案
|
||||
|
||||
- 如果没有授权成功,停止微服务
|
||||
|
||||
---
|
||||
|
||||
## 核心功能
|
||||
- 授权主机信息
|
||||
- 未授权,无法运行,停止
|
||||
- 已授权,可以正常运行
|
||||
- watchdog稳定性测试
|
||||
- 没有的情况
|
||||
- 重启是否正常
|
||||
- 授权文件验证
|
||||
- 验证流程是否完整
|
||||
- 验证信息是否会被篡改
|
||||
|
||||
|
||||
## 一般功能
|
||||
- 磁盘数据信息
|
||||
- JVM参数是否生效
|
||||
|
||||
|
||||
---
|
||||
# 需要新增的功能接口
|
||||
|
||||
1
cmii-uav-watchdog-agent/cmd/host_info.go
Normal file
1
cmii-uav-watchdog-agent/cmd/host_info.go
Normal file
@@ -0,0 +1 @@
|
||||
package cmd
|
||||
118
cmii-uav-watchdog-agent/cmd/start_up.go
Normal file
118
cmii-uav-watchdog-agent/cmd/start_up.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
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")
|
||||
stopRequested bool
|
||||
currentCmd *exec.Cmd
|
||||
mu sync.Mutex
|
||||
)
|
||||
|
||||
func startBusinessProcess(programType, programPath string) *exec.Cmd {
|
||||
var cmd *exec.Cmd
|
||||
switch programType {
|
||||
case "java":
|
||||
cmd = exec.Command("java", "-jar", programPath)
|
||||
case "python":
|
||||
cmd = exec.Command("python", programPath)
|
||||
default:
|
||||
log.Fatalf("Unsupported business program type: %s", programType)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func main() {
|
||||
// 解析命令行参数
|
||||
flag.Parse()
|
||||
if *businessProgramType == "" || *businessProgramPath == "" {
|
||||
log.Fatal("Missing required flags: -business-program-type and -business-program-path must be specified")
|
||||
}
|
||||
|
||||
// 信号处理
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
for sig := range signalChan {
|
||||
log.Printf("Received signal: %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)
|
||||
}
|
||||
// 等待 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")
|
||||
currentCmd.Process.Kill()
|
||||
}
|
||||
})
|
||||
}
|
||||
mu.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
// 主循环
|
||||
for {
|
||||
mu.Lock()
|
||||
if stopRequested {
|
||||
mu.Unlock()
|
||||
log.Println("Shutting down due to stop request")
|
||||
os.Exit(0)
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
cmd := startBusinessProcess(*businessProgramType, *businessProgramPath)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
// 启动业务进程
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Printf("Failed to start business process: %v", err)
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
currentCmd = cmd
|
||||
mu.Unlock()
|
||||
|
||||
// 等待业务进程退出
|
||||
err := cmd.Wait()
|
||||
mu.Lock()
|
||||
currentCmd = nil
|
||||
mu.Unlock()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Business process exited with error: %v", err)
|
||||
} else {
|
||||
log.Println("Business process exited normally")
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
if stopRequested {
|
||||
mu.Unlock()
|
||||
log.Println("Shutting down due to stop request")
|
||||
os.Exit(0)
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
// 等待 5 秒后重启
|
||||
log.Println("Restarting business process in 5 seconds...")
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
148
cmii-uav-watchdog-agent/cmd/watchdog-agent.go
Normal file
148
cmii-uav-watchdog-agent/cmd/watchdog-agent.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"cmii-uav-watchdog-agent/host_info"
|
||||
"cmii-uav-watchdog-agent/rpc"
|
||||
"cmii-uav-watchdog-agent/totp"
|
||||
"cmii-uav-watchdog-common/models"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// 最大重试次数
|
||||
maxRetryCount = 5
|
||||
|
||||
// 默认心跳检测间隔
|
||||
defaultHeartbeatInterval = 30 * time.Second
|
||||
|
||||
// 检测失败后的等待间隔
|
||||
failWaitInterval = 5 * time.Second
|
||||
|
||||
// 环境变量名称
|
||||
appNameEnv = "APP_NAME"
|
||||
)
|
||||
|
||||
// 启动心跳检测
|
||||
func StartHeartbeatDetection() {
|
||||
log.Println("启动心跳检测任务...")
|
||||
|
||||
// 创建RPC客户端
|
||||
client := rpc.NewClient(nil)
|
||||
|
||||
// 监听终止信号
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
// 失败计数器
|
||||
failCount := 0
|
||||
|
||||
// 心跳检测循环
|
||||
for {
|
||||
select {
|
||||
case <-signalChan:
|
||||
log.Println("收到终止信号,停止心跳检测")
|
||||
return
|
||||
default:
|
||||
// 尝试发送心跳请求
|
||||
authorized, err := sendHeartbeat(client)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("心跳检测失败: %v", err)
|
||||
failCount++
|
||||
} else if !authorized {
|
||||
log.Println("未获得授权")
|
||||
failCount++
|
||||
} else {
|
||||
// 检测成功,重置失败计数
|
||||
failCount = 0
|
||||
log.Println("心跳检测成功,已获得授权")
|
||||
}
|
||||
|
||||
// 检查是否达到最大失败次数
|
||||
if failCount >= maxRetryCount {
|
||||
log.Printf("心跳检测连续失败 %d 次,发送终止信号", failCount)
|
||||
// 发送终止信号给start_up.go
|
||||
process, err := os.FindProcess(os.Getpid())
|
||||
if err == nil {
|
||||
process.Signal(syscall.SIGTERM)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 等待下一次检测
|
||||
if err != nil || !authorized {
|
||||
// 失败后等待较短时间
|
||||
time.Sleep(failWaitInterval)
|
||||
} else {
|
||||
// 成功后等待正常间隔
|
||||
time.Sleep(defaultHeartbeatInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 发送心跳请求
|
||||
func sendHeartbeat(client *rpc.Client) (bool, error) {
|
||||
// 1. 获取主机信息
|
||||
hostInfoData := services.GetAllInfo()
|
||||
hostInfo := models.HostInfo{
|
||||
SystemInfo: hostInfoData.SystemInfo,
|
||||
CPUInfo: hostInfoData.CPUInfo,
|
||||
DiskInfo: hostInfoData.DiskInfo,
|
||||
MemoryInfo: hostInfoData.MemoryInfo,
|
||||
NetInfo: hostInfoData.NetInfo,
|
||||
}
|
||||
|
||||
// 2. 获取应用名称
|
||||
appName := os.Getenv(appNameEnv)
|
||||
if appName == "" {
|
||||
appName = "unknown-app"
|
||||
log.Printf("警告: 环境变量 %s 未设置,使用默认值: %s", appNameEnv, appName)
|
||||
}
|
||||
|
||||
// 构建心跳请求
|
||||
request := &models.HeartbeatRequest{
|
||||
HostInfo: hostInfo,
|
||||
Timestamp: time.Now().Unix(),
|
||||
AppName: appName,
|
||||
}
|
||||
|
||||
// 3. 如果已有TOTP密钥,则生成TOTP验证码
|
||||
totpSecret := totp.GetTOTPSecret()
|
||||
if totpSecret != "" {
|
||||
totpCode, err := totp.GenerateTOTPCode()
|
||||
if err != nil {
|
||||
log.Printf("生成TOTP验证码失败: %v", err)
|
||||
} else {
|
||||
request.TOTPCode = totpCode
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 发送心跳请求
|
||||
response, err := client.SendHeartbeatWithRetry(request, 10*time.Second)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("发送心跳请求失败: %w", err)
|
||||
}
|
||||
|
||||
// 5. 处理响应
|
||||
if response.SecondTOTPSecret != "" {
|
||||
// 存储TOTP密钥
|
||||
totp.SetTOTPSecret(response.SecondTOTPSecret)
|
||||
log.Println("已更新TOTP密钥")
|
||||
}
|
||||
|
||||
// 6. 如果有TOTP验证码,进行验证
|
||||
if response.TOTPCode != "" && totpSecret != "" {
|
||||
if !totp.ValidateTOTPCode(response.TOTPCode) {
|
||||
log.Println("TOTP验证码验证失败")
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return response.Authorized, nil
|
||||
}
|
||||
@@ -1,3 +1,39 @@
|
||||
module cmii-uav-watchdog-agent
|
||||
|
||||
go 1.23
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
cmii-uav-watchdog-common v0.0.0
|
||||
github.com/gin-gonic/gin v1.10.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
|
||||
)
|
||||
|
||||
replace cmii-uav-watchdog-common => ../cmii-uav-watchdog-common
|
||||
|
||||
89
cmii-uav-watchdog-agent/go.sum
Normal file
89
cmii-uav-watchdog-agent/go.sum
Normal file
@@ -0,0 +1,89 @@
|
||||
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=
|
||||
163
cmii-uav-watchdog-agent/host_info/cpu_service.go
Normal file
163
cmii-uav-watchdog-agent/host_info/cpu_service.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"cmii-uav-watchdog-common/models"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewCPUInfo 创建一个默认的 CPUInfo
|
||||
func NewCPUInfo() models.CPUInfo {
|
||||
return models.CPUInfo{
|
||||
ModelName: "unknown",
|
||||
Cores: 0,
|
||||
Architecture: "unknown",
|
||||
UUID: "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 信息
|
||||
func GetCPUInfo() models.CPUInfo {
|
||||
cpuInfo := NewCPUInfo()
|
||||
|
||||
// 读取 /proc/cpuinfo
|
||||
file, err := os.Open("/proc/cpuinfo")
|
||||
if err != nil {
|
||||
fmt.Println("Error opening /proc/cpuinfo:", err)
|
||||
return cpuInfo // 返回默认值
|
||||
}
|
||||
defer func() {
|
||||
if err := file.Close(); err != nil {
|
||||
fmt.Println("Error closing /proc/cpuinfo:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
parseLine(scanner.Text(), &cpuInfo)
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
fmt.Println("Error reading /proc/cpuinfo:", err)
|
||||
return cpuInfo // 返回默认值
|
||||
}
|
||||
|
||||
// 获取 CPU 核心数
|
||||
cores, err := getCPUCores()
|
||||
if err != nil {
|
||||
fmt.Println("Error getting CPU cores:", err)
|
||||
} else {
|
||||
cpuInfo.Cores = cores
|
||||
}
|
||||
|
||||
// 获取 CPU UUID
|
||||
uuid, err := getCPUUUID()
|
||||
if err != nil {
|
||||
fmt.Println("Error getting CPU UUID:", err)
|
||||
} else {
|
||||
cpuInfo.UUID = uuid
|
||||
}
|
||||
|
||||
cpuInfo.Architecture = getCPUArchitecture()
|
||||
|
||||
return cpuInfo
|
||||
}
|
||||
|
||||
/*
|
||||
CPU模型名称: Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz
|
||||
核心数量: 4
|
||||
架构信息: x86_64
|
||||
|
||||
*/
|
||||
134
cmii-uav-watchdog-agent/host_info/disk_service.go
Normal file
134
cmii-uav-watchdog-agent/host_info/disk_service.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"cmii-uav-watchdog-common/models"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// GetDiskInfo retrieves disk information similar to df -Th.
|
||||
func GetDiskInfo() []models.DiskInfo {
|
||||
var diskInfos []models.DiskInfo
|
||||
|
||||
// Read /proc/mounts to get mounted filesystems
|
||||
mountsData, err := readFileWithWarning("/proc/mounts")
|
||||
if err != nil {
|
||||
return diskInfos
|
||||
}
|
||||
|
||||
mounts := strings.Split(string(mountsData), "\n")
|
||||
|
||||
// Read /proc/partitions to get physical disks
|
||||
partitionsData, err := readFileWithWarning("/proc/partitions")
|
||||
if err != nil {
|
||||
return diskInfos
|
||||
}
|
||||
|
||||
partitions := strings.Split(string(partitionsData), "\n")
|
||||
physicalDevices := make(map[string]uint64)
|
||||
|
||||
// Map physical devices to their sizes
|
||||
for _, line := range partitions {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 4 {
|
||||
continue
|
||||
}
|
||||
device := fields[3] // The device name is in the 4th column
|
||||
size, err := strconv.ParseUint(fields[2], 10, 64) // 读取大小
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
physicalDevices[device] = size * 1024 // 转换为字节
|
||||
}
|
||||
|
||||
for _, mount := range mounts {
|
||||
if mount == "" {
|
||||
continue
|
||||
}
|
||||
fields := strings.Fields(mount)
|
||||
if len(fields) < 3 {
|
||||
continue
|
||||
}
|
||||
logicalDevice := fields[0] // 逻辑分区设备名
|
||||
mountPoint := fields[1] // 挂载点
|
||||
fstype := fields[2] // 文件系统类型
|
||||
|
||||
// Skip NFS mounts
|
||||
if fstype == "nfs" {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip overlay mounts
|
||||
if fstype == "overlay" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 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)
|
||||
continue
|
||||
}
|
||||
|
||||
// Calculate total, used, and available space
|
||||
total := stat.Blocks * uint64(stat.Bsize)
|
||||
available := stat.Bavail * uint64(stat.Bsize)
|
||||
used := total - available
|
||||
|
||||
// Skip if size is 0
|
||||
if total == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Calculate percentage used
|
||||
usePercent := "0%"
|
||||
if total > 0 {
|
||||
usePercent = fmt.Sprintf("%.1f%%", float64(used)/float64(total)*100)
|
||||
}
|
||||
|
||||
// Determine the physical device and its size
|
||||
physicalDevice := ""
|
||||
physicalSize := uint64(0)
|
||||
|
||||
// Check if the logical device is a partition (e.g., /dev/sda1)
|
||||
if strings.HasPrefix(logicalDevice, "/dev/") {
|
||||
// Get the corresponding physical device (e.g., /dev/sda)
|
||||
physicalDevice = strings.TrimSuffix(logicalDevice, "1") // 假设逻辑分区是 /dev/sda1
|
||||
if size, exists := physicalDevices[physicalDevice]; exists {
|
||||
physicalSize = size
|
||||
}
|
||||
}
|
||||
|
||||
// If not found, use the logical device as the physical device
|
||||
if physicalDevice == "" || physicalSize == 0 {
|
||||
physicalDevice = logicalDevice
|
||||
if size, exists := physicalDevices[physicalDevice]; exists {
|
||||
physicalSize = size
|
||||
}
|
||||
}
|
||||
|
||||
diskInfos = append(diskInfos, models.DiskInfo{
|
||||
Device: logicalDevice, // 设置逻辑分区的设备名称
|
||||
Filesystem: logicalDevice, // 逻辑分区
|
||||
Type: fstype, // 文件系统类型
|
||||
Size: total, // 总大小
|
||||
Used: used, // 已用空间
|
||||
Available: available, // 可用空间
|
||||
UsePercent: usePercent, // 使用百分比
|
||||
MountPoint: mountPoint, // 挂载点
|
||||
PhysicalDevice: physicalDevice, // 物理设备名称
|
||||
PhysicalSize: physicalSize, // 物理盘大小
|
||||
})
|
||||
}
|
||||
|
||||
return diskInfos
|
||||
}
|
||||
|
||||
/*
|
||||
Device: sda, Total: 500107862016 bytes, Used: 250000000000 bytes, Available: 200000000000 bytes
|
||||
Device: sdb, Total: 400107862016 bytes, Used: 150000000000 bytes, Available: 250000000000 bytes
|
||||
Device: sdc, Total: 100107862016 bytes, Used: 50000000000 bytes, Available: 50000000000 bytes
|
||||
Device: unknown, Total: 0 bytes, Used: 0 bytes, Available: 0 bytes
|
||||
*/
|
||||
27
cmii-uav-watchdog-agent/host_info/host_info.go
Normal file
27
cmii-uav-watchdog-agent/host_info/host_info.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package services
|
||||
|
||||
import "cmii-uav-watchdog-common/models"
|
||||
|
||||
type Data struct {
|
||||
SystemInfo models.SystemInfo `json:"system_info"`
|
||||
CPUInfo models.CPUInfo `json:"cpu_info"`
|
||||
DiskInfo []models.DiskInfo `json:"disk_info"`
|
||||
MemoryInfo models.MemoryInfo `json:"memory_info"`
|
||||
NetInfo []models.NetworkInterfaceInfo `json:"net_info"`
|
||||
}
|
||||
|
||||
/*
|
||||
*/
|
||||
|
||||
func GetAllInfo() Data {
|
||||
|
||||
data := Data{
|
||||
SystemInfo: GetSystemInfo(),
|
||||
CPUInfo: GetCPUInfo(),
|
||||
DiskInfo: GetDiskInfo(),
|
||||
MemoryInfo: GetMemoryInfo(),
|
||||
NetInfo: GetNetworkInterfaces(),
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
58
cmii-uav-watchdog-agent/host_info/mboard_service.go
Normal file
58
cmii-uav-watchdog-agent/host_info/mboard_service.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"cmii-uav-watchdog-common/models"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DefaultMotherboardInfo provides a default value for MotherboardInfo.
|
||||
var DefaultMotherboardInfo = models.MotherboardInfo{
|
||||
Manufacturer: "unknown",
|
||||
Product: "unknown",
|
||||
Version: "unknown",
|
||||
Serial: "unknown",
|
||||
}
|
||||
|
||||
// readFileWithWarning attempts to read a file and logs a warning if it fails.
|
||||
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)
|
||||
return nil, err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// GetMotherboardInfo retrieves motherboard information from the system files.
|
||||
func GetMotherboardInfo() models.MotherboardInfo {
|
||||
info := DefaultMotherboardInfo // 初始化为默认值
|
||||
|
||||
// 文件路径与对应字段的映射
|
||||
paths := map[string]*string{
|
||||
"/sys/class/dmi/id/board_vendor": &info.Manufacturer,
|
||||
"/sys/class/dmi/id/board_name": &info.Product,
|
||||
"/sys/class/dmi/id/board_version": &info.Version,
|
||||
"/sys/class/dmi/id/board_serial": &info.Serial,
|
||||
}
|
||||
|
||||
// 遍历路径并更新信息
|
||||
for path, field := range paths {
|
||||
if value, err := readFileWithWarning(path); err == nil {
|
||||
*field = strings.TrimSpace(string(value))
|
||||
}
|
||||
}
|
||||
|
||||
return info // 返回最终的主板信息
|
||||
}
|
||||
|
||||
/*
|
||||
Motherboard Information:
|
||||
Manufacturer: ASUSTeK COMPUTER INC.
|
||||
Product: ROG STRIX B450-F GAMING
|
||||
Version: Rev 1.xx
|
||||
Serial: 123456789012345678
|
||||
|
||||
|
||||
*/
|
||||
73
cmii-uav-watchdog-agent/host_info/mem_service.go
Normal file
73
cmii-uav-watchdog-agent/host_info/mem_service.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"cmii-uav-watchdog-common/models"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NewMemoryInfo creates a new MemoryInfo instance with default values.
|
||||
func NewMemoryInfo() models.MemoryInfo {
|
||||
return models.MemoryInfo{
|
||||
Total: 0,
|
||||
Free: 0,
|
||||
Available: 0,
|
||||
Used: 0,
|
||||
Buffers: 0,
|
||||
Cached: 0,
|
||||
Shared: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// GetMemoryInfo reads memory information from /proc/meminfo.
|
||||
func GetMemoryInfo() models.MemoryInfo {
|
||||
memInfo := NewMemoryInfo()
|
||||
data, err := os.ReadFile("/proc/meminfo")
|
||||
if err != nil {
|
||||
fmt.Println("Error reading /proc/meminfo:", err)
|
||||
return memInfo
|
||||
}
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) < 2 {
|
||||
continue
|
||||
}
|
||||
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)
|
||||
continue
|
||||
}
|
||||
|
||||
switch key {
|
||||
case "MemTotal:":
|
||||
memInfo.Total = value
|
||||
case "MemFree:":
|
||||
memInfo.Free = value
|
||||
case "MemAvailable:":
|
||||
memInfo.Available = value
|
||||
case "Buffers:":
|
||||
memInfo.Buffers = value // 存储 Buffers 值
|
||||
case "Cached:":
|
||||
memInfo.Cached = value // 存储 Cached 值
|
||||
case "Shmem:":
|
||||
memInfo.Shared = value // 存储 Shared 值
|
||||
}
|
||||
}
|
||||
|
||||
// 计算已用内存
|
||||
memInfo.Used = memInfo.Total - memInfo.Free - memInfo.Buffers - memInfo.Cached - memInfo.Shared
|
||||
|
||||
return memInfo
|
||||
}
|
||||
|
||||
/*
|
||||
Total Memory: 16384228 kB
|
||||
Free Memory: 1234567 kB
|
||||
Available Memory: 2345678 kB
|
||||
|
||||
*/
|
||||
77
cmii-uav-watchdog-agent/host_info/net_service.go
Normal file
77
cmii-uav-watchdog-agent/host_info/net_service.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"cmii-uav-watchdog-common/models"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// getMACAddress 获取指定接口的 MAC 地址
|
||||
func getMACAddress(iface net.Interface) (string, error) {
|
||||
_, err := iface.Addrs()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 获取 MAC 地址
|
||||
return iface.HardwareAddr.String(), nil
|
||||
}
|
||||
|
||||
// getNetworkInterfaces 获取网卡信息
|
||||
func GetNetworkInterfaces() []models.NetworkInterfaceInfo {
|
||||
var interfaces []models.NetworkInterfaceInfo
|
||||
|
||||
// 获取所有网络接口
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
fmt.Println("Error getting network interfaces:", err)
|
||||
return []models.NetworkInterfaceInfo{{Name: "unknown", MACAddress: "00:00:00:00:00:00", IPAddresses: []string{}}}
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
// 过滤掉 Docker 和 Kubernetes 网络插件创建的网卡
|
||||
if strings.HasPrefix(iface.Name, "docker") || strings.HasPrefix(iface.Name, "cali") ||
|
||||
strings.HasPrefix(iface.Name, "flannel") || strings.HasPrefix(iface.Name, "br") ||
|
||||
strings.HasPrefix(iface.Name, "lo") || strings.HasPrefix(iface.Name, "tunl0") {
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取 MAC 地址
|
||||
macAddress, err := getMACAddress(iface)
|
||||
if err != nil {
|
||||
fmt.Println("Error getting MAC address for", iface.Name, ":", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取有效的 IP 地址
|
||||
var ipAddresses []string
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
fmt.Println("Error getting addresses for", iface.Name, ":", err)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
if ipNet, ok := addr.(*net.IPNet); ok && ipNet.IP.To4() != nil {
|
||||
ipAddresses = append(ipAddresses, ipNet.IP.String())
|
||||
}
|
||||
}
|
||||
|
||||
// 仅在有 IP 地址时才添加到接口列表
|
||||
if len(ipAddresses) > 0 {
|
||||
interfaces = append(interfaces, models.NetworkInterfaceInfo{
|
||||
Name: iface.Name,
|
||||
MACAddress: macAddress,
|
||||
IPAddresses: ipAddresses,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到任何有效的接口,返回默认值
|
||||
if len(interfaces) == 0 {
|
||||
return []models.NetworkInterfaceInfo{{Name: "unknown", MACAddress: "00:00:00:00:00:00", IPAddresses: []string{}}}
|
||||
}
|
||||
|
||||
return interfaces
|
||||
}
|
||||
123
cmii-uav-watchdog-agent/host_info/system_service.go
Normal file
123
cmii-uav-watchdog-agent/host_info/system_service.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"cmii-uav-watchdog-common/models"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 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",
|
||||
}
|
||||
}
|
||||
|
||||
// NewSystemInfo 创建并返回一个 SystemInfo 实例,设置默认值
|
||||
func NewSystemInfo() models.SystemInfo {
|
||||
return models.SystemInfo{
|
||||
MachineID: "unknown",
|
||||
OS: NewOSInfo(),
|
||||
KernelVersion: "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
// GetSystemInfo 获取 Linux 系统信息
|
||||
func GetSystemInfo() models.SystemInfo {
|
||||
sysInfo := NewSystemInfo() // 初始化结构体
|
||||
|
||||
// 获取机器 ID
|
||||
machineID := getMachineID()
|
||||
if machineID != "" {
|
||||
sysInfo.MachineID = machineID
|
||||
}
|
||||
// 获取操作系统版本
|
||||
sysInfo.OS = getOSVersion()
|
||||
|
||||
// 获取内核版本
|
||||
kernelVersion := getKernelVersion()
|
||||
if kernelVersion != "" {
|
||||
sysInfo.KernelVersion = kernelVersion
|
||||
}
|
||||
|
||||
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)
|
||||
return NewOSInfo() // 返回默认值
|
||||
}
|
||||
|
||||
osInfo := NewOSInfo() // 初始化 OSInfo 结构体
|
||||
lines := strings.Split(string(data), "\n")
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
key := strings.Trim(parts[0], `"`)
|
||||
value := strings.Trim(parts[1], `"`)
|
||||
|
||||
// 解析不同的字段
|
||||
switch key {
|
||||
case "NAME":
|
||||
osInfo.Name = value
|
||||
case "VERSION":
|
||||
osInfo.Version = value
|
||||
case "ID":
|
||||
osInfo.ID = value
|
||||
case "ID_LIKE":
|
||||
osInfo.IDLike = value
|
||||
case "VERSION_ID":
|
||||
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
|
||||
func getMachineID() string {
|
||||
data, err := os.ReadFile("/etc/machine-id")
|
||||
if err != nil {
|
||||
log.Printf("Error reading /etc/machine-id: %v", err)
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(data)) // 去除多余空格
|
||||
}
|
||||
|
||||
// getKernelVersion 获取内核版本
|
||||
func getKernelVersion() string {
|
||||
data, err := os.ReadFile("/proc/version")
|
||||
if err != nil {
|
||||
log.Printf("Error reading /proc/version: %v", err)
|
||||
return ""
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
@@ -1 +1,69 @@
|
||||
package cmii_uav_watchdog_agent
|
||||
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...")
|
||||
}
|
||||
|
||||
197
cmii-uav-watchdog-agent/rpc/rpc.go
Normal file
197
cmii-uav-watchdog-agent/rpc/rpc.go
Normal file
@@ -0,0 +1,197 @@
|
||||
package rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"cmii-uav-watchdog-common/models"
|
||||
)
|
||||
|
||||
// 错误类型常量定义
|
||||
var (
|
||||
ErrRequestFailed = errors.New("请求失败")
|
||||
ErrResponseParsing = errors.New("响应解析失败")
|
||||
ErrInvalidStatusCode = errors.New("无效的状态码")
|
||||
ErrTimeout = errors.New("请求超时")
|
||||
ErrCancelled = errors.New("请求被取消")
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultHeartbeatURL = "http://cmii-uav-watchdog/heartbeat"
|
||||
)
|
||||
|
||||
// ClientOptions HTTP客户端配置选项
|
||||
type ClientOptions struct {
|
||||
Timeout time.Duration // 请求超时时间
|
||||
RetryCount int // 重试次数
|
||||
RetryWaitTime time.Duration // 重试等待时间
|
||||
MaxIdleConns int // 最大空闲连接数
|
||||
IdleConnTimeout time.Duration // 空闲连接超时时间
|
||||
}
|
||||
|
||||
// DefaultClientOptions 返回默认的客户端配置
|
||||
func DefaultClientOptions() *ClientOptions {
|
||||
return &ClientOptions{
|
||||
Timeout: 10 * time.Second,
|
||||
RetryCount: 3,
|
||||
RetryWaitTime: 1 * time.Second,
|
||||
MaxIdleConns: 10,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
// Client HTTP客户端封装
|
||||
type Client struct {
|
||||
httpClient *http.Client
|
||||
options *ClientOptions
|
||||
}
|
||||
|
||||
// NewClient 创建一个新的HTTP客户端
|
||||
// 参数:
|
||||
// - options: 客户端配置选项,如果为nil则使用默认配置
|
||||
//
|
||||
// 返回:
|
||||
// - *Client: HTTP客户端实例
|
||||
func NewClient(options *ClientOptions) *Client {
|
||||
if options == nil {
|
||||
options = DefaultClientOptions()
|
||||
}
|
||||
|
||||
// 创建自定义的Transport
|
||||
transport := &http.Transport{
|
||||
MaxIdleConns: options.MaxIdleConns,
|
||||
IdleConnTimeout: options.IdleConnTimeout,
|
||||
}
|
||||
|
||||
// 创建HTTP客户端
|
||||
httpClient := &http.Client{
|
||||
Timeout: options.Timeout,
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
return &Client{
|
||||
httpClient: httpClient,
|
||||
options: options,
|
||||
}
|
||||
}
|
||||
|
||||
// SendHeartbeat 发送心跳请求并处理响应
|
||||
// 参数:
|
||||
// - ctx: 上下文,用于取消请求
|
||||
// - url: 心跳请求的URL地址
|
||||
// - request: 心跳请求数据
|
||||
//
|
||||
// 返回:
|
||||
// - *models.HeartbeatResponse: 心跳响应
|
||||
// - error: 错误信息
|
||||
func (c *Client) SendHeartbeat(ctx context.Context, request *models.HeartbeatRequest) (*models.HeartbeatResponse, error) {
|
||||
// 将请求结构体序列化为JSON
|
||||
requestBody, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("序列化请求失败: %w", err)
|
||||
}
|
||||
|
||||
// 重试逻辑
|
||||
var resp *http.Response
|
||||
var responseBody []byte
|
||||
var lastError error
|
||||
|
||||
for attempt := 0; attempt <= c.options.RetryCount; attempt++ {
|
||||
// 如果不是第一次尝试,则等待一段时间
|
||||
if attempt > 0 {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, fmt.Errorf("%w: %v", ErrCancelled, ctx.Err())
|
||||
case <-time.After(c.options.RetryWaitTime):
|
||||
// 继续下一次尝试
|
||||
}
|
||||
}
|
||||
|
||||
// 创建HTTP请求
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, DefaultHeartbeatURL, bytes.NewBuffer(requestBody))
|
||||
if err != nil {
|
||||
lastError = fmt.Errorf("创建请求失败: %w", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", "cmii-uav-watchdog-agent")
|
||||
|
||||
// 发送请求
|
||||
resp, err = c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
lastError = fmt.Errorf("%w: %v", ErrTimeout, err)
|
||||
} else if ctx.Err() == context.Canceled {
|
||||
lastError = fmt.Errorf("%w: %v", ErrCancelled, err)
|
||||
} else {
|
||||
lastError = fmt.Errorf("%w: %v", ErrRequestFailed, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// 确保响应体被关闭
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 检查HTTP状态码
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
lastError = fmt.Errorf("%w: 状态码 %d", ErrInvalidStatusCode, resp.StatusCode)
|
||||
// 读取并丢弃响应体,避免连接泄漏
|
||||
_, _ = io.Copy(io.Discard, resp.Body)
|
||||
continue
|
||||
}
|
||||
|
||||
// 读取响应体
|
||||
responseBody, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
lastError = fmt.Errorf("读取响应体失败: %w", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 成功获取响应,跳出重试循环
|
||||
lastError = nil
|
||||
break
|
||||
}
|
||||
|
||||
// 如果最后依然有错误,返回错误
|
||||
if lastError != nil {
|
||||
return nil, lastError
|
||||
}
|
||||
|
||||
// 如果没有响应体,返回错误
|
||||
if responseBody == nil {
|
||||
return nil, fmt.Errorf("%w: 没有响应体", ErrResponseParsing)
|
||||
}
|
||||
|
||||
// 解析响应JSON
|
||||
var heartbeatResponse models.HeartbeatResponse
|
||||
if err := json.Unmarshal(responseBody, &heartbeatResponse); err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrResponseParsing, err)
|
||||
}
|
||||
|
||||
return &heartbeatResponse, nil
|
||||
}
|
||||
|
||||
// SendHeartbeatWithRetry 发送心跳请求并自动处理超时和重试
|
||||
// 参数:
|
||||
// - url: 心跳请求的URL地址
|
||||
// - request: 心跳请求数据
|
||||
// - timeout: 整体操作超时时间
|
||||
//
|
||||
// 返回:
|
||||
// - *models.HeartbeatResponse: 心跳响应
|
||||
// - error: 错误信息
|
||||
func (c *Client) SendHeartbeatWithRetry(request *models.HeartbeatRequest, timeout time.Duration) (*models.HeartbeatResponse, error) {
|
||||
// 创建带超时的上下文
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
return c.SendHeartbeat(ctx, request)
|
||||
}
|
||||
105
cmii-uav-watchdog-agent/totp/auth.go
Normal file
105
cmii-uav-watchdog-agent/totp/auth.go
Normal file
@@ -0,0 +1,105 @@
|
||||
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
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$/cmii-uav-watchdog-common" />
|
||||
|
||||
28
cmii-uav-watchdog-common/models/between_project_model.go
Normal file
28
cmii-uav-watchdog-common/models/between_project_model.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// AuthorizationFile 授权文件模型
|
||||
type AuthorizationFile struct {
|
||||
EncryptedHosts []string `json:"encrypted_hosts"` // 加密后的主机信息列表
|
||||
TOTPCode string `json:"totp_code"` // TOTP验证码
|
||||
CurrentTime time.Time `json:"current_time"` // 当前系统时间
|
||||
FirstAuthTime time.Time `json:"first_auth_time"` // 初次授权时间
|
||||
TimeOffset int64 `json:"time_offset"` // 授权时间偏移
|
||||
}
|
||||
|
||||
// AuthorizationCode 授权码模型
|
||||
type AuthorizationCode struct {
|
||||
TOTPCode string `json:"totp_code"` // TOTP验证码
|
||||
CurrentTime time.Time `json:"current_time"` // 当前系统时间
|
||||
EncryptedHosts []string `json:"encrypted_hosts"` // 授权主机的加密字符串列表
|
||||
}
|
||||
|
||||
// AuthorizationStorage 授权存储信息
|
||||
type AuthorizationStorage struct {
|
||||
EncryptedCode string `json:"encrypted_code"` // 加密后的授权码
|
||||
FirstAuthTime time.Time `json:"first_auth_time"` // 初次授权时间
|
||||
TimeOffset int64 `json:"time_offset"` // 授权时间偏移
|
||||
AuthorizedHosts []string `json:"authorized_hosts"` // 已授权主机列表
|
||||
SecondTOTPSecret string `json:"second_totp_secret"` // 第二级的totp密钥
|
||||
}
|
||||
@@ -1,12 +1,80 @@
|
||||
package models
|
||||
|
||||
// CPUInfo 结构体用于存储 CPU 信息
|
||||
type CPUInfo struct {
|
||||
ModelName string `json:"model_name"`
|
||||
Cores int `json:"cores"`
|
||||
Architecture string `json:"architecture"`
|
||||
UUID string `json:"uuid"`
|
||||
}
|
||||
|
||||
// MemoryInfo holds the memory information.
|
||||
type MemoryInfo struct {
|
||||
Total uint64 `json:"total"`
|
||||
Free uint64 `json:"free"`
|
||||
Available uint64 `json:"available"`
|
||||
Used uint64 `json:"used"` // 已用内存
|
||||
Buffers uint64 `json:"buffers"` // 缓冲区内存
|
||||
Cached uint64 `json:"cached"` // 缓存内存
|
||||
Shared uint64 `json:"shared"` // 共享内存
|
||||
}
|
||||
|
||||
// DiskInfo holds the disk information similar to df -Th output.
|
||||
type DiskInfo struct {
|
||||
Device string `json:"device"` // 逻辑分区设备名称
|
||||
Filesystem string `json:"filesystem"` // 逻辑分区
|
||||
Type string `json:"type"` // 文件系统类型
|
||||
Size uint64 `json:"size"` // 总大小
|
||||
Used uint64 `json:"used"` // 已用空间
|
||||
Available uint64 `json:"available"` // 可用空间
|
||||
UsePercent string `json:"use_percent"` // 使用百分比
|
||||
MountPoint string `json:"mount_point"`
|
||||
PhysicalDevice string `json:"physical_device"` // 物理设备名称
|
||||
PhysicalSize uint64 `json:"physical_size"` // 物理盘大小
|
||||
}
|
||||
|
||||
// NetworkInterfaceInfo 结构体用于存储网卡信息
|
||||
type NetworkInterfaceInfo struct {
|
||||
Name string `json:"name"`
|
||||
MACAddress string `json:"mac_address"`
|
||||
IPAddresses []string `json:"ip_addresses"`
|
||||
}
|
||||
|
||||
// MotherboardInfo holds the motherboard information.
|
||||
type MotherboardInfo struct {
|
||||
Manufacturer string `json:"manufacturer"`
|
||||
Product string `json:"product"`
|
||||
Version string `json:"version"`
|
||||
Serial string `json:"serial"`
|
||||
}
|
||||
|
||||
type SystemInfo struct {
|
||||
MachineID string `json:"machine_id"` // 唯一标识符
|
||||
OS OSInfo `json:"os"` // 操作系统
|
||||
KernelVersion string `json:"kernel_version"` // 内核版本
|
||||
}
|
||||
|
||||
type OSInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
ID string `json:"id"`
|
||||
IDLike string `json:"id_like"`
|
||||
VersionID string `json:"version_id"`
|
||||
PrettyName string `json:"pretty_name"`
|
||||
HomeURL string `json:"home_url"`
|
||||
SupportURL string `json:"support_url"`
|
||||
BugReportURL string `json:"bug_report_url"`
|
||||
PrivacyURL string `json:"privacy_url"`
|
||||
}
|
||||
|
||||
// HostInfo 主机信息模型
|
||||
type HostInfo struct {
|
||||
UUID string `json:"uuid"` // 主机UUID
|
||||
CPU string `json:"cpu"` // CPU信息
|
||||
Motherboard string `json:"motherboard"` // 主板信息
|
||||
MAC string `json:"mac"` // MAC地址
|
||||
Disk string `json:"disk"` // 硬盘信息
|
||||
SystemInfo SystemInfo `json:"system_info"`
|
||||
CPUInfo CPUInfo `json:"cpu_info"`
|
||||
DiskInfo []DiskInfo `json:"disk_info"`
|
||||
MemoryInfo MemoryInfo `json:"memory_info"`
|
||||
NetInfo []NetworkInterfaceInfo `json:"net_info"`
|
||||
MotherboardInfo MotherboardInfo `json:"motherboard_info"`
|
||||
}
|
||||
|
||||
// HeartbeatRequest 心跳请求
|
||||
|
||||
@@ -77,6 +77,14 @@ type ValidateOpts struct {
|
||||
Encoder otp.Encoder
|
||||
}
|
||||
|
||||
func (opts *ValidateOpts) ConvertToValidateOpts(generateOpts GenerateOpts) {
|
||||
opts.Period = generateOpts.Period
|
||||
opts.Skew = 1
|
||||
opts.Digits = generateOpts.Digits
|
||||
opts.Algorithm = generateOpts.Algorithm
|
||||
opts.Encoder = otp.EncoderDefault
|
||||
}
|
||||
|
||||
// GenerateCodeCustom takes a timepoint and produces a passcode using a
|
||||
// secret and the provided opts. (Under the hood, this is making an adapted
|
||||
// call to hcmii-uav-watchdog-otp.GenerateCodeCustom)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
models2 "cmii-uav-watchdog-common/models"
|
||||
"cmii-uav-watchdog/models"
|
||||
"cmii-uav-watchdog/services"
|
||||
"net/http"
|
||||
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -32,7 +33,7 @@ func (ac *AuthController) GenerateAuthFile(c *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "生成授权文件成功",
|
||||
@@ -42,7 +43,7 @@ func (ac *AuthController) GenerateAuthFile(c *gin.Context) {
|
||||
|
||||
// ReceiveAuthCode 接收授权码
|
||||
func (ac *AuthController) ReceiveAuthCode(c *gin.Context) {
|
||||
var authCode models.AuthorizationCode
|
||||
var authCode models2.AuthorizationCode
|
||||
if err := c.ShouldBindJSON(&authCode); err != nil {
|
||||
c.JSON(http.StatusBadRequest, models.Response{
|
||||
Code: 400,
|
||||
@@ -51,7 +52,7 @@ func (ac *AuthController) ReceiveAuthCode(c *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 处理授权码
|
||||
err := ac.authService.ProcessAuthorizationCode(authCode)
|
||||
if err != nil {
|
||||
@@ -62,7 +63,7 @@ func (ac *AuthController) ReceiveAuthCode(c *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "处理授权码成功",
|
||||
@@ -74,7 +75,7 @@ func (ac *AuthController) ReceiveAuthCode(c *gin.Context) {
|
||||
func (ac *AuthController) NotifyAuthInfo(c *gin.Context) {
|
||||
// 获取授权信息
|
||||
authInfo := ac.authService.GetAuthorizationInfo()
|
||||
|
||||
|
||||
c.JSON(http.StatusOK, models.Response{
|
||||
Code: 200,
|
||||
Message: "获取授权信息成功",
|
||||
|
||||
@@ -10,8 +10,19 @@ import (
|
||||
|
||||
func main() {
|
||||
|
||||
// 初始化授权服务
|
||||
// 初始化配置信息
|
||||
err := config.LoadConfig("./config/config.yaml")
|
||||
if err != nil {
|
||||
log.Fatalf("加载配置文件失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 初始化授权服务(使用单例模式)
|
||||
authService := services.NewAuthService()
|
||||
if authService == nil {
|
||||
log.Fatalf("初始化授权服务失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 启动授权码检测定时任务
|
||||
go func() {
|
||||
|
||||
@@ -3,77 +3,115 @@ package services
|
||||
import (
|
||||
models2 "cmii-uav-watchdog-common/models"
|
||||
"cmii-uav-watchdog/config"
|
||||
"cmii-uav-watchdog/models"
|
||||
"cmii-uav-watchdog/utils"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 单例相关变量
|
||||
var (
|
||||
authServiceInstance *AuthService
|
||||
authServiceOnce sync.Once
|
||||
authServiceMutex sync.Mutex
|
||||
)
|
||||
|
||||
// AuthService 授权服务
|
||||
type AuthService struct {
|
||||
mu sync.RWMutex
|
||||
hostInfoSet map[string]models2.HostInfo // 主机信息集合
|
||||
authorizationInfo models.AuthorizationStorage // 授权信息
|
||||
hostInfoSet map[string]models2.HostInfo // 主机信息集合
|
||||
authorizationInfo models2.AuthorizationStorage // 授权信息
|
||||
totpService *TOTPService
|
||||
initialized bool
|
||||
}
|
||||
|
||||
// NewAuthService 创建授权服务
|
||||
// NewAuthService 创建授权服务(单例模式)
|
||||
func NewAuthService() *AuthService {
|
||||
service := &AuthService{
|
||||
hostInfoSet: make(map[string]models2.HostInfo),
|
||||
totpService: NewTOTPService(),
|
||||
initialized: false,
|
||||
}
|
||||
|
||||
// 尝试从本地加载授权信息
|
||||
service.loadAuthorizationInfo()
|
||||
|
||||
return service
|
||||
// 使用sync.Once确保初始化逻辑只执行一次
|
||||
authServiceOnce.Do(func() {
|
||||
authServiceMutex.Lock()
|
||||
defer authServiceMutex.Unlock()
|
||||
|
||||
// 如果实例已存在,直接返回
|
||||
if authServiceInstance != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 创建新实例
|
||||
service := &AuthService{
|
||||
hostInfoSet: make(map[string]models2.HostInfo),
|
||||
totpService: NewTOTPService(),
|
||||
initialized: false,
|
||||
}
|
||||
|
||||
// 尝试从本地加载授权信息
|
||||
service.loadAuthorizationInfo()
|
||||
|
||||
// 判断 项目级别的 TOTP密钥是否为空
|
||||
// 若为空 则生成一个 二级TOTP密钥 然后持久化写入到授权文件中
|
||||
if service.authorizationInfo.SecondTOTPSecret == "" {
|
||||
secondTOTPSecret, err := service.totpService.GenerateTierTwoTOTPSecret()
|
||||
if err != nil {
|
||||
log.Printf("生成二级TOTP密钥失败: %v", err)
|
||||
return
|
||||
}
|
||||
service.authorizationInfo.SecondTOTPSecret = secondTOTPSecret
|
||||
|
||||
// 持久化写入到授权文件中
|
||||
err = service.saveAuthorizationInfo()
|
||||
if err != nil {
|
||||
log.Printf("持久化写入授权文件失败: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 设置全局实例
|
||||
authServiceInstance = service
|
||||
})
|
||||
|
||||
return authServiceInstance
|
||||
}
|
||||
|
||||
// AddHostInfo 添加主机信息
|
||||
func (as *AuthService) AddHostInfo(hostInfo models2.HostInfo) {
|
||||
as.mu.Lock()
|
||||
defer as.mu.Unlock()
|
||||
|
||||
|
||||
hostKey := utils.GenerateHostKey(hostInfo)
|
||||
as.hostInfoSet[hostKey] = hostInfo
|
||||
}
|
||||
|
||||
// GenerateAuthorizationFile 生成授权文件
|
||||
func (as *AuthService) GenerateAuthorizationFile() (*models.AuthorizationFile, error) {
|
||||
func (as *AuthService) GenerateAuthorizationFile() (*models2.AuthorizationFile, error) {
|
||||
as.mu.RLock()
|
||||
defer as.mu.RUnlock()
|
||||
|
||||
|
||||
// 检查是否有主机信息
|
||||
if len(as.hostInfoSet) == 0 {
|
||||
return nil, errors.New("没有可用的主机信息")
|
||||
}
|
||||
|
||||
|
||||
// 生成TOTP验证码
|
||||
totpCode, err := as.totpService.GenerateTOTP()
|
||||
totpCode, err := as.totpService.GenerateTierTwoTOTPCode(as.authorizationInfo.SecondTOTPSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
// 获取当前时间
|
||||
now := time.Now()
|
||||
|
||||
|
||||
// 准备初次授权时间
|
||||
firstAuthTime := now
|
||||
if as.initialized {
|
||||
firstAuthTime = as.authorizationInfo.FirstAuthTime
|
||||
}
|
||||
|
||||
|
||||
// 计算时间偏移
|
||||
timeOffset := now.Unix() - firstAuthTime.Unix()
|
||||
|
||||
|
||||
// 加密主机信息
|
||||
encryptedHosts := make([]string, 0)
|
||||
for _, hostInfo := range as.hostInfoSet {
|
||||
@@ -83,67 +121,67 @@ func (as *AuthService) GenerateAuthorizationFile() (*models.AuthorizationFile, e
|
||||
}
|
||||
encryptedHosts = append(encryptedHosts, encrypted)
|
||||
}
|
||||
|
||||
|
||||
// 创建授权文件
|
||||
authFile := &models.AuthorizationFile{
|
||||
authFile := &models2.AuthorizationFile{
|
||||
EncryptedHosts: encryptedHosts,
|
||||
TOTPCode: totpCode,
|
||||
CurrentTime: now,
|
||||
FirstAuthTime: firstAuthTime,
|
||||
TimeOffset: timeOffset,
|
||||
}
|
||||
|
||||
|
||||
return authFile, nil
|
||||
}
|
||||
|
||||
// ProcessAuthorizationCode 处理授权码
|
||||
func (as *AuthService) ProcessAuthorizationCode(code models.AuthorizationCode) error {
|
||||
func (as *AuthService) ProcessAuthorizationCode(code models2.AuthorizationCode) error {
|
||||
as.mu.Lock()
|
||||
defer as.mu.Unlock()
|
||||
|
||||
|
||||
// 验证TOTP
|
||||
if err := as.totpService.VerifyTOTP(code.TOTPCode); err != nil {
|
||||
if !as.totpService.VerifyTierTwoTOTPCode(code.TOTPCode, as.authorizationInfo.SecondTOTPSecret) {
|
||||
return errors.New("无效的授权码: TOTP验证失败")
|
||||
}
|
||||
|
||||
|
||||
// 获取当前时间
|
||||
now := time.Now()
|
||||
|
||||
|
||||
// 准备初次授权时间
|
||||
firstAuthTime := now
|
||||
if as.initialized {
|
||||
firstAuthTime = as.authorizationInfo.FirstAuthTime
|
||||
}
|
||||
|
||||
|
||||
// 计算授权时间偏移
|
||||
timeOffset := now.Unix() - code.CurrentTime.Unix()
|
||||
|
||||
|
||||
// 加密授权码
|
||||
authCodeJSON, err := json.Marshal(code)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
encryptedCode, err := utils.Encrypt(string(authCodeJSON), config.GetConfig().Auth.Secret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// 保存授权信息
|
||||
as.authorizationInfo = models.AuthorizationStorage{
|
||||
as.authorizationInfo = models2.AuthorizationStorage{
|
||||
EncryptedCode: encryptedCode,
|
||||
FirstAuthTime: firstAuthTime,
|
||||
TimeOffset: timeOffset,
|
||||
AuthorizedHosts: code.EncryptedHosts,
|
||||
}
|
||||
|
||||
|
||||
// 保存到文件
|
||||
if err := as.saveAuthorizationInfo(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
as.initialized = true
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -151,24 +189,24 @@ func (as *AuthService) ProcessAuthorizationCode(code models.AuthorizationCode) e
|
||||
func (as *AuthService) IsHostAuthorized(hostInfo models2.HostInfo) bool {
|
||||
as.mu.RLock()
|
||||
defer as.mu.RUnlock()
|
||||
|
||||
|
||||
if !as.initialized {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// 加密主机信息
|
||||
encrypted, err := utils.EncryptHostInfo(hostInfo)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// 检查是否在已授权列表中
|
||||
for _, host := range as.authorizationInfo.AuthorizedHosts {
|
||||
if host == encrypted {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -176,24 +214,24 @@ func (as *AuthService) IsHostAuthorized(hostInfo models2.HostInfo) bool {
|
||||
func (as *AuthService) VerifyAuthorizationTime() {
|
||||
as.mu.RLock()
|
||||
defer as.mu.RUnlock()
|
||||
|
||||
|
||||
if !as.initialized {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
// 获取当前时间和存储的初次授权时间
|
||||
now := time.Now()
|
||||
storedOffset := as.authorizationInfo.TimeOffset
|
||||
|
||||
|
||||
// 计算实际时间偏移
|
||||
actualOffset := now.Unix() - as.authorizationInfo.FirstAuthTime.Unix()
|
||||
|
||||
|
||||
// 计算偏差
|
||||
offsetDiff := actualOffset - storedOffset
|
||||
|
||||
|
||||
// 获取允许的时间偏移
|
||||
allowedOffset := config.GetConfig().Auth.TimeOffsetAllowed
|
||||
|
||||
|
||||
// 检查偏差是否超过允许范围
|
||||
if offsetDiff > allowedOffset || offsetDiff < -allowedOffset {
|
||||
log.Printf("检测到时间篡改! 存储偏移: %d, 实际偏移: %d, 差值: %d",
|
||||
@@ -206,14 +244,14 @@ func (as *AuthService) VerifyAuthorizationTime() {
|
||||
func (as *AuthService) GetAuthorizationInfo() interface{} {
|
||||
as.mu.RLock()
|
||||
defer as.mu.RUnlock()
|
||||
|
||||
|
||||
if !as.initialized {
|
||||
return map[string]interface{}{
|
||||
"authorized": false,
|
||||
"message": "未授权",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return map[string]interface{}{
|
||||
"authorized": true,
|
||||
"authorized_hosts": len(as.authorizationInfo.AuthorizedHosts),
|
||||
@@ -227,31 +265,31 @@ func (as *AuthService) saveAuthorizationInfo() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(config.GetConfig().Auth.AuthFilePath, data, 0600)
|
||||
|
||||
return os.WriteFile(config.GetConfig().Auth.AuthFilePath, data, 0600)
|
||||
}
|
||||
|
||||
// loadAuthorizationInfo 从文件加载授权信息
|
||||
func (as *AuthService) loadAuthorizationInfo() {
|
||||
filePath := config.GetConfig().Auth.AuthFilePath
|
||||
|
||||
|
||||
// 检查文件是否存在
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
return
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(filePath)
|
||||
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
log.Printf("读取授权文件失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var authInfo models.AuthorizationStorage
|
||||
|
||||
var authInfo models2.AuthorizationStorage
|
||||
if err := json.Unmarshal(data, &authInfo); err != nil {
|
||||
log.Printf("解析授权文件失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
as.authorizationInfo = authInfo
|
||||
as.initialized = true
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package services
|
||||
import (
|
||||
"cmii-uav-watchdog-common/models"
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -27,6 +28,11 @@ func (hs *HeartbeatService) ProcessHeartbeat(req models.HeartbeatRequest) (*mode
|
||||
return nil, errors.New("无效的时间戳")
|
||||
}
|
||||
|
||||
secondTOTPSecret := hs.authService.authorizationInfo.SecondTOTPSecret
|
||||
if secondTOTPSecret == "" {
|
||||
return nil, errors.New("二级TOTP密钥为空")
|
||||
}
|
||||
|
||||
// 添加主机信息到集合
|
||||
hs.authService.AddHostInfo(req.HostInfo)
|
||||
|
||||
@@ -36,17 +42,26 @@ func (hs *HeartbeatService) ProcessHeartbeat(req models.HeartbeatRequest) (*mode
|
||||
Authorized: false,
|
||||
TOTPCode: "",
|
||||
Timestamp: time.Now().Unix(),
|
||||
SecondTOTPSecret: "",
|
||||
SecondTOTPSecret: secondTOTPSecret,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 检查totp密码是都有效
|
||||
// 检查totp验证码是否有效
|
||||
if !hs.totpService.VerifyTierTwoTOTPCode(req.TOTPCode, secondTOTPSecret) {
|
||||
// 解析认证主机的相关信息
|
||||
|
||||
// 计算 请求时间与当前时间的时间差
|
||||
diff := time.Now().Unix() - req.Timestamp
|
||||
log.Printf("心跳请求时间与当前时间的时间差: %d", diff)
|
||||
|
||||
return nil, errors.New("无效的TOTP验证码,请检查系统时间是否正确!")
|
||||
}
|
||||
|
||||
// 检查主机是否已授权
|
||||
authorized := hs.authService.IsHostAuthorized(req.HostInfo)
|
||||
|
||||
// 生成TOTP验证码
|
||||
totpCode, err := hs.totpService.GenerateTOTP()
|
||||
totpCode, err := hs.totpService.GenerateTierTwoTOTPCode(secondTOTPSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -2,12 +2,24 @@ package services
|
||||
|
||||
import (
|
||||
"cmii-uav-watchdog/config"
|
||||
"errors"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
otp "cmii-uav-watchdog-otp"
|
||||
"cmii-uav-watchdog-otp/totp"
|
||||
)
|
||||
|
||||
var tierTwoTOTPSecretOpts = totp.GenerateOpts{
|
||||
SecretSize: 32,
|
||||
Issuer: "cmii-uav-watchdog",
|
||||
AccountName: "cmii-uav-watchdog",
|
||||
Period: 30,
|
||||
Secret: []byte{},
|
||||
Digits: otp.DigitsSix,
|
||||
Algorithm: otp.AlgorithmSHA1,
|
||||
Rand: nil,
|
||||
}
|
||||
|
||||
// TOTPService TOTP服务
|
||||
type TOTPService struct {
|
||||
secret string
|
||||
@@ -20,8 +32,8 @@ func NewTOTPService() *TOTPService {
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateTOTP 生成TOTP验证码
|
||||
func (ts *TOTPService) GenerateTOTP() (string, error) {
|
||||
// GenerateTierOneTOTP 生成一级TOTP验证码
|
||||
func (ts *TOTPService) GenerateTierOneTOTP() (string, error) {
|
||||
// 使用当前时间生成TOTP
|
||||
code, err := totp.GenerateCode(ts.secret, time.Now())
|
||||
if err != nil {
|
||||
@@ -31,13 +43,44 @@ func (ts *TOTPService) GenerateTOTP() (string, error) {
|
||||
return code, nil
|
||||
}
|
||||
|
||||
// VerifyTOTP 验证TOTP验证码
|
||||
func (ts *TOTPService) VerifyTOTP(code string) error {
|
||||
// VerifyTierOneTOTP 验证一级TOTP验证码
|
||||
func (ts *TOTPService) VerifyTierOneTOTP(code string) bool {
|
||||
// 验证TOTP
|
||||
valid := totp.Validate(code, ts.secret)
|
||||
if !valid {
|
||||
return errors.New("无效的TOTP验证码")
|
||||
return false
|
||||
}
|
||||
|
||||
return nil
|
||||
return true
|
||||
}
|
||||
|
||||
// GenerateTierTwoTOTPSecret 生成二级TOTP密钥
|
||||
func (ts *TOTPService) GenerateTierTwoTOTPSecret() (string, error) {
|
||||
secret, err := totp.Generate(tierTwoTOTPSecretOpts)
|
||||
if err != nil {
|
||||
log.Printf("生成TOTP密钥失败: %v", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return secret.Secret(), nil
|
||||
}
|
||||
|
||||
// GenerateTierTwoTOTPCode 生成二级TOTP验证码
|
||||
func (ts *TOTPService) GenerateTierTwoTOTPCode(secret string) (string, error) {
|
||||
code, err := totp.GenerateCode(secret, time.Now())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return code, nil
|
||||
}
|
||||
|
||||
// VerifyTierTwoTOTPCode 验证二级TOTP验证码
|
||||
func (ts *TOTPService) VerifyTierTwoTOTPCode(code string, secret string) bool {
|
||||
validateOpts := totp.ValidateOpts{}
|
||||
validateOpts.ConvertToValidateOpts(tierTwoTOTPSecretOpts)
|
||||
valid, err := totp.ValidateCustom(code, secret, time.Now(), validateOpts)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return valid
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user