From 5d59ca427e64837eb3b96cd71259a05968232447 Mon Sep 17 00:00:00 2001 From: logan Date: Wed, 27 May 2026 00:15:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20gh-ost=20=E5=B9=B3=E5=8F=B0=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=BC=80=E5=85=B3=E9=94=AE=20+=20=E4=BA=91=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93=E5=8F=82=E6=95=B0=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ghost_enabled: 总开关, 关闭时返回403禁止dry-run/execute - ghost_path: gh-ost二进制路径, 从system_settings读取 - ghost_default_chunk_size: 每批拷贝行数 - ghost_max_load: 负载阈值限流 - ghost_allow_on_master: 单机MySQL允许直接执行 - handler改为从DB读配置, 不再依赖config包 - 5个配置项已插入system_settings(id=26-30) --- backend/internal/handler/ghost.go | 59 ++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/backend/internal/handler/ghost.go b/backend/internal/handler/ghost.go index f06270e..0fcfb97 100644 --- a/backend/internal/handler/ghost.go +++ b/backend/internal/handler/ghost.go @@ -3,17 +3,41 @@ package handler import ( "fmt" "net/http" + "strconv" "time" - "sql-platform/config" "sql-platform/internal/ghost" "sql-platform/internal/model" "github.com/gin-gonic/gin" ) +// getSetting reads a system_setting value by key +func getSetting(key, fallback string) string { + var s model.SystemSetting + if model.DB.Where("`key` = ?", key).Take(&s).Error == nil && s.Value != "" { + return s.Value + } + return fallback +} + +// isGhostEnabled checks if gh-ost is enabled in system settings +func isGhostEnabled() bool { + return getSetting("ghost_enabled", "true") == "true" +} + +// getGhostPath returns the gh-ost binary path from settings +func getGhostPath() string { + return getSetting("ghost_path", "gh-ost") +} + // POST /api/v1/ghost/dry-run func GhOstDryRun(c *gin.Context) { + if !isGhostEnabled() { + c.JSON(http.StatusForbidden, gin.H{"detail": "gh-ost 功能已禁用,请在系统设置中开启"}) + return + } + var req struct { InstanceID int `json:"instance_id"` Database string `json:"database"` @@ -25,7 +49,6 @@ func GhOstDryRun(c *gin.Context) { return } - // Look up instance connection info task, err := buildGhOstTask(req.InstanceID, req.Database, req.Table, req.Alter) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) @@ -33,8 +56,9 @@ func GhOstDryRun(c *gin.Context) { } task.DryRun = true task.Execute = false + applyGhostSettings(task) - result, err := ghost.RunGhOst(c.Request.Context(), *task, config.C.GhOstPath) + result, err := ghost.RunGhOst(c.Request.Context(), *task, getGhostPath()) if err != nil { c.JSON(http.StatusInternalServerError, result) return @@ -51,12 +75,9 @@ func buildGhOstTask(instanceID int, database, table, alter string) (*ghost.GhOst if inst.DBType != "mysql" { return nil, fmt.Errorf("gh-ost only supports MySQL instances, got %s", inst.DBType) } - // gh-ost needs separate host and port - host := inst.Host - port := inst.Port return &ghost.GhOstTask{ - Host: host, - Port: port, + Host: inst.Host, + Port: inst.Port, User: inst.Username, Password: inst.Password, Database: database, @@ -65,8 +86,27 @@ func buildGhOstTask(instanceID int, database, table, alter string) (*ghost.GhOst }, nil } +// applyGhostSettings applies system_settings to the task (chunk-size, max-load, cloud flags) +func applyGhostSettings(task *ghost.GhOstTask) { + // Chunk size + if cs, err := strconv.Atoi(getSetting("ghost_default_chunk_size", "1000")); err == nil && cs > 0 { + task.ChunkSize = cs + } + // Max load + if ml := getSetting("ghost_max_load", ""); ml != "" { + task.MaxLoad = ml + } + // Allow on master + task.AllowOnMaster = getSetting("ghost_allow_on_master", "true") == "true" +} + // POST /api/v1/ghost/execute func GhOstExecute(c *gin.Context) { + if !isGhostEnabled() { + c.JSON(http.StatusForbidden, gin.H{"detail": "gh-ost 功能已禁用,请在系统设置中开启"}) + return + } + var req struct { InstanceID int `json:"instance_id"` Database string `json:"database"` @@ -85,8 +125,9 @@ func GhOstExecute(c *gin.Context) { } task.DryRun = false task.Execute = true + applyGhostSettings(task) - taskID := ghost.RunGhOstAsync(*task, config.C.GhOstPath, nil) + taskID := ghost.RunGhOstAsync(*task, getGhostPath(), nil) c.JSON(http.StatusAccepted, gin.H{ "task_id": taskID, "message": "gh-ost execution started",