Browse Source

feat: 完成数据库备份列表

pull/34/head
ssongliu 2 years ago committed by ssongliu
parent
commit
9f1e417c06
  1. 19
      backend/app/api/v1/backup.go
  2. 6
      backend/app/dto/backup.go
  3. 42
      backend/app/service/backup.go
  4. 1
      backend/router/ro_backup.go
  5. 5
      frontend/src/api/interface/backup.ts
  6. 4
      frontend/src/api/modules/backup.ts
  7. 2
      frontend/src/lang/modules/zh.ts
  8. 45
      frontend/src/views/database/mysql/backup/index.vue

19
backend/app/api/v1/backup.go

@ -61,6 +61,25 @@ func (b *BaseApi) DeleteBackup(c *gin.Context) {
helper.SuccessWithData(c, nil)
}
func (b *BaseApi) DownloadRecord(c *gin.Context) {
var req dto.DownloadRecord
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
}
filePath, err := backupService.DownloadRecord(req)
if err != nil {
helper.ErrorWithDetail(c, constant.CodeErrInternalServer, constant.ErrTypeInternalServer, err)
return
}
c.File(filePath)
}
func (b *BaseApi) DeleteBackupRecord(c *gin.Context) {
var req dto.BatchDeleteReq
if err := c.ShouldBindJSON(&req); err != nil {

6
backend/app/dto/backup.go

@ -32,6 +32,12 @@ type BackupRecords struct {
FileName string `json:"fileName"`
}
type DownloadRecord struct {
Source string `json:"source" validate:"required,oneof=OSS S3 SFTP MINIO LOCAL"`
FileDir string `json:"fileDir" validate:"required"`
FileName string `json:"fileName" validate:"required"`
}
type ForBuckets struct {
Type string `json:"type" validate:"required"`
Credential string `json:"credential" validate:"required"`

42
backend/app/service/backup.go

@ -2,6 +2,7 @@ package service
import (
"encoding/json"
"fmt"
"os"
"github.com/1Panel-dev/1Panel/backend/app/dto"
@ -18,6 +19,7 @@ type BackupService struct{}
type IBackupService interface {
List() ([]dto.BackupInfo, error)
SearchRecordWithPage(search dto.BackupSearch) (int64, []dto.BackupRecords, error)
DownloadRecord(info dto.DownloadRecord) (string, error)
Create(backupDto dto.BackupOperate) error
GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error)
Update(id uint, upMap map[string]interface{}) error
@ -62,6 +64,46 @@ func (u *BackupService) SearchRecordWithPage(search dto.BackupSearch) (int64, []
return total, dtobas, err
}
func (u *BackupService) DownloadRecord(info dto.DownloadRecord) (string, error) {
if info.Source == "LOCAL" {
return info.FileDir + info.FileName, nil
}
backup, _ := backupRepo.Get(commonRepo.WithByType(info.Source))
if backup.ID == 0 {
return "", constant.ErrRecordNotFound
}
varMap := make(map[string]interface{})
if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil {
return "", err
}
varMap["type"] = backup.Type
varMap["bucket"] = backup.Bucket
switch backup.Type {
case constant.Sftp:
varMap["password"] = backup.Credential
case constant.OSS, constant.S3, constant.MinIo:
varMap["secretKey"] = backup.Credential
}
backClient, err := cloud_storage.NewCloudStorageClient(varMap)
if err != nil {
return "", fmt.Errorf("new cloud storage client failed, err: %v", err)
}
tempPath := fmt.Sprintf("%s%s", constant.DownloadDir, info.FileDir)
if _, err := os.Stat(tempPath); err != nil && os.IsNotExist(err) {
if err = os.MkdirAll(tempPath, os.ModePerm); err != nil {
fmt.Println(err)
}
}
targetPath := tempPath + info.FileName
if _, err = os.Stat(targetPath); err != nil && os.IsNotExist(err) {
isOK, err := backClient.Download(info.FileName, targetPath)
if !isOK {
return "", fmt.Errorf("cloud storage download failed, err: %v", err)
}
}
return targetPath, nil
}
func (u *BackupService) Create(backupDto dto.BackupOperate) error {
backup, _ := backupRepo.Get(commonRepo.WithByType(backupDto.Type))
if backup.ID != 0 {

1
backend/router/ro_backup.go

@ -25,6 +25,7 @@ func (s *BackupRouter) InitBackupRouter(Router *gin.RouterGroup) {
baRouter.POST("/buckets", baseApi.ListBuckets)
withRecordRouter.POST("", baseApi.CreateBackup)
withRecordRouter.POST("/del", baseApi.DeleteBackup)
withRecordRouter.POST("/record/download", baseApi.DownloadRecord)
withRecordRouter.POST("/record/del", baseApi.DeleteBackupRecord)
withRecordRouter.PUT(":id", baseApi.UpdateBackup)
}

5
frontend/src/api/interface/backup.ts

@ -13,6 +13,11 @@ export namespace Backup {
credential: string;
vars: string;
}
export interface RecordDownload {
source: string;
fileDir: string;
fileName: string;
}
export interface RecordInfo {
id: number;
createdAt: Date;

4
frontend/src/api/modules/backup.ts

@ -16,6 +16,10 @@ export const editBackup = (params: Backup.BackupOperate) => {
export const deleteBackup = (params: { ids: number[] }) => {
return http.post(`/backups/del`, params);
};
export const downloadBackupRecord = (params: Backup.RecordDownload) => {
return http.download<BlobPart>(`/backups/record/download`, params, { responseType: 'blob' });
};
export const deleteBackupRecord = (params: { ids: number[] }) => {
return http.post(`/backups/record/del`, params);
};

2
frontend/src/lang/modules/zh.ts

@ -27,6 +27,8 @@ export default {
expand: '展开',
log: '日志',
back: '返回',
recover: '恢复',
download: '下载',
saveAndEnable: '保存并启用',
},
search: {

45
frontend/src/views/database/mysql/backup/index.vue

@ -25,7 +25,7 @@
show-overflow-tooltip
/>
<fu-table-operations type="icon" :buttons="buttons" :label="$t('commons.table.operate')" fix />
<fu-table-operations :buttons="buttons" :label="$t('commons.table.operate')" fix />
</ComplexTable>
</el-dialog>
</div>
@ -36,10 +36,10 @@ import ComplexTable from '@/components/complex-table/index.vue';
import { reactive, ref } from 'vue';
import { dateFromat } from '@/utils/util';
import { useDeleteData } from '@/hooks/use-delete-data';
import { backup, searchBackupRecords } from '@/api/modules/database';
import { backup, recover, searchBackupRecords } from '@/api/modules/database';
import i18n from '@/lang';
import { ElMessage } from 'element-plus';
import { deleteBackupRecord } from '@/api/modules/backup';
import { deleteBackupRecord, downloadBackupRecord } from '@/api/modules/backup';
import { Backup } from '@/api/interface/backup';
const selects = ref<any>([]);
@ -87,6 +87,32 @@ const onBackup = async () => {
search();
};
const onRecover = async (row: Backup.RecordInfo) => {
let params = {
version: version.value,
dbName: dbName.value,
backupName: row.fileDir + row.fileName,
};
await recover(params);
ElMessage.success(i18n.global.t('commons.msg.operationSuccess'));
};
const onDownload = async (row: Backup.RecordInfo) => {
let params = {
source: row.source,
fileDir: row.fileDir,
fileName: row.fileName,
};
const res = await downloadBackupRecord(params);
const downloadUrl = window.URL.createObjectURL(new Blob([res]));
const a = document.createElement('a');
a.style.display = 'none';
a.href = downloadUrl;
a.download = row.fileName;
const event = new MouseEvent('click');
a.dispatchEvent(event);
};
const onBatchDelete = async (row: Backup.RecordInfo | null) => {
let ids: Array<number> = [];
if (row) {
@ -103,11 +129,22 @@ const onBatchDelete = async (row: Backup.RecordInfo | null) => {
const buttons = [
{
label: i18n.global.t('commons.button.delete'),
icon: 'Delete',
click: (row: Backup.RecordInfo) => {
onBatchDelete(row);
},
},
{
label: i18n.global.t('commons.button.recover'),
click: (row: Backup.RecordInfo) => {
onRecover(row);
},
},
{
label: i18n.global.t('commons.button.download'),
click: (row: Backup.RecordInfo) => {
onDownload(row);
},
},
];
defineExpose({

Loading…
Cancel
Save