You've already forked VptPassiveAdapter
feat(core): 添加断连检测和命令执行功能
- 新增 ConnError 类型用于区分连接级错误和应用层错误 - 在 sync_task 中将网络请求错误包装为 ConnError - 添加 DisconnectActionConfig 配置结构支持断连操作 - 在配置文件中增加 disconnectAction 配置项 - 实现 executeDisconnectCommand 函数支持跨平台命令执行 - 在主循环中添加断连检测逻辑和阈值判断 - 支持服务器连接恢复时重置断连状态 - 添加详细的日志记录用于断连状态追踪
This commit is contained in:
@@ -12,6 +12,14 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ConnError 表示与服务器的连接级错误(非 HTTP 应用层错误)
|
||||||
|
type ConnError struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *ConnError) Error() string { return e.Err.Error() }
|
||||||
|
func (e *ConnError) Unwrap() error { return e.Err }
|
||||||
|
|
||||||
func SyncTask() ([]dto.Task, error) {
|
func SyncTask() ([]dto.Task, error) {
|
||||||
url := config.Config.Api.BaseUrl + "/sync"
|
url := config.Config.Api.BaseUrl + "/sync"
|
||||||
requestBody := map[string]interface{}{
|
requestBody := map[string]interface{}{
|
||||||
@@ -32,7 +40,7 @@ func SyncTask() ([]dto.Task, error) {
|
|||||||
resp, err := GetAPIClient().Do(req)
|
resp, err := GetAPIClient().Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("发送请求失败", zap.Error(err))
|
logger.Error("发送请求失败", zap.Error(err))
|
||||||
return nil, err
|
return nil, &ConnError{Err: err}
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
|||||||
@@ -28,3 +28,7 @@ viid:
|
|||||||
serverUrl: "http://127.0.0.1:18083"
|
serverUrl: "http://127.0.0.1:18083"
|
||||||
scenicId: 3975985126059413504
|
scenicId: 3975985126059413504
|
||||||
port: 8080
|
port: 8080
|
||||||
|
disconnectAction:
|
||||||
|
enabled: false
|
||||||
|
thresholdMinutes: 5
|
||||||
|
command: ""
|
||||||
|
|||||||
@@ -58,10 +58,17 @@ type ViidConfig struct {
|
|||||||
Port int `mapstructure:"port"`
|
Port int `mapstructure:"port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MainConfig struct {
|
type DisconnectActionConfig struct {
|
||||||
Api ApiConfig `mapstructure:"api"`
|
Enabled bool `mapstructure:"enabled"`
|
||||||
Record RecordConfig `mapstructure:"record"`
|
ThresholdMinutes int `mapstructure:"thresholdMinutes"`
|
||||||
Devices []DeviceMapping `mapstructure:"devices"`
|
Command string `mapstructure:"command"`
|
||||||
FileName FileNameConfig `mapstructure:"fileName"`
|
}
|
||||||
Viid ViidConfig `mapstructure:"viid"`
|
|
||||||
|
type MainConfig struct {
|
||||||
|
Api ApiConfig `mapstructure:"api"`
|
||||||
|
Record RecordConfig `mapstructure:"record"`
|
||||||
|
Devices []DeviceMapping `mapstructure:"devices"`
|
||||||
|
FileName FileNameConfig `mapstructure:"fileName"`
|
||||||
|
Viid ViidConfig `mapstructure:"viid"`
|
||||||
|
DisconnectAction DisconnectActionConfig `mapstructure:"disconnectAction"`
|
||||||
}
|
}
|
||||||
|
|||||||
68
main.go
68
main.go
@@ -8,9 +8,12 @@ import (
|
|||||||
"ZhenTuLocalPassiveAdapter/logger"
|
"ZhenTuLocalPassiveAdapter/logger"
|
||||||
"ZhenTuLocalPassiveAdapter/telemetry"
|
"ZhenTuLocalPassiveAdapter/telemetry"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"runtime"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -70,10 +73,35 @@ func startTask(device config.DeviceMapping, task dto.Task) {
|
|||||||
zap.String("deviceNo", task.DeviceNo))
|
zap.String("deviceNo", task.DeviceNo))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func executeDisconnectCommand(command string) {
|
||||||
|
go func() {
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
cmd = exec.Command("cmd", "/C", command)
|
||||||
|
} else {
|
||||||
|
cmd = exec.Command("sh", "-c", command)
|
||||||
|
}
|
||||||
|
output, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("断连命令执行失败",
|
||||||
|
zap.String("command", command),
|
||||||
|
zap.String("output", string(output)),
|
||||||
|
zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Info("断连命令执行成功",
|
||||||
|
zap.String("command", command),
|
||||||
|
zap.String("output", string(output)))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
func runTaskLoop(ctx context.Context) {
|
func runTaskLoop(ctx context.Context) {
|
||||||
ticker := time.NewTicker(2 * time.Second)
|
ticker := time.NewTicker(2 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
var firstFailureTime time.Time
|
||||||
|
commandExecuted := false
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
@@ -81,7 +109,43 @@ func runTaskLoop(ctx context.Context) {
|
|||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
// 执行任务
|
// 执行任务
|
||||||
tasks, err := api.SyncTask()
|
tasks, err := api.SyncTask()
|
||||||
if err == nil {
|
if err != nil {
|
||||||
|
var connErr *api.ConnError
|
||||||
|
if errors.As(err, &connErr) {
|
||||||
|
if config.Config.DisconnectAction.Enabled {
|
||||||
|
// 连接级错误:开始/继续计时
|
||||||
|
if firstFailureTime.IsZero() {
|
||||||
|
firstFailureTime = time.Now()
|
||||||
|
logger.Warn("服务器连接失败,开始计时",
|
||||||
|
zap.Error(err))
|
||||||
|
}
|
||||||
|
threshold := time.Duration(config.Config.DisconnectAction.ThresholdMinutes) * time.Minute
|
||||||
|
if !commandExecuted && time.Since(firstFailureTime) >= threshold && config.Config.DisconnectAction.Command != "" {
|
||||||
|
logger.Warn("服务器持续不可达,执行断连命令",
|
||||||
|
zap.Duration("duration", time.Since(firstFailureTime)),
|
||||||
|
zap.String("command", config.Config.DisconnectAction.Command))
|
||||||
|
executeDisconnectCommand(config.Config.DisconnectAction.Command)
|
||||||
|
commandExecuted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 非连接错误(服务器可达,但业务/解析出错):重置断连状态
|
||||||
|
if !firstFailureTime.IsZero() {
|
||||||
|
logger.Info("服务器连接已恢复",
|
||||||
|
zap.Duration("disconnectDuration", time.Since(firstFailureTime)))
|
||||||
|
firstFailureTime = time.Time{}
|
||||||
|
commandExecuted = false
|
||||||
|
}
|
||||||
|
logger.Error("同步任务失败", zap.Error(err))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 服务器可达:重置状态
|
||||||
|
if !firstFailureTime.IsZero() {
|
||||||
|
logger.Info("服务器连接已恢复",
|
||||||
|
zap.Duration("disconnectDuration", time.Since(firstFailureTime)))
|
||||||
|
firstFailureTime = time.Time{}
|
||||||
|
commandExecuted = false
|
||||||
|
}
|
||||||
for _, task := range tasks {
|
for _, task := range tasks {
|
||||||
logger.Info("开始处理任务",
|
logger.Info("开始处理任务",
|
||||||
zap.String("taskID", task.TaskID),
|
zap.String("taskID", task.TaskID),
|
||||||
@@ -97,8 +161,6 @@ func runTaskLoop(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
logger.Error("同步任务失败", zap.Error(err))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user