feat: gh-ost 平台配置开关键 + 云数据库参数支持

- 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)
This commit is contained in:
logan committed 2026-05-27 00:15:32 +08:00
1 parent 8aaf2271b8
commit 5d59ca427e
1 file changed
+50 -9
+50 -9
View File
@@ -3,17 +3,41 @@ package handler
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"strconv"
"time" "time"
"sql-platform/config"
"sql-platform/internal/ghost" "sql-platform/internal/ghost"
"sql-platform/internal/model" "sql-platform/internal/model"
"github.com/gin-gonic/gin" "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 // POST /api/v1/ghost/dry-run
func GhOstDryRun(c *gin.Context) { func GhOstDryRun(c *gin.Context) {
if !isGhostEnabled() {
c.JSON(http.StatusForbidden, gin.H{"detail": "gh-ost 功能已禁用,请在系统设置中开启"})
return
}
var req struct { var req struct {
InstanceID int `json:"instance_id"` InstanceID int `json:"instance_id"`
Database string `json:"database"` Database string `json:"database"`
@@ -25,7 +49,6 @@ func GhOstDryRun(c *gin.Context) {
return return
} }
// Look up instance connection info
task, err := buildGhOstTask(req.InstanceID, req.Database, req.Table, req.Alter) task, err := buildGhOstTask(req.InstanceID, req.Database, req.Table, req.Alter)
if err != nil { if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()}) c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
@@ -33,8 +56,9 @@ func GhOstDryRun(c *gin.Context) {
} }
task.DryRun = true task.DryRun = true
task.Execute = false 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 { if err != nil {
c.JSON(http.StatusInternalServerError, result) c.JSON(http.StatusInternalServerError, result)
return return
@@ -51,12 +75,9 @@ func buildGhOstTask(instanceID int, database, table, alter string) (*ghost.GhOst
if inst.DBType != "mysql" { if inst.DBType != "mysql" {
return nil, fmt.Errorf("gh-ost only supports MySQL instances, got %s", inst.DBType) 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{ return &ghost.GhOstTask{
Host: host, Host: inst.Host,
Port: port, Port: inst.Port,
User: inst.Username, User: inst.Username,
Password: inst.Password, Password: inst.Password,
Database: database, Database: database,
@@ -65,8 +86,27 @@ func buildGhOstTask(instanceID int, database, table, alter string) (*ghost.GhOst
}, nil }, 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 // POST /api/v1/ghost/execute
func GhOstExecute(c *gin.Context) { func GhOstExecute(c *gin.Context) {
if !isGhostEnabled() {
c.JSON(http.StatusForbidden, gin.H{"detail": "gh-ost 功能已禁用,请在系统设置中开启"})
return
}
var req struct { var req struct {
InstanceID int `json:"instance_id"` InstanceID int `json:"instance_id"`
Database string `json:"database"` Database string `json:"database"`
@@ -85,8 +125,9 @@ func GhOstExecute(c *gin.Context) {
} }
task.DryRun = false task.DryRun = false
task.Execute = true 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{ c.JSON(http.StatusAccepted, gin.H{
"task_id": taskID, "task_id": taskID,
"message": "gh-ost execution started", "message": "gh-ost execution started",