diff --git a/app/http/controllers/plugins/mysql57/mysql57_controller.go b/app/http/controllers/plugins/mysql57/mysql57_controller.go index 074d239f..894df634 100644 --- a/app/http/controllers/plugins/mysql57/mysql57_controller.go +++ b/app/http/controllers/plugins/mysql57/mysql57_controller.go @@ -3,15 +3,13 @@ package mysql57 import ( "database/sql" "fmt" - "os" - "path/filepath" "regexp" "strings" "github.com/goravel/framework/contracts/http" "github.com/goravel/framework/facades" - "github.com/goravel/framework/support/carbon" "github.com/spf13/cast" + "panel/app/http/controllers" "panel/app/models" "panel/app/services" @@ -20,11 +18,13 @@ import ( type Mysql57Controller struct { setting services.Setting + backup services.Backup } func NewMysql57Controller() *Mysql57Controller { return &Mysql57Controller{ setting: services.NewSettingImpl(), + backup: services.NewBackupImpl(), } } @@ -441,36 +441,14 @@ func (c *Mysql57Controller) DeleteDatabase(ctx http.Context) { // BackupList 获取备份列表 func (c *Mysql57Controller) BackupList(ctx http.Context) { - backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql" - - if !tools.Exists(backupPath) { - tools.Mkdir(backupPath, 0644) - } - - files, err := os.ReadDir(backupPath) + backupList, err := c.backup.MysqlList() if err != nil { + facades.Log().Error("[MySQL57] 获取备份列表失败:" + err.Error()) controllers.Error(ctx, http.StatusInternalServerError, "获取备份列表失败") return } - var backupFiles []map[string]string - for _, file := range files { - if file.IsDir() { - continue - } - - info, err := file.Info() - if err != nil { - continue - } - - backupFiles = append(backupFiles, map[string]string{ - "file": file.Name(), - "size": tools.FormatBytes(float64(info.Size())), - }) - } - - controllers.Success(ctx, backupFiles) + controllers.Success(ctx, backupList) } // UploadBackup 上传备份 @@ -518,25 +496,14 @@ func (c *Mysql57Controller) CreateBackup(ctx http.Context) { return } - backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql" - rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) database := ctx.Request().Input("database") - backupFile := database + "_" + carbon.Now().ToShortDateTimeString() + ".sql" - if !tools.Exists(backupPath) { - tools.Mkdir(backupPath, 0644) - } - err = os.Setenv("MYSQL_PWD", rootPassword) + err = c.backup.MysqlBackup(database) if err != nil { - facades.Log().Error("[MySQL57] 设置环境变量 MYSQL_PWD 失败:" + err.Error()) - controllers.Error(ctx, http.StatusInternalServerError, "备份失败") + facades.Log().Error("[MYSQL57] 创建备份失败:" + err.Error()) + controllers.Error(ctx, http.StatusInternalServerError, "创建备份失败") return } - tools.ExecShell("mysqldump -uroot " + database + " > " + backupPath + "/" + backupFile) - tools.ExecShell("cd " + backupPath + " && zip -r " + backupPath + "/" + backupFile + ".zip " + backupFile) - tools.RemoveFile(backupPath + "/" + backupFile) - _ = os.Unsetenv("MYSQL_PWD") - controllers.Success(ctx, "备份成功") } @@ -547,7 +514,7 @@ func (c *Mysql57Controller) DeleteBackup(ctx http.Context) { } validator, err := ctx.Request().Validate(map[string]string{ - "file": "required|min_len:1|max_len:255", + "name": "required|min_len:1|max_len:255", }) if err != nil { controllers.Error(ctx, http.StatusBadRequest, err.Error()) @@ -559,8 +526,8 @@ func (c *Mysql57Controller) DeleteBackup(ctx http.Context) { } backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql" - file := ctx.Request().Input("file") - tools.RemoveFile(backupPath + "/" + file) + fileName := ctx.Request().Input("name") + tools.RemoveFile(backupPath + "/" + fileName) controllers.Success(ctx, "删除备份成功") } @@ -572,7 +539,7 @@ func (c *Mysql57Controller) RestoreBackup(ctx http.Context) { } validator, err := ctx.Request().Validate(map[string]string{ - "file": "required|min_len:1|max_len:255", + "name": "required|min_len:1|max_len:255", "database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys", }) if err != nil { @@ -584,57 +551,13 @@ func (c *Mysql57Controller) RestoreBackup(ctx http.Context) { return } - backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql" - rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) - file := ctx.Request().Input("file") - backupFile := backupPath + "/" + file - if !tools.Exists(backupFile) { - controllers.Error(ctx, http.StatusBadRequest, "备份文件不存在") - return - } - - err = os.Setenv("MYSQL_PWD", rootPassword) + err = c.backup.MysqlRestore(ctx.Request().Input("database"), ctx.Request().Input("name")) if err != nil { - facades.Log().Error("[MYSQL57] 设置环境变量 MYSQL_PWD 失败:" + err.Error()) - controllers.Error(ctx, http.StatusInternalServerError, "还原失败") + facades.Log().Error("[MYSQL57] 还原失败:" + err.Error()) + controllers.Error(ctx, http.StatusInternalServerError, "还原失败: "+err.Error()) return } - // 获取文件拓展名 - ext := filepath.Ext(file) - switch ext { - case ".zip": - tools.ExecShell("unzip -o " + backupFile + " -d " + backupPath) - backupFile = strings.TrimSuffix(backupFile, ext) - case ".gz": - if strings.HasSuffix(file, ".tar.gz") { - // 解压.tar.gz文件 - tools.ExecShell("tar -zxvf " + backupFile + " -C " + backupPath) - backupFile = strings.TrimSuffix(backupFile, ".tar.gz") - } else { - // 解压.gz文件 - tools.ExecShell("gzip -d " + backupFile) - backupFile = strings.TrimSuffix(backupFile, ext) - } - case ".bz2": - tools.ExecShell("bzip2 -d " + backupFile) - backupFile = strings.TrimSuffix(backupFile, ext) - case ".tar": - tools.ExecShell("tar -xvf " + backupFile + " -C " + backupPath) - backupFile = strings.TrimSuffix(backupFile, ext) - case ".rar": - tools.ExecShell("unrar x " + backupFile + " " + backupPath) - backupFile = strings.TrimSuffix(backupFile, ext) - } - - if !tools.Exists(backupFile) { - controllers.Error(ctx, http.StatusBadRequest, "自动解压备份文件失败,请手动解压") - return - } - - tools.ExecShell("mysql -uroot " + ctx.Request().Input("database") + " < " + backupFile) - _ = os.Unsetenv("MYSQL_PWD") - controllers.Success(ctx, "还原成功") } diff --git a/app/http/controllers/plugins/mysql80/mysql80_controller.go b/app/http/controllers/plugins/mysql80/mysql80_controller.go index 1954c1a2..f2ef20fd 100644 --- a/app/http/controllers/plugins/mysql80/mysql80_controller.go +++ b/app/http/controllers/plugins/mysql80/mysql80_controller.go @@ -3,15 +3,13 @@ package mysql80 import ( "database/sql" "fmt" - "os" - "path/filepath" "regexp" "strings" "github.com/goravel/framework/contracts/http" "github.com/goravel/framework/facades" - "github.com/goravel/framework/support/carbon" "github.com/spf13/cast" + "panel/app/http/controllers" "panel/app/models" "panel/app/services" @@ -20,11 +18,13 @@ import ( type Mysql80Controller struct { setting services.Setting + backup services.Backup } func NewMysql80Controller() *Mysql80Controller { return &Mysql80Controller{ setting: services.NewSettingImpl(), + backup: services.NewBackupImpl(), } } @@ -441,36 +441,14 @@ func (c *Mysql80Controller) DeleteDatabase(ctx http.Context) { // BackupList 获取备份列表 func (c *Mysql80Controller) BackupList(ctx http.Context) { - backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql" - - if !tools.Exists(backupPath) { - tools.Mkdir(backupPath, 0644) - } - - files, err := os.ReadDir(backupPath) + backupList, err := c.backup.MysqlList() if err != nil { + facades.Log().Error("[MySQL80] 获取备份列表失败:" + err.Error()) controllers.Error(ctx, http.StatusInternalServerError, "获取备份列表失败") return } - var backupFiles []map[string]string - for _, file := range files { - if file.IsDir() { - continue - } - - info, err := file.Info() - if err != nil { - continue - } - - backupFiles = append(backupFiles, map[string]string{ - "file": file.Name(), - "size": tools.FormatBytes(float64(info.Size())), - }) - } - - controllers.Success(ctx, backupFiles) + controllers.Success(ctx, backupList) } // UploadBackup 上传备份 @@ -518,25 +496,14 @@ func (c *Mysql80Controller) CreateBackup(ctx http.Context) { return } - backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql" - rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) database := ctx.Request().Input("database") - backupFile := database + "_" + carbon.Now().ToShortDateTimeString() + ".sql" - if !tools.Exists(backupPath) { - tools.Mkdir(backupPath, 0644) - } - err = os.Setenv("MYSQL_PWD", rootPassword) + err = c.backup.MysqlBackup(database) if err != nil { - facades.Log().Error("[MySQL80] 设置环境变量 MYSQL_PWD 失败:" + err.Error()) - controllers.Error(ctx, http.StatusInternalServerError, "备份失败") + facades.Log().Error("[MYSQL80] 创建备份失败:" + err.Error()) + controllers.Error(ctx, http.StatusInternalServerError, "创建备份失败") return } - tools.ExecShell("mysqldump -uroot " + database + " > " + backupPath + "/" + backupFile) - tools.ExecShell("cd " + backupPath + " && zip -r " + backupPath + "/" + backupFile + ".zip " + backupFile) - tools.RemoveFile(backupPath + "/" + backupFile) - _ = os.Unsetenv("MYSQL_PWD") - controllers.Success(ctx, "备份成功") } @@ -547,7 +514,7 @@ func (c *Mysql80Controller) DeleteBackup(ctx http.Context) { } validator, err := ctx.Request().Validate(map[string]string{ - "file": "required|min_len:1|max_len:255", + "name": "required|min_len:1|max_len:255", }) if err != nil { controllers.Error(ctx, http.StatusBadRequest, err.Error()) @@ -559,8 +526,8 @@ func (c *Mysql80Controller) DeleteBackup(ctx http.Context) { } backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql" - file := ctx.Request().Input("file") - tools.RemoveFile(backupPath + "/" + file) + fileName := ctx.Request().Input("name") + tools.RemoveFile(backupPath + "/" + fileName) controllers.Success(ctx, "删除备份成功") } @@ -572,7 +539,7 @@ func (c *Mysql80Controller) RestoreBackup(ctx http.Context) { } validator, err := ctx.Request().Validate(map[string]string{ - "file": "required|min_len:1|max_len:255", + "name": "required|min_len:1|max_len:255", "database": "required|min_len:1|max_len:255|regex:^[a-zA-Z][a-zA-Z0-9_]+$|not_in:information_schema,mysql,performance_schema,sys", }) if err != nil { @@ -584,57 +551,13 @@ func (c *Mysql80Controller) RestoreBackup(ctx http.Context) { return } - backupPath := c.setting.Get(models.SettingKeyBackupPath) + "/mysql" - rootPassword := c.setting.Get(models.SettingKeyMysqlRootPassword) - file := ctx.Request().Input("file") - backupFile := backupPath + "/" + file - if !tools.Exists(backupFile) { - controllers.Error(ctx, http.StatusBadRequest, "备份文件不存在") - return - } - - err = os.Setenv("MYSQL_PWD", rootPassword) + err = c.backup.MysqlRestore(ctx.Request().Input("database"), ctx.Request().Input("name")) if err != nil { - facades.Log().Error("[MYSQL80] 设置环境变量 MYSQL_PWD 失败:" + err.Error()) - controllers.Error(ctx, http.StatusInternalServerError, "还原失败") + facades.Log().Error("[MYSQL80] 还原失败:" + err.Error()) + controllers.Error(ctx, http.StatusInternalServerError, "还原失败: "+err.Error()) return } - // 获取文件拓展名 - ext := filepath.Ext(file) - switch ext { - case ".zip": - tools.ExecShell("unzip -o " + backupFile + " -d " + backupPath) - backupFile = strings.TrimSuffix(backupFile, ext) - case ".gz": - if strings.HasSuffix(file, ".tar.gz") { - // 解压.tar.gz文件 - tools.ExecShell("tar -zxvf " + backupFile + " -C " + backupPath) - backupFile = strings.TrimSuffix(backupFile, ".tar.gz") - } else { - // 解压.gz文件 - tools.ExecShell("gzip -d " + backupFile) - backupFile = strings.TrimSuffix(backupFile, ext) - } - case ".bz2": - tools.ExecShell("bzip2 -d " + backupFile) - backupFile = strings.TrimSuffix(backupFile, ext) - case ".tar": - tools.ExecShell("tar -xvf " + backupFile + " -C " + backupPath) - backupFile = strings.TrimSuffix(backupFile, ext) - case ".rar": - tools.ExecShell("unrar x " + backupFile + " " + backupPath) - backupFile = strings.TrimSuffix(backupFile, ext) - } - - if !tools.Exists(backupFile) { - controllers.Error(ctx, http.StatusBadRequest, "自动解压备份文件失败,请手动解压") - return - } - - tools.ExecShell("mysql -uroot " + ctx.Request().Input("database") + " < " + backupFile) - _ = os.Unsetenv("MYSQL_PWD") - controllers.Success(ctx, "还原成功") } diff --git a/app/services/backup.go b/app/services/backup.go index 66a376ea..d2fda8ed 100644 --- a/app/services/backup.go +++ b/app/services/backup.go @@ -4,6 +4,8 @@ package services import ( "errors" "os" + "path/filepath" + "strings" "github.com/goravel/framework/support/carbon" @@ -15,6 +17,9 @@ type Backup interface { WebsiteList() ([]BackupFile, error) WebSiteBackup(website models.Website) error WebsiteRestore(website models.Website, backupFile string) error + MysqlList() ([]BackupFile, error) + MysqlBackup(database string) error + MysqlRestore(database string, backupFile string) error } type BackupFile struct { @@ -32,18 +37,19 @@ func NewBackupImpl() *BackupImpl { } } +// WebsiteList 网站备份列表 func (s *BackupImpl) WebsiteList() ([]BackupFile, error) { - path := s.setting.Get(models.SettingKeyBackupPath) - if len(path) == 0 { + backupPath := s.setting.Get(models.SettingKeyBackupPath) + if len(backupPath) == 0 { return []BackupFile{}, nil } - path += "/website" - if !tools.Exists(path) { - tools.Mkdir(path, 0644) + backupPath += "/website" + if !tools.Exists(backupPath) { + tools.Mkdir(backupPath, 0644) } - files, err := os.ReadDir(path) + files, err := os.ReadDir(backupPath) if err != nil { return []BackupFile{}, err } @@ -62,6 +68,7 @@ func (s *BackupImpl) WebsiteList() ([]BackupFile, error) { return backupList, nil } +// WebSiteBackup 网站备份 func (s *BackupImpl) WebSiteBackup(website models.Website) error { backupPath := s.setting.Get(models.SettingKeyBackupPath) if len(backupPath) == 0 { @@ -79,6 +86,7 @@ func (s *BackupImpl) WebSiteBackup(website models.Website) error { return nil } +// WebsiteRestore 网站恢复 func (s *BackupImpl) WebsiteRestore(website models.Website, backupFile string) error { backupPath := s.setting.Get(models.SettingKeyBackupPath) if len(backupPath) == 0 { @@ -102,3 +110,105 @@ func (s *BackupImpl) WebsiteRestore(website models.Website, backupFile string) e return nil } + +// MysqlList MySQL备份列表 +func (s *BackupImpl) MysqlList() ([]BackupFile, error) { + backupPath := s.setting.Get(models.SettingKeyBackupPath) + if len(backupPath) == 0 { + return []BackupFile{}, nil + } + + backupPath += "/mysql" + if !tools.Exists(backupPath) { + tools.Mkdir(backupPath, 0644) + } + + files, err := os.ReadDir(backupPath) + if err != nil { + return []BackupFile{}, err + } + var backupList []BackupFile + for _, file := range files { + info, err := file.Info() + if err != nil { + continue + } + backupList = append(backupList, BackupFile{ + Name: file.Name(), + Size: tools.FormatBytes(float64(info.Size())), + }) + } + + return backupList, nil +} + +// MysqlBackup MySQL备份 +func (s *BackupImpl) MysqlBackup(database string) error { + backupPath := s.setting.Get(models.SettingKeyBackupPath) + "/mysql" + rootPassword := s.setting.Get(models.SettingKeyMysqlRootPassword) + backupFile := database + "_" + carbon.Now().ToShortDateTimeString() + ".sql" + if !tools.Exists(backupPath) { + tools.Mkdir(backupPath, 0644) + } + err := os.Setenv("MYSQL_PWD", rootPassword) + if err != nil { + return err + } + + tools.ExecShell("mysqldump -uroot " + database + " > " + backupPath + "/" + backupFile) + tools.ExecShell("cd " + backupPath + " && zip -r " + backupPath + "/" + backupFile + ".zip " + backupFile) + tools.RemoveFile(backupPath + "/" + backupFile) + _ = os.Unsetenv("MYSQL_PWD") + + return nil +} + +// MysqlRestore MySQL恢复 +func (s *BackupImpl) MysqlRestore(database string, backupFile string) error { + backupPath := s.setting.Get(models.SettingKeyBackupPath) + "/mysql" + rootPassword := s.setting.Get(models.SettingKeyMysqlRootPassword) + ext := filepath.Ext(backupFile) + backupFile = backupPath + "/" + backupFile + if !tools.Exists(backupFile) { + return errors.New("备份文件不存在") + } + + err := os.Setenv("MYSQL_PWD", rootPassword) + if err != nil { + return err + } + + switch ext { + case ".zip": + tools.ExecShell("unzip -o " + backupFile + " -d " + backupPath) + backupFile = strings.TrimSuffix(backupFile, ext) + case ".gz": + if strings.HasSuffix(backupFile, ".tar.gz") { + // 解压.tar.gz文件 + tools.ExecShell("tar -zxvf " + backupFile + " -C " + backupPath) + backupFile = strings.TrimSuffix(backupFile, ".tar.gz") + } else { + // 解压.gz文件 + tools.ExecShell("gzip -d " + backupFile) + backupFile = strings.TrimSuffix(backupFile, ext) + } + case ".bz2": + tools.ExecShell("bzip2 -d " + backupFile) + backupFile = strings.TrimSuffix(backupFile, ext) + case ".tar": + tools.ExecShell("tar -xvf " + backupFile + " -C " + backupPath) + backupFile = strings.TrimSuffix(backupFile, ext) + case ".rar": + tools.ExecShell("unrar x " + backupFile + " " + backupPath) + backupFile = strings.TrimSuffix(backupFile, ext) + } + + if !tools.Exists(backupFile) { + return errors.New("自动解压失败,请手动解压") + } + + tools.ExecShell("mysql -uroot " + database + " < " + backupFile) + _ = os.Unsetenv("MYSQL_PWD") + + return nil +} diff --git a/public/panel/views/plugins/mysql57/backup.html b/public/panel/views/plugins/mysql57/backup.html index 8b7a8368..1689c7b3 100644 --- a/public/panel/views/plugins/mysql57/backup.html +++ b/public/panel/views/plugins/mysql57/backup.html @@ -38,7 +38,7 @@ Date: 2023-07-22 , toolbar: '#mysql-database-backup-bar' , title: '备份列表' , cols: [[ - {field: 'file', title: '备份名称', width: 500} + {field: 'name', title: '备份名称', width: 500} , {field: 'size', title: '文件大小'} , {field: 'right', title: '操作', width: 150, toolbar: '#mysql-database-backup-control'} ]] @@ -95,7 +95,7 @@ Date: 2023-07-22 table.on('tool(mysql-backup-list)', function (obj) { let data = obj.data; if (obj.event === 'del') { - layer.confirm('确定要删除数据库备份 ' + data.file + ' 吗?', function (index) { + layer.confirm('确定要删除数据库备份 ' + data.name + ' 吗?', function (index) { index = layer.msg('正在删除数据库备份,请稍等...', { icon: 16 , time: 0 @@ -112,12 +112,12 @@ Date: 2023-07-22 return false; } obj.del(); - layer.alert('数据库备份' + data.file + '删除成功!'); + layer.alert('数据库备份' + data.name + '删除成功!'); } }); }); } else if (obj.event === 'restore') { - layer.confirm('高风险操作,确定要恢复数据库备份 ' + data.file + ' 吗?', function (index) { + layer.confirm('高风险操作,确定要恢复数据库备份 ' + data.name + ' 吗?', function (index) { index = layer.msg('正在恢复数据库备份,可能需要较长时间,请勿操作...', { icon: 16 , time: 0 @@ -134,7 +134,7 @@ Date: 2023-07-22 layer.msg('数据库备份恢复失败,请刷新重试!') return false; } - layer.alert('数据库备份' + data.file + '恢复成功!'); + layer.alert('数据库备份' + data.name + '恢复成功!'); } }); }); diff --git a/public/panel/views/plugins/mysql80/backup.html b/public/panel/views/plugins/mysql80/backup.html index 950f47b6..bc83f293 100644 --- a/public/panel/views/plugins/mysql80/backup.html +++ b/public/panel/views/plugins/mysql80/backup.html @@ -38,7 +38,7 @@ Date: 2023-07-22 , toolbar: '#mysql-database-backup-bar' , title: '备份列表' , cols: [[ - {field: 'file', title: '备份名称', width: 500} + {field: 'name', title: '备份名称', width: 500} , {field: 'size', title: '文件大小'} , {field: 'right', title: '操作', width: 150, toolbar: '#mysql-database-backup-control'} ]] @@ -95,7 +95,7 @@ Date: 2023-07-22 table.on('tool(mysql-backup-list)', function (obj) { let data = obj.data; if (obj.event === 'del') { - layer.confirm('确定要删除数据库备份 ' + data.file + ' 吗?', function (index) { + layer.confirm('确定要删除数据库备份 ' + data.name + ' 吗?', function (index) { index = layer.msg('正在删除数据库备份,请稍等...', { icon: 16 , time: 0 @@ -112,12 +112,12 @@ Date: 2023-07-22 return false; } obj.del(); - layer.alert('数据库备份' + data.file + '删除成功!'); + layer.alert('数据库备份' + data.name + '删除成功!'); } }); }); } else if (obj.event === 'restore') { - layer.confirm('高风险操作,确定要恢复数据库备份 ' + data.file + ' 吗?', function (index) { + layer.confirm('高风险操作,确定要恢复数据库备份 ' + data.name + ' 吗?', function (index) { index = layer.msg('正在恢复数据库备份,可能需要较长时间,请勿操作...', { icon: 16 , time: 0 @@ -134,7 +134,7 @@ Date: 2023-07-22 layer.msg('数据库备份恢复失败,请刷新重试!') return false; } - layer.alert('数据库备份' + data.file + '恢复成功!'); + layer.alert('数据库备份' + data.name + '恢复成功!'); } }); });