From 0d95cee92446934a5b53120447bfcdcc710854fc Mon Sep 17 00:00:00 2001 From: ssongliu Date: Thu, 1 Dec 2022 16:21:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E7=BD=91=E7=AB=99?= =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E4=B8=8E=E6=81=A2=E5=A4=8D=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/v1/website.go | 18 ++ backend/app/dto/website.go | 7 + backend/app/service/backup.go | 2 +- backend/app/service/website.go | 237 ++++++++++-------- backend/init/migration/migrations/init.go | 2 +- backend/router/ro_website.go | 1 + frontend/src/api/interface/website.ts | 6 + frontend/src/api/modules/website.ts | 3 + frontend/src/lang/modules/en.ts | 3 + frontend/src/lang/modules/zh.ts | 2 + .../views/website/website/backup/index.vue | 33 ++- frontend/src/views/website/website/index.vue | 4 +- .../views/website/website/upload/index.vue | 176 +++++++------ 13 files changed, 298 insertions(+), 196 deletions(-) diff --git a/backend/app/api/v1/website.go b/backend/app/api/v1/website.go index 98bb6a431..c995abe72 100644 --- a/backend/app/api/v1/website.go +++ b/backend/app/api/v1/website.go @@ -63,6 +63,24 @@ func (b *BaseApi) BackupWebsite(c *gin.Context) { helper.SuccessWithData(c, nil) } +func (b *BaseApi) RecoverWebsiteByUpload(c *gin.Context) { + var req dto.WebSiteRecoverByFile + if err := c.ShouldBindJSON(&req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + if err := global.VALID.Struct(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrBadRequest, constant.ErrTypeInvalidParams, err) + return + } + + if err := websiteService.RecoverByUpload(req); err != nil { + helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err) + return + } + helper.SuccessWithData(c, nil) +} + func (b *BaseApi) RecoverWebsite(c *gin.Context) { var req dto.WebSiteRecover if err := c.ShouldBindJSON(&req); err != nil { diff --git a/backend/app/dto/website.go b/backend/app/dto/website.go index 6dc38a9ce..f0506de15 100644 --- a/backend/app/dto/website.go +++ b/backend/app/dto/website.go @@ -57,6 +57,13 @@ type WebSiteRecover struct { BackupName string `json:"backupName" validate:"required"` } +type WebSiteRecoverByFile struct { + WebsiteName string `json:"websiteName" validate:"required"` + Type string `json:"type" validate:"required"` + FileDir string `json:"fileDir" validate:"required"` + FileName string `json:"fileName" validate:"required"` +} + type WebSiteDTO struct { model.WebSite } diff --git a/backend/app/service/backup.go b/backend/app/service/backup.go index 6dc2be950..9576d0859 100644 --- a/backend/app/service/backup.go +++ b/backend/app/service/backup.go @@ -152,7 +152,7 @@ func (u *BackupService) BatchDeleteRecord(ids []uint) error { } for _, record := range records { if record.Source == "LOCAL" { - if err := os.Remove(record.FileDir + record.FileName); err != nil { + if err := os.Remove(record.FileDir + "/" + record.FileName); err != nil { global.LOG.Errorf("remove file %s failed, err: %v", record.FileDir+record.FileName, err) } } else { diff --git a/backend/app/service/website.go b/backend/app/service/website.go index 0361624d7..931e5e51a 100644 --- a/backend/app/service/website.go +++ b/backend/app/service/website.go @@ -34,6 +34,7 @@ type IWebsiteService interface { GetWebsiteOptions() ([]string, error) Backup(domain string) error Recover(req dto.WebSiteRecover) error + RecoverByUpload(req dto.WebSiteRecoverByFile) error UpdateWebsite(req dto.WebSiteUpdate) error DeleteWebSite(req dto.WebSiteDel) error } @@ -149,15 +150,40 @@ func (w WebsiteService) Backup(domain string) error { return nil } +func (w WebsiteService) RecoverByUpload(req dto.WebSiteRecoverByFile) error { + if err := handleUnTar(fmt.Sprintf("%s/%s", req.FileDir, req.FileName), req.FileDir); err != nil { + return err + } + tmpDir := fmt.Sprintf("%s/%s", req.FileDir, strings.ReplaceAll(req.FileName, ".tar.gz", "")) + webJson, err := os.ReadFile(fmt.Sprintf("%s/website.json", tmpDir)) + if err != nil { + return err + } + var websiteInfo WebSiteInfo + if err := json.Unmarshal(webJson, &websiteInfo); err != nil { + return err + } + if websiteInfo.WebsiteName != req.WebsiteName || websiteInfo.WebsiteType != req.Type { + return errors.New("上传文件与选中网站不匹配,无法恢复") + } + + website, err := websiteRepo.GetFirst(websiteRepo.WithByDomain(req.WebsiteName)) + if err != nil { + return err + } + if err := handleWebsiteRecover(&website, tmpDir); err != nil { + return err + } + + return nil +} + func (w WebsiteService) Recover(req dto.WebSiteRecover) error { website, err := websiteRepo.GetFirst(websiteRepo.WithByDomain(req.WebsiteName)) if err != nil { return err } - app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) - if err != nil { - return err - } + if !strings.Contains(req.BackupName, "/") { return errors.New("error path of request") } @@ -167,71 +193,9 @@ func (w WebsiteService) Recover(req dto.WebSiteRecover) error { return err } fileDir = fileDir + "/" + fileName - resource, err := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(website.AppInstallID)) - if err != nil { - return err - } - nginxInfo, err := appInstallRepo.LoadBaseInfoByKey("nginx") - if err != nil { - return err - } - mysqlInfo, err := appInstallRepo.LoadBaseInfoByKey("mysql") - if err != nil { - return err - } - db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId)) - if err != nil { - return err - } - src, err := os.OpenFile(fmt.Sprintf("%s/%s.conf", fileDir, website.PrimaryDomain), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) - if err != nil { - return err - } - defer src.Close() - var out *os.File - nginxConfDir := fmt.Sprintf("%s/nginx/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxInfo.Name, website.PrimaryDomain) - if _, err := os.Stat(nginxConfDir); err != nil { - out, err = os.Create(fmt.Sprintf("%s/%s.conf", nginxConfDir, website.PrimaryDomain)) - if err != nil { - return err - } - } else { - out, err = os.OpenFile(nginxConfDir, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) - if err != nil { - return err - } - } - defer out.Close() - _, _ = io.Copy(out, src) - if website.Type == "deployment" { - cmd := exec.Command("docker", "exec", "-i", mysqlInfo.ContainerName, "mysql", "-uroot", "-p"+mysqlInfo.Password, db.Name) - sql, err := os.OpenFile(fmt.Sprintf("%s/%s.sql", fileDir, website.PrimaryDomain), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) - if err != nil { - return err - } - cmd.Stdin = sql - stdout, err := cmd.CombinedOutput() - if err != nil { - return errors.New(string(stdout)) - } - appDir := fmt.Sprintf("%s/%s", constant.AppInstallDir, app.App.Key) - if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", fileDir, website.PrimaryDomain), appDir); err != nil { - return err - } - if _, err := compose.Restart(fmt.Sprintf("%s/%s/docker-compose.yml", appDir, app.Name)); err != nil { - return err - } - } else { - appDir := fmt.Sprintf("%s/nginx/%s/www", constant.AppInstallDir, nginxInfo.Name) - if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", fileDir, website.PrimaryDomain), appDir); err != nil { - return err - } - } - cmd := exec.Command("docker", "exec", "-i", nginxInfo.ContainerName, "nginx", "-s", "reload") - stdout, err := cmd.CombinedOutput() - if err != nil { - return errors.New(string(stdout)) + if err := handleWebsiteRecover(&website, fileDir); err != nil { + return err } return nil } @@ -534,26 +498,6 @@ func handleWebsiteBackup(backupType, baseDir, backupDir, domain, backupName stri if err != nil { return err } - app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) - if err != nil { - return err - } - resource, err := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(website.AppInstallID)) - if err != nil { - return err - } - mysqlInfo, err := appInstallRepo.LoadBaseInfoByKey(resource.Key) - if err != nil { - return err - } - nginxInfo, err := appInstallRepo.LoadBaseInfoByKey("nginx") - if err != nil { - return err - } - db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId)) - if err != nil { - return err - } tmpDir := fmt.Sprintf("%s/%s/%s", baseDir, backupDir, backupName) if _, err := os.Stat(tmpDir); err != nil && os.IsNotExist(err) { @@ -563,21 +507,28 @@ func handleWebsiteBackup(backupType, baseDir, backupDir, domain, backupName stri } } } - if err := saveNginxConf(nginxInfo.Name, website.PrimaryDomain, tmpDir); err != nil { - return err - } if err := saveWebsiteJson(&website, tmpDir); err != nil { return err } - if website.Type == "deployment" { - dbFile := fmt.Sprintf("%s/%s.sql", tmpDir, website.PrimaryDomain) - outfile, _ := os.OpenFile(dbFile, os.O_RDWR|os.O_CREATE, 0755) - cmd := exec.Command("docker", "exec", mysqlInfo.ContainerName, "mysqldump", "-uroot", "-p"+mysqlInfo.Password, db.Name) - cmd.Stdout = outfile - _ = cmd.Run() - _ = cmd.Wait() + nginxInfo, err := appInstallRepo.LoadBaseInfoByKey("nginx") + if err != nil { + return err + } + nginxConfFile := fmt.Sprintf("%s/nginx/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxInfo.Name, website.PrimaryDomain) + if err := copyConf(nginxConfFile, fmt.Sprintf("%s/%s.conf", tmpDir, website.PrimaryDomain)); err != nil { + return err + } + if website.Type == "deployment" { + if err := mysqlOpration(&website, "backup", tmpDir); err != nil { + return err + } + + app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) + if err != nil { + return err + } websiteDir := fmt.Sprintf("%s/%s/%s", constant.AppInstallDir, app.App.Key, app.Name) if err := handleTar(websiteDir, tmpDir, fmt.Sprintf("%s.web.tar.gz", website.PrimaryDomain), ""); err != nil { return err @@ -612,6 +563,85 @@ func handleWebsiteBackup(backupType, baseDir, backupDir, domain, backupName stri return nil } +func handleWebsiteRecover(website *model.WebSite, fileDir string) error { + nginxInfo, err := appInstallRepo.LoadBaseInfoByKey("nginx") + if err != nil { + return err + } + nginxConfFile := fmt.Sprintf("%s/nginx/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxInfo.Name, website.PrimaryDomain) + if err := copyConf(fmt.Sprintf("%s/%s.conf", fileDir, website.PrimaryDomain), nginxConfFile); err != nil { + return err + } + + if website.Type == "deployment" { + if err := mysqlOpration(website, "recover", fileDir); err != nil { + return err + } + + app, err := appInstallRepo.GetFirst(commonRepo.WithByID(website.AppInstallID)) + if err != nil { + return err + } + appDir := fmt.Sprintf("%s/%s", constant.AppInstallDir, app.App.Key) + if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", fileDir, website.PrimaryDomain), appDir); err != nil { + return err + } + if _, err := compose.Restart(fmt.Sprintf("%s/%s/docker-compose.yml", appDir, app.Name)); err != nil { + return err + } + } else { + appDir := fmt.Sprintf("%s/nginx/%s/www", constant.AppInstallDir, nginxInfo.Name) + if err := handleUnTar(fmt.Sprintf("%s/%s.web.tar.gz", fileDir, website.PrimaryDomain), appDir); err != nil { + return err + } + } + cmd := exec.Command("docker", "exec", "-i", nginxInfo.ContainerName, "nginx", "-s", "reload") + stdout, err := cmd.CombinedOutput() + if err != nil { + return errors.New(string(stdout)) + } + _ = os.RemoveAll(fileDir) + + return nil +} + +func mysqlOpration(website *model.WebSite, operation, filePath string) error { + mysqlInfo, err := appInstallRepo.LoadBaseInfoByKey("mysql") + if err != nil { + return err + } + resource, err := appInstallResourceRepo.GetFirst(appInstallResourceRepo.WithAppInstallId(website.AppInstallID)) + if err != nil { + return err + } + db, err := mysqlRepo.Get(commonRepo.WithByID(resource.ResourceId)) + if err != nil { + return err + } + if operation == "backup" { + dbFile := fmt.Sprintf("%s/%s.sql", filePath, website.PrimaryDomain) + outfile, _ := os.OpenFile(dbFile, os.O_RDWR|os.O_CREATE, 0755) + defer outfile.Close() + cmd := exec.Command("docker", "exec", mysqlInfo.ContainerName, "mysqldump", "-uroot", "-p"+mysqlInfo.Password, db.Name) + cmd.Stdout = outfile + _ = cmd.Run() + _ = cmd.Wait() + return nil + } + cmd := exec.Command("docker", "exec", "-i", mysqlInfo.ContainerName, "mysql", "-uroot", "-p"+mysqlInfo.Password, db.Name) + sqlfile, err := os.Open(fmt.Sprintf("%s/%s.sql", filePath, website.PrimaryDomain)) + if err != nil { + return err + } + defer sqlfile.Close() + cmd.Stdin = sqlfile + stdout, err := cmd.CombinedOutput() + if err != nil { + return errors.New(string(stdout)) + } + return nil +} + func saveWebsiteJson(website *model.WebSite, tmpDir string) error { var WebSiteInfo WebSiteInfo WebSiteInfo.WebsiteType = website.Type @@ -629,19 +659,20 @@ func saveWebsiteJson(website *model.WebSite, tmpDir string) error { return nil } -func saveNginxConf(nginxName, websiteDomain, tmpDir string) error { - nginxConfFile := fmt.Sprintf("%s/nginx/%s/conf/conf.d/%s.conf", constant.AppInstallDir, nginxName, websiteDomain) - src, err := os.OpenFile(nginxConfFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) +func copyConf(srcPath, dstPath string) error { + if _, err := os.Stat(srcPath); err != nil { + return err + } + src, err := os.OpenFile(srcPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) if err != nil { return err } defer src.Close() - out, err := os.Create(fmt.Sprintf("%s/%s.conf", tmpDir, websiteDomain)) + out, err := os.Create(dstPath) if err != nil { return err } defer out.Close() _, _ = io.Copy(out, src) - return nil } diff --git a/backend/init/migration/migrations/init.go b/backend/init/migration/migrations/init.go index b9b54ca2e..14539ae36 100644 --- a/backend/init/migration/migrations/init.go +++ b/backend/init/migration/migrations/init.go @@ -131,7 +131,7 @@ var AddTableBackupAccount = &gormigrate.Migration{ } item := &model.BackupAccount{ Type: "LOCAL", - Vars: "{\"dir\":\"/opt/1Panel/backup\"}", + Vars: "{\"dir\":\"/opt/1Panel/data/backup\"}", } if err := tx.Create(item).Error; err != nil { return err diff --git a/backend/router/ro_website.go b/backend/router/ro_website.go index 108af1b0c..b898e6a24 100644 --- a/backend/router/ro_website.go +++ b/backend/router/ro_website.go @@ -19,6 +19,7 @@ func (a *WebsiteRouter) InitWebsiteRouter(Router *gin.RouterGroup) { groupRouter.GET("/options", baseApi.GetWebsiteOptions) groupRouter.POST("/backup/:domain", baseApi.BackupWebsite) groupRouter.POST("/recover", baseApi.RecoverWebsite) + groupRouter.POST("/recover/byupload", baseApi.RecoverWebsiteByUpload) groupRouter.POST("/update", baseApi.UpdateWebSite) groupRouter.GET("/:id", baseApi.GetWebSite) groupRouter.GET("/:id/nginx", baseApi.GetWebSiteNginx) diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts index b767fd333..7c79ae80b 100644 --- a/frontend/src/api/interface/website.ts +++ b/frontend/src/api/interface/website.ts @@ -43,6 +43,12 @@ export namespace WebSite { type: string; backupName: string; } + export interface WebsiteRecoverByUpload { + websiteName: string; + type: string; + fileDir: string; + fileName: string; + } export interface WebSiteDel { id: number; diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts index 825d09351..f807bacc0 100644 --- a/frontend/src/api/modules/website.ts +++ b/frontend/src/api/modules/website.ts @@ -17,6 +17,9 @@ export const BackupWebsite = (id: number) => { export const RecoverWebsite = (req: WebSite.WebSiteRecover) => { return http.post(`/websites/recover`, req); }; +export const RecoverWebsiteByUpload = (req: WebSite.WebsiteRecoverByUpload) => { + return http.post(`/websites/recover/byupload`, req); +}; export const UpdateWebsite = (req: WebSite.WebSiteUpdateReq) => { return http.post(`/websites/update`, req); diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts index 6d10fba28..06306eb64 100644 --- a/frontend/src/lang/modules/en.ts +++ b/frontend/src/lang/modules/en.ts @@ -695,6 +695,9 @@ export default { author: 'author', source: 'source', sync: 'sync', + supportUpType: '.tar.gz files are supported only', + zipFormat: + '.tar.gz compressed package structure: test.tar.gz compressed package must contain the website.json file', appName: 'App Name', status: 'status', container: 'Container', diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts index a5a49e521..b504a5005 100644 --- a/frontend/src/lang/modules/zh.ts +++ b/frontend/src/lang/modules/zh.ts @@ -739,6 +739,8 @@ export default { type: '类型', static: '静态网站', deployment: '反向代理', + supportUpType: '仅支持 tar.gz 文件', + zipFormat: 'tar.gz 压缩包结构:test.tar.gz 压缩包内,必需包含 website.json 文件', proxy: '反向代理', alias: '代号', remark: '备注', diff --git a/frontend/src/views/website/website/backup/index.vue b/frontend/src/views/website/website/backup/index.vue index 89c7519fa..1fe12cc3f 100644 --- a/frontend/src/views/website/website/backup/index.vue +++ b/frontend/src/views/website/website/backup/index.vue @@ -6,7 +6,13 @@ {{ $t('database.backup') }} - {{ websiteName }} - +