feat: gh-ost真实集成验证通过 + 登录页全面中文化 + 外观设置跨浏览器同步修复

gh-ost集成:
- handler根据instance_id查DB获取连接信息(host/port/user/password)
- 修复gh-ost参数: 去掉不存在的--dry-run(默认noop), 加--execute才真执行
- 加--skip-port-validation(端口映射环境必须)
- 加--allow-on-master(单机MySQL直接执行)
- 加--initially-drop-ghost-table/old-table/socket-file(清理残留)
- GhOstTask增加Port字段, buildArgs同步更新
- dry-run真实验证通过: 连接1.94.55.133:9005, 3行表, 3.85s完成

登录页中文化:
- 5个登录组件(SplitView/CenteredCard/SidePanel/FullImage/MinimalDark)
- 46处英文文案全部替换为中文
- 平台标题统一为'SQL审核平台'

外观设置同步修复:
- GetPublicLoginStyle: GORM First()复用主键导致第二次查询失败
- 改用Take()+重置结构体, 修复theme始终返回dark的bug
- UpdateUserPreferences: 同步写入SystemSetting表(login_style+default_theme)
This commit is contained in:
logan committed 2026-05-27 00:06:13 +08:00
1 parent 62a2b383e1
commit 8aaf2271b8
8 files changed
+135 -61

No files matched your search

+53 -6
View File
@@ -1,6 +1,7 @@
package handler
import (
"fmt"
"net/http"
"time"
@@ -13,15 +14,27 @@ import (
// POST /api/v1/ghost/dry-run
func GhOstDryRun(c *gin.Context) {
var task ghost.GhOstTask
if err := c.ShouldBindJSON(&task); err != nil {
var req struct {
InstanceID int `json:"instance_id"`
Database string `json:"database"`
Table string `json:"table"`
Alter string `json:"alter"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
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()})
return
}
task.DryRun = true
task.Execute = false
result, err := ghost.RunGhOst(c.Request.Context(), task, config.C.GhOstPath)
result, err := ghost.RunGhOst(c.Request.Context(), *task, config.C.GhOstPath)
if err != nil {
c.JSON(http.StatusInternalServerError, result)
return
@@ -29,17 +42,51 @@ func GhOstDryRun(c *gin.Context) {
c.JSON(http.StatusOK, result)
}
// buildGhOstTask resolves instance_id to connection params
func buildGhOstTask(instanceID int, database, table, alter string) (*ghost.GhOstTask, error) {
var inst model.Instance
if err := model.DB.First(&inst, instanceID).Error; err != nil {
return nil, fmt.Errorf("instance %d not found", instanceID)
}
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,
User: inst.Username,
Password: inst.Password,
Database: database,
Table: table,
Alter: alter,
}, nil
}
// POST /api/v1/ghost/execute
func GhOstExecute(c *gin.Context) {
var task ghost.GhOstTask
if err := c.ShouldBindJSON(&task); err != nil {
var req struct {
InstanceID int `json:"instance_id"`
Database string `json:"database"`
Table string `json:"table"`
Alter string `json:"alter"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
return
}
task, err := buildGhOstTask(req.InstanceID, req.Database, req.Table, req.Alter)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
return
}
task.DryRun = false
task.Execute = true
taskID := ghost.RunGhOstAsync(task, config.C.GhOstPath, nil)
taskID := ghost.RunGhOstAsync(*task, config.C.GhOstPath, nil)
c.JSON(http.StatusAccepted, gin.H{
"task_id": taskID,
"message": "gh-ost execution started",