优化部分设置逻辑

This commit is contained in:
2026-07-23 19:23:09 +08:00
parent 8c3d7246c2
commit 4cfb684f86
9 changed files with 467 additions and 420 deletions
+85 -76
View File
@@ -8,6 +8,7 @@ import (
"os/exec"
"runtime"
"strings"
"sync"
"syscall"
"text/template"
)
@@ -15,10 +16,30 @@ import (
//go:embed bin/*
var embeddedFrpc embed.FS
// 获取当前平台对应的 frpc 路径(从 embed 解压到临时目录)
func getFrpcPath() (string, error) {
var fileName string
//go:embed frpc.tmpl
var frpcTemplateContent string
var (
cachedFrpcPath string
frpcPathMutex sync.Mutex
)
// getFrpcPath 获取当前平台对应的 frpc 路径(优先使用缓存)
func getFrpcPath() (string, error) {
frpcPathMutex.Lock()
defer frpcPathMutex.Unlock()
// 如果缓存有效,直接返回
if cachedFrpcPath != "" {
if _, err := os.Stat(cachedFrpcPath); err == nil {
return cachedFrpcPath, nil
}
// 缓存失效,清空
cachedFrpcPath = ""
}
// 确定文件名
var fileName string
switch {
case runtime.GOOS == "windows" && runtime.GOARCH == "amd64":
fileName = "frpc_windows_amd64.exe"
@@ -27,20 +48,22 @@ func getFrpcPath() (string, error) {
case runtime.GOOS == "linux" && runtime.GOARCH == "arm64":
fileName = "frpc_linux_arm64"
default:
// 降级:尝试从 PATH 查找 frpc
// 降级:尝试从系统 PATH 查找 frpc
path, err := exec.LookPath("frpc")
if err == nil {
cachedFrpcPath = path
return path, nil
}
return "", fmt.Errorf("不支持的平台: %s/%s,且 PATH 中未找到 frpc", runtime.GOOS, runtime.GOARCH)
return "", fmt.Errorf("不支持的平台: %s/%s", runtime.GOOS, runtime.GOARCH)
}
// 从 embed 读取对应文件
// 从 embed 读取
data, err := embeddedFrpc.ReadFile("bin/" + fileName)
if err != nil {
// 如果 embed 中没有,降级到 PATH
// 降级到 PATH
path, lookErr := exec.LookPath("frpc")
if lookErr == nil {
cachedFrpcPath = path
return path, nil
}
return "", fmt.Errorf("未找到 frpc 文件 (embed 和 PATH 均无): %w", err)
@@ -51,40 +74,21 @@ func getFrpcPath() (string, error) {
if runtime.GOOS == "windows" {
tmpPath += ".exe"
}
// 尝试写入(如果文件已存在且正在使用,忽略错误,直接使用已有文件)
if err := os.WriteFile(tmpPath, data, 0755); err != nil {
// 如果写入失败,检查是否已有可用的 frpc
if _, statErr := os.Stat(tmpPath); statErr == nil {
cachedFrpcPath = tmpPath
return tmpPath, nil
}
return "", fmt.Errorf("写入临时文件失败: %w", err)
}
cachedFrpcPath = tmpPath
return tmpPath, nil
}
// frpc.toml 模板 - 完全复制你的配置文件结构
const frpcTemplate = `serverAddr = "{{.ServerAddr}}"
serverPort = {{.ServerPort}}
[auth]
token = "{{.Token}}"
[log]
to = "./frpc.log"
level = "{{.LogLevel}}"
maxDays = {{.LogMaxDays}}
[transport]
tcpMux = {{.TcpMux}}
tcpMuxKeepaliveInterval = {{.TcpMuxKeepalive}}
heartbeatInterval = {{.HeartbeatInterval}}
heartbeatTimeout = {{.HeartbeatTimeout}}
poolCount = {{.PoolCount}}
{{range .Proxies}}
[[proxies]]
name = "{{.Name}}"
type = "{{.Type}}"
localIP = "{{.LocalIP}}"
localPort = {{.LocalPort}}
remotePort = {{.RemotePort}}
{{end}}`
// GenerateFrpcConfig 生成 frpc.toml 配置文件
func GenerateFrpcConfig() error {
cfg, err := GetGlobalConfig()
@@ -112,19 +116,35 @@ func GenerateFrpcConfig() error {
Proxies: activeProxies,
}
tmpl, err := template.New("frpc").Parse(frpcTemplate)
// ===== 核心改动 =====
// 优先使用外部模板文件 frpc.tmpl,如果不存在则使用 embed 中的模板
var tmplContent string
if _, err := os.Stat("frpc.tmpl"); err == nil {
// 外部文件存在,读取它
content, readErr := os.ReadFile("frpc.tmpl")
if readErr == nil {
tmplContent = string(content)
} else {
// 读取失败时回退到 embed
tmplContent = frpcTemplateContent
}
} else {
// 外部文件不存在,使用 embed 中的模板
tmplContent = frpcTemplateContent
}
// ===== 核心改动结束 =====
tmpl, err := template.New("frpc").Parse(tmplContent)
if err != nil {
return fmt.Errorf("解析模板失败: %w", err)
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, data)
if err != nil {
if err := tmpl.Execute(&buf, data); err != nil {
return fmt.Errorf("渲染模板失败: %w", err)
}
err = os.WriteFile("./frpc.toml", buf.Bytes(), 0644)
if err != nil {
if err := os.WriteFile("./frpc.toml", buf.Bytes(), 0644); err != nil {
return fmt.Errorf("写入配置文件失败: %w", err)
}
@@ -138,50 +158,47 @@ func StartFrpc() error {
return fmt.Errorf("获取 frpc 路径失败: %w", err)
}
// 检查配置文件是否存在
// 确保配置文件存在
if _, err := os.Stat("./frpc.toml"); os.IsNotExist(err) {
if err := GenerateFrpcConfig(); err != nil {
return fmt.Errorf("生成配置文件失败: %w", err)
}
}
// 检查是否已在运行
// 检查是否已在运行
running, _ := GetFrpcStatus()
if running {
return nil // 已经在运行,不重复启动
return nil
}
cmd := exec.Command(frpcPath, "-c", "./frpc.toml")
// Windows 下需要隐藏窗口(可选)
if runtime.GOOS == "windows" {
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
}
err = cmd.Start()
if err != nil {
if err := cmd.Start(); err != nil {
return fmt.Errorf("启动 frpc 失败: %w", err)
}
return nil
}
// ReloadFrpc 热加载 frpc 配置
// ReloadFrpc 热加载 frpc 配置(仅发信号,不重复写入二进制)
func ReloadFrpc() error {
frpcPath, err := getFrpcPath()
if err != nil {
return fmt.Errorf("获取 frpc 路径失败: %w", err)
}
// 检查 frpc 是否在运行
running, err := GetFrpcStatus()
if err != nil {
return fmt.Errorf("检查 frpc 状态失败: %w", err)
}
if !running {
// 没运行就直接启动
return StartFrpc()
}
// 执行 reload
cmd := exec.Command(frpcPath, "reload", "-c", "./frpc.toml")
// 确保缓存路径存在
if cachedFrpcPath == "" {
if _, err := getFrpcPath(); err != nil {
return fmt.Errorf("获取 frpc 路径失败: %w", err)
}
}
cmd := exec.Command(cachedFrpcPath, "reload", "-c", "./frpc.toml")
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("reload 失败: %s, %w", string(output), err)
@@ -192,41 +209,33 @@ func ReloadFrpc() error {
// StopFrpc 停止 frpc
func StopFrpc() error {
if runtime.GOOS == "windows" {
// Windows 用 taskkill
cmd := exec.Command("taskkill", "/F", "/IM", "frpc.exe")
err := cmd.Run()
if err != nil && !strings.Contains(err.Error(), "not found") {
return fmt.Errorf("停止 frpc 失败: %w", err)
}
return nil
} else {
// Linux/Unix 用 pkill
cmd := exec.Command("pkill", "-f", "frpc")
err := cmd.Run()
if err != nil && !strings.Contains(err.Error(), "no process") {
if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "not found") {
return fmt.Errorf("停止 frpc 失败: %w", err)
}
return nil
}
cmd := exec.Command("pkill", "-f", "frpc")
if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "no process") {
return fmt.Errorf("停止 frpc 失败: %w", err)
}
return nil
}
// GetFrpcStatus 检查 frpc 是否在运行
func GetFrpcStatus() (bool, error) {
if runtime.GOOS == "windows" {
// Windows 用 tasklist 检查
cmd := exec.Command("tasklist", "/FI", "IMAGENAME eq frpc.exe")
output, err := cmd.CombinedOutput()
if err != nil {
return false, nil
}
return strings.Contains(string(output), "frpc.exe"), nil
} else {
// Linux/Unix 用 pgrep
cmd := exec.Command("pgrep", "-f", "frpc")
output, err := cmd.Output()
if err != nil {
return false, nil
}
return len(output) > 0, nil
}
cmd := exec.Command("pgrep", "-f", "frpc")
output, err := cmd.Output()
if err != nil {
return false, nil
}
return len(output) > 0, nil
}