feat: 完成 mysql 数据库备份与恢复功能

This commit is contained in:
ssongliu
2022-10-27 23:09:39 +08:00
committed by ssongliu
parent 325bb7bb5f
commit 8cf9c27f5f
21 changed files with 494 additions and 82 deletions

View File

@@ -1,8 +1,10 @@
package service
import (
"compress/gzip"
"encoding/json"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
@@ -11,6 +13,7 @@ import (
"github.com/1Panel-dev/1Panel/backend/app/dto"
"github.com/1Panel-dev/1Panel/backend/app/model"
"github.com/1Panel-dev/1Panel/backend/constant"
"github.com/1Panel-dev/1Panel/backend/global"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
@@ -20,9 +23,14 @@ type MysqlService struct{}
type IMysqlService interface {
SearchWithPage(search dto.SearchDBWithPage) (int64, interface{}, error)
SearchBacpupsWithPage(search dto.SearchBackupsWithPage) (int64, interface{}, error)
Create(mysqlDto dto.MysqlDBCreate) error
ChangeInfo(info dto.ChangeDBInfo) error
UpdateVariables(variables dto.MysqlVariablesUpdate) error
Backup(db dto.BackupDB) error
Recover(db dto.RecoverDB) error
Delete(version string, ids []uint) error
LoadStatus(version string) (*dto.MysqlStatus, error)
LoadVariables(version string) (*dto.MysqlVariables, error)
@@ -47,6 +55,21 @@ func (u *MysqlService) SearchWithPage(search dto.SearchDBWithPage) (int64, inter
return total, dtoMysqls, err
}
func (u *MysqlService) SearchBacpupsWithPage(search dto.SearchBackupsWithPage) (int64, interface{}, error) {
app, err := mysqlRepo.LoadBaseInfoByVersion(search.Version)
if err != nil {
return 0, nil, err
}
searchDto := dto.BackupSearch{
Type: "database-mysql",
PageInfo: search.PageInfo,
Name: app.Name,
DetailName: search.DBName,
}
return NewIBackupService().SearchRecordWithPage(searchDto)
}
func (u *MysqlService) LoadRunningVersion() ([]string, error) {
return mysqlRepo.LoadRunningVersion()
}
@@ -87,6 +110,66 @@ func (u *MysqlService) Create(mysqlDto dto.MysqlDBCreate) error {
return nil
}
func (u *MysqlService) Backup(db dto.BackupDB) error {
app, err := mysqlRepo.LoadBaseInfoByVersion(db.Version)
if err != nil {
return err
}
backupDir := fmt.Sprintf("%s/%s/%s/", constant.DatabaseDir, app.Name, db.DBName)
if _, err := os.Stat(backupDir); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(backupDir, os.ModePerm); err != nil {
return err
}
}
backupName := fmt.Sprintf("%s%s_%s.sql.gz", backupDir, db.DBName, time.Now().Format("20060102150405"))
outfile, _ := os.OpenFile(backupName, os.O_RDWR|os.O_CREATE, 0755)
cmd := exec.Command("docker", "exec", app.ContainerName, "mysqldump", "-uroot", "-p"+app.Password, db.DBName)
gzipCmd := exec.Command("gzip", "-cf")
gzipCmd.Stdin, _ = cmd.StdoutPipe()
gzipCmd.Stdout = outfile
_ = gzipCmd.Start()
_ = cmd.Run()
_ = gzipCmd.Wait()
if err := backupRepo.CreateRecord(&model.BackupRecord{
Type: "database-mysql",
Name: app.Name,
DetailName: db.DBName,
Source: "LOCAL",
FileDir: backupDir,
FileName: strings.ReplaceAll(backupName, backupDir, ""),
}); err != nil {
global.LOG.Errorf("save backup record failed, err: %v", err)
}
return nil
}
func (u *MysqlService) Recover(db dto.RecoverDB) error {
app, err := mysqlRepo.LoadBaseInfoByVersion(db.Version)
if err != nil {
return err
}
gzipFile, err := os.Open(db.BackupName)
if err != nil {
fmt.Println(err)
}
defer gzipFile.Close()
gzipReader, err := gzip.NewReader(gzipFile)
if err != nil {
fmt.Println(err)
}
defer gzipReader.Close()
cmd := exec.Command("docker", "exec", "-i", app.ContainerName, "mysql", "-uroot", "-p"+app.Password, db.DBName)
cmd.Stdin = gzipReader
stdout, err := cmd.CombinedOutput()
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
return errors.New(stdStr)
}
return nil
}
func (u *MysqlService) Delete(version string, ids []uint) error {
app, err := mysqlRepo.LoadBaseInfoByVersion(version)
if err != nil {
@@ -330,13 +413,13 @@ func (u *MysqlService) LoadStatus(version string) (*dto.MysqlStatus, error) {
}
func excuteSqlForMaps(containerName, password, command string) (map[string]string, error) {
cmd := exec.Command("docker", "exec", "-i", containerName, "mysql", "-uroot", fmt.Sprintf("-p%s", password), "-e", command)
cmd := exec.Command("docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command)
stdout, err := cmd.CombinedOutput()
if err != nil {
return nil, err
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
return nil, errors.New(stdStr)
}
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
rows := strings.Split(stdStr, "\n")
rowMap := make(map[string]string)
for _, v := range rows {
@@ -349,25 +432,21 @@ func excuteSqlForMaps(containerName, password, command string) (map[string]strin
}
func excuteSqlForRows(containerName, password, command string) ([]string, error) {
cmd := exec.Command("docker", "exec", "-i", containerName, "mysql", "-uroot", fmt.Sprintf("-p%s", password), "-e", command)
cmd := exec.Command("docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command)
stdout, err := cmd.CombinedOutput()
if err != nil {
return nil, err
}
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
return nil, errors.New(stdStr)
}
return strings.Split(stdStr, "\n"), nil
}
func excuteSql(containerName, password, command string) error {
cmd := exec.Command("docker", "exec", "-i", containerName, "mysql", "-uroot", fmt.Sprintf("-p%s", password), "-e", command)
cmd := exec.Command("docker", "exec", containerName, "mysql", "-uroot", "-p"+password, "-e", command)
stdout, err := cmd.CombinedOutput()
if err != nil {
return err
}
stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "")
if strings.HasPrefix(string(stdStr), "ERROR ") {
return errors.New(string(stdStr))
if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") {
return errors.New(stdStr)
}
return nil
}