diff --git a/goecs.go b/goecs.go index da75c08..74836ce 100644 --- a/goecs.go +++ b/goecs.go @@ -27,7 +27,7 @@ import ( ) var ( - ecsVersion = "v0.1.102" // 融合怪版本号 + ecsVersion = "v0.1.103" // 融合怪版本号 configs = params.NewConfig(ecsVersion) // 全局配置实例 userSetFlags = make(map[string]bool) // 用于跟踪哪些参数是用户显式设置的 ) @@ -83,6 +83,7 @@ func main() { basicInfo, securityInfo, emailInfo, mediaInfo, ptInfo string output, tempOutput string outputMutex sync.Mutex + infoMutex sync.Mutex // 保护并发字符串写入 ) startTime := time.Now() uploadDone := make(chan bool, 1) @@ -91,9 +92,9 @@ func main() { go runner.HandleSignalInterrupt(sig, configs, &startTime, &output, tempOutput, uploadDone, &outputMutex) switch configs.Language { case "zh": - runner.RunChineseTests(preCheck, configs, &wg1, &wg2, &wg3, &basicInfo, &securityInfo, &emailInfo, &mediaInfo, &ptInfo, &output, tempOutput, startTime, &outputMutex) + runner.RunChineseTests(preCheck, configs, &wg1, &wg2, &wg3, &basicInfo, &securityInfo, &emailInfo, &mediaInfo, &ptInfo, &output, tempOutput, startTime, &outputMutex, &infoMutex) case "en": - runner.RunEnglishTests(preCheck, configs, &wg1, &wg2, &wg3, &basicInfo, &securityInfo, &emailInfo, &mediaInfo, &ptInfo, &output, tempOutput, startTime, &outputMutex) + runner.RunEnglishTests(preCheck, configs, &wg1, &wg2, &wg3, &basicInfo, &securityInfo, &emailInfo, &mediaInfo, &ptInfo, &output, tempOutput, startTime, &outputMutex, &infoMutex) default: fmt.Println("Unsupported language") } diff --git a/internal/menu/menu.go b/internal/menu/menu.go index e5160e9..637dc7d 100644 --- a/internal/menu/menu.go +++ b/internal/menu/menu.go @@ -21,7 +21,7 @@ func GetMenuChoice(language string) string { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) defer signal.Stop(sigChan) - inputChan := make(chan string, 1) + go func() { select { case <-sigChan: @@ -31,43 +31,33 @@ func GetMenuChoice(language string) string { return } }() + for { - go func() { - var input string - fmt.Print("请输入选项 / Please enter your choice: ") - fmt.Scanln(&input) - input = strings.TrimSpace(input) - input = strings.TrimRight(input, "\n") - select { - case inputChan <- input: - case <-ctx.Done(): - return - } - }() - select { - case input := <-inputChan: - re := regexp.MustCompile(`^\d+$`) - if re.MatchString(input) { - inChoice := input - switch inChoice { - case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10": - return inChoice - default: - if language == "zh" { - fmt.Println("无效的选项") - } else { - fmt.Println("Invalid choice") - } - } - } else { + var input string + fmt.Print("请输入选项 / Please enter your choice: ") + fmt.Scanln(&input) + input = strings.TrimSpace(input) + input = strings.TrimRight(input, "\n") + + re := regexp.MustCompile(`^\d+$`) + if re.MatchString(input) { + inChoice := input + switch inChoice { + case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10": + return inChoice + default: if language == "zh" { - fmt.Println("输入错误,请输入一个纯数字") + fmt.Println("无效的选项") } else { - fmt.Println("Invalid input, please enter a number") + fmt.Println("Invalid choice") } } - case <-ctx.Done(): - return "" + } else { + if language == "zh" { + fmt.Println("输入错误,请输入一个纯数字") + } else { + fmt.Println("Invalid input, please enter a number") + } } } } diff --git a/internal/params/params.go b/internal/params/params.go index 511d052..58560f2 100644 --- a/internal/params/params.go +++ b/internal/params/params.go @@ -210,72 +210,116 @@ func (c *Config) SaveUserSetParams() map[string]interface{} { // RestoreUserSetParams restores user-set parameters func (c *Config) RestoreUserSetParams(saved map[string]interface{}) { if val, ok := saved["basic"]; ok { - c.BasicStatus = val.(bool) + if boolVal, ok := val.(bool); ok { + c.BasicStatus = boolVal + } } if val, ok := saved["cpu"]; ok { - c.CpuTestStatus = val.(bool) + if boolVal, ok := val.(bool); ok { + c.CpuTestStatus = boolVal + } } if val, ok := saved["memory"]; ok { - c.MemoryTestStatus = val.(bool) + if boolVal, ok := val.(bool); ok { + c.MemoryTestStatus = boolVal + } } if val, ok := saved["disk"]; ok { - c.DiskTestStatus = val.(bool) + if boolVal, ok := val.(bool); ok { + c.DiskTestStatus = boolVal + } } if val, ok := saved["ut"]; ok { - c.UtTestStatus = val.(bool) + if boolVal, ok := val.(bool); ok { + c.UtTestStatus = boolVal + } } if val, ok := saved["security"]; ok { - c.SecurityTestStatus = val.(bool) + if boolVal, ok := val.(bool); ok { + c.SecurityTestStatus = boolVal + } } if val, ok := saved["email"]; ok { - c.EmailTestStatus = val.(bool) + if boolVal, ok := val.(bool); ok { + c.EmailTestStatus = boolVal + } } if val, ok := saved["backtrace"]; ok { - c.BacktraceStatus = val.(bool) + if boolVal, ok := val.(bool); ok { + c.BacktraceStatus = boolVal + } } if val, ok := saved["nt3"]; ok { - c.Nt3Status = val.(bool) + if boolVal, ok := val.(bool); ok { + c.Nt3Status = boolVal + } } if val, ok := saved["speed"]; ok { - c.SpeedTestStatus = val.(bool) + if boolVal, ok := val.(bool); ok { + c.SpeedTestStatus = boolVal + } } if val, ok := saved["ping"]; ok { - c.PingTestStatus = val.(bool) + if boolVal, ok := val.(bool); ok { + c.PingTestStatus = boolVal + } } if val, ok := saved["tgdc"]; ok { - c.TgdcTestStatus = val.(bool) + if boolVal, ok := val.(bool); ok { + c.TgdcTestStatus = boolVal + } } if val, ok := saved["web"]; ok { - c.WebTestStatus = val.(bool) + if boolVal, ok := val.(bool); ok { + c.WebTestStatus = boolVal + } } if val, ok := saved["cpum"]; ok { - c.CpuTestMethod = val.(string) + if strVal, ok := val.(string); ok { + c.CpuTestMethod = strVal + } } if val, ok := saved["cput"]; ok { - c.CpuTestThreadMode = val.(string) + if strVal, ok := val.(string); ok { + c.CpuTestThreadMode = strVal + } } if val, ok := saved["memorym"]; ok { - c.MemoryTestMethod = val.(string) + if strVal, ok := val.(string); ok { + c.MemoryTestMethod = strVal + } } if val, ok := saved["diskm"]; ok { - c.DiskTestMethod = val.(string) + if strVal, ok := val.(string); ok { + c.DiskTestMethod = strVal + } } if val, ok := saved["diskp"]; ok { - c.DiskTestPath = val.(string) + if strVal, ok := val.(string); ok { + c.DiskTestPath = strVal + } } if val, ok := saved["diskmc"]; ok { - c.DiskMultiCheck = val.(bool) + if boolVal, ok := val.(bool); ok { + c.DiskMultiCheck = boolVal + } } if val, ok := saved["nt3loc"]; ok { if c.Choice != "10" { - c.Nt3Location = val.(string) + if strVal, ok := val.(string); ok { + c.Nt3Location = strVal + } } } if val, ok := saved["nt3t"]; ok { - c.Nt3CheckType = val.(string) + if strVal, ok := val.(string); ok { + c.Nt3CheckType = strVal + } } if val, ok := saved["spnum"]; ok { - c.SpNum = val.(int) + if intVal, ok := val.(int); ok { + c.SpNum = intVal + } } c.ValidateParams() diff --git a/internal/runner/runner.go b/internal/runner/runner.go index caa70d9..4722494 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -2,6 +2,7 @@ package runner import ( "bufio" + "context" "fmt" "os" "runtime" @@ -17,7 +18,7 @@ import ( ) // RunChineseTests runs all tests in Chinese mode -func RunChineseTests(preCheck utils.NetCheckResult, config *params.Config, wg1, wg2, wg3 *sync.WaitGroup, basicInfo, securityInfo, emailInfo, mediaInfo, ptInfo *string, output *string, tempOutput string, startTime time.Time, outputMutex *sync.Mutex) { +func RunChineseTests(preCheck utils.NetCheckResult, config *params.Config, wg1, wg2, wg3 *sync.WaitGroup, basicInfo, securityInfo, emailInfo, mediaInfo, ptInfo *string, output *string, tempOutput string, startTime time.Time, outputMutex *sync.Mutex, infoMutex *sync.Mutex) { *output = RunBasicTests(preCheck, config, basicInfo, securityInfo, *output, tempOutput, outputMutex) *output = RunCPUTest(config, *output, tempOutput, outputMutex) *output = RunMemoryTest(config, *output, tempOutput, outputMutex) @@ -29,30 +30,39 @@ func RunChineseTests(preCheck utils.NetCheckResult, config *params.Config, wg1, wg1.Add(1) go func() { defer wg1.Done() - *mediaInfo = tests.MediaTest(config.Language) + result := tests.MediaTest(config.Language) + infoMutex.Lock() + *mediaInfo = result + infoMutex.Unlock() }() } if config.EmailTestStatus && preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" { wg2.Add(1) go func() { defer wg2.Done() - *emailInfo = email.EmailCheck() + result := email.EmailCheck() + infoMutex.Lock() + *emailInfo = result + infoMutex.Unlock() }() } if (config.OnlyChinaTest || config.PingTestStatus) && preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" { wg3.Add(1) go func() { defer wg3.Done() - *ptInfo = pt.PingTest() + result := pt.PingTest() + infoMutex.Lock() + *ptInfo = result + infoMutex.Unlock() }() } if preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" { - *output = RunStreamingTests(config, wg1, mediaInfo, *output, tempOutput, outputMutex) + *output = RunStreamingTests(config, wg1, mediaInfo, *output, tempOutput, outputMutex, infoMutex) *output = RunSecurityTests(config, *securityInfo, *output, tempOutput, outputMutex) - *output = RunEmailTests(config, wg2, emailInfo, *output, tempOutput, outputMutex) + *output = RunEmailTests(config, wg2, emailInfo, *output, tempOutput, outputMutex, infoMutex) } if runtime.GOOS != "windows" && preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" { - *output = RunNetworkTests(config, wg3, ptInfo, *output, tempOutput, outputMutex) + *output = RunNetworkTests(config, wg3, ptInfo, *output, tempOutput, outputMutex, infoMutex) } if preCheck.Connected && preCheck.StackType != "" && preCheck.StackType != "None" { *output = RunSpeedTests(config, *output, tempOutput, outputMutex) @@ -61,7 +71,7 @@ func RunChineseTests(preCheck utils.NetCheckResult, config *params.Config, wg1, } // RunEnglishTests runs all tests in English mode -func RunEnglishTests(preCheck utils.NetCheckResult, config *params.Config, wg1, wg2, wg3 *sync.WaitGroup, basicInfo, securityInfo, emailInfo, mediaInfo, ptInfo *string, output *string, tempOutput string, startTime time.Time, outputMutex *sync.Mutex) { +func RunEnglishTests(preCheck utils.NetCheckResult, config *params.Config, wg1, wg2, wg3 *sync.WaitGroup, basicInfo, securityInfo, emailInfo, mediaInfo, ptInfo *string, output *string, tempOutput string, startTime time.Time, outputMutex *sync.Mutex, infoMutex *sync.Mutex) { *output = RunBasicTests(preCheck, config, basicInfo, securityInfo, *output, tempOutput, outputMutex) *output = RunCPUTest(config, *output, tempOutput, outputMutex) *output = RunMemoryTest(config, *output, tempOutput, outputMutex) @@ -74,19 +84,25 @@ func RunEnglishTests(preCheck utils.NetCheckResult, config *params.Config, wg1, wg1.Add(1) go func() { defer wg1.Done() - *mediaInfo = tests.MediaTest(config.Language) + result := tests.MediaTest(config.Language) + infoMutex.Lock() + *mediaInfo = result + infoMutex.Unlock() }() } if config.EmailTestStatus { wg2.Add(1) go func() { defer wg2.Done() - *emailInfo = email.EmailCheck() + result := email.EmailCheck() + infoMutex.Lock() + *emailInfo = result + infoMutex.Unlock() }() } - *output = RunStreamingTests(config, wg1, mediaInfo, *output, tempOutput, outputMutex) + *output = RunStreamingTests(config, wg1, mediaInfo, *output, tempOutput, outputMutex, infoMutex) *output = RunSecurityTests(config, *securityInfo, *output, tempOutput, outputMutex) - *output = RunEmailTests(config, wg2, emailInfo, *output, tempOutput, outputMutex) + *output = RunEmailTests(config, wg2, emailInfo, *output, tempOutput, outputMutex, infoMutex) *output = RunEnglishNetworkTests(config, wg3, ptInfo, *output, tempOutput, outputMutex) *output = RunEnglishSpeedTests(config, *output, tempOutput, outputMutex) } @@ -218,7 +234,7 @@ func RunDiskTest(config *params.Config, output, tempOutput string, outputMutex * } // RunStreamingTests runs platform unlock tests -func RunStreamingTests(config *params.Config, wg1 *sync.WaitGroup, mediaInfo *string, output, tempOutput string, outputMutex *sync.Mutex) string { +func RunStreamingTests(config *params.Config, wg1 *sync.WaitGroup, mediaInfo *string, output, tempOutput string, outputMutex *sync.Mutex, infoMutex *sync.Mutex) string { outputMutex.Lock() defer outputMutex.Unlock() return utils.PrintAndCapture(func() { @@ -229,7 +245,10 @@ func RunStreamingTests(config *params.Config, wg1 *sync.WaitGroup, mediaInfo *st } else { utils.PrintCenteredTitle("Cross-Border-Platform-Unlock", config.Width) } - fmt.Printf("%s", *mediaInfo) + infoMutex.Lock() + info := *mediaInfo + infoMutex.Unlock() + fmt.Printf("%s", info) } }, tempOutput, output) } @@ -251,7 +270,7 @@ func RunSecurityTests(config *params.Config, securityInfo, output, tempOutput st } // RunEmailTests runs email port tests -func RunEmailTests(config *params.Config, wg2 *sync.WaitGroup, emailInfo *string, output, tempOutput string, outputMutex *sync.Mutex) string { +func RunEmailTests(config *params.Config, wg2 *sync.WaitGroup, emailInfo *string, output, tempOutput string, outputMutex *sync.Mutex, infoMutex *sync.Mutex) string { outputMutex.Lock() defer outputMutex.Unlock() return utils.PrintAndCapture(func() { @@ -262,13 +281,16 @@ func RunEmailTests(config *params.Config, wg2 *sync.WaitGroup, emailInfo *string } else { utils.PrintCenteredTitle("Email-Port-Check", config.Width) } - fmt.Println(*emailInfo) + infoMutex.Lock() + info := *emailInfo + infoMutex.Unlock() + fmt.Println(info) } }, tempOutput, output) } // RunNetworkTests runs network tests (Chinese mode) -func RunNetworkTests(config *params.Config, wg3 *sync.WaitGroup, ptInfo *string, output, tempOutput string, outputMutex *sync.Mutex) string { +func RunNetworkTests(config *params.Config, wg3 *sync.WaitGroup, ptInfo *string, output, tempOutput string, outputMutex *sync.Mutex, infoMutex *sync.Mutex) string { outputMutex.Lock() defer outputMutex.Unlock() return utils.PrintAndCapture(func() { @@ -280,15 +302,18 @@ func RunNetworkTests(config *params.Config, wg3 *sync.WaitGroup, ptInfo *string, utils.PrintCenteredTitle("三网回程路由检测", config.Width) tests.NextTrace3Check(config.Language, config.Nt3Location, config.Nt3CheckType) } - if config.OnlyChinaTest && *ptInfo != "" { + infoMutex.Lock() + info := *ptInfo + infoMutex.Unlock() + if config.OnlyChinaTest && info != "" { wg3.Wait() utils.PrintCenteredTitle("PING值检测", config.Width) - fmt.Println(*ptInfo) + fmt.Println(info) } - if config.PingTestStatus && *ptInfo != "" { + if config.PingTestStatus && info != "" { wg3.Wait() utils.PrintCenteredTitle("PING值检测", config.Width) - fmt.Println(*ptInfo) + fmt.Println(info) if config.TgdcTestStatus { fmt.Println(pt.TelegramDCTest()) } @@ -414,16 +439,26 @@ func HandleSignalInterrupt(sig chan os.Signal, config *params.Config, startTime httpsURL string }, 1) if config.EnableUpload { + // 使用context来控制上传goroutine + uploadCtx, uploadCancel := context.WithTimeout(context.Background(), 30*time.Second) + defer uploadCancel() + go func() { httpURL, httpsURL := utils.ProcessAndUpload(finalOutput, config.FilePath, config.EnableUpload) - resultChan <- struct { + select { + case resultChan <- struct { httpURL string httpsURL string - }{httpURL, httpsURL} - uploadDone <- true + }{httpURL, httpsURL}: + case <-uploadCtx.Done(): + // 上传被取消或超时,直接返回 + return + } }() + select { case result := <-resultChan: + uploadCancel() // 成功完成,取消context if result.httpURL != "" || result.httpsURL != "" { if config.Language == "en" { fmt.Printf("Upload successfully!\nHttp URL: %s\nHttps URL: %s\n", result.httpURL, result.httpsURL) @@ -437,7 +472,7 @@ func HandleSignalInterrupt(sig chan os.Signal, config *params.Config, startTime fmt.Scanln() } os.Exit(0) - case <-time.After(30 * time.Second): + case <-uploadCtx.Done(): if config.Language == "en" { fmt.Println("Upload timeout, program exit") } else { diff --git a/internal/tests/cpu.go b/internal/tests/cpu.go index 92453fa..c7191ca 100644 --- a/internal/tests/cpu.go +++ b/internal/tests/cpu.go @@ -1,6 +1,8 @@ package tests import ( + "fmt" + "os" "runtime" "strings" @@ -8,6 +10,14 @@ import ( ) func CpuTest(language, testMethod, testThread string) (realTestMethod, res string) { + defer func() { + if r := recover(); r != nil { + fmt.Fprintf(os.Stderr, "[WARN] CpuTest panic: %v\n", r) + res = fmt.Sprintf("\nCPU test failed: %v\n", r) + realTestMethod = "error" + } + }() + if runtime.GOOS == "windows" { if testMethod != "winsat" && testMethod != "" { // res = "Detected host is Windows, using Winsat for testing.\n" diff --git a/internal/tests/disk.go b/internal/tests/disk.go index 871a260..0c6f017 100644 --- a/internal/tests/disk.go +++ b/internal/tests/disk.go @@ -1,6 +1,8 @@ package tests import ( + "fmt" + "os" "runtime" "strings" @@ -8,6 +10,14 @@ import ( ) func DiskTest(language, testMethod, testPath string, isMultiCheck bool, autoChange bool) (realTestMethod, res string) { + defer func() { + if r := recover(); r != nil { + fmt.Fprintf(os.Stderr, "[WARN] DiskTest panic: %v\n", r) + res = fmt.Sprintf("\nDisk test failed: %v\n", r) + realTestMethod = "error" + } + }() + switch testMethod { case "fio": res = disk.FioTest(language, isMultiCheck, testPath) diff --git a/internal/tests/memory.go b/internal/tests/memory.go index c920369..09048d6 100644 --- a/internal/tests/memory.go +++ b/internal/tests/memory.go @@ -1,6 +1,8 @@ package tests import ( + "fmt" + "os" "runtime" "strings" @@ -8,6 +10,14 @@ import ( ) func MemoryTest(language, testMethod string) (realTestMethod, res string) { + defer func() { + if r := recover(); r != nil { + fmt.Fprintf(os.Stderr, "[WARN] MemoryTest panic: %v\n", r) + res = fmt.Sprintf("\nMemory test failed: %v\n", r) + realTestMethod = "error" + } + }() + testMethod = strings.ToLower(testMethod) if testMethod == "" { testMethod = "auto" diff --git a/internal/tests/nexttrace.go b/internal/tests/nexttrace.go index 112b4e0..cda399e 100644 --- a/internal/tests/nexttrace.go +++ b/internal/tests/nexttrace.go @@ -2,14 +2,43 @@ package tests import ( "fmt" + "os" "strings" "github.com/oneclickvirt/nt3/nt" ) func NextTrace3Check(language, nt3Location, nt3CheckType string) { + // 添加panic恢复机制,防止因权限问题导致程序崩溃 + defer func() { + if r := recover(); r != nil { + if language == "zh" { + fmt.Println("\n路由追踪测试出现错误(可能因为权限不足),已跳过") + fmt.Fprintf(os.Stderr, "[WARN] 路由追踪panic: %v\n", r) + } else { + fmt.Println("\nRoute tracing test failed (possibly due to insufficient permissions), skipped") + fmt.Fprintf(os.Stderr, "[WARN] Route tracing panic: %v\n", r) + } + } + }() + resultChan := make(chan nt.TraceResult, 100) - go nt.TraceRoute(language, nt3Location, nt3CheckType, resultChan) + go func() { + // 在goroutine中也添加错误恢复 + defer func() { + if r := recover(); r != nil { + // 发送错误结果并关闭channel + resultChan <- nt.TraceResult{ + Index: -1, + ISPName: "Error", + Output: []string{fmt.Sprintf("Route tracing error: %v", r)}, + } + close(resultChan) + } + }() + nt.TraceRoute(language, nt3Location, nt3CheckType, resultChan) + }() + for result := range resultChan { if result.Index == -1 { for index, res := range result.Output { diff --git a/internal/tests/speed.go b/internal/tests/speed.go index 9883654..59e60e6 100644 --- a/internal/tests/speed.go +++ b/internal/tests/speed.go @@ -1,6 +1,8 @@ package tests import ( + "fmt" + "os" "runtime" "strings" @@ -9,10 +11,20 @@ import ( ) func ShowHead(language string) { + defer func() { + if r := recover(); r != nil { + fmt.Fprintf(os.Stderr, "[WARN] ShowHead panic: %v\n", r) + } + }() sp.ShowHead(language) } func NearbySP() { + defer func() { + if r := recover(); r != nil { + fmt.Fprintf(os.Stderr, "[WARN] NearbySP panic: %v\n", r) + } + }() if runtime.GOOS == "windows" || sp.OfficialAvailableTest() != nil { sp.NearbySpeedTest() } else { @@ -21,6 +33,11 @@ func NearbySP() { } func CustomSP(platform, operator string, num int, language string) { + defer func() { + if r := recover(); r != nil { + fmt.Fprintf(os.Stderr, "[WARN] CustomSP panic: %v\n", r) + } + }() var url, parseType string if strings.ToLower(platform) == "cn" { if strings.ToLower(operator) == "cmcc" { diff --git a/internal/tests/unlock.go b/internal/tests/unlock.go index 86f45a5..365d560 100644 --- a/internal/tests/unlock.go +++ b/internal/tests/unlock.go @@ -1,12 +1,21 @@ package tests import ( + "fmt" + "os" + "github.com/oneclickvirt/UnlockTests/executor" "github.com/oneclickvirt/UnlockTests/utils" "github.com/oneclickvirt/defaultset" ) func MediaTest(language string) string { + defer func() { + if r := recover(); r != nil { + fmt.Fprintf(os.Stderr, "[WARN] MediaTest panic: %v\n", r) + } + }() + var res string readStatus := executor.ReadSelect(language, "0") if !readStatus { diff --git a/internal/tests/upstreams.go b/internal/tests/upstreams.go index 908b94c..f274923 100644 --- a/internal/tests/upstreams.go +++ b/internal/tests/upstreams.go @@ -2,6 +2,7 @@ package tests import ( "fmt" + "os" "sync" "time" @@ -29,12 +30,25 @@ type ConcurrentResults struct { var IPV4, IPV6 string func UpstreamsCheck() { + // 添加panic恢复机制 + defer func() { + if r := recover(); r != nil { + fmt.Println("\n上游检测出现错误,已跳过") + fmt.Fprintf(os.Stderr, "[WARN] Upstream check panic: %v\n", r) + } + }() + results := ConcurrentResults{} var wg sync.WaitGroup if IPV4 != "" { wg.Add(1) go func() { defer wg.Done() + defer func() { + if r := recover(); r != nil { + fmt.Fprintf(os.Stderr, "[WARN] BGP info panic: %v\n", r) + } + }() for i := 0; i < 2; i++ { result, err := bgptools.GetPoPInfo(IPV4) results.bgpError = err @@ -51,6 +65,11 @@ func UpstreamsCheck() { wg.Add(1) go func() { defer wg.Done() + defer func() { + if r := recover(); r != nil { + fmt.Fprintf(os.Stderr, "[WARN] Backtrace panic: %v\n", r) + } + }() result := backtrace.BackTrace(executor.IPV6) results.backtraceResult = result }() diff --git a/utils/utils.go b/utils/utils.go index adfa69a..6c93606 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -203,19 +203,10 @@ func CaptureOutput(f func()) string { // 替换标准输出和标准错误输出为管道写入端 os.Stdout = stdoutPipeW os.Stderr = stderrPipeW - // 恢复标准输出和标准错误输出 - defer func() { - os.Stdout = oldStdout - os.Stderr = oldStderr - stdoutPipeW.Close() - stderrPipeW.Close() - stdoutPipeR.Close() - stderrPipeR.Close() - }() // 缓冲区 var stdoutBuf, stderrBuf bytes.Buffer // 并发读取 stdout 和 stderr - done := make(chan struct{}) + done := make(chan struct{}, 2) go func() { multiWriter := io.MultiWriter(&stdoutBuf, oldStdout) io.Copy(multiWriter, stdoutPipeR) @@ -234,6 +225,11 @@ func CaptureOutput(f func()) string { // 等待两个 goroutine 完成 <-done <-done + // 恢复标准输出和标准错误输出,并关闭管道读取端 + os.Stdout = oldStdout + os.Stderr = oldStderr + stdoutPipeR.Close() + stderrPipeR.Close() // 返回捕获的输出字符串 // stderrBuf.String() return stdoutBuf.String() @@ -317,7 +313,9 @@ func ProcessAndUpload(output string, filePath string, enableUplaod bool) (string // 使用 defer 来处理 panic defer func() { if r := recover(); r != nil { - fmt.Printf("处理上传时发生错误: %v\n", r) + fmt.Fprintf(os.Stderr, "[ERROR] 处理上传时发生严重错误: %v\n", r) + // 可以选择打印堆栈信息以便调试 + // debug.PrintStack() } }() // 检查文件是否存在 @@ -425,6 +423,8 @@ func CheckPublicAccess(timeout time.Duration) NetCheckResult { defer wg.Done() defer func() { if r := recover(); r != nil { + // 记录panic但不影响其他检查,输出到stderr避免污染主输出 + fmt.Fprintf(os.Stderr, "[WARN] Panic in network check for %s (%s): %v\n", tag, addr, r) } }() switch kind {