From defc9d00d38b034e835af57fcf529d24506f8828 Mon Sep 17 00:00:00 2001 From: lxh2875931338 Date: Thu, 30 Jul 2026 00:18:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=A4=B9=E6=8C=82=E8=BD=BD?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=8F=98=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api.go | 3 +- db.go | 47 +------------------ deploy.sh | 5 +- frp.go | 136 ++++++++++++++++++++++++++++++++++++++++-------------- main.go | 10 +++- 5 files changed, 115 insertions(+), 86 deletions(-) diff --git a/api.go b/api.go index e514d8d..665d1bd 100644 --- a/api.go +++ b/api.go @@ -399,7 +399,8 @@ func getFrpcStatusHandler(c *gin.Context) { // ========== 日志 Handler ========== func getFrpcLogHandler(c *gin.Context) { - lines, err := readTailLog("./frpc.log", 200) + // 从 ./data/frpc.log 读取日志 + lines, err := readTailLog("./data/frpc.log", 200) if err != nil { c.JSON(http.StatusOK, gin.H{ "code": 0, diff --git a/db.go b/db.go index 5cb93ff..178abdb 100644 --- a/db.go +++ b/db.go @@ -17,17 +17,7 @@ import ( var DB *sql.DB -// ============================================================ -// 数据库版本常量 -// ============================================================ - -const ( - SchemaVersion = "v2" // 当前数据库 Schema 版本 -) - -// ============================================================ -// 数据模型 -// ============================================================ +const SchemaVersion = "v2" type GlobalConfig struct { ID int `json:"id"` @@ -61,10 +51,6 @@ type User struct { CreatedAt string `json:"createdAt"` } -// ============================================================ -// 数据库初始化 -// ============================================================ - func InitDB() error { // 确保 data 目录存在 if err := os.MkdirAll("./data", 0755); err != nil { @@ -94,12 +80,7 @@ func InitDB() error { return nil } -// ============================================================ -// 建表 -// ============================================================ - func createTables() error { - // 用户表 _, err := DB.Exec(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -113,7 +94,6 @@ func createTables() error { return err } - // 全局配置表 _, err = DB.Exec(` CREATE TABLE IF NOT EXISTS global_config ( id INTEGER PRIMARY KEY CHECK (id = 1), @@ -135,7 +115,6 @@ func createTables() error { return err } - // 隧道表 _, err = DB.Exec(` CREATE TABLE IF NOT EXISTS proxies ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -153,7 +132,6 @@ func createTables() error { return err } - // 应用配置表 _, err = DB.Exec(` CREATE TABLE IF NOT EXISTS app_config ( key TEXT PRIMARY KEY, @@ -165,7 +143,6 @@ func createTables() error { return err } - // 初始化默认配置 var count int DB.QueryRow("SELECT COUNT(*) FROM global_config").Scan(&count) if count == 0 { @@ -185,10 +162,6 @@ func createTables() error { return nil } -// ============================================================ -// 迁移引擎 -// ============================================================ - func getCurrentSchemaVersion() string { var version string err := DB.QueryRow("SELECT value FROM app_config WHERE key = 'schema_version'").Scan(&version) @@ -272,7 +245,6 @@ func runMigrations() error { log.Printf("📌 当前数据库 Schema: %s, 目标版本: %s", currentVer, targetVer) if currentVer == targetVer { - // 检查数据库是否包含有效数据 var userCount int err := DB.QueryRow("SELECT COUNT(*) FROM users").Scan(&userCount) if err != nil || userCount == 0 { @@ -302,7 +274,6 @@ func runMigrations() error { if currentSchema == nil || schemaVersionsEqual(currentSchema, targetSchema) { log.Println(" 迁移类型: 轻量复制(Schema 无变更)") - // 验证数据库是否有效 var userCount int err := DB.QueryRow("SELECT COUNT(*) FROM users").Scan(&userCount) if err != nil || userCount == 0 { @@ -448,10 +419,6 @@ func buildInsertSQL(oldTable, newTable string, oldDef, newDef *SchemaVersionDef) ), nil } -// ============================================================ -// JWT 密钥管理 -// ============================================================ - func ensureJwtSecret() error { var value string err := DB.QueryRow("SELECT value FROM app_config WHERE key = 'jwt_secret'").Scan(&value) @@ -484,10 +451,6 @@ func GetJwtSecret() (string, error) { return secret, nil } -// ============================================================ -// 全局配置 CRUD -// ============================================================ - func GetGlobalConfig() (*GlobalConfig, error) { var cfg GlobalConfig err := DB.QueryRow(` @@ -523,10 +486,6 @@ func UpdateGlobalConfig(cfg *GlobalConfig) error { return err } -// ============================================================ -// 隧道 CRUD -// ============================================================ - func GetProxies() ([]Proxy, error) { rows, err := DB.Query(` SELECT id, name, type, local_ip, local_port, remote_port, enabled @@ -589,10 +548,6 @@ func DeleteProxy(id int) error { return err } -// ============================================================ -// 用户 CRUD -// ============================================================ - func GetUserByUsername(username string) (*User, error) { var u User err := DB.QueryRow(` diff --git a/deploy.sh b/deploy.sh index e68d67c..340797f 100644 --- a/deploy.sh +++ b/deploy.sh @@ -431,11 +431,12 @@ do_deploy() { # ----- 启动新容器 ----- print_step "启动 frpc-console 容器..." +# 启动容器(只挂载数据目录) docker run -d \ --name frpc-console \ --restart=always \ --network host \ - -v ${DEPLOY_DIR}:/app \ + -v ${DEPLOY_DIR}/data:/app/data \ -e PORT=${PORT} \ -e TZ=Asia/Shanghai \ ${IMAGE_NAME}:${IMAGE_TAG} @@ -449,7 +450,7 @@ do_deploy() { # ----- 写入版本文件 ----- print_step "写入版本文件..." - echo "$TARGET_VERSION" > "${DEPLOY_DIR}/version.ini" + echo "$TARGET_VERSION" > "${DEPLOY_DIR}/data/version.ini" print_success "版本写入: ${TARGET_VERSION}" # ----- 检查 frpc 子进程 ----- diff --git a/frp.go b/frp.go index 85b63b9..1462e58 100644 --- a/frp.go +++ b/frp.go @@ -4,6 +4,7 @@ import ( "bytes" "embed" "fmt" + "io" "log" "os" "os/exec" @@ -24,16 +25,12 @@ var FrpcTemplateContent string var ( cachedFrpcPath string - frpcPathMutex sync.Mutex + frpsPathMutex sync.Mutex ) -// ============================================================ -// 获取 frpc 路径 -// ============================================================ - func getFrpcPath() (string, error) { - frpcPathMutex.Lock() - defer frpcPathMutex.Unlock() + frpsPathMutex.Lock() + defer frpsPathMutex.Unlock() if cachedFrpcPath != "" { if _, err := os.Stat(cachedFrpcPath); err == nil { @@ -61,14 +58,12 @@ func getFrpcPath() (string, error) { return "", fmt.Errorf("不支持的平台: %s/%s", runtime.GOOS, runtime.GOARCH) } - // 优先级1:本地 ./bin/ localPath := filepath.Join(".", "bin", fileName) if _, err := os.Stat(localPath); err == nil { cachedFrpcPath = localPath return localPath, nil } - // 优先级2:embed 解压 data, err := embeddedFrpc.ReadFile("bin/" + fileName) if err == nil { tmpPath := filepath.Join(os.TempDir(), "frpc") @@ -85,7 +80,6 @@ func getFrpcPath() (string, error) { } } - // 优先级3:系统 PATH path, err := exec.LookPath("frpc") if err == nil { cachedFrpcPath = path @@ -95,10 +89,6 @@ func getFrpcPath() (string, error) { return "", fmt.Errorf("未找到 frpc 文件") } -// ============================================================ -// 生成 frpc.toml(v2.0 支持 wireProtocol) -// ============================================================ - func GenerateFrpcConfig() error { cfg, err := GetGlobalConfig() if err != nil { @@ -117,24 +107,21 @@ func GenerateFrpcConfig() error { } } - // 构建模板数据 data := struct { *GlobalConfig Proxies []Proxy - WireProtocolLine string // v2 协议配置行(为空则不输出) + WireProtocolLine string }{ GlobalConfig: cfg, Proxies: activeProxies, } - // v2.0: 如果启用 v2 协议,生成配置行 if cfg.WireProtocolV2 { data.WireProtocolLine = `wireProtocol = "v2"` } else { - data.WireProtocolLine = "" // 不输出,使用默认 v1 + data.WireProtocolLine = "" } - // 读取模板 var tmplContent string if _, err := os.Stat("frpc.tmpl"); err == nil { content, readErr := os.ReadFile("frpc.tmpl") @@ -157,19 +144,20 @@ func GenerateFrpcConfig() error { return fmt.Errorf("渲染模板失败: %w", err) } - if err := os.WriteFile("./frpc.toml", buf.Bytes(), 0644); err != nil { + // 所有文件写入 ./data/ 目录 + if err := os.MkdirAll("./data", 0755); err != nil { + return fmt.Errorf("创建 data 目录失败: %w", err) + } + + if err := os.WriteFile("./data/frpc.toml", buf.Bytes(), 0644); err != nil { return fmt.Errorf("写入配置文件失败: %w", err) } return nil } -// ============================================================ -// frpc 进程管理 -// ============================================================ - func isFrpcRunning() bool { - pidData, err := os.ReadFile("./frpc.pid") + pidData, err := os.ReadFile("./data/frpc.pid") if err != nil { return false } @@ -200,7 +188,11 @@ func StartFrpc() error { return fmt.Errorf("获取 frpc 路径失败: %w", err) } - if _, err := os.Stat("./frpc.toml"); os.IsNotExist(err) { + if err := os.MkdirAll("./data", 0755); err != nil { + return fmt.Errorf("创建 data 目录失败: %w", err) + } + + if _, err := os.Stat("./data/frpc.toml"); os.IsNotExist(err) { if err := GenerateFrpcConfig(); err != nil { return fmt.Errorf("生成配置文件失败: %w", err) } @@ -210,13 +202,13 @@ func StartFrpc() error { return nil } - os.Remove("./frpc.pid") + os.Remove("./data/frpc.pid") - cmd := exec.Command(frpcPath, "-c", "./frpc.toml") + cmd := exec.Command(frpcPath, "-c", "./data/frpc.toml") setWindowHide(cmd) setSysProcAttr(cmd) - logFile, err := os.OpenFile("./frpc.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + logFile, err := os.OpenFile("./data/frpc.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) if err != nil { return fmt.Errorf("打开日志文件失败: %w", err) } @@ -227,7 +219,7 @@ func StartFrpc() error { return fmt.Errorf("启动 frpc 失败: %w", err) } - if err := os.WriteFile("./frpc.pid", []byte(fmt.Sprintf("%d", cmd.Process.Pid)), 0644); err != nil { + if err := os.WriteFile("./data/frpc.pid", []byte(fmt.Sprintf("%d", cmd.Process.Pid)), 0644); err != nil { return fmt.Errorf("保存 PID 失败: %w", err) } @@ -240,11 +232,11 @@ func StopFrpc() error { if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "not found") { return fmt.Errorf("停止 frpc 失败: %w", err) } - os.Remove("./frpc.pid") + os.Remove("./data/frpc.pid") return nil } - pidData, err := os.ReadFile("./frpc.pid") + pidData, err := os.ReadFile("./data/frpc.pid") if err != nil { cmd := exec.Command("pkill", "-f", "frpc") if err := cmd.Run(); err != nil && !strings.Contains(err.Error(), "no process") { @@ -256,7 +248,7 @@ func StopFrpc() error { pid, _ := strconv.Atoi(strings.TrimSpace(string(pidData))) process, err := os.FindProcess(pid) if err != nil { - os.Remove("./frpc.pid") + os.Remove("./data/frpc.pid") return nil } @@ -264,7 +256,7 @@ func StopFrpc() error { return fmt.Errorf("杀死进程失败: %w", err) } - os.Remove("./frpc.pid") + os.Remove("./data/frpc.pid") return nil } @@ -283,7 +275,7 @@ func ReloadFrpc() error { return fmt.Errorf("获取 frpc 路径失败: %w", err) } - cmd := exec.Command(frpcPath, "reload", "-c", "./frpc.toml") + cmd := exec.Command(frpcPath, "reload", "-c", "./data/frpc.toml") _, err = cmd.CombinedOutput() if err != nil { log.Printf("⚠️ 热加载失败 (%v),自动降级为重启 frpc", err) @@ -297,3 +289,77 @@ func ReloadFrpc() error { } return nil } + +// readTailLog 读取文件末尾 n 行 +func readTailLog(filePath string, n int) ([]string, error) { + file, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer file.Close() + + info, err := file.Stat() + if err != nil { + return nil, err + } + fileSize := info.Size() + if fileSize == 0 { + return []string{}, nil + } + + const chunkSize = 4096 + var lines []string + var leftover []byte + offset := fileSize + + for len(lines) < n && offset > 0 { + readSize := chunkSize + if offset < int64(chunkSize) { + readSize = int(offset) + } + offset -= int64(readSize) + + buf := make([]byte, readSize) + _, err := file.ReadAt(buf, offset) + if err != nil && err != io.EOF { + return nil, err + } + + data := append(buf, leftover...) + leftover = nil + + start := 0 + for i := len(data) - 1; i >= 0; i-- { + if data[i] == '\n' { + if i+1 < len(data) { + line := string(data[i+1:]) + if line != "" { + lines = append([]string{line}, lines...) + if len(lines) >= n { + break + } + } + } + start = i + } + } + + if len(lines) < n && start > 0 { + leftover = data[:start] + } + } + + if len(lines) < n && len(leftover) > 0 { + parts := strings.Split(string(leftover), "\n") + for i := len(parts) - 1; i >= 0; i-- { + if parts[i] != "" { + lines = append([]string{parts[i]}, lines...) + if len(lines) >= n { + break + } + } + } + } + + return lines, nil +} diff --git a/main.go b/main.go index 7762c7b..4bef73f 100644 --- a/main.go +++ b/main.go @@ -8,8 +8,13 @@ import ( ) func main() { - // 读取 version.ini 显示版本(如果存在) - if data, err := os.ReadFile("./version.ini"); err == nil { + // 确保 data 目录存在 + if err := os.MkdirAll("./data", 0755); err != nil { + log.Printf("⚠️ 创建 data 目录失败: %v", err) + } + + // 读取 version.ini 显示版本 + if data, err := os.ReadFile("./data/version.ini"); err == nil { version := strings.TrimSpace(string(data)) log.Printf("📌 版本: %s", version) } else { @@ -43,6 +48,7 @@ func main() { func startWatchdog() { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() + for range ticker.C { if !isFrpcRunning() { log.Println("⚠️ frpc 进程已停止,自动重启...")