mirror of
https://gitee.com/dromara/mayfly-go
synced 2025-11-03 07:50:25 +08:00
!96 删除数据库备份和恢复历史
* feat: 删除数据库备份历史 * refactor dbScheduler * feat: 从数据库备份历史中恢复数据库 * feat: 删除数据库恢复历史记录 * refactor dbScheuler
This commit is contained in:
155
mayfly_go_web/src/views/ops/db/DbBackupHistoryList.vue
Normal file
155
mayfly_go_web/src/views/ops/db/DbBackupHistoryList.vue
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
<template>
|
||||||
|
<div class="db-backup-history">
|
||||||
|
<page-table
|
||||||
|
height="100%"
|
||||||
|
ref="pageTableRef"
|
||||||
|
:page-api="dbApi.getDbBackupHistories"
|
||||||
|
:show-selection="true"
|
||||||
|
v-model:selection-data="state.selectedData"
|
||||||
|
:searchItems="searchItems"
|
||||||
|
:before-query-fn="beforeQueryFn"
|
||||||
|
v-model:query-form="query"
|
||||||
|
:columns="columns"
|
||||||
|
>
|
||||||
|
<template #dbSelect>
|
||||||
|
<el-select v-model="query.dbName" placeholder="请选择数据库" style="width: 200px" filterable clearable>
|
||||||
|
<el-option v-for="item in props.dbNames" :key="item" :label="`${item}`" :value="item"> </el-option>
|
||||||
|
</el-select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #tableHeader>
|
||||||
|
<el-button type="primary" icon="back" @click="restoreDbBackupHistory(null)">立即恢复</el-button>
|
||||||
|
<el-button type="danger" icon="delete" @click="deleteDbBackupHistory(null)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #action="{ data }">
|
||||||
|
<div>
|
||||||
|
<el-button @click="restoreDbBackupHistory(data)" type="primary" link>立即恢复</el-button>
|
||||||
|
<el-button @click="deleteDbBackupHistory(data)" type="danger" link>删除</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</page-table>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { toRefs, reactive, Ref, ref } from 'vue';
|
||||||
|
import { dbApi } from './api';
|
||||||
|
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||||
|
import { TableColumn } from '@/components/pagetable';
|
||||||
|
import { SearchItem } from '@/components/SearchForm';
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
|
|
||||||
|
const pageTableRef: Ref<any> = ref(null);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
dbId: {
|
||||||
|
type: [Number],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
dbNames: {
|
||||||
|
type: [Array<String>],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const searchItems = [SearchItem.slot('dbName', '数据库名称', 'dbSelect')];
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
TableColumn.new('dbName', '数据库名称'),
|
||||||
|
TableColumn.new('name', '备份名称'),
|
||||||
|
TableColumn.new('createTime', '创建时间').isTime(),
|
||||||
|
TableColumn.new('lastResult', '恢复结果'),
|
||||||
|
TableColumn.new('lastTime', '恢复时间').isTime(),
|
||||||
|
TableColumn.new('action', '操作').isSlot().setMinWidth(160).fixedRight(),
|
||||||
|
];
|
||||||
|
|
||||||
|
const emptyQuery = {
|
||||||
|
dbId: 0,
|
||||||
|
dbName: '',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
query: emptyQuery,
|
||||||
|
/**
|
||||||
|
* 选中的数据
|
||||||
|
*/
|
||||||
|
selectedData: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { query } = toRefs(state);
|
||||||
|
|
||||||
|
const beforeQueryFn = (query: any) => {
|
||||||
|
query.dbId = props.dbId;
|
||||||
|
return query;
|
||||||
|
};
|
||||||
|
|
||||||
|
const search = async () => {
|
||||||
|
await pageTableRef.value.search();
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteDbBackupHistory = async (data: any) => {
|
||||||
|
let backupHistoryId: string;
|
||||||
|
if (data) {
|
||||||
|
backupHistoryId = data.id;
|
||||||
|
} else if (state.selectedData.length > 0) {
|
||||||
|
backupHistoryId = state.selectedData.map((x: any) => x.id).join(' ');
|
||||||
|
} else {
|
||||||
|
ElMessage.error('请选择需要删除的数据库备份历史');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await ElMessageBox.confirm(`确定删除 “数据库备份历史” 吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
await dbApi.deleteDbBackupHistory.request({ dbId: props.dbId, backupHistoryId: backupHistoryId });
|
||||||
|
await search();
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
};
|
||||||
|
|
||||||
|
const restoreDbBackupHistory = async (data: any) => {
|
||||||
|
let backupHistoryId: string;
|
||||||
|
if (data) {
|
||||||
|
backupHistoryId = data.id;
|
||||||
|
} else if (state.selectedData.length > 0) {
|
||||||
|
const pluralDbNames: string[] = [];
|
||||||
|
const dbNames: Map<string, boolean> = new Map();
|
||||||
|
state.selectedData.forEach((item: any) => {
|
||||||
|
if (!dbNames.has(item.dbName)) {
|
||||||
|
dbNames.set(item.dbName, false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!dbNames.get(item.dbName)) {
|
||||||
|
dbNames.set(item.dbName, true);
|
||||||
|
pluralDbNames.push(item.dbName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (pluralDbNames.length > 0) {
|
||||||
|
ElMessage.error('多次选择相同数据库:' + pluralDbNames.join(', '));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
backupHistoryId = state.selectedData.map((x: any) => x.id).join(' ');
|
||||||
|
} else {
|
||||||
|
ElMessage.error('请选择需要恢复的数据库备份历史');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await ElMessageBox.confirm(`确定从 “数据库备份历史” 中恢复数据库吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
|
||||||
|
await dbApi.restoreDbBackupHistory.request({
|
||||||
|
dbId: props.dbId,
|
||||||
|
backupHistoryId: backupHistoryId,
|
||||||
|
});
|
||||||
|
await search();
|
||||||
|
ElMessage.success('成功创建数据库恢复任务');
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss"></style>
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
<el-button type="primary" icon="plus" @click="createDbBackup()">添加</el-button>
|
<el-button type="primary" icon="plus" @click="createDbBackup()">添加</el-button>
|
||||||
<el-button type="primary" icon="video-play" @click="enableDbBackup(null)">启用</el-button>
|
<el-button type="primary" icon="video-play" @click="enableDbBackup(null)">启用</el-button>
|
||||||
<el-button type="primary" icon="video-pause" @click="disableDbBackup(null)">禁用</el-button>
|
<el-button type="primary" icon="video-pause" @click="disableDbBackup(null)">禁用</el-button>
|
||||||
|
<el-button type="danger" icon="delete" @click="deleteDbBackup(null)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #action="{ data }">
|
<template #action="{ data }">
|
||||||
@@ -29,6 +30,7 @@
|
|||||||
<el-button v-if="!data.enabled" @click="enableDbBackup(data)" type="primary" link>启用</el-button>
|
<el-button v-if="!data.enabled" @click="enableDbBackup(data)" type="primary" link>启用</el-button>
|
||||||
<el-button v-if="data.enabled" @click="disableDbBackup(data)" type="primary" link>禁用</el-button>
|
<el-button v-if="data.enabled" @click="disableDbBackup(data)" type="primary" link>禁用</el-button>
|
||||||
<el-button v-if="data.enabled" @click="startDbBackup(data)" type="primary" link>立即备份</el-button>
|
<el-button v-if="data.enabled" @click="startDbBackup(data)" type="primary" link>立即备份</el-button>
|
||||||
|
<el-button @click="deleteDbBackup(data)" type="danger" link>删除</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</page-table>
|
</page-table>
|
||||||
@@ -49,7 +51,7 @@ import { dbApi } from './api';
|
|||||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||||
import { TableColumn } from '@/components/pagetable';
|
import { TableColumn } from '@/components/pagetable';
|
||||||
import { SearchItem } from '@/components/SearchForm';
|
import { SearchItem } from '@/components/SearchForm';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
|
|
||||||
const DbBackupEdit = defineAsyncComponent(() => import('./DbBackupEdit.vue'));
|
const DbBackupEdit = defineAsyncComponent(() => import('./DbBackupEdit.vue'));
|
||||||
const pageTableRef: Ref<any> = ref(null);
|
const pageTableRef: Ref<any> = ref(null);
|
||||||
@@ -72,10 +74,10 @@ const columns = [
|
|||||||
TableColumn.new('name', '任务名称'),
|
TableColumn.new('name', '任务名称'),
|
||||||
TableColumn.new('startTime', '启动时间').isTime(),
|
TableColumn.new('startTime', '启动时间').isTime(),
|
||||||
TableColumn.new('intervalDay', '备份周期'),
|
TableColumn.new('intervalDay', '备份周期'),
|
||||||
TableColumn.new('enabled', '是否启用'),
|
TableColumn.new('enabledDesc', '是否启用'),
|
||||||
TableColumn.new('lastResult', '执行结果'),
|
TableColumn.new('lastResult', '执行结果'),
|
||||||
TableColumn.new('lastTime', '执行时间').isTime(),
|
TableColumn.new('lastTime', '执行时间').isTime(),
|
||||||
TableColumn.new('action', '操作').isSlot().setMinWidth(180).fixedRight(),
|
TableColumn.new('action', '操作').isSlot().setMinWidth(220).fixedRight(),
|
||||||
];
|
];
|
||||||
|
|
||||||
const emptyQuery = {
|
const emptyQuery = {
|
||||||
@@ -168,5 +170,25 @@ const startDbBackup = async (data: any) => {
|
|||||||
await search();
|
await search();
|
||||||
ElMessage.success('备份任务启动成功');
|
ElMessage.success('备份任务启动成功');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteDbBackup = async (data: any) => {
|
||||||
|
let backupId: string;
|
||||||
|
if (data) {
|
||||||
|
backupId = data.id;
|
||||||
|
} else if (state.selectedData.length > 0) {
|
||||||
|
backupId = state.selectedData.map((x: any) => x.id).join(' ');
|
||||||
|
} else {
|
||||||
|
ElMessage.error('请选择需要删除的数据库备份任务');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await ElMessageBox.confirm(`确定删除 “数据库备份任务” 吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
await dbApi.deleteDbBackup.request({ dbId: props.dbId, backupId: backupId });
|
||||||
|
await search();
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss"></style>
|
<style lang="scss"></style>
|
||||||
|
|||||||
@@ -63,13 +63,19 @@
|
|||||||
<el-dropdown-item :command="{ type: 'detail', data }"> 详情 </el-dropdown-item>
|
<el-dropdown-item :command="{ type: 'detail', data }"> 详情 </el-dropdown-item>
|
||||||
<el-dropdown-item :command="{ type: 'dumpDb', data }" v-if="supportAction('dumpDb', data.type)"> 导出 </el-dropdown-item>
|
<el-dropdown-item :command="{ type: 'dumpDb', data }" v-if="supportAction('dumpDb', data.type)"> 导出 </el-dropdown-item>
|
||||||
<el-dropdown-item :command="{ type: 'backupDb', data }" v-if="actionBtns[perms.backupDb] && supportAction('backupDb', data.type)">
|
<el-dropdown-item :command="{ type: 'backupDb', data }" v-if="actionBtns[perms.backupDb] && supportAction('backupDb', data.type)">
|
||||||
备份
|
备份任务
|
||||||
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item
|
||||||
|
:command="{ type: 'backupHistory', data }"
|
||||||
|
v-if="actionBtns[perms.backupDb] && supportAction('backupDb', data.type)"
|
||||||
|
>
|
||||||
|
备份历史
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
<el-dropdown-item
|
<el-dropdown-item
|
||||||
:command="{ type: 'restoreDb', data }"
|
:command="{ type: 'restoreDb', data }"
|
||||||
v-if="actionBtns[perms.restoreDb] && supportAction('restoreDb', data.type)"
|
v-if="actionBtns[perms.restoreDb] && supportAction('restoreDb', data.type)"
|
||||||
>
|
>
|
||||||
恢复
|
恢复任务
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
@@ -138,6 +144,16 @@
|
|||||||
<db-backup-list :dbId="dbBackupDialog.dbId" :dbNames="dbBackupDialog.dbs" />
|
<db-backup-list :dbId="dbBackupDialog.dbId" :dbNames="dbBackupDialog.dbs" />
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
width="80%"
|
||||||
|
:title="`${dbBackupHistoryDialog.title} - 数据库备份历史`"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
v-model="dbBackupHistoryDialog.visible"
|
||||||
|
>
|
||||||
|
<db-backup-history-list :dbId="dbBackupHistoryDialog.dbId" :dbNames="dbBackupHistoryDialog.dbs" />
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog
|
<el-dialog
|
||||||
width="80%"
|
width="80%"
|
||||||
:title="`${dbRestoreDialog.title} - 数据库恢复`"
|
:title="`${dbRestoreDialog.title} - 数据库恢复`"
|
||||||
@@ -192,6 +208,7 @@ import { getDbDialect } from './dialect/index';
|
|||||||
import { getTagPathSearchItem } from '../component/tag';
|
import { getTagPathSearchItem } from '../component/tag';
|
||||||
import { SearchItem } from '@/components/SearchForm';
|
import { SearchItem } from '@/components/SearchForm';
|
||||||
import DbBackupList from './DbBackupList.vue';
|
import DbBackupList from './DbBackupList.vue';
|
||||||
|
import DbBackupHistoryList from './DbBackupHistoryList.vue';
|
||||||
import DbRestoreList from './DbRestoreList.vue';
|
import DbRestoreList from './DbRestoreList.vue';
|
||||||
|
|
||||||
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
|
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
|
||||||
@@ -263,6 +280,13 @@ const state = reactive({
|
|||||||
dbs: [],
|
dbs: [],
|
||||||
dbId: 0,
|
dbId: 0,
|
||||||
},
|
},
|
||||||
|
// 数据库备份历史弹框
|
||||||
|
dbBackupHistoryDialog: {
|
||||||
|
title: '',
|
||||||
|
visible: false,
|
||||||
|
dbs: [],
|
||||||
|
dbId: 0,
|
||||||
|
},
|
||||||
// 数据库恢复弹框
|
// 数据库恢复弹框
|
||||||
dbRestoreDialog: {
|
dbRestoreDialog: {
|
||||||
title: '',
|
title: '',
|
||||||
@@ -295,7 +319,8 @@ const state = reactive({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { db, selectionData, query, infoDialog, sqlExecLogDialog, exportDialog, dbEditDialog, dbBackupDialog, dbRestoreDialog } = toRefs(state);
|
const { db, selectionData, query, infoDialog, sqlExecLogDialog, exportDialog, dbEditDialog, dbBackupDialog, dbBackupHistoryDialog, dbRestoreDialog } =
|
||||||
|
toRefs(state);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (Object.keys(actionBtns).length > 0) {
|
if (Object.keys(actionBtns).length > 0) {
|
||||||
@@ -359,6 +384,10 @@ const handleMoreActionCommand = (commond: any) => {
|
|||||||
onShowDbBackupDialog(data);
|
onShowDbBackupDialog(data);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
case 'backupHistory': {
|
||||||
|
onShowDbBackupHistoryDialog(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
case 'restoreDb': {
|
case 'restoreDb': {
|
||||||
onShowDbRestoreDialog(data);
|
onShowDbRestoreDialog(data);
|
||||||
return;
|
return;
|
||||||
@@ -412,6 +441,13 @@ const onShowDbBackupDialog = async (row: any) => {
|
|||||||
state.dbBackupDialog.visible = true;
|
state.dbBackupDialog.visible = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onShowDbBackupHistoryDialog = async (row: any) => {
|
||||||
|
state.dbBackupHistoryDialog.title = `${row.name}`;
|
||||||
|
state.dbBackupHistoryDialog.dbId = row.id;
|
||||||
|
state.dbBackupHistoryDialog.dbs = row.database.split(' ');
|
||||||
|
state.dbBackupHistoryDialog.visible = true;
|
||||||
|
};
|
||||||
|
|
||||||
const onShowDbRestoreDialog = async (row: any) => {
|
const onShowDbRestoreDialog = async (row: any) => {
|
||||||
state.dbRestoreDialog.title = `${row.name}`;
|
state.dbRestoreDialog.title = `${row.name}`;
|
||||||
state.dbRestoreDialog.dbId = row.id;
|
state.dbRestoreDialog.dbId = row.id;
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, reactive, ref, watch } from 'vue';
|
import { onMounted, reactive, ref, watch } from 'vue';
|
||||||
import { dbApi } from './api';
|
import { dbApi } from './api';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
@@ -161,7 +161,7 @@ const state = reactive({
|
|||||||
id: 0,
|
id: 0,
|
||||||
dbId: 0,
|
dbId: 0,
|
||||||
dbName: null as any,
|
dbName: null as any,
|
||||||
intervalDay: 1,
|
intervalDay: 0,
|
||||||
startTime: null as any,
|
startTime: null as any,
|
||||||
repeated: null as any,
|
repeated: null as any,
|
||||||
dbBackupId: null as any,
|
dbBackupId: null as any,
|
||||||
@@ -233,7 +233,8 @@ const init = async (data: any) => {
|
|||||||
} else {
|
} else {
|
||||||
state.form.dbName = '';
|
state.form.dbName = '';
|
||||||
state.editOrCreate = false;
|
state.editOrCreate = false;
|
||||||
state.form.intervalDay = 1;
|
state.form.intervalDay = 0;
|
||||||
|
state.form.repeated = false;
|
||||||
state.form.pointInTime = new Date();
|
state.form.pointInTime = new Date();
|
||||||
state.form.startTime = new Date();
|
state.form.startTime = new Date();
|
||||||
state.histories = [];
|
state.histories = [];
|
||||||
@@ -252,6 +253,12 @@ const getDbNamesWithoutRestore = async () => {
|
|||||||
const btnOk = async () => {
|
const btnOk = async () => {
|
||||||
restoreForm.value.validate(async (valid: any) => {
|
restoreForm.value.validate(async (valid: any) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
|
await ElMessageBox.confirm(`确定恢复数据库吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
|
||||||
if (state.restoreMode == 'point-in-time') {
|
if (state.restoreMode == 'point-in-time') {
|
||||||
state.form.dbBackupId = 0;
|
state.form.dbBackupId = 0;
|
||||||
state.form.dbBackupHistoryId = 0;
|
state.form.dbBackupHistoryId = 0;
|
||||||
@@ -260,13 +267,14 @@ const btnOk = async () => {
|
|||||||
state.form.pointInTime = null;
|
state.form.pointInTime = null;
|
||||||
}
|
}
|
||||||
state.form.repeated = false;
|
state.form.repeated = false;
|
||||||
|
state.form.intervalDay = 0;
|
||||||
const reqForm = { ...state.form };
|
const reqForm = { ...state.form };
|
||||||
let api = dbApi.createDbRestore;
|
let api = dbApi.createDbRestore;
|
||||||
if (props.data) {
|
if (props.data) {
|
||||||
api = dbApi.saveDbRestore;
|
api = dbApi.saveDbRestore;
|
||||||
}
|
}
|
||||||
api.request(reqForm).then(() => {
|
api.request(reqForm).then(() => {
|
||||||
ElMessage.success('保存成功');
|
ElMessage.success('成功创建数据库恢复任务');
|
||||||
emit('val-change', state.form);
|
emit('val-change', state.form);
|
||||||
state.btnLoading = true;
|
state.btnLoading = true;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -21,12 +21,14 @@
|
|||||||
<el-button type="primary" icon="plus" @click="createDbRestore()">添加</el-button>
|
<el-button type="primary" icon="plus" @click="createDbRestore()">添加</el-button>
|
||||||
<el-button type="primary" icon="video-play" @click="enableDbRestore(null)">启用</el-button>
|
<el-button type="primary" icon="video-play" @click="enableDbRestore(null)">启用</el-button>
|
||||||
<el-button type="primary" icon="video-pause" @click="disableDbRestore(null)">禁用</el-button>
|
<el-button type="primary" icon="video-pause" @click="disableDbRestore(null)">禁用</el-button>
|
||||||
|
<el-button type="danger" icon="delete" @click="deleteDbRestore(null)">删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #action="{ data }">
|
<template #action="{ data }">
|
||||||
<el-button @click="showDbRestore(data)" type="primary" link>详情</el-button>
|
<el-button @click="showDbRestore(data)" type="primary" link>详情</el-button>
|
||||||
<el-button @click="enableDbRestore(data)" v-if="!data.enabled" type="primary" link>启用</el-button>
|
<el-button @click="enableDbRestore(data)" v-if="!data.enabled" type="primary" link>启用</el-button>
|
||||||
<el-button @click="disableDbRestore(data)" v-if="data.enabled" type="primary" link>禁用</el-button>
|
<el-button @click="disableDbRestore(data)" v-if="data.enabled" type="primary" link>禁用</el-button>
|
||||||
|
<el-button @click="deleteDbRestore(data)" type="danger" link>删除</el-button>
|
||||||
</template>
|
</template>
|
||||||
</page-table>
|
</page-table>
|
||||||
|
|
||||||
@@ -49,7 +51,7 @@
|
|||||||
infoDialog.data.dbBackupHistoryName
|
infoDialog.data.dbBackupHistoryName
|
||||||
}}</el-descriptions-item>
|
}}</el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="开始时间">{{ dateFormat(infoDialog.data.startTime) }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="开始时间">{{ dateFormat(infoDialog.data.startTime) }}</el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="是否启用">{{ infoDialog.data.enabled }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="是否启用">{{ infoDialog.data.enabledDesc }}</el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="执行时间">{{ dateFormat(infoDialog.data.lastTime) }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="执行时间">{{ dateFormat(infoDialog.data.lastTime) }}</el-descriptions-item>
|
||||||
<el-descriptions-item :span="1" label="执行结果">{{ infoDialog.data.lastResult }}</el-descriptions-item>
|
<el-descriptions-item :span="1" label="执行结果">{{ infoDialog.data.lastResult }}</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
@@ -63,7 +65,7 @@ import { dbApi } from './api';
|
|||||||
import PageTable from '@/components/pagetable/PageTable.vue';
|
import PageTable from '@/components/pagetable/PageTable.vue';
|
||||||
import { TableColumn } from '@/components/pagetable';
|
import { TableColumn } from '@/components/pagetable';
|
||||||
import { SearchItem } from '@/components/SearchForm';
|
import { SearchItem } from '@/components/SearchForm';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||||
import { dateFormat } from '@/common/utils/date';
|
import { dateFormat } from '@/common/utils/date';
|
||||||
const DbRestoreEdit = defineAsyncComponent(() => import('./DbRestoreEdit.vue'));
|
const DbRestoreEdit = defineAsyncComponent(() => import('./DbRestoreEdit.vue'));
|
||||||
const pageTableRef: Ref<any> = ref(null);
|
const pageTableRef: Ref<any> = ref(null);
|
||||||
@@ -85,7 +87,7 @@ const searchItems = [SearchItem.slot('dbName', '数据库名称', 'dbSelect')];
|
|||||||
const columns = [
|
const columns = [
|
||||||
TableColumn.new('dbName', '数据库名称'),
|
TableColumn.new('dbName', '数据库名称'),
|
||||||
TableColumn.new('startTime', '启动时间').isTime(),
|
TableColumn.new('startTime', '启动时间').isTime(),
|
||||||
TableColumn.new('enabled', '是否启用'),
|
TableColumn.new('enabledDesc', '是否启用'),
|
||||||
TableColumn.new('lastTime', '执行时间').isTime(),
|
TableColumn.new('lastTime', '执行时间').isTime(),
|
||||||
TableColumn.new('lastResult', '执行结果'),
|
TableColumn.new('lastResult', '执行结果'),
|
||||||
TableColumn.new('action', '操作').isSlot().setMinWidth(220).fixedRight().alignCenter(),
|
TableColumn.new('action', '操作').isSlot().setMinWidth(220).fixedRight().alignCenter(),
|
||||||
@@ -135,19 +137,39 @@ const createDbRestore = async () => {
|
|||||||
state.dbRestoreEditDialog.visible = true;
|
state.dbRestoreEditDialog.visible = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteDbRestore = async (data: any) => {
|
||||||
|
let restoreId: string;
|
||||||
|
if (data) {
|
||||||
|
restoreId = data.id;
|
||||||
|
} else if (state.selectedData.length > 0) {
|
||||||
|
restoreId = state.selectedData.map((x: any) => x.id).join(' ');
|
||||||
|
} else {
|
||||||
|
ElMessage.error('请选择需要删除的数据库恢复任务');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await ElMessageBox.confirm(`确定删除 “数据库恢复任务” 吗?`, '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning',
|
||||||
|
});
|
||||||
|
await dbApi.deleteDbRestore.request({ dbId: props.dbId, restoreId: restoreId });
|
||||||
|
await search();
|
||||||
|
ElMessage.success('删除成功');
|
||||||
|
};
|
||||||
|
|
||||||
const showDbRestore = async (data: any) => {
|
const showDbRestore = async (data: any) => {
|
||||||
state.infoDialog.data = data;
|
state.infoDialog.data = data;
|
||||||
state.infoDialog.visible = true;
|
state.infoDialog.visible = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const enableDbRestore = async (data: any) => {
|
const enableDbRestore = async (data: any) => {
|
||||||
let restoreId: String;
|
let restoreId: string;
|
||||||
if (data) {
|
if (data) {
|
||||||
restoreId = data.id;
|
restoreId = data.id;
|
||||||
} else if (state.selectedData.length > 0) {
|
} else if (state.selectedData.length > 0) {
|
||||||
restoreId = state.selectedData.map((x: any) => x.id).join(' ');
|
restoreId = state.selectedData.map((x: any) => x.id).join(' ');
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error('请选择需要启用的恢复任务');
|
ElMessage.error('请选择需要启用的数据库恢复任务');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await dbApi.enableDbRestore.request({ dbId: props.dbId, restoreId: restoreId });
|
await dbApi.enableDbRestore.request({ dbId: props.dbId, restoreId: restoreId });
|
||||||
@@ -156,13 +178,13 @@ const enableDbRestore = async (data: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const disableDbRestore = async (data: any) => {
|
const disableDbRestore = async (data: any) => {
|
||||||
let restoreId: String;
|
let restoreId: string;
|
||||||
if (data) {
|
if (data) {
|
||||||
restoreId = data.id;
|
restoreId = data.id;
|
||||||
} else if (state.selectedData.length > 0) {
|
} else if (state.selectedData.length > 0) {
|
||||||
restoreId = state.selectedData.map((x: any) => x.id).join(' ');
|
restoreId = state.selectedData.map((x: any) => x.id).join(' ');
|
||||||
} else {
|
} else {
|
||||||
ElMessage.error('请选择需要禁用的恢复任务');
|
ElMessage.error('请选择需要禁用的数据库恢复任务');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await dbApi.disableDbRestore.request({ dbId: props.dbId, restoreId: restoreId });
|
await dbApi.disableDbRestore.request({ dbId: props.dbId, restoreId: restoreId });
|
||||||
|
|||||||
@@ -49,16 +49,20 @@ export const dbApi = {
|
|||||||
// 获取数据库备份列表
|
// 获取数据库备份列表
|
||||||
getDbBackups: Api.newGet('/dbs/{dbId}/backups'),
|
getDbBackups: Api.newGet('/dbs/{dbId}/backups'),
|
||||||
createDbBackup: Api.newPost('/dbs/{dbId}/backups'),
|
createDbBackup: Api.newPost('/dbs/{dbId}/backups'),
|
||||||
|
deleteDbBackup: Api.newDelete('/dbs/{dbId}/backups/{backupId}'),
|
||||||
getDbNamesWithoutBackup: Api.newGet('/dbs/{dbId}/db-names-without-backup'),
|
getDbNamesWithoutBackup: Api.newGet('/dbs/{dbId}/db-names-without-backup'),
|
||||||
enableDbBackup: Api.newPut('/dbs/{dbId}/backups/{backupId}/enable'),
|
enableDbBackup: Api.newPut('/dbs/{dbId}/backups/{backupId}/enable'),
|
||||||
disableDbBackup: Api.newPut('/dbs/{dbId}/backups/{backupId}/disable'),
|
disableDbBackup: Api.newPut('/dbs/{dbId}/backups/{backupId}/disable'),
|
||||||
startDbBackup: Api.newPut('/dbs/{dbId}/backups/{backupId}/start'),
|
startDbBackup: Api.newPut('/dbs/{dbId}/backups/{backupId}/start'),
|
||||||
saveDbBackup: Api.newPut('/dbs/{dbId}/backups/{id}'),
|
saveDbBackup: Api.newPut('/dbs/{dbId}/backups/{id}'),
|
||||||
getDbBackupHistories: Api.newGet('/dbs/{dbId}/backup-histories'),
|
getDbBackupHistories: Api.newGet('/dbs/{dbId}/backup-histories'),
|
||||||
|
restoreDbBackupHistory: Api.newPost('/dbs/{dbId}/backup-histories/{backupHistoryId}/restore'),
|
||||||
|
deleteDbBackupHistory: Api.newDelete('/dbs/{dbId}/backup-histories/{backupHistoryId}'),
|
||||||
|
|
||||||
// 获取数据库备份列表
|
// 获取数据库恢复列表
|
||||||
getDbRestores: Api.newGet('/dbs/{dbId}/restores'),
|
getDbRestores: Api.newGet('/dbs/{dbId}/restores'),
|
||||||
createDbRestore: Api.newPost('/dbs/{dbId}/restores'),
|
createDbRestore: Api.newPost('/dbs/{dbId}/restores'),
|
||||||
|
deleteDbRestore: Api.newDelete('/dbs/{dbId}/restores/{restoreId}'),
|
||||||
getDbNamesWithoutRestore: Api.newGet('/dbs/{dbId}/db-names-without-restore'),
|
getDbNamesWithoutRestore: Api.newGet('/dbs/{dbId}/db-names-without-restore'),
|
||||||
enableDbRestore: Api.newPut('/dbs/{dbId}/restores/{restoreId}/enable'),
|
enableDbRestore: Api.newPut('/dbs/{dbId}/restores/{restoreId}/enable'),
|
||||||
disableDbRestore: Api.newPut('/dbs/{dbId}/restores/{restoreId}/disable'),
|
disableDbRestore: Api.newPut('/dbs/{dbId}/restores/{restoreId}/disable'),
|
||||||
|
|||||||
@@ -9,13 +9,16 @@ import (
|
|||||||
"mayfly-go/pkg/biz"
|
"mayfly-go/pkg/biz"
|
||||||
"mayfly-go/pkg/ginx"
|
"mayfly-go/pkg/ginx"
|
||||||
"mayfly-go/pkg/req"
|
"mayfly-go/pkg/req"
|
||||||
|
"mayfly-go/pkg/utils/timex"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DbBackup struct {
|
type DbBackup struct {
|
||||||
dbBackupApp *application.DbBackupApp `inject:"DbBackupApp"`
|
backupApp *application.DbBackupApp `inject:"DbBackupApp"`
|
||||||
dbApp application.Db `inject:"DbApp"`
|
dbApp application.Db `inject:"DbApp"`
|
||||||
|
restoreApp *application.DbRestoreApp `inject:"DbRestoreApp"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: 鉴权,避免未经授权进行数据库备份和恢复
|
// todo: 鉴权,避免未经授权进行数据库备份和恢复
|
||||||
@@ -28,10 +31,10 @@ func (d *DbBackup) GetPageList(rc *req.Ctx) {
|
|||||||
db, err := d.dbApp.GetById(new(entity.Db), dbId, "db_instance_id", "database")
|
db, err := d.dbApp.GetById(new(entity.Db), dbId, "db_instance_id", "database")
|
||||||
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
||||||
|
|
||||||
queryCond, page := ginx.BindQueryAndPage[*entity.DbJobQuery](rc.GinCtx, new(entity.DbJobQuery))
|
queryCond, page := ginx.BindQueryAndPage[*entity.DbBackupQuery](rc.GinCtx, new(entity.DbBackupQuery))
|
||||||
queryCond.DbInstanceId = db.InstanceId
|
queryCond.DbInstanceId = db.InstanceId
|
||||||
queryCond.InDbNames = strings.Fields(db.Database)
|
queryCond.InDbNames = strings.Fields(db.Database)
|
||||||
res, err := d.dbBackupApp.GetPageList(queryCond, page, new([]vo.DbBackup))
|
res, err := d.backupApp.GetPageList(queryCond, page, new([]vo.DbBackup))
|
||||||
biz.ErrIsNilAppendErr(err, "获取数据库备份任务失败: %v")
|
biz.ErrIsNilAppendErr(err, "获取数据库备份任务失败: %v")
|
||||||
rc.ResData = res
|
rc.ResData = res
|
||||||
}
|
}
|
||||||
@@ -50,21 +53,20 @@ func (d *DbBackup) Create(rc *req.Ctx) {
|
|||||||
biz.IsTrue(dbId > 0, "无效的 dbId: %v", dbId)
|
biz.IsTrue(dbId > 0, "无效的 dbId: %v", dbId)
|
||||||
db, err := d.dbApp.GetById(new(entity.Db), dbId, "instanceId")
|
db, err := d.dbApp.GetById(new(entity.Db), dbId, "instanceId")
|
||||||
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
||||||
|
|
||||||
jobs := make([]*entity.DbBackup, 0, len(dbNames))
|
jobs := make([]*entity.DbBackup, 0, len(dbNames))
|
||||||
for _, dbName := range dbNames {
|
for _, dbName := range dbNames {
|
||||||
job := &entity.DbBackup{
|
job := &entity.DbBackup{
|
||||||
DbJobBaseImpl: entity.NewDbBJobBase(db.InstanceId, entity.DbJobTypeBackup),
|
DbInstanceId: db.InstanceId,
|
||||||
DbName: dbName,
|
DbName: dbName,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Repeated: backupForm.Repeated,
|
Repeated: backupForm.Repeated,
|
||||||
StartTime: backupForm.StartTime,
|
StartTime: backupForm.StartTime,
|
||||||
Interval: backupForm.Interval,
|
Interval: backupForm.Interval,
|
||||||
Name: backupForm.Name,
|
Name: backupForm.Name,
|
||||||
}
|
}
|
||||||
jobs = append(jobs, job)
|
jobs = append(jobs, job)
|
||||||
}
|
}
|
||||||
biz.ErrIsNilAppendErr(d.dbBackupApp.Create(rc.MetaCtx, jobs), "添加数据库备份任务失败: %v")
|
biz.ErrIsNilAppendErr(d.backupApp.Create(rc.MetaCtx, jobs), "添加数据库备份任务失败: %v")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update 保存数据库备份任务
|
// Update 保存数据库备份任务
|
||||||
@@ -74,17 +76,17 @@ func (d *DbBackup) Update(rc *req.Ctx) {
|
|||||||
ginx.BindJsonAndValid(rc.GinCtx, backupForm)
|
ginx.BindJsonAndValid(rc.GinCtx, backupForm)
|
||||||
rc.ReqParam = backupForm
|
rc.ReqParam = backupForm
|
||||||
|
|
||||||
job := entity.NewDbJob(entity.DbJobTypeBackup).(*entity.DbBackup)
|
job := &entity.DbBackup{}
|
||||||
job.Id = backupForm.Id
|
job.Id = backupForm.Id
|
||||||
job.Name = backupForm.Name
|
job.Name = backupForm.Name
|
||||||
job.StartTime = backupForm.StartTime
|
job.StartTime = backupForm.StartTime
|
||||||
job.Interval = backupForm.Interval
|
job.Interval = backupForm.Interval
|
||||||
biz.ErrIsNilAppendErr(d.dbBackupApp.Update(rc.MetaCtx, job), "保存数据库备份任务失败: %v")
|
biz.ErrIsNilAppendErr(d.backupApp.Update(rc.MetaCtx, job), "保存数据库备份任务失败: %v")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DbBackup) walk(rc *req.Ctx, fn func(ctx context.Context, backupId uint64) error) error {
|
func (d *DbBackup) walk(rc *req.Ctx, paramName string, fn func(ctx context.Context, id uint64) error) error {
|
||||||
idsStr := ginx.PathParam(rc.GinCtx, "backupId")
|
idsStr := ginx.PathParam(rc.GinCtx, paramName)
|
||||||
biz.NotEmpty(idsStr, "backupId 为空")
|
biz.NotEmpty(idsStr, paramName+" 为空")
|
||||||
rc.ReqParam = idsStr
|
rc.ReqParam = idsStr
|
||||||
ids := strings.Fields(idsStr)
|
ids := strings.Fields(idsStr)
|
||||||
for _, v := range ids {
|
for _, v := range ids {
|
||||||
@@ -104,28 +106,28 @@ func (d *DbBackup) walk(rc *req.Ctx, fn func(ctx context.Context, backupId uint6
|
|||||||
// Delete 删除数据库备份任务
|
// Delete 删除数据库备份任务
|
||||||
// @router /api/dbs/:dbId/backups/:backupId [DELETE]
|
// @router /api/dbs/:dbId/backups/:backupId [DELETE]
|
||||||
func (d *DbBackup) Delete(rc *req.Ctx) {
|
func (d *DbBackup) Delete(rc *req.Ctx) {
|
||||||
err := d.walk(rc, d.dbBackupApp.Delete)
|
err := d.walk(rc, "backupId", d.backupApp.Delete)
|
||||||
biz.ErrIsNilAppendErr(err, "删除数据库备份任务失败: %v")
|
biz.ErrIsNilAppendErr(err, "删除数据库备份任务失败: %v")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable 启用数据库备份任务
|
// Enable 启用数据库备份任务
|
||||||
// @router /api/dbs/:dbId/backups/:backupId/enable [PUT]
|
// @router /api/dbs/:dbId/backups/:backupId/enable [PUT]
|
||||||
func (d *DbBackup) Enable(rc *req.Ctx) {
|
func (d *DbBackup) Enable(rc *req.Ctx) {
|
||||||
err := d.walk(rc, d.dbBackupApp.Enable)
|
err := d.walk(rc, "backupId", d.backupApp.Enable)
|
||||||
biz.ErrIsNilAppendErr(err, "启用数据库备份任务失败: %v")
|
biz.ErrIsNilAppendErr(err, "启用数据库备份任务失败: %v")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable 禁用数据库备份任务
|
// Disable 禁用数据库备份任务
|
||||||
// @router /api/dbs/:dbId/backups/:backupId/disable [PUT]
|
// @router /api/dbs/:dbId/backups/:backupId/disable [PUT]
|
||||||
func (d *DbBackup) Disable(rc *req.Ctx) {
|
func (d *DbBackup) Disable(rc *req.Ctx) {
|
||||||
err := d.walk(rc, d.dbBackupApp.Disable)
|
err := d.walk(rc, "backupId", d.backupApp.Disable)
|
||||||
biz.ErrIsNilAppendErr(err, "禁用数据库备份任务失败: %v")
|
biz.ErrIsNilAppendErr(err, "禁用数据库备份任务失败: %v")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start 禁用数据库备份任务
|
// Start 禁用数据库备份任务
|
||||||
// @router /api/dbs/:dbId/backups/:backupId/start [PUT]
|
// @router /api/dbs/:dbId/backups/:backupId/start [PUT]
|
||||||
func (d *DbBackup) Start(rc *req.Ctx) {
|
func (d *DbBackup) Start(rc *req.Ctx) {
|
||||||
err := d.walk(rc, d.dbBackupApp.Start)
|
err := d.walk(rc, "backupId", d.backupApp.StartNow)
|
||||||
biz.ErrIsNilAppendErr(err, "运行数据库备份任务失败: %v")
|
biz.ErrIsNilAppendErr(err, "运行数据库备份任务失败: %v")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +138,7 @@ func (d *DbBackup) GetDbNamesWithoutBackup(rc *req.Ctx) {
|
|||||||
db, err := d.dbApp.GetById(new(entity.Db), dbId, "instance_id", "database")
|
db, err := d.dbApp.GetById(new(entity.Db), dbId, "instance_id", "database")
|
||||||
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
||||||
dbNames := strings.Fields(db.Database)
|
dbNames := strings.Fields(db.Database)
|
||||||
dbNamesWithoutBackup, err := d.dbBackupApp.GetDbNamesWithoutBackup(db.InstanceId, dbNames)
|
dbNamesWithoutBackup, err := d.backupApp.GetDbNamesWithoutBackup(db.InstanceId, dbNames)
|
||||||
biz.ErrIsNilAppendErr(err, "获取未配置定时备份的数据库名称失败: %v")
|
biz.ErrIsNilAppendErr(err, "获取未配置定时备份的数据库名称失败: %v")
|
||||||
rc.ResData = dbNamesWithoutBackup
|
rc.ResData = dbNamesWithoutBackup
|
||||||
}
|
}
|
||||||
@@ -149,10 +151,71 @@ func (d *DbBackup) GetHistoryPageList(rc *req.Ctx) {
|
|||||||
db, err := d.dbApp.GetById(new(entity.Db), dbId, "db_instance_id", "database")
|
db, err := d.dbApp.GetById(new(entity.Db), dbId, "db_instance_id", "database")
|
||||||
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
||||||
|
|
||||||
queryCond, page := ginx.BindQueryAndPage[*entity.DbBackupHistoryQuery](rc.GinCtx, new(entity.DbBackupHistoryQuery))
|
backupHistoryCond, page := ginx.BindQueryAndPage[*entity.DbBackupHistoryQuery](rc.GinCtx, new(entity.DbBackupHistoryQuery))
|
||||||
queryCond.DbInstanceId = db.InstanceId
|
backupHistoryCond.DbInstanceId = db.InstanceId
|
||||||
queryCond.InDbNames = strings.Fields(db.Database)
|
backupHistoryCond.InDbNames = strings.Fields(db.Database)
|
||||||
res, err := d.dbBackupApp.GetHistoryPageList(queryCond, page, new([]vo.DbBackupHistory))
|
backupHistories := make([]*vo.DbBackupHistory, 0, page.PageSize)
|
||||||
|
res, err := d.backupApp.GetHistoryPageList(backupHistoryCond, page, &backupHistories)
|
||||||
biz.ErrIsNilAppendErr(err, "获取数据库备份历史失败: %v")
|
biz.ErrIsNilAppendErr(err, "获取数据库备份历史失败: %v")
|
||||||
|
historyIds := make([]uint64, 0, len(backupHistories))
|
||||||
|
for _, history := range backupHistories {
|
||||||
|
historyIds = append(historyIds, history.Id)
|
||||||
|
}
|
||||||
|
restores := make([]*entity.DbRestore, 0, page.PageSize)
|
||||||
|
if err := d.restoreApp.GetRestoresEnabled(&restores, historyIds...); err != nil {
|
||||||
|
biz.ErrIsNilAppendErr(err, "获取数据库备份恢复记录失败")
|
||||||
|
}
|
||||||
|
for _, history := range backupHistories {
|
||||||
|
for _, restore := range restores {
|
||||||
|
if restore.DbBackupHistoryId == history.Id {
|
||||||
|
history.LastStatus = restore.LastStatus
|
||||||
|
history.LastResult = restore.LastResult
|
||||||
|
history.LastTime = restore.LastTime
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
rc.ResData = res
|
rc.ResData = res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RestoreHistories 删除数据库备份历史
|
||||||
|
// @router /api/dbs/:dbId/backup-histories/:backupHistoryId/restore [POST]
|
||||||
|
func (d *DbBackup) RestoreHistories(rc *req.Ctx) {
|
||||||
|
pm := ginx.PathParam(rc.GinCtx, "backupHistoryId")
|
||||||
|
biz.NotEmpty(pm, "backupHistoryId 为空")
|
||||||
|
idsStr := strings.Fields(pm)
|
||||||
|
ids := make([]uint64, 0, len(idsStr))
|
||||||
|
for _, s := range idsStr {
|
||||||
|
id, err := strconv.ParseUint(s, 10, 64)
|
||||||
|
biz.ErrIsNilAppendErr(err, "从数据库备份历史恢复数据库失败: %v")
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
histories := make([]*entity.DbBackupHistory, 0, len(ids))
|
||||||
|
err := d.backupApp.GetHistories(ids, &histories)
|
||||||
|
biz.ErrIsNilAppendErr(err, "添加数据库恢复任务失败: %v")
|
||||||
|
restores := make([]*entity.DbRestore, 0, len(histories))
|
||||||
|
now := time.Now()
|
||||||
|
for _, history := range histories {
|
||||||
|
job := &entity.DbRestore{
|
||||||
|
DbInstanceId: history.DbInstanceId,
|
||||||
|
DbName: history.DbName,
|
||||||
|
Enabled: true,
|
||||||
|
Repeated: false,
|
||||||
|
StartTime: now,
|
||||||
|
Interval: 0,
|
||||||
|
PointInTime: timex.NewNullTime(time.Time{}),
|
||||||
|
DbBackupId: history.DbBackupId,
|
||||||
|
DbBackupHistoryId: history.Id,
|
||||||
|
DbBackupHistoryName: history.Name,
|
||||||
|
}
|
||||||
|
restores = append(restores, job)
|
||||||
|
}
|
||||||
|
biz.ErrIsNilAppendErr(d.restoreApp.Create(rc.MetaCtx, restores), "添加数据库恢复任务失败: %v")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteHistories 删除数据库备份历史
|
||||||
|
// @router /api/dbs/:dbId/backup-histories/:backupHistoryId [DELETE]
|
||||||
|
func (d *DbBackup) DeleteHistories(rc *req.Ctx) {
|
||||||
|
err := d.walk(rc, "backupHistoryId", d.backupApp.DeleteHistory)
|
||||||
|
biz.ErrIsNilAppendErr(err, "删除数据库备份历史失败: %v")
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func (d *DbRestore) GetPageList(rc *req.Ctx) {
|
|||||||
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
||||||
|
|
||||||
var restores []vo.DbRestore
|
var restores []vo.DbRestore
|
||||||
queryCond, page := ginx.BindQueryAndPage[*entity.DbJobQuery](rc.GinCtx, new(entity.DbJobQuery))
|
queryCond, page := ginx.BindQueryAndPage[*entity.DbRestoreQuery](rc.GinCtx, new(entity.DbRestoreQuery))
|
||||||
queryCond.DbInstanceId = db.InstanceId
|
queryCond.DbInstanceId = db.InstanceId
|
||||||
queryCond.InDbNames = strings.Fields(db.Database)
|
queryCond.InDbNames = strings.Fields(db.Database)
|
||||||
res, err := d.restoreApp.GetPageList(queryCond, page, &restores)
|
res, err := d.restoreApp.GetPageList(queryCond, page, &restores)
|
||||||
@@ -48,7 +48,8 @@ func (d *DbRestore) Create(rc *req.Ctx) {
|
|||||||
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
biz.ErrIsNilAppendErr(err, "获取数据库信息失败: %v")
|
||||||
|
|
||||||
job := &entity.DbRestore{
|
job := &entity.DbRestore{
|
||||||
DbJobBaseImpl: entity.NewDbBJobBase(db.InstanceId, entity.DbJobTypeRestore),
|
DbInstanceId: db.InstanceId,
|
||||||
|
DbName: restoreForm.DbName,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Repeated: restoreForm.Repeated,
|
Repeated: restoreForm.Repeated,
|
||||||
StartTime: restoreForm.StartTime,
|
StartTime: restoreForm.StartTime,
|
||||||
@@ -58,10 +59,13 @@ func (d *DbRestore) Create(rc *req.Ctx) {
|
|||||||
DbBackupHistoryId: restoreForm.DbBackupHistoryId,
|
DbBackupHistoryId: restoreForm.DbBackupHistoryId,
|
||||||
DbBackupHistoryName: restoreForm.DbBackupHistoryName,
|
DbBackupHistoryName: restoreForm.DbBackupHistoryName,
|
||||||
}
|
}
|
||||||
job.DbName = restoreForm.DbName
|
|
||||||
biz.ErrIsNilAppendErr(d.restoreApp.Create(rc.MetaCtx, job), "添加数据库恢复任务失败: %v")
|
biz.ErrIsNilAppendErr(d.restoreApp.Create(rc.MetaCtx, job), "添加数据库恢复任务失败: %v")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DbRestore) createWithBackupHistory(backupHistoryIds string) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Update 保存数据库恢复任务
|
// Update 保存数据库恢复任务
|
||||||
// @router /api/dbs/:dbId/restores/:restoreId [PUT]
|
// @router /api/dbs/:dbId/restores/:restoreId [PUT]
|
||||||
func (d *DbRestore) Update(rc *req.Ctx) {
|
func (d *DbRestore) Update(rc *req.Ctx) {
|
||||||
|
|||||||
@@ -2,38 +2,50 @@ package vo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"mayfly-go/internal/db/domain/entity"
|
||||||
"mayfly-go/pkg/utils/timex"
|
"mayfly-go/pkg/utils/timex"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DbBackup 数据库备份任务
|
// DbBackup 数据库备份任务
|
||||||
type DbBackup struct {
|
type DbBackup struct {
|
||||||
Id uint64 `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
DbName string `json:"dbName"` // 数据库名
|
DbName string `json:"dbName"` // 数据库名
|
||||||
CreateTime time.Time `json:"createTime"` // 创建时间
|
CreateTime time.Time `json:"createTime"` // 创建时间
|
||||||
StartTime time.Time `json:"startTime"` // 开始时间
|
StartTime time.Time `json:"startTime"` // 开始时间
|
||||||
Interval time.Duration `json:"-"` // 间隔时间
|
Interval time.Duration `json:"-"` // 间隔时间
|
||||||
IntervalDay uint64 `json:"intervalDay" gorm:"-"` // 间隔天数
|
IntervalDay uint64 `json:"intervalDay" gorm:"-"` // 间隔天数
|
||||||
Enabled bool `json:"enabled"` // 是否启用
|
Enabled bool `json:"enabled"` // 是否启用
|
||||||
LastTime timex.NullTime `json:"lastTime"` // 最近一次执行时间
|
EnabledDesc string `json:"enabledDesc"` // 启用状态描述
|
||||||
LastStatus string `json:"lastStatus"` // 最近一次执行状态
|
LastTime timex.NullTime `json:"lastTime"` // 最近一次执行时间
|
||||||
LastResult string `json:"lastResult"` // 最近一次执行结果
|
LastStatus entity.DbJobStatus `json:"lastStatus"` // 最近一次执行状态
|
||||||
DbInstanceId uint64 `json:"dbInstanceId"` // 数据库实例ID
|
LastResult string `json:"lastResult"` // 最近一次执行结果
|
||||||
Name string `json:"name"` // 备份任务名称
|
DbInstanceId uint64 `json:"dbInstanceId"` // 数据库实例ID
|
||||||
|
Name string `json:"name"` // 备份任务名称
|
||||||
}
|
}
|
||||||
|
|
||||||
func (backup *DbBackup) MarshalJSON() ([]byte, error) {
|
func (backup *DbBackup) MarshalJSON() ([]byte, error) {
|
||||||
type dbBackup DbBackup
|
type dbBackup DbBackup
|
||||||
backup.IntervalDay = uint64(backup.Interval / time.Hour / 24)
|
backup.IntervalDay = uint64(backup.Interval / time.Hour / 24)
|
||||||
|
if len(backup.EnabledDesc) == 0 {
|
||||||
|
if backup.Enabled {
|
||||||
|
backup.EnabledDesc = "任务已启用"
|
||||||
|
} else {
|
||||||
|
backup.EnabledDesc = "任务已禁用"
|
||||||
|
}
|
||||||
|
}
|
||||||
return json.Marshal((*dbBackup)(backup))
|
return json.Marshal((*dbBackup)(backup))
|
||||||
}
|
}
|
||||||
|
|
||||||
// DbBackupHistory 数据库备份历史
|
// DbBackupHistory 数据库备份历史
|
||||||
type DbBackupHistory struct {
|
type DbBackupHistory struct {
|
||||||
Id uint64 `json:"id"`
|
Id uint64 `json:"id"`
|
||||||
DbBackupId uint64 `json:"dbBackupId"`
|
DbBackupId uint64 `json:"dbBackupId"`
|
||||||
CreateTime time.Time `json:"createTime"`
|
CreateTime time.Time `json:"createTime"`
|
||||||
DbName string `json:"dbName"` // 数据库名称
|
DbName string `json:"dbName"` // 数据库名称
|
||||||
Name string `json:"name"` // 备份历史名称
|
Name string `json:"name"` // 备份历史名称
|
||||||
BinlogFileName string `json:"binlogFileName"`
|
BinlogFileName string `json:"binlogFileName"`
|
||||||
|
LastTime timex.NullTime `json:"lastTime" gorm:"-"` // 最近一次恢复时间
|
||||||
|
LastStatus entity.DbJobStatus `json:"lastStatus" gorm:"-"` // 最近一次恢复状态
|
||||||
|
LastResult string `json:"lastResult" gorm:"-"` // 最近一次恢复结果
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type DbRestore struct {
|
|||||||
Interval time.Duration `json:"-"` // 间隔时间
|
Interval time.Duration `json:"-"` // 间隔时间
|
||||||
IntervalDay uint64 `json:"intervalDay" gorm:"-"` // 间隔天数
|
IntervalDay uint64 `json:"intervalDay" gorm:"-"` // 间隔天数
|
||||||
Enabled bool `json:"enabled"` // 是否启用
|
Enabled bool `json:"enabled"` // 是否启用
|
||||||
|
EnabledDesc string `json:"enabledDesc"` // 启用状态描述
|
||||||
LastTime timex.NullTime `json:"lastTime"` // 最近一次执行时间
|
LastTime timex.NullTime `json:"lastTime"` // 最近一次执行时间
|
||||||
LastStatus string `json:"lastStatus"` // 最近一次执行状态
|
LastStatus string `json:"lastStatus"` // 最近一次执行状态
|
||||||
LastResult string `json:"lastResult"` // 最近一次执行结果
|
LastResult string `json:"lastResult"` // 最近一次执行结果
|
||||||
@@ -27,6 +28,13 @@ type DbRestore struct {
|
|||||||
func (restore *DbRestore) MarshalJSON() ([]byte, error) {
|
func (restore *DbRestore) MarshalJSON() ([]byte, error) {
|
||||||
type dbBackup DbRestore
|
type dbBackup DbRestore
|
||||||
restore.IntervalDay = uint64(restore.Interval / time.Hour / 24)
|
restore.IntervalDay = uint64(restore.Interval / time.Hour / 24)
|
||||||
|
if len(restore.EnabledDesc) == 0 {
|
||||||
|
if restore.Enabled {
|
||||||
|
restore.EnabledDesc = "任务已启用"
|
||||||
|
} else {
|
||||||
|
restore.EnabledDesc = "任务已禁用"
|
||||||
|
}
|
||||||
|
}
|
||||||
return json.Marshal((*dbBackup)(restore))
|
return json.Marshal((*dbBackup)(restore))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,14 @@ package application
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"gorm.io/gorm"
|
||||||
"mayfly-go/internal/db/domain/entity"
|
"mayfly-go/internal/db/domain/entity"
|
||||||
"mayfly-go/internal/db/domain/repository"
|
"mayfly-go/internal/db/domain/repository"
|
||||||
|
"mayfly-go/pkg/logx"
|
||||||
"mayfly-go/pkg/model"
|
"mayfly-go/pkg/model"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
@@ -14,6 +19,9 @@ type DbBackupApp struct {
|
|||||||
scheduler *dbScheduler `inject:"DbScheduler"`
|
scheduler *dbScheduler `inject:"DbScheduler"`
|
||||||
backupRepo repository.DbBackup `inject:"DbBackupRepo"`
|
backupRepo repository.DbBackup `inject:"DbBackupRepo"`
|
||||||
backupHistoryRepo repository.DbBackupHistory `inject:"DbBackupHistoryRepo"`
|
backupHistoryRepo repository.DbBackupHistory `inject:"DbBackupHistoryRepo"`
|
||||||
|
restoreRepo repository.DbRestore `inject:"DbRestoreRepo"`
|
||||||
|
dbApp Db `inject:"DbApp"`
|
||||||
|
mutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *DbBackupApp) Init() error {
|
func (app *DbBackupApp) Init() error {
|
||||||
@@ -21,7 +29,7 @@ func (app *DbBackupApp) Init() error {
|
|||||||
if err := app.backupRepo.ListToDo(&jobs); err != nil {
|
if err := app.backupRepo.ListToDo(&jobs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := app.scheduler.AddJob(context.Background(), false, entity.DbJobTypeBackup, jobs); err != nil {
|
if err := app.scheduler.AddJob(context.Background(), jobs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -32,32 +40,111 @@ func (app *DbBackupApp) Close() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (app *DbBackupApp) Create(ctx context.Context, jobs []*entity.DbBackup) error {
|
func (app *DbBackupApp) Create(ctx context.Context, jobs []*entity.DbBackup) error {
|
||||||
return app.scheduler.AddJob(ctx, true /* 保存到数据库 */, entity.DbJobTypeBackup, jobs)
|
app.mutex.Lock()
|
||||||
|
defer app.mutex.Unlock()
|
||||||
|
|
||||||
|
if err := app.backupRepo.AddJob(ctx, jobs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return app.scheduler.AddJob(ctx, jobs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *DbBackupApp) Update(ctx context.Context, job *entity.DbBackup) error {
|
func (app *DbBackupApp) Update(ctx context.Context, job *entity.DbBackup) error {
|
||||||
return app.scheduler.UpdateJob(ctx, job)
|
app.mutex.Lock()
|
||||||
|
defer app.mutex.Unlock()
|
||||||
|
|
||||||
|
if err := app.backupRepo.UpdateById(ctx, job); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = app.scheduler.UpdateJob(ctx, job)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *DbBackupApp) Delete(ctx context.Context, jobId uint64) error {
|
func (app *DbBackupApp) Delete(ctx context.Context, jobId uint64) error {
|
||||||
// todo: 删除数据库备份历史文件
|
// todo: 删除数据库备份历史文件
|
||||||
return app.scheduler.RemoveJob(ctx, entity.DbJobTypeBackup, jobId)
|
app.mutex.Lock()
|
||||||
|
defer app.mutex.Unlock()
|
||||||
|
|
||||||
|
if err := app.scheduler.RemoveJob(ctx, entity.DbJobTypeBackup, jobId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
history := &entity.DbBackupHistory{
|
||||||
|
DbBackupId: jobId,
|
||||||
|
}
|
||||||
|
err := app.backupHistoryRepo.GetBy(history, "name")
|
||||||
|
switch {
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
case err == nil:
|
||||||
|
return fmt.Errorf("数据库备份存在历史记录【%s】,无法删除该任务", history.Name)
|
||||||
|
case errors.Is(err, gorm.ErrRecordNotFound):
|
||||||
|
}
|
||||||
|
if err := app.backupRepo.DeleteById(ctx, jobId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *DbBackupApp) Enable(ctx context.Context, jobId uint64) error {
|
func (app *DbBackupApp) Enable(ctx context.Context, jobId uint64) error {
|
||||||
return app.scheduler.EnableJob(ctx, entity.DbJobTypeBackup, jobId)
|
app.mutex.Lock()
|
||||||
|
defer app.mutex.Unlock()
|
||||||
|
|
||||||
|
repo := app.backupRepo
|
||||||
|
job := &entity.DbBackup{}
|
||||||
|
if err := repo.GetById(job, jobId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if job.IsEnabled() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if job.IsExpired() {
|
||||||
|
return errors.New("任务已过期")
|
||||||
|
}
|
||||||
|
_ = app.scheduler.EnableJob(ctx, job)
|
||||||
|
if err := repo.UpdateEnabled(ctx, jobId, true); err != nil {
|
||||||
|
logx.Errorf("数据库备份任务已启用( jobId: %d ),任务状态保存失败: %v", jobId, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *DbBackupApp) Disable(ctx context.Context, jobId uint64) error {
|
func (app *DbBackupApp) Disable(ctx context.Context, jobId uint64) error {
|
||||||
return app.scheduler.DisableJob(ctx, entity.DbJobTypeBackup, jobId)
|
app.mutex.Lock()
|
||||||
|
defer app.mutex.Unlock()
|
||||||
|
|
||||||
|
repo := app.backupRepo
|
||||||
|
job := &entity.DbBackup{}
|
||||||
|
if err := repo.GetById(job, jobId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !job.IsEnabled() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_ = app.scheduler.DisableJob(ctx, entity.DbJobTypeBackup, jobId)
|
||||||
|
if err := repo.UpdateEnabled(ctx, jobId, false); err != nil {
|
||||||
|
logx.Errorf("数据库恢复任务已禁用( jobId: %d ),任务状态保存失败: %v", jobId, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *DbBackupApp) Start(ctx context.Context, jobId uint64) error {
|
func (app *DbBackupApp) StartNow(ctx context.Context, jobId uint64) error {
|
||||||
return app.scheduler.StartJobNow(ctx, entity.DbJobTypeBackup, jobId)
|
app.mutex.Lock()
|
||||||
|
defer app.mutex.Unlock()
|
||||||
|
|
||||||
|
job := &entity.DbBackup{}
|
||||||
|
if err := app.backupRepo.GetById(job, jobId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !job.IsEnabled() {
|
||||||
|
return errors.New("任务未启用")
|
||||||
|
}
|
||||||
|
_ = app.scheduler.StartJobNow(ctx, job)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPageList 分页获取数据库备份任务
|
// GetPageList 分页获取数据库备份任务
|
||||||
func (app *DbBackupApp) GetPageList(condition *entity.DbJobQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
func (app *DbBackupApp) GetPageList(condition *entity.DbBackupQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||||
return app.backupRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
return app.backupRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +155,11 @@ func (app *DbBackupApp) GetDbNamesWithoutBackup(instanceId uint64, dbNames []str
|
|||||||
|
|
||||||
// GetHistoryPageList 分页获取数据库备份历史
|
// GetHistoryPageList 分页获取数据库备份历史
|
||||||
func (app *DbBackupApp) GetHistoryPageList(condition *entity.DbBackupHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
func (app *DbBackupApp) GetHistoryPageList(condition *entity.DbBackupHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||||
return app.backupHistoryRepo.GetHistories(condition, pageParam, toEntity, orderBy...)
|
return app.backupHistoryRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *DbBackupApp) GetHistories(backupHistoryIds []uint64, toEntity any) error {
|
||||||
|
return app.backupHistoryRepo.GetHistories(backupHistoryIds, toEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIncUUID() (uuid.UUID, error) {
|
func NewIncUUID() (uuid.UUID, error) {
|
||||||
@@ -91,3 +182,41 @@ func NewIncUUID() (uuid.UUID, error) {
|
|||||||
|
|
||||||
return uid, nil
|
return uid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *DbBackupApp) DeleteHistory(ctx context.Context, historyId uint64) (retErr error) {
|
||||||
|
// todo: 删除数据库备份历史文件
|
||||||
|
app.mutex.Lock()
|
||||||
|
defer app.mutex.Unlock()
|
||||||
|
|
||||||
|
ok, err := app.backupHistoryRepo.UpdateDeleting(true, historyId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
_, err = app.backupHistoryRepo.UpdateDeleting(false, historyId)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if retErr == nil {
|
||||||
|
retErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
retErr = fmt.Errorf("%w, %w", retErr, err)
|
||||||
|
}()
|
||||||
|
if !ok {
|
||||||
|
return errors.New("正在从备份历史中恢复数据库")
|
||||||
|
}
|
||||||
|
job := &entity.DbBackupHistory{}
|
||||||
|
if err := app.backupHistoryRepo.GetById(job, historyId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conn, err := app.dbApp.GetDbConnByInstanceId(job.DbInstanceId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dbProgram := conn.GetDialect().GetDbProgram()
|
||||||
|
if err := dbProgram.RemoveBackupHistory(ctx, job.DbBackupId, job.Uuid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return app.backupHistoryRepo.DeleteById(ctx, historyId)
|
||||||
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func (app *DbBinlogApp) run() {
|
|||||||
if app.closed() {
|
if app.closed() {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err := app.scheduler.AddJob(app.context, false, entity.DbJobTypeBinlog, jobs); err != nil {
|
if err := app.scheduler.AddJob(app.context, jobs); err != nil {
|
||||||
logx.Error("DbBinlogApp: 添加 BINLOG 同步任务失败: ", err.Error())
|
logx.Error("DbBinlogApp: 添加 BINLOG 同步任务失败: ", err.Error())
|
||||||
}
|
}
|
||||||
timex.SleepWithContext(app.context, entity.BinlogDownloadInterval)
|
timex.SleepWithContext(app.context, entity.BinlogDownloadInterval)
|
||||||
|
|||||||
@@ -2,15 +2,19 @@ package application
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"mayfly-go/internal/db/domain/entity"
|
"mayfly-go/internal/db/domain/entity"
|
||||||
"mayfly-go/internal/db/domain/repository"
|
"mayfly-go/internal/db/domain/repository"
|
||||||
|
"mayfly-go/pkg/logx"
|
||||||
"mayfly-go/pkg/model"
|
"mayfly-go/pkg/model"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DbRestoreApp struct {
|
type DbRestoreApp struct {
|
||||||
scheduler *dbScheduler `inject:"DbScheduler"`
|
scheduler *dbScheduler `inject:"DbScheduler"`
|
||||||
restoreRepo repository.DbRestore `inject:"DbRestoreRepo"`
|
restoreRepo repository.DbRestore `inject:"DbRestoreRepo"`
|
||||||
restoreHistoryRepo repository.DbRestoreHistory `inject:"DbRestoreHistoryRepo"`
|
restoreHistoryRepo repository.DbRestoreHistory `inject:"DbRestoreHistoryRepo"`
|
||||||
|
mutex sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *DbRestoreApp) Init() error {
|
func (app *DbRestoreApp) Init() error {
|
||||||
@@ -18,7 +22,7 @@ func (app *DbRestoreApp) Init() error {
|
|||||||
if err := app.restoreRepo.ListToDo(&jobs); err != nil {
|
if err := app.restoreRepo.ListToDo(&jobs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := app.scheduler.AddJob(context.Background(), false, entity.DbJobTypeRestore, jobs); err != nil {
|
if err := app.scheduler.AddJob(context.Background(), jobs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -28,32 +32,101 @@ func (app *DbRestoreApp) Close() {
|
|||||||
app.scheduler.Close()
|
app.scheduler.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *DbRestoreApp) Create(ctx context.Context, job *entity.DbRestore) error {
|
func (app *DbRestoreApp) Create(ctx context.Context, jobs any) error {
|
||||||
return app.scheduler.AddJob(ctx, true /* 保存到数据库 */, entity.DbJobTypeRestore, job)
|
app.mutex.Lock()
|
||||||
|
defer app.mutex.Unlock()
|
||||||
|
|
||||||
|
if err := app.restoreRepo.AddJob(ctx, jobs); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = app.scheduler.AddJob(ctx, jobs)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *DbRestoreApp) Update(ctx context.Context, job *entity.DbRestore) error {
|
func (app *DbRestoreApp) Update(ctx context.Context, job *entity.DbRestore) error {
|
||||||
return app.scheduler.UpdateJob(ctx, job)
|
app.mutex.Lock()
|
||||||
|
defer app.mutex.Unlock()
|
||||||
|
|
||||||
|
if err := app.restoreRepo.UpdateById(ctx, job); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = app.scheduler.UpdateJob(ctx, job)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *DbRestoreApp) Delete(ctx context.Context, jobId uint64) error {
|
func (app *DbRestoreApp) Delete(ctx context.Context, jobId uint64) error {
|
||||||
// todo: 删除数据库恢复历史文件
|
// todo: 删除数据库恢复历史文件
|
||||||
return app.scheduler.RemoveJob(ctx, entity.DbJobTypeRestore, jobId)
|
app.mutex.Lock()
|
||||||
|
defer app.mutex.Unlock()
|
||||||
|
|
||||||
|
if err := app.scheduler.RemoveJob(ctx, entity.DbJobTypeRestore, jobId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
history := &entity.DbRestoreHistory{
|
||||||
|
DbRestoreId: jobId,
|
||||||
|
}
|
||||||
|
if err := app.restoreHistoryRepo.DeleteByCond(ctx, history); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := app.restoreRepo.DeleteById(ctx, jobId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *DbRestoreApp) Enable(ctx context.Context, jobId uint64) error {
|
func (app *DbRestoreApp) Enable(ctx context.Context, jobId uint64) error {
|
||||||
return app.scheduler.EnableJob(ctx, entity.DbJobTypeRestore, jobId)
|
app.mutex.Lock()
|
||||||
|
defer app.mutex.Unlock()
|
||||||
|
|
||||||
|
repo := app.restoreRepo
|
||||||
|
job := &entity.DbRestore{}
|
||||||
|
if err := repo.GetById(job, jobId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if job.IsEnabled() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if job.IsExpired() {
|
||||||
|
return errors.New("任务已过期")
|
||||||
|
}
|
||||||
|
_ = app.scheduler.EnableJob(ctx, job)
|
||||||
|
if err := repo.UpdateEnabled(ctx, jobId, true); err != nil {
|
||||||
|
logx.Errorf("数据库恢复任务已启用( jobId: %d ),任务状态保存失败: %v", jobId, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *DbRestoreApp) Disable(ctx context.Context, jobId uint64) error {
|
func (app *DbRestoreApp) Disable(ctx context.Context, jobId uint64) error {
|
||||||
return app.scheduler.DisableJob(ctx, entity.DbJobTypeRestore, jobId)
|
app.mutex.Lock()
|
||||||
|
defer app.mutex.Unlock()
|
||||||
|
|
||||||
|
repo := app.restoreRepo
|
||||||
|
job := &entity.DbRestore{}
|
||||||
|
if err := repo.GetById(job, jobId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !job.IsEnabled() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_ = app.scheduler.DisableJob(ctx, entity.DbJobTypeRestore, jobId)
|
||||||
|
if err := repo.UpdateEnabled(ctx, jobId, false); err != nil {
|
||||||
|
logx.Errorf("数据库恢复任务已禁用( jobId: %d ),任务状态保存失败: %v", jobId, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPageList 分页获取数据库恢复任务
|
// GetPageList 分页获取数据库恢复任务
|
||||||
func (app *DbRestoreApp) GetPageList(condition *entity.DbJobQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
func (app *DbRestoreApp) GetPageList(condition *entity.DbRestoreQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||||
return app.restoreRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
return app.restoreRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRestoresEnabled 获取数据库恢复任务
|
||||||
|
func (app *DbRestoreApp) GetRestoresEnabled(toEntity any, backupHistoryId ...uint64) error {
|
||||||
|
return app.restoreRepo.GetEnabledRestores(toEntity, backupHistoryId...)
|
||||||
|
}
|
||||||
|
|
||||||
// GetDbNamesWithoutRestore 获取未配置定时恢复的数据库名称
|
// GetDbNamesWithoutRestore 获取未配置定时恢复的数据库名称
|
||||||
func (app *DbRestoreApp) GetDbNamesWithoutRestore(instanceId uint64, dbNames []string) ([]string, error) {
|
func (app *DbRestoreApp) GetDbNamesWithoutRestore(instanceId uint64, dbNames []string) ([]string, error) {
|
||||||
return app.restoreRepo.GetDbNamesWithoutRestore(instanceId, dbNames)
|
return app.restoreRepo.GetDbNamesWithoutRestore(instanceId, dbNames)
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"gorm.io/gorm"
|
||||||
"mayfly-go/internal/db/dbm/dbi"
|
"mayfly-go/internal/db/dbm/dbi"
|
||||||
"mayfly-go/internal/db/domain/entity"
|
"mayfly-go/internal/db/domain/entity"
|
||||||
"mayfly-go/internal/db/domain/repository"
|
"mayfly-go/internal/db/domain/repository"
|
||||||
"mayfly-go/pkg/logx"
|
|
||||||
"mayfly-go/pkg/runner"
|
"mayfly-go/pkg/runner"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -35,6 +35,7 @@ func newDbScheduler() *dbScheduler {
|
|||||||
scheduler.runner = runner.NewRunner[entity.DbJob](maxRunning, scheduler.runJob,
|
scheduler.runner = runner.NewRunner[entity.DbJob](maxRunning, scheduler.runJob,
|
||||||
runner.WithScheduleJob[entity.DbJob](scheduler.scheduleJob),
|
runner.WithScheduleJob[entity.DbJob](scheduler.scheduleJob),
|
||||||
runner.WithRunnableJob[entity.DbJob](scheduler.runnableJob),
|
runner.WithRunnableJob[entity.DbJob](scheduler.runnableJob),
|
||||||
|
runner.WithUpdateJob[entity.DbJob](scheduler.updateJob),
|
||||||
)
|
)
|
||||||
return scheduler
|
return scheduler
|
||||||
}
|
}
|
||||||
@@ -43,27 +44,11 @@ func (s *dbScheduler) scheduleJob(job entity.DbJob) (time.Time, error) {
|
|||||||
return job.Schedule()
|
return job.Schedule()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dbScheduler) repo(typ entity.DbJobType) repository.DbJob {
|
|
||||||
switch typ {
|
|
||||||
case entity.DbJobTypeBackup:
|
|
||||||
return s.backupRepo
|
|
||||||
case entity.DbJobTypeRestore:
|
|
||||||
return s.restoreRepo
|
|
||||||
case entity.DbJobTypeBinlog:
|
|
||||||
return s.binlogRepo
|
|
||||||
default:
|
|
||||||
panic(fmt.Errorf("无效的数据库任务类型: %v", typ))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *dbScheduler) UpdateJob(ctx context.Context, job entity.DbJob) error {
|
func (s *dbScheduler) UpdateJob(ctx context.Context, job entity.DbJob) error {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
if err := s.repo(job.GetJobType()).UpdateById(ctx, job); err != nil {
|
_ = s.runner.Update(ctx, job)
|
||||||
return err
|
|
||||||
}
|
|
||||||
_ = s.runner.UpdateOrAdd(ctx, job)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,28 +56,20 @@ func (s *dbScheduler) Close() {
|
|||||||
s.runner.Close()
|
s.runner.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dbScheduler) AddJob(ctx context.Context, saving bool, jobType entity.DbJobType, jobs any) error {
|
func (s *dbScheduler) AddJob(ctx context.Context, jobs any) error {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
if saving {
|
|
||||||
if err := s.repo(jobType).AddJob(ctx, jobs); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reflectValue := reflect.ValueOf(jobs)
|
reflectValue := reflect.ValueOf(jobs)
|
||||||
switch reflectValue.Kind() {
|
switch reflectValue.Kind() {
|
||||||
case reflect.Array, reflect.Slice:
|
case reflect.Array, reflect.Slice:
|
||||||
reflectLen := reflectValue.Len()
|
reflectLen := reflectValue.Len()
|
||||||
for i := 0; i < reflectLen; i++ {
|
for i := 0; i < reflectLen; i++ {
|
||||||
job := reflectValue.Index(i).Interface().(entity.DbJob)
|
job := reflectValue.Index(i).Interface().(entity.DbJob)
|
||||||
job.SetJobType(jobType)
|
|
||||||
_ = s.runner.Add(ctx, job)
|
_ = s.runner.Add(ctx, job)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
job := jobs.(entity.DbJob)
|
job := jobs.(entity.DbJob)
|
||||||
job.SetJobType(jobType)
|
|
||||||
_ = s.runner.Add(ctx, job)
|
_ = s.runner.Add(ctx, job)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -103,29 +80,16 @@ func (s *dbScheduler) RemoveJob(ctx context.Context, jobType entity.DbJobType, j
|
|||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
if err := s.repo(jobType).DeleteById(ctx, jobId); err != nil {
|
if err := s.runner.Remove(ctx, entity.FormatJobKey(jobType, jobId)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_ = s.runner.Remove(ctx, entity.FormatJobKey(jobType, jobId))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dbScheduler) EnableJob(ctx context.Context, jobType entity.DbJobType, jobId uint64) error {
|
func (s *dbScheduler) EnableJob(ctx context.Context, job entity.DbJob) error {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
repo := s.repo(jobType)
|
|
||||||
job := entity.NewDbJob(jobType)
|
|
||||||
if err := repo.GetById(job, jobId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if job.IsEnabled() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
job.SetEnabled(true)
|
|
||||||
if err := repo.UpdateEnabled(ctx, jobId, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_ = s.runner.Add(ctx, job)
|
_ = s.runner.Add(ctx, job)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -134,37 +98,19 @@ func (s *dbScheduler) DisableJob(ctx context.Context, jobType entity.DbJobType,
|
|||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
repo := s.repo(jobType)
|
_ = s.runner.Remove(ctx, entity.FormatJobKey(jobType, jobId))
|
||||||
job := entity.NewDbJob(jobType)
|
|
||||||
if err := repo.GetById(job, jobId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !job.IsEnabled() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := repo.UpdateEnabled(ctx, jobId, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_ = s.runner.Remove(ctx, job.GetKey())
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dbScheduler) StartJobNow(ctx context.Context, jobType entity.DbJobType, jobId uint64) error {
|
func (s *dbScheduler) StartJobNow(ctx context.Context, job entity.DbJob) error {
|
||||||
s.mutex.Lock()
|
s.mutex.Lock()
|
||||||
defer s.mutex.Unlock()
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
job := entity.NewDbJob(jobType)
|
|
||||||
if err := s.repo(jobType).GetById(job, jobId); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !job.IsEnabled() {
|
|
||||||
return errors.New("任务未启用")
|
|
||||||
}
|
|
||||||
_ = s.runner.StartNow(ctx, job)
|
_ = s.runner.StartNow(ctx, job)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dbScheduler) backupMysql(ctx context.Context, job entity.DbJob) error {
|
func (s *dbScheduler) backup(ctx context.Context, dbProgram dbi.DbProgram, job entity.DbJob) error {
|
||||||
id, err := NewIncUUID()
|
id, err := NewIncUUID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -176,19 +122,14 @@ func (s *dbScheduler) backupMysql(ctx context.Context, job entity.DbJob) error {
|
|||||||
DbInstanceId: backup.DbInstanceId,
|
DbInstanceId: backup.DbInstanceId,
|
||||||
DbName: backup.DbName,
|
DbName: backup.DbName,
|
||||||
}
|
}
|
||||||
conn, err := s.dbApp.GetDbConnByInstanceId(backup.DbInstanceId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dbProgram := conn.GetDialect().GetDbProgram()
|
|
||||||
binlogInfo, err := dbProgram.Backup(ctx, history)
|
binlogInfo, err := dbProgram.Backup(ctx, history)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
name := backup.Name
|
name := backup.DbName
|
||||||
if len(name) == 0 {
|
if len(backup.Name) > 0 {
|
||||||
name = backup.DbName
|
name = fmt.Sprintf("%s-%s", backup.DbName, backup.Name)
|
||||||
}
|
}
|
||||||
history.Name = fmt.Sprintf("%s[%s]", name, now.Format(time.DateTime))
|
history.Name = fmt.Sprintf("%s[%s]", name, now.Format(time.DateTime))
|
||||||
history.CreateTime = now
|
history.CreateTime = now
|
||||||
@@ -202,54 +143,59 @@ func (s *dbScheduler) backupMysql(ctx context.Context, job entity.DbJob) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dbScheduler) restoreMysql(ctx context.Context, job entity.DbJob) error {
|
func (s *dbScheduler) restore(ctx context.Context, dbProgram dbi.DbProgram, job entity.DbJob) error {
|
||||||
restore := job.(*entity.DbRestore)
|
restore := job.(*entity.DbRestore)
|
||||||
conn, err := s.dbApp.GetDbConnByInstanceId(restore.DbInstanceId)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dbProgram := conn.GetDialect().GetDbProgram()
|
|
||||||
if restore.PointInTime.Valid {
|
if restore.PointInTime.Valid {
|
||||||
if enabled, err := dbProgram.CheckBinlogEnabled(ctx); err != nil {
|
//if enabled, err := dbProgram.CheckBinlogEnabled(ctx); err != nil {
|
||||||
return err
|
// return err
|
||||||
} else if !enabled {
|
//} else if !enabled {
|
||||||
return errors.New("数据库未启用 BINLOG")
|
// return errors.New("数据库未启用 BINLOG")
|
||||||
}
|
//}
|
||||||
if enabled, err := dbProgram.CheckBinlogRowFormat(ctx); err != nil {
|
//if enabled, err := dbProgram.CheckBinlogRowFormat(ctx); err != nil {
|
||||||
return err
|
// return err
|
||||||
} else if !enabled {
|
//} else if !enabled {
|
||||||
return errors.New("数据库未启用 BINLOG 行模式")
|
// return errors.New("数据库未启用 BINLOG 行模式")
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
latestBinlogSequence, earliestBackupSequence := int64(-1), int64(-1)
|
//latestBinlogSequence, earliestBackupSequence := int64(-1), int64(-1)
|
||||||
binlogHistory, ok, err := s.binlogHistoryRepo.GetLatestHistory(restore.DbInstanceId)
|
//binlogHistory, ok, err := s.binlogHistoryRepo.GetLatestHistory(restore.DbInstanceId)
|
||||||
if err != nil {
|
//if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
//}
|
||||||
if ok {
|
//if ok {
|
||||||
latestBinlogSequence = binlogHistory.Sequence
|
// latestBinlogSequence = binlogHistory.Sequence
|
||||||
} else {
|
//} else {
|
||||||
backupHistory, ok, err := s.backupHistoryRepo.GetEarliestHistory(restore.DbInstanceId)
|
// backupHistory, ok, err := s.backupHistoryRepo.GetEarliestHistory(restore.DbInstanceId)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
if !ok {
|
// if !ok {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
earliestBackupSequence = backupHistory.BinlogSequence
|
// earliestBackupSequence = backupHistory.BinlogSequence
|
||||||
}
|
//}
|
||||||
binlogFiles, err := dbProgram.FetchBinlogs(ctx, true, earliestBackupSequence, latestBinlogSequence)
|
//binlogFiles, err := dbProgram.FetchBinlogs(ctx, true, earliestBackupSequence, latestBinlogSequence)
|
||||||
if err != nil {
|
//if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
//}
|
||||||
if err := s.binlogHistoryRepo.InsertWithBinlogFiles(ctx, restore.DbInstanceId, binlogFiles); err != nil {
|
//if err := s.binlogHistoryRepo.InsertWithBinlogFiles(ctx, restore.DbInstanceId, binlogFiles); err != nil {
|
||||||
|
// return err
|
||||||
|
//}
|
||||||
|
if err := s.fetchBinlog(ctx, dbProgram, job.GetInstanceId(), true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := s.restorePointInTime(ctx, dbProgram, restore); err != nil {
|
if err := s.restorePointInTime(ctx, dbProgram, restore); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := s.restoreBackupHistory(ctx, dbProgram, restore); err != nil {
|
backupHistory := &entity.DbBackupHistory{}
|
||||||
|
if err := s.backupHistoryRepo.GetById(backupHistory, restore.DbBackupHistoryId); err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
err = errors.New("备份历史已删除")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.restoreBackupHistory(ctx, dbProgram, backupHistory); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -264,76 +210,108 @@ func (s *dbScheduler) restoreMysql(ctx context.Context, job entity.DbJob) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dbScheduler) runJob(ctx context.Context, job entity.DbJob) {
|
//func (s *dbScheduler) updateLastStatus(ctx context.Context, job entity.DbJob) error {
|
||||||
job.SetLastStatus(entity.DbJobRunning, nil)
|
// switch typ := job.GetJobType(); typ {
|
||||||
if err := s.repo(job.GetJobType()).UpdateLastStatus(ctx, job); err != nil {
|
// case entity.DbJobTypeBackup:
|
||||||
logx.Errorf("failed to update job status: %v", err)
|
// return s.backupRepo.UpdateLastStatus(ctx, job)
|
||||||
return
|
// case entity.DbJobTypeRestore:
|
||||||
}
|
// return s.restoreRepo.UpdateLastStatus(ctx, job)
|
||||||
|
// case entity.DbJobTypeBinlog:
|
||||||
|
// return s.binlogRepo.UpdateLastStatus(ctx, job)
|
||||||
|
// default:
|
||||||
|
// panic(fmt.Errorf("无效的数据库任务类型: %v", typ))
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
var errRun error
|
func (s *dbScheduler) updateJob(ctx context.Context, job entity.DbJob) error {
|
||||||
switch typ := job.GetJobType(); typ {
|
switch typ := job.GetJobType(); typ {
|
||||||
case entity.DbJobTypeBackup:
|
case entity.DbJobTypeBackup:
|
||||||
errRun = s.backupMysql(ctx, job)
|
return s.backupRepo.UpdateById(ctx, job)
|
||||||
case entity.DbJobTypeRestore:
|
case entity.DbJobTypeRestore:
|
||||||
errRun = s.restoreMysql(ctx, job)
|
return s.restoreRepo.UpdateById(ctx, job)
|
||||||
case entity.DbJobTypeBinlog:
|
case entity.DbJobTypeBinlog:
|
||||||
errRun = s.fetchBinlogMysql(ctx, job)
|
return s.binlogRepo.UpdateById(ctx, job)
|
||||||
default:
|
default:
|
||||||
errRun = fmt.Errorf("无效的数据库任务类型: %v", typ)
|
return fmt.Errorf("无效的数据库任务类型: %v", typ)
|
||||||
}
|
|
||||||
status := entity.DbJobSuccess
|
|
||||||
if errRun != nil {
|
|
||||||
status = entity.DbJobFailed
|
|
||||||
}
|
|
||||||
job.SetLastStatus(status, errRun)
|
|
||||||
if err := s.repo(job.GetJobType()).UpdateLastStatus(ctx, job); err != nil {
|
|
||||||
logx.Errorf("failed to update job status: %v", err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dbScheduler) runnableJob(job entity.DbJob, next runner.NextJobFunc[entity.DbJob]) bool {
|
func (s *dbScheduler) runJob(ctx context.Context, job entity.DbJob) error {
|
||||||
|
//job.SetLastStatus(entity.DbJobRunning, nil)
|
||||||
|
//if err := s.updateLastStatus(ctx, job); err != nil {
|
||||||
|
// logx.Errorf("failed to update job status: %v", err)
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
|
||||||
|
//var errRun error
|
||||||
|
conn, err := s.dbApp.GetDbConnByInstanceId(job.GetInstanceId())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dbProgram := conn.GetDialect().GetDbProgram()
|
||||||
|
switch typ := job.GetJobType(); typ {
|
||||||
|
case entity.DbJobTypeBackup:
|
||||||
|
return s.backup(ctx, dbProgram, job)
|
||||||
|
case entity.DbJobTypeRestore:
|
||||||
|
return s.restore(ctx, dbProgram, job)
|
||||||
|
case entity.DbJobTypeBinlog:
|
||||||
|
return s.fetchBinlog(ctx, dbProgram, job.GetInstanceId(), false)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("无效的数据库任务类型: %v", typ)
|
||||||
|
}
|
||||||
|
//status := entity.DbJobSuccess
|
||||||
|
//if errRun != nil {
|
||||||
|
// status = entity.DbJobFailed
|
||||||
|
//}
|
||||||
|
//job.SetLastStatus(status, errRun)
|
||||||
|
//if err := s.updateLastStatus(ctx, job); err != nil {
|
||||||
|
// logx.Errorf("failed to update job status: %v", err)
|
||||||
|
// return
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dbScheduler) runnableJob(job entity.DbJob, next runner.NextJobFunc[entity.DbJob]) (bool, error) {
|
||||||
|
if job.IsExpired() {
|
||||||
|
return false, runner.ErrJobExpired
|
||||||
|
}
|
||||||
const maxCountByInstanceId = 4
|
const maxCountByInstanceId = 4
|
||||||
const maxCountByDbName = 1
|
const maxCountByDbName = 1
|
||||||
var countByInstanceId, countByDbName int
|
var countByInstanceId, countByDbName int
|
||||||
jobBase := job.GetJobBase()
|
|
||||||
for item, ok := next(); ok; item, ok = next() {
|
for item, ok := next(); ok; item, ok = next() {
|
||||||
itemBase := item.GetJobBase()
|
if job.GetInstanceId() == item.GetInstanceId() {
|
||||||
if jobBase.DbInstanceId == itemBase.DbInstanceId {
|
|
||||||
countByInstanceId++
|
countByInstanceId++
|
||||||
if countByInstanceId >= maxCountByInstanceId {
|
if countByInstanceId >= maxCountByInstanceId {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if relatedToBinlog(job.GetJobType()) {
|
if relatedToBinlog(job.GetJobType()) {
|
||||||
// todo: 恢复数据库前触发 BINLOG 同步,BINLOG 同步完成后才能恢复数据库
|
// todo: 恢复数据库前触发 BINLOG 同步,BINLOG 同步完成后才能恢复数据库
|
||||||
if relatedToBinlog(item.GetJobType()) {
|
if relatedToBinlog(item.GetJobType()) {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if job.GetDbName() == item.GetDbName() {
|
if job.GetDbName() == item.GetDbName() {
|
||||||
countByDbName++
|
countByDbName++
|
||||||
if countByDbName >= maxCountByDbName {
|
if countByDbName >= maxCountByDbName {
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func relatedToBinlog(typ entity.DbJobType) bool {
|
func relatedToBinlog(typ entity.DbJobType) bool {
|
||||||
return typ == entity.DbJobTypeRestore || typ == entity.DbJobTypeBinlog
|
return typ == entity.DbJobTypeRestore || typ == entity.DbJobTypeBinlog
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dbScheduler) restorePointInTime(ctx context.Context, program dbi.DbProgram, job *entity.DbRestore) error {
|
func (s *dbScheduler) restorePointInTime(ctx context.Context, dbProgram dbi.DbProgram, job *entity.DbRestore) error {
|
||||||
binlogHistory, err := s.binlogHistoryRepo.GetHistoryByTime(job.DbInstanceId, job.PointInTime.Time)
|
binlogHistory, err := s.binlogHistoryRepo.GetHistoryByTime(job.DbInstanceId, job.PointInTime.Time)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
position, err := program.GetBinlogEventPositionAtOrAfterTime(ctx, binlogHistory.FileName, job.PointInTime.Time)
|
position, err := dbProgram.GetBinlogEventPositionAtOrAfterTime(ctx, binlogHistory.FileName, job.PointInTime.Time)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -362,40 +340,63 @@ func (s *dbScheduler) restorePointInTime(ctx context.Context, program dbi.DbProg
|
|||||||
TargetPosition: target.Position,
|
TargetPosition: target.Position,
|
||||||
TargetTime: job.PointInTime.Time,
|
TargetTime: job.PointInTime.Time,
|
||||||
}
|
}
|
||||||
if err := program.RestoreBackupHistory(ctx, backupHistory.DbName, backupHistory.DbBackupId, backupHistory.Uuid); err != nil {
|
if err := dbProgram.ReplayBinlog(ctx, job.DbName, job.DbName, restoreInfo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := program.ReplayBinlog(ctx, job.DbName, job.DbName, restoreInfo); err != nil {
|
if err := s.restoreBackupHistory(ctx, dbProgram, backupHistory); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 由于 ReplayBinlog 未记录 BINLOG 事件,系统自动备份,避免数据丢失
|
// 由于 ReplayBinlog 未记录 BINLOG 事件,系统自动备份,避免数据丢失
|
||||||
backup := &entity.DbBackup{
|
backup := &entity.DbBackup{
|
||||||
DbJobBaseImpl: entity.NewDbBJobBase(backupHistory.DbInstanceId, entity.DbJobTypeBackup),
|
DbInstanceId: backupHistory.DbInstanceId,
|
||||||
DbName: backupHistory.DbName,
|
DbName: backupHistory.DbName,
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
Repeated: false,
|
Repeated: false,
|
||||||
StartTime: time.Now(),
|
StartTime: time.Now(),
|
||||||
Interval: 0,
|
Interval: 0,
|
||||||
Name: fmt.Sprintf("%s-系统自动备份", backupHistory.DbName),
|
Name: "系统备份",
|
||||||
}
|
}
|
||||||
backup.Id = backupHistory.DbBackupId
|
backup.Id = backupHistory.DbBackupId
|
||||||
if err := s.backupMysql(ctx, backup); err != nil {
|
if err := s.backup(ctx, dbProgram, backup); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dbScheduler) restoreBackupHistory(ctx context.Context, program dbi.DbProgram, job *entity.DbRestore) error {
|
func (s *dbScheduler) restoreBackupHistory(ctx context.Context, program dbi.DbProgram, backupHistory *entity.DbBackupHistory) (retErr error) {
|
||||||
backupHistory := &entity.DbBackupHistory{}
|
ok, err := s.backupHistoryRepo.UpdateRestoring(true, backupHistory.Id)
|
||||||
if err := s.backupHistoryRepo.GetById(backupHistory, job.DbBackupHistoryId); err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer func() {
|
||||||
|
_, err = s.backupHistoryRepo.UpdateRestoring(false, backupHistory.Id)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if retErr == nil {
|
||||||
|
retErr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
retErr = fmt.Errorf("%w, %w", retErr, err)
|
||||||
|
}()
|
||||||
|
if !ok {
|
||||||
|
return errors.New("关联的数据库备份历史已删除")
|
||||||
|
}
|
||||||
return program.RestoreBackupHistory(ctx, backupHistory.DbName, backupHistory.DbBackupId, backupHistory.Uuid)
|
return program.RestoreBackupHistory(ctx, backupHistory.DbName, backupHistory.DbBackupId, backupHistory.Uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *dbScheduler) fetchBinlogMysql(ctx context.Context, backup entity.DbJob) error {
|
func (s *dbScheduler) fetchBinlog(ctx context.Context, dbProgram dbi.DbProgram, instanceId uint64, downloadLatestBinlogFile bool) error {
|
||||||
instanceId := backup.GetJobBase().DbInstanceId
|
if enabled, err := dbProgram.CheckBinlogEnabled(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !enabled {
|
||||||
|
return errors.New("数据库未启用 BINLOG")
|
||||||
|
}
|
||||||
|
if enabled, err := dbProgram.CheckBinlogRowFormat(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !enabled {
|
||||||
|
return errors.New("数据库未启用 BINLOG 行模式")
|
||||||
|
}
|
||||||
|
|
||||||
latestBinlogSequence, earliestBackupSequence := int64(-1), int64(-1)
|
latestBinlogSequence, earliestBackupSequence := int64(-1), int64(-1)
|
||||||
binlogHistory, ok, err := s.binlogHistoryRepo.GetLatestHistory(instanceId)
|
binlogHistory, ok, err := s.binlogHistoryRepo.GetLatestHistory(instanceId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -413,12 +414,7 @@ func (s *dbScheduler) fetchBinlogMysql(ctx context.Context, backup entity.DbJob)
|
|||||||
}
|
}
|
||||||
earliestBackupSequence = backupHistory.BinlogSequence
|
earliestBackupSequence = backupHistory.BinlogSequence
|
||||||
}
|
}
|
||||||
conn, err := s.dbApp.GetDbConnByInstanceId(instanceId)
|
binlogFiles, err := dbProgram.FetchBinlogs(ctx, downloadLatestBinlogFile, earliestBackupSequence, latestBinlogSequence)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
dbProgram := conn.GetDialect().GetDbProgram()
|
|
||||||
binlogFiles, err := dbProgram.FetchBinlogs(ctx, false, earliestBackupSequence, latestBinlogSequence)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ type DbProgram interface {
|
|||||||
|
|
||||||
RestoreBackupHistory(ctx context.Context, dbName string, dbBackupId uint64, dbBackupHistoryUuid string) error
|
RestoreBackupHistory(ctx context.Context, dbName string, dbBackupId uint64, dbBackupHistoryUuid string) error
|
||||||
|
|
||||||
|
RemoveBackupHistory(ctx context.Context, dbBackupId uint64, dbBackupHistoryUuid string) error
|
||||||
|
|
||||||
GetBinlogEventPositionAtOrAfterTime(ctx context.Context, binlogName string, targetTime time.Time) (position int64, parseErr error)
|
GetBinlogEventPositionAtOrAfterTime(ctx context.Context, binlogName string, targetTime time.Time) (position int64, parseErr error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,6 +142,12 @@ func (svc *DbProgramMysql) Backup(ctx context.Context, backupHistory *entity.DbB
|
|||||||
return binlogInfo, nil
|
return binlogInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svc *DbProgramMysql) RemoveBackupHistory(_ context.Context, dbBackupId uint64, dbBackupHistoryUuid string) error {
|
||||||
|
fileName := filepath.Join(svc.getDbBackupDir(svc.dbInfo().InstanceId, dbBackupId),
|
||||||
|
fmt.Sprintf("%v.sql", dbBackupHistoryUuid))
|
||||||
|
return os.Remove(fileName)
|
||||||
|
}
|
||||||
|
|
||||||
func (svc *DbProgramMysql) RestoreBackupHistory(ctx context.Context, dbName string, dbBackupId uint64, dbBackupHistoryUuid string) error {
|
func (svc *DbProgramMysql) RestoreBackupHistory(ctx context.Context, dbName string, dbBackupId uint64, dbBackupHistoryUuid string) error {
|
||||||
dbInfo := svc.dbInfo()
|
dbInfo := svc.dbInfo()
|
||||||
args := []string{
|
args := []string{
|
||||||
|
|||||||
@@ -9,20 +9,30 @@ var _ DbJob = (*DbBackup)(nil)
|
|||||||
|
|
||||||
// DbBackup 数据库备份任务
|
// DbBackup 数据库备份任务
|
||||||
type DbBackup struct {
|
type DbBackup struct {
|
||||||
*DbJobBaseImpl
|
DbJobBaseImpl
|
||||||
|
|
||||||
Enabled bool // 是否启用
|
DbInstanceId uint64 // 数据库实例ID
|
||||||
StartTime time.Time // 开始时间
|
DbName string // 数据库名称
|
||||||
Interval time.Duration // 间隔时间
|
Name string // 数据库备份名称
|
||||||
Repeated bool // 是否重复执行
|
Enabled bool // 是否启用
|
||||||
DbName string // 数据库名称
|
EnabledDesc string // 启用状态描述
|
||||||
Name string // 数据库备份名称
|
StartTime time.Time // 开始时间
|
||||||
|
Interval time.Duration // 间隔时间
|
||||||
|
Repeated bool // 是否重复执行
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DbBackup) GetInstanceId() uint64 {
|
||||||
|
return b.DbInstanceId
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *DbBackup) GetDbName() string {
|
func (b *DbBackup) GetDbName() string {
|
||||||
return b.DbName
|
return b.DbName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *DbBackup) GetJobType() DbJobType {
|
||||||
|
return DbJobTypeBackup
|
||||||
|
}
|
||||||
|
|
||||||
func (b *DbBackup) Schedule() (time.Time, error) {
|
func (b *DbBackup) Schedule() (time.Time, error) {
|
||||||
if b.IsFinished() {
|
if b.IsFinished() {
|
||||||
return time.Time{}, runner.ErrJobFinished
|
return time.Time{}, runner.ErrJobFinished
|
||||||
@@ -37,7 +47,7 @@ func (b *DbBackup) Schedule() (time.Time, error) {
|
|||||||
lastTime = b.StartTime.Add(-b.Interval)
|
lastTime = b.StartTime.Add(-b.Interval)
|
||||||
}
|
}
|
||||||
return lastTime.Add(b.Interval - lastTime.Sub(b.StartTime)%b.Interval), nil
|
return lastTime.Add(b.Interval - lastTime.Sub(b.StartTime)%b.Interval), nil
|
||||||
case DbJobFailed:
|
case DbJobRunning, DbJobFailed:
|
||||||
return time.Now().Add(time.Minute), nil
|
return time.Now().Add(time.Minute), nil
|
||||||
default:
|
default:
|
||||||
return b.StartTime, nil
|
return b.StartTime, nil
|
||||||
@@ -52,8 +62,13 @@ func (b *DbBackup) IsEnabled() bool {
|
|||||||
return b.Enabled
|
return b.Enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *DbBackup) SetEnabled(enabled bool) {
|
func (b *DbBackup) IsExpired() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DbBackup) SetEnabled(enabled bool, desc string) {
|
||||||
b.Enabled = enabled
|
b.Enabled = enabled
|
||||||
|
b.EnabledDesc = desc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *DbBackup) Update(job runner.Job) {
|
func (b *DbBackup) Update(job runner.Job) {
|
||||||
@@ -65,3 +80,15 @@ func (b *DbBackup) Update(job runner.Job) {
|
|||||||
func (b *DbBackup) GetInterval() time.Duration {
|
func (b *DbBackup) GetInterval() time.Duration {
|
||||||
return b.Interval
|
return b.Interval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *DbBackup) SetLastStatus(status DbJobStatus, err error) {
|
||||||
|
b.setLastStatus(b.GetJobType(), status, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DbBackup) GetKey() DbJobKey {
|
||||||
|
return b.getKey(b.GetJobType())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DbBackup) SetStatus(status runner.JobStatus, err error) {
|
||||||
|
b.setLastStatus(b.GetJobType(), status, err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ var _ DbJob = (*DbBinlog)(nil)
|
|||||||
// DbBinlog 数据库备份任务
|
// DbBinlog 数据库备份任务
|
||||||
type DbBinlog struct {
|
type DbBinlog struct {
|
||||||
DbJobBaseImpl
|
DbJobBaseImpl
|
||||||
|
DbInstanceId uint64 // 数据库实例ID
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDbBinlog(instanceId uint64) *DbBinlog {
|
func NewDbBinlog(instanceId uint64) *DbBinlog {
|
||||||
@@ -35,13 +36,17 @@ func NewDbBinlog(instanceId uint64) *DbBinlog {
|
|||||||
return job
|
return job
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *DbBinlog) GetInstanceId() uint64 {
|
||||||
|
return b.DbInstanceId
|
||||||
|
}
|
||||||
|
|
||||||
func (b *DbBinlog) GetDbName() string {
|
func (b *DbBinlog) GetDbName() string {
|
||||||
// binlog 是全库级别的
|
// binlog 是全库级别的
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *DbBinlog) Schedule() (time.Time, error) {
|
func (b *DbBinlog) Schedule() (time.Time, error) {
|
||||||
switch b.GetJobBase().LastStatus {
|
switch b.LastStatus {
|
||||||
case DbJobSuccess:
|
case DbJobSuccess:
|
||||||
return time.Time{}, runner.ErrJobFinished
|
return time.Time{}, runner.ErrJobFinished
|
||||||
case DbJobFailed:
|
case DbJobFailed:
|
||||||
@@ -57,8 +62,28 @@ func (b *DbBinlog) IsEnabled() bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *DbBinlog) SetEnabled(_ bool) {}
|
func (b *DbBinlog) IsExpired() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DbBinlog) SetEnabled(_ bool, _ string) {}
|
||||||
|
|
||||||
func (b *DbBinlog) GetInterval() time.Duration {
|
func (b *DbBinlog) GetInterval() time.Duration {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *DbBinlog) GetJobType() DbJobType {
|
||||||
|
return DbJobTypeBinlog
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DbBinlog) SetLastStatus(status DbJobStatus, err error) {
|
||||||
|
b.setLastStatus(b.GetJobType(), status, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DbBinlog) GetKey() DbJobKey {
|
||||||
|
return b.getKey(b.GetJobType())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *DbBinlog) SetStatus(status DbJobStatus, err error) {
|
||||||
|
b.setLastStatus(b.GetJobType(), status, err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ const LastResultSize = 256
|
|||||||
|
|
||||||
type DbJobKey = runner.JobKey
|
type DbJobKey = runner.JobKey
|
||||||
|
|
||||||
type DbJobStatus int
|
type DbJobStatus = runner.JobStatus
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DbJobRunning DbJobStatus = iota
|
DbJobRunning = runner.JobRunning
|
||||||
DbJobSuccess
|
DbJobSuccess = runner.JobSuccess
|
||||||
DbJobFailed
|
DbJobFailed = runner.JobFailed
|
||||||
)
|
)
|
||||||
|
|
||||||
type DbJobType string
|
type DbJobType string
|
||||||
@@ -28,12 +28,14 @@ func (typ DbJobType) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
DbJobUnknown DbJobType = "db-unknown"
|
||||||
DbJobTypeBackup DbJobType = "db-backup"
|
DbJobTypeBackup DbJobType = "db-backup"
|
||||||
DbJobTypeRestore DbJobType = "db-restore"
|
DbJobTypeRestore DbJobType = "db-restore"
|
||||||
DbJobTypeBinlog DbJobType = "db-binlog"
|
DbJobTypeBinlog DbJobType = "db-binlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
DbJobNameUnknown = "未知任务"
|
||||||
DbJobNameBackup = "数据库备份"
|
DbJobNameBackup = "数据库备份"
|
||||||
DbJobNameRestore = "数据库恢复"
|
DbJobNameRestore = "数据库恢复"
|
||||||
DbJobNameBinlog = "BINLOG同步"
|
DbJobNameBinlog = "BINLOG同步"
|
||||||
@@ -43,41 +45,24 @@ var _ runner.Job = (DbJob)(nil)
|
|||||||
|
|
||||||
type DbJobBase interface {
|
type DbJobBase interface {
|
||||||
model.ModelI
|
model.ModelI
|
||||||
|
GetLastStatus() DbJobStatus
|
||||||
GetKey() string
|
|
||||||
GetJobType() DbJobType
|
|
||||||
SetJobType(typ DbJobType)
|
|
||||||
GetJobBase() *DbJobBaseImpl
|
|
||||||
SetLastStatus(status DbJobStatus, err error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DbJob interface {
|
type DbJob interface {
|
||||||
runner.Job
|
runner.Job
|
||||||
DbJobBase
|
DbJobBase
|
||||||
|
|
||||||
|
GetInstanceId() uint64
|
||||||
|
GetKey() string
|
||||||
|
GetJobType() DbJobType
|
||||||
GetDbName() string
|
GetDbName() string
|
||||||
Schedule() (time.Time, error)
|
Schedule() (time.Time, error)
|
||||||
IsEnabled() bool
|
IsEnabled() bool
|
||||||
SetEnabled(enabled bool)
|
IsExpired() bool
|
||||||
|
SetEnabled(enabled bool, desc string)
|
||||||
Update(job runner.Job)
|
Update(job runner.Job)
|
||||||
GetInterval() time.Duration
|
GetInterval() time.Duration
|
||||||
}
|
SetLastStatus(status DbJobStatus, err error)
|
||||||
|
|
||||||
func NewDbJob(typ DbJobType) DbJob {
|
|
||||||
switch typ {
|
|
||||||
case DbJobTypeBackup:
|
|
||||||
return &DbBackup{
|
|
||||||
DbJobBaseImpl: &DbJobBaseImpl{
|
|
||||||
jobType: DbJobTypeBackup},
|
|
||||||
}
|
|
||||||
case DbJobTypeRestore:
|
|
||||||
return &DbRestore{
|
|
||||||
DbJobBaseImpl: &DbJobBaseImpl{
|
|
||||||
jobType: DbJobTypeRestore},
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("invalid DbJobType: %v", typ))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ DbJobBase = (*DbJobBaseImpl)(nil)
|
var _ DbJobBase = (*DbJobBaseImpl)(nil)
|
||||||
@@ -85,30 +70,25 @@ var _ DbJobBase = (*DbJobBaseImpl)(nil)
|
|||||||
type DbJobBaseImpl struct {
|
type DbJobBaseImpl struct {
|
||||||
model.Model
|
model.Model
|
||||||
|
|
||||||
DbInstanceId uint64 // 数据库实例ID
|
LastStatus DbJobStatus // 最近一次执行状态
|
||||||
LastStatus DbJobStatus // 最近一次执行状态
|
LastResult string // 最近一次执行结果
|
||||||
LastResult string // 最近一次执行结果
|
LastTime timex.NullTime // 最近一次执行时间
|
||||||
LastTime timex.NullTime // 最近一次执行时间
|
jobKey runner.JobKey
|
||||||
jobType DbJobType
|
|
||||||
jobKey runner.JobKey
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDbBJobBase(instanceId uint64, jobType DbJobType) *DbJobBaseImpl {
|
func (d *DbJobBaseImpl) getJobType() DbJobType {
|
||||||
return &DbJobBaseImpl{
|
job, ok := any(d).(DbJob)
|
||||||
DbInstanceId: instanceId,
|
if !ok {
|
||||||
jobType: jobType,
|
return DbJobUnknown
|
||||||
}
|
}
|
||||||
|
return job.GetJobType()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DbJobBaseImpl) GetJobType() DbJobType {
|
func (d *DbJobBaseImpl) GetLastStatus() DbJobStatus {
|
||||||
return d.jobType
|
return d.LastStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DbJobBaseImpl) SetJobType(typ DbJobType) {
|
func (d *DbJobBaseImpl) setLastStatus(jobType DbJobType, status DbJobStatus, err error) {
|
||||||
d.jobType = typ
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DbJobBaseImpl) SetLastStatus(status DbJobStatus, err error) {
|
|
||||||
var statusName, jobName string
|
var statusName, jobName string
|
||||||
switch status {
|
switch status {
|
||||||
case DbJobRunning:
|
case DbJobRunning:
|
||||||
@@ -120,7 +100,8 @@ func (d *DbJobBaseImpl) SetLastStatus(status DbJobStatus, err error) {
|
|||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch d.jobType {
|
|
||||||
|
switch jobType {
|
||||||
case DbJobTypeBackup:
|
case DbJobTypeBackup:
|
||||||
jobName = DbJobNameBackup
|
jobName = DbJobNameBackup
|
||||||
case DbJobTypeRestore:
|
case DbJobTypeRestore:
|
||||||
@@ -128,7 +109,7 @@ func (d *DbJobBaseImpl) SetLastStatus(status DbJobStatus, err error) {
|
|||||||
case DbJobTypeBinlog:
|
case DbJobTypeBinlog:
|
||||||
jobName = DbJobNameBinlog
|
jobName = DbJobNameBinlog
|
||||||
default:
|
default:
|
||||||
jobName = d.jobType.String()
|
jobName = jobType.String()
|
||||||
}
|
}
|
||||||
d.LastStatus = status
|
d.LastStatus = status
|
||||||
var result = jobName + statusName
|
var result = jobName + statusName
|
||||||
@@ -139,17 +120,13 @@ func (d *DbJobBaseImpl) SetLastStatus(status DbJobStatus, err error) {
|
|||||||
d.LastTime = timex.NewNullTime(time.Now())
|
d.LastTime = timex.NewNullTime(time.Now())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DbJobBaseImpl) GetJobBase() *DbJobBaseImpl {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatJobKey(typ DbJobType, jobId uint64) DbJobKey {
|
func FormatJobKey(typ DbJobType, jobId uint64) DbJobKey {
|
||||||
return fmt.Sprintf("%v-%d", typ, jobId)
|
return fmt.Sprintf("%v-%d", typ, jobId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DbJobBaseImpl) GetKey() DbJobKey {
|
func (d *DbJobBaseImpl) getKey(jobType DbJobType) DbJobKey {
|
||||||
if len(d.jobKey) == 0 {
|
if len(d.jobKey) == 0 {
|
||||||
d.jobKey = FormatJobKey(d.jobType, d.Id)
|
d.jobKey = FormatJobKey(jobType, d.Id)
|
||||||
}
|
}
|
||||||
return d.jobKey
|
return d.jobKey
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,10 +10,12 @@ var _ DbJob = (*DbRestore)(nil)
|
|||||||
|
|
||||||
// DbRestore 数据库恢复任务
|
// DbRestore 数据库恢复任务
|
||||||
type DbRestore struct {
|
type DbRestore struct {
|
||||||
*DbJobBaseImpl
|
DbJobBaseImpl
|
||||||
|
|
||||||
|
DbInstanceId uint64 // 数据库实例ID
|
||||||
DbName string // 数据库名称
|
DbName string // 数据库名称
|
||||||
Enabled bool // 是否启用
|
Enabled bool // 是否启用
|
||||||
|
EnabledDesc string // 启用状态描述
|
||||||
StartTime time.Time // 开始时间
|
StartTime time.Time // 开始时间
|
||||||
Interval time.Duration // 间隔时间
|
Interval time.Duration // 间隔时间
|
||||||
Repeated bool // 是否重复执行
|
Repeated bool // 是否重复执行
|
||||||
@@ -23,6 +25,10 @@ type DbRestore struct {
|
|||||||
DbBackupHistoryName string `json:"dbBackupHistoryName"` // 数据库恢复历史名称
|
DbBackupHistoryName string `json:"dbBackupHistoryName"` // 数据库恢复历史名称
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *DbRestore) GetInstanceId() uint64 {
|
||||||
|
return r.DbInstanceId
|
||||||
|
}
|
||||||
|
|
||||||
func (r *DbRestore) GetDbName() string {
|
func (r *DbRestore) GetDbName() string {
|
||||||
return r.DbName
|
return r.DbName
|
||||||
}
|
}
|
||||||
@@ -36,7 +42,7 @@ func (r *DbRestore) Schedule() (time.Time, error) {
|
|||||||
return time.Time{}, runner.ErrJobFinished
|
return time.Time{}, runner.ErrJobFinished
|
||||||
default:
|
default:
|
||||||
if time.Now().Sub(r.StartTime) > time.Hour {
|
if time.Now().Sub(r.StartTime) > time.Hour {
|
||||||
return time.Time{}, runner.ErrJobTimeout
|
return time.Time{}, runner.ErrJobExpired
|
||||||
}
|
}
|
||||||
return r.StartTime, nil
|
return r.StartTime, nil
|
||||||
}
|
}
|
||||||
@@ -46,8 +52,13 @@ func (r *DbRestore) IsEnabled() bool {
|
|||||||
return r.Enabled
|
return r.Enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DbRestore) SetEnabled(enabled bool) {
|
func (r *DbRestore) SetEnabled(enabled bool, desc string) {
|
||||||
r.Enabled = enabled
|
r.Enabled = enabled
|
||||||
|
r.EnabledDesc = desc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DbRestore) IsExpired() bool {
|
||||||
|
return !r.Repeated && time.Now().After(r.StartTime.Add(time.Hour))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DbRestore) IsFinished() bool {
|
func (r *DbRestore) IsFinished() bool {
|
||||||
@@ -63,3 +74,19 @@ func (r *DbRestore) Update(job runner.Job) {
|
|||||||
func (r *DbRestore) GetInterval() time.Duration {
|
func (r *DbRestore) GetInterval() time.Duration {
|
||||||
return r.Interval
|
return r.Interval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *DbRestore) GetJobType() DbJobType {
|
||||||
|
return DbJobTypeRestore
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DbRestore) SetLastStatus(status DbJobStatus, err error) {
|
||||||
|
r.setLastStatus(r.GetJobType(), status, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DbRestore) GetKey() DbJobKey {
|
||||||
|
return r.getKey(r.GetJobType())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *DbRestore) SetStatus(status DbJobStatus, err error) {
|
||||||
|
r.setLastStatus(r.GetJobType(), status, err)
|
||||||
|
}
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ type DbSqlExecQuery struct {
|
|||||||
CreatorId uint64
|
CreatorId uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// DbJobQuery 数据库备份任务查询
|
// DbBackupQuery 数据库备份任务查询
|
||||||
type DbJobQuery struct {
|
type DbBackupQuery struct {
|
||||||
Id uint64 `json:"id" form:"id"`
|
Id uint64 `json:"id" form:"id"`
|
||||||
DbName string `json:"dbName" form:"dbName"`
|
DbName string `json:"dbName" form:"dbName"`
|
||||||
IntervalDay int `json:"intervalDay" form:"intervalDay"`
|
IntervalDay int `json:"intervalDay" form:"intervalDay"`
|
||||||
@@ -61,13 +61,13 @@ type DbBackupHistoryQuery struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DbRestoreQuery 数据库备份任务查询
|
// DbRestoreQuery 数据库备份任务查询
|
||||||
//type DbRestoreQuery struct {
|
type DbRestoreQuery struct {
|
||||||
// Id uint64 `json:"id" form:"id"`
|
Id uint64 `json:"id" form:"id"`
|
||||||
// DbName string `json:"dbName" form:"dbName"`
|
DbName string `json:"dbName" form:"dbName"`
|
||||||
// InDbNames []string `json:"-" form:"-"`
|
InDbNames []string `json:"-" form:"-"`
|
||||||
// DbInstanceId uint64 `json:"-" form:"-"`
|
DbInstanceId uint64 `json:"-" form:"-"`
|
||||||
// Repeated bool `json:"repeated" form:"repeated"` // 是否重复执行
|
Repeated bool `json:"repeated" form:"repeated"` // 是否重复执行
|
||||||
//}
|
}
|
||||||
|
|
||||||
// DbRestoreHistoryQuery 数据库备份任务查询
|
// DbRestoreHistoryQuery 数据库备份任务查询
|
||||||
type DbRestoreHistoryQuery struct {
|
type DbRestoreHistoryQuery struct {
|
||||||
|
|||||||
@@ -13,5 +13,5 @@ type DbBackup interface {
|
|||||||
GetDbNamesWithoutBackup(instanceId uint64, dbNames []string) ([]string, error)
|
GetDbNamesWithoutBackup(instanceId uint64, dbNames []string) ([]string, error)
|
||||||
|
|
||||||
// GetPageList 分页获取数据库任务列表
|
// GetPageList 分页获取数据库任务列表
|
||||||
GetPageList(condition *entity.DbJobQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
GetPageList(condition *entity.DbBackupQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,15 @@ import (
|
|||||||
type DbBackupHistory interface {
|
type DbBackupHistory interface {
|
||||||
base.Repo[*entity.DbBackupHistory]
|
base.Repo[*entity.DbBackupHistory]
|
||||||
|
|
||||||
// GetHistories 分页获取数据备份历史
|
// GetPageList 分页获取数据备份历史
|
||||||
GetHistories(condition *entity.DbBackupHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
GetPageList(condition *entity.DbBackupHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||||
|
|
||||||
GetLatestHistory(instanceId uint64, dbName string, bi *entity.BinlogInfo) (*entity.DbBackupHistory, error)
|
GetLatestHistory(instanceId uint64, dbName string, bi *entity.BinlogInfo) (*entity.DbBackupHistory, error)
|
||||||
|
|
||||||
GetEarliestHistory(instanceId uint64) (*entity.DbBackupHistory, bool, error)
|
GetEarliestHistory(instanceId uint64) (*entity.DbBackupHistory, bool, error)
|
||||||
|
|
||||||
|
GetHistories(backupHistoryIds []uint64, toEntity any) error
|
||||||
|
|
||||||
|
UpdateDeleting(deleting bool, backupHistoryId ...uint64) (bool, error)
|
||||||
|
UpdateRestoring(restoring bool, backupHistoryId ...uint64) (bool, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,5 +12,7 @@ type DbRestore interface {
|
|||||||
GetDbNamesWithoutRestore(instanceId uint64, dbNames []string) ([]string, error)
|
GetDbNamesWithoutRestore(instanceId uint64, dbNames []string) ([]string, error)
|
||||||
|
|
||||||
// GetPageList 分页获取数据库任务列表
|
// GetPageList 分页获取数据库任务列表
|
||||||
GetPageList(condition *entity.DbJobQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
GetPageList(condition *entity.DbRestoreQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error)
|
||||||
|
|
||||||
|
GetEnabledRestores(toEntity any, backupHistoryId ...uint64) error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func (d *dbBackupRepoImpl) ListToDo(jobs any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetPageList 分页获取数据库备份任务列表
|
// GetPageList 分页获取数据库备份任务列表
|
||||||
func (d *dbBackupRepoImpl) GetPageList(condition *entity.DbJobQuery, pageParam *model.PageParam, toEntity any, _ ...string) (*model.PageResult[any], error) {
|
func (d *dbBackupRepoImpl) GetPageList(condition *entity.DbBackupQuery, pageParam *model.PageParam, toEntity any, _ ...string) (*model.PageResult[any], error) {
|
||||||
d.GetModel()
|
d.GetModel()
|
||||||
qd := gormx.NewQuery(d.GetModel()).
|
qd := gormx.NewQuery(d.GetModel()).
|
||||||
Eq("id", condition.Id).
|
Eq("id", condition.Id).
|
||||||
@@ -83,7 +83,12 @@ func (d *dbBackupRepoImpl) UpdateEnabled(_ context.Context, jobId uint64, enable
|
|||||||
cond := map[string]any{
|
cond := map[string]any{
|
||||||
"id": jobId,
|
"id": jobId,
|
||||||
}
|
}
|
||||||
|
desc := "任务已禁用"
|
||||||
|
if enabled {
|
||||||
|
desc = "任务已启用"
|
||||||
|
}
|
||||||
return d.Updates(cond, map[string]any{
|
return d.Updates(cond, map[string]any{
|
||||||
"enabled": enabled,
|
"enabled": enabled,
|
||||||
|
"enabled_desc": desc,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ func NewDbBackupHistoryRepo() repository.DbBackupHistory {
|
|||||||
return &dbBackupHistoryRepoImpl{}
|
return &dbBackupHistoryRepoImpl{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *dbBackupHistoryRepoImpl) GetHistories(condition *entity.DbBackupHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
func (repo *dbBackupHistoryRepoImpl) GetPageList(condition *entity.DbBackupHistoryQuery, pageParam *model.PageParam, toEntity any, orderBy ...string) (*model.PageResult[any], error) {
|
||||||
qd := gormx.NewQuery(new(entity.DbBackupHistory)).
|
qd := gormx.NewQuery(repo.GetModel()).
|
||||||
Eq("id", condition.Id).
|
Eq("id", condition.Id).
|
||||||
Eq0("db_instance_id", condition.DbInstanceId).
|
Eq0("db_instance_id", condition.DbInstanceId).
|
||||||
In0("db_name", condition.InDbNames).
|
In0("db_name", condition.InDbNames).
|
||||||
@@ -31,6 +31,14 @@ func (repo *dbBackupHistoryRepoImpl) GetHistories(condition *entity.DbBackupHist
|
|||||||
return gormx.PageQuery(qd, pageParam, toEntity)
|
return gormx.PageQuery(qd, pageParam, toEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *dbBackupHistoryRepoImpl) GetHistories(backupHistoryIds []uint64, toEntity any) error {
|
||||||
|
return global.Db.Model(repo.GetModel()).
|
||||||
|
Where("id in ?", backupHistoryIds).
|
||||||
|
Scopes(gormx.UndeleteScope).
|
||||||
|
Find(toEntity).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *dbBackupHistoryRepoImpl) GetLatestHistory(instanceId uint64, dbName string, bi *entity.BinlogInfo) (*entity.DbBackupHistory, error) {
|
func (repo *dbBackupHistoryRepoImpl) GetLatestHistory(instanceId uint64, dbName string, bi *entity.BinlogInfo) (*entity.DbBackupHistory, error) {
|
||||||
history := &entity.DbBackupHistory{}
|
history := &entity.DbBackupHistory{}
|
||||||
db := global.Db
|
db := global.Db
|
||||||
@@ -65,3 +73,33 @@ func (repo *dbBackupHistoryRepoImpl) GetEarliestHistory(instanceId uint64) (*ent
|
|||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *dbBackupHistoryRepoImpl) UpdateDeleting(deleting bool, backupHistoryId ...uint64) (bool, error) {
|
||||||
|
db := global.Db.Model(repo.GetModel()).
|
||||||
|
Where("id in ?", backupHistoryId).
|
||||||
|
Where("restoring = false").
|
||||||
|
Scopes(gormx.UndeleteScope).
|
||||||
|
Update("restoring", deleting)
|
||||||
|
if db.Error != nil {
|
||||||
|
return false, db.Error
|
||||||
|
}
|
||||||
|
if db.RowsAffected != int64(len(backupHistoryId)) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *dbBackupHistoryRepoImpl) UpdateRestoring(restoring bool, backupHistoryId ...uint64) (bool, error) {
|
||||||
|
db := global.Db.Model(repo.GetModel()).
|
||||||
|
Where("id in ?", backupHistoryId).
|
||||||
|
Where("deleting = false").
|
||||||
|
Scopes(gormx.UndeleteScope).
|
||||||
|
Update("restoring", restoring)
|
||||||
|
if db.Error != nil {
|
||||||
|
return false, db.Error
|
||||||
|
}
|
||||||
|
if db.RowsAffected != int64(len(backupHistoryId)) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func (d *dbJobBaseImpl[T]) UpdateLastStatus(ctx context.Context, job entity.DbJo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addJob[T entity.DbJob](ctx context.Context, repo dbJobBaseImpl[T], jobs any) error {
|
func addJob[T entity.DbJob](ctx context.Context, repo dbJobBaseImpl[T], jobs any) error {
|
||||||
// refactor and jobs from any to []T
|
// refactor jobs from any to []T
|
||||||
return gormx.Tx(func(db *gorm.DB) error {
|
return gormx.Tx(func(db *gorm.DB) error {
|
||||||
var instanceId uint64
|
var instanceId uint64
|
||||||
var dbNames []string
|
var dbNames []string
|
||||||
@@ -44,11 +44,10 @@ func addJob[T entity.DbJob](ctx context.Context, repo dbJobBaseImpl[T], jobs any
|
|||||||
dbNames = make([]string, 0, reflectLen)
|
dbNames = make([]string, 0, reflectLen)
|
||||||
for i := 0; i < reflectLen; i++ {
|
for i := 0; i < reflectLen; i++ {
|
||||||
job := reflectValue.Index(i).Interface().(entity.DbJob)
|
job := reflectValue.Index(i).Interface().(entity.DbJob)
|
||||||
jobBase := job.GetJobBase()
|
|
||||||
if instanceId == 0 {
|
if instanceId == 0 {
|
||||||
instanceId = jobBase.DbInstanceId
|
instanceId = job.GetInstanceId()
|
||||||
}
|
}
|
||||||
if jobBase.DbInstanceId != instanceId {
|
if job.GetInstanceId() != instanceId {
|
||||||
return errors.New("不支持同时为多个数据库实例添加数据库任务")
|
return errors.New("不支持同时为多个数据库实例添加数据库任务")
|
||||||
}
|
}
|
||||||
if job.GetInterval() == 0 {
|
if job.GetInterval() == 0 {
|
||||||
@@ -59,8 +58,7 @@ func addJob[T entity.DbJob](ctx context.Context, repo dbJobBaseImpl[T], jobs any
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
job := jobs.(entity.DbJob)
|
job := jobs.(entity.DbJob)
|
||||||
jobBase := job.GetJobBase()
|
instanceId = job.GetInstanceId()
|
||||||
instanceId = jobBase.DbInstanceId
|
|
||||||
if job.GetInterval() > 0 {
|
if job.GetInterval() > 0 {
|
||||||
dbNames = append(dbNames, job.GetDbName())
|
dbNames = append(dbNames, job.GetDbName())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,8 +54,7 @@ func (d *dbRestoreRepoImpl) ListToDo(jobs any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetPageList 分页获取数据库备份任务列表
|
// GetPageList 分页获取数据库备份任务列表
|
||||||
func (d *dbRestoreRepoImpl) GetPageList(condition *entity.DbJobQuery, pageParam *model.PageParam, toEntity any, _ ...string) (*model.PageResult[any], error) {
|
func (d *dbRestoreRepoImpl) GetPageList(condition *entity.DbRestoreQuery, pageParam *model.PageParam, toEntity any, _ ...string) (*model.PageResult[any], error) {
|
||||||
d.GetModel()
|
|
||||||
qd := gormx.NewQuery(d.GetModel()).
|
qd := gormx.NewQuery(d.GetModel()).
|
||||||
Eq("id", condition.Id).
|
Eq("id", condition.Id).
|
||||||
Eq0("db_instance_id", condition.DbInstanceId).
|
Eq0("db_instance_id", condition.DbInstanceId).
|
||||||
@@ -65,6 +64,17 @@ func (d *dbRestoreRepoImpl) GetPageList(condition *entity.DbJobQuery, pageParam
|
|||||||
return gormx.PageQuery(qd, pageParam, toEntity)
|
return gormx.PageQuery(qd, pageParam, toEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *dbRestoreRepoImpl) GetEnabledRestores(toEntity any, backupHistoryId ...uint64) error {
|
||||||
|
return global.Db.Model(d.GetModel()).
|
||||||
|
Select("id", "db_backup_history_id", "last_status", "last_result", "last_time").
|
||||||
|
Where("db_backup_history_id in ?", backupHistoryId).
|
||||||
|
Where("enabled = true").
|
||||||
|
Scopes(gormx.UndeleteScope).
|
||||||
|
Order("id DESC").
|
||||||
|
Find(toEntity).
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
// AddJob 添加数据库任务
|
// AddJob 添加数据库任务
|
||||||
func (d *dbRestoreRepoImpl) AddJob(ctx context.Context, jobs any) error {
|
func (d *dbRestoreRepoImpl) AddJob(ctx context.Context, jobs any) error {
|
||||||
return addJob[*entity.DbRestore](ctx, d.dbJobBaseImpl, jobs)
|
return addJob[*entity.DbRestore](ctx, d.dbJobBaseImpl, jobs)
|
||||||
@@ -74,7 +84,12 @@ func (d *dbRestoreRepoImpl) UpdateEnabled(_ context.Context, jobId uint64, enabl
|
|||||||
cond := map[string]any{
|
cond := map[string]any{
|
||||||
"id": jobId,
|
"id": jobId,
|
||||||
}
|
}
|
||||||
|
desc := "任务已禁用"
|
||||||
|
if enabled {
|
||||||
|
desc = "任务已启用"
|
||||||
|
}
|
||||||
return d.Updates(cond, map[string]any{
|
return d.Updates(cond, map[string]any{
|
||||||
"enabled": enabled,
|
"enabled": enabled,
|
||||||
|
"enabled_desc": desc,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ func InitDbBackupRouter(router *gin.RouterGroup) {
|
|||||||
|
|
||||||
// 获取数据库备份历史
|
// 获取数据库备份历史
|
||||||
req.NewGet(":dbId/backup-histories/", d.GetHistoryPageList),
|
req.NewGet(":dbId/backup-histories/", d.GetHistoryPageList),
|
||||||
|
// 从数据库备份历史中恢复数据库
|
||||||
|
req.NewPost(":dbId/backup-histories/:backupHistoryId/restore", d.RestoreHistories),
|
||||||
|
// 删除数据库备份历史
|
||||||
|
req.NewDelete(":dbId/backup-histories/:backupHistoryId", d.DeleteHistories),
|
||||||
}
|
}
|
||||||
|
|
||||||
req.BatchSetGroup(dbs, reqs)
|
req.BatchSetGroup(dbs, reqs)
|
||||||
|
|||||||
@@ -50,10 +50,6 @@ func (d *wrapper[T]) GetKey() string {
|
|||||||
return d.key
|
return d.key
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *wrapper[T]) Payload() T {
|
|
||||||
return d.job
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDelayQueue[T Delayable](cap int) *DelayQueue[T] {
|
func NewDelayQueue[T Delayable](cap int) *DelayQueue[T] {
|
||||||
singleDequeue := make(chan struct{}, 1)
|
singleDequeue := make(chan struct{}, 1)
|
||||||
singleDequeue <- struct{}{}
|
singleDequeue <- struct{}{}
|
||||||
|
|||||||
@@ -11,18 +11,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrJobNotFound = errors.New("job not found")
|
ErrJobNotFound = errors.New("任务未找到")
|
||||||
ErrJobExist = errors.New("job already exists")
|
ErrJobExist = errors.New("任务已存在")
|
||||||
ErrJobFinished = errors.New("job already finished")
|
ErrJobFinished = errors.New("任务已完成")
|
||||||
ErrJobDisabled = errors.New("job has been disabled")
|
ErrJobDisabled = errors.New("任务已禁用")
|
||||||
ErrJobTimeout = errors.New("job has timed out")
|
ErrJobExpired = errors.New("任务已过期")
|
||||||
|
ErrJobRunning = errors.New("任务执行中")
|
||||||
)
|
)
|
||||||
|
|
||||||
type JobKey = string
|
type JobKey = string
|
||||||
type RunJobFunc[T Job] func(ctx context.Context, job T)
|
type RunJobFunc[T Job] func(ctx context.Context, job T) error
|
||||||
type NextJobFunc[T Job] func() (T, bool)
|
type NextJobFunc[T Job] func() (T, bool)
|
||||||
type RunnableJobFunc[T Job] func(job T, next NextJobFunc[T]) bool
|
type RunnableJobFunc[T Job] func(job T, next NextJobFunc[T]) (bool, error)
|
||||||
type ScheduleJobFunc[T Job] func(job T) (deadline time.Time, err error)
|
type ScheduleJobFunc[T Job] func(job T) (deadline time.Time, err error)
|
||||||
|
type UpdateJobFunc[T Job] func(ctx context.Context, job T) error
|
||||||
|
|
||||||
type JobStatus int
|
type JobStatus int
|
||||||
|
|
||||||
@@ -31,11 +33,15 @@ const (
|
|||||||
JobDelaying
|
JobDelaying
|
||||||
JobWaiting
|
JobWaiting
|
||||||
JobRunning
|
JobRunning
|
||||||
|
JobSuccess
|
||||||
|
JobFailed
|
||||||
)
|
)
|
||||||
|
|
||||||
type Job interface {
|
type Job interface {
|
||||||
GetKey() JobKey
|
GetKey() JobKey
|
||||||
Update(job Job)
|
Update(job Job)
|
||||||
|
SetStatus(status JobStatus, err error)
|
||||||
|
SetEnabled(enabled bool, desc string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type iterator[T Job] struct {
|
type iterator[T Job] struct {
|
||||||
@@ -114,6 +120,7 @@ type Runner[T Job] struct {
|
|||||||
runJob RunJobFunc[T]
|
runJob RunJobFunc[T]
|
||||||
runnableJob RunnableJobFunc[T]
|
runnableJob RunnableJobFunc[T]
|
||||||
scheduleJob ScheduleJobFunc[T]
|
scheduleJob ScheduleJobFunc[T]
|
||||||
|
updateJob UpdateJobFunc[T]
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
context context.Context
|
context context.Context
|
||||||
@@ -138,6 +145,12 @@ func WithScheduleJob[T Job](scheduleJob ScheduleJobFunc[T]) Opt[T] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithUpdateJob[T Job](updateJob UpdateJobFunc[T]) Opt[T] {
|
||||||
|
return func(runner *Runner[T]) {
|
||||||
|
runner.updateJob = updateJob
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewRunner[T Job](maxRunning int, runJob RunJobFunc[T], opts ...Opt[T]) *Runner[T] {
|
func NewRunner[T Job](maxRunning int, runJob RunJobFunc[T], opts ...Opt[T]) *Runner[T] {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
runner := &Runner[T]{
|
runner := &Runner[T]{
|
||||||
@@ -151,14 +164,15 @@ func NewRunner[T Job](maxRunning int, runJob RunJobFunc[T], opts ...Opt[T]) *Run
|
|||||||
delayQueue: NewDelayQueue[*wrapper[T]](0),
|
delayQueue: NewDelayQueue[*wrapper[T]](0),
|
||||||
}
|
}
|
||||||
runner.runJob = runJob
|
runner.runJob = runJob
|
||||||
|
runner.runnableJob = func(job T, _ NextJobFunc[T]) (bool, error) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
runner.updateJob = func(ctx context.Context, job T) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(runner)
|
opt(runner)
|
||||||
}
|
}
|
||||||
if runner.runnableJob == nil {
|
|
||||||
runner.runnableJob = func(job T, _ NextJobFunc[T]) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runner.wg.Add(maxRunning + 1)
|
runner.wg.Add(maxRunning + 1)
|
||||||
for i := 0; i < maxRunning; i++ {
|
for i := 0; i < maxRunning; i++ {
|
||||||
@@ -211,10 +225,32 @@ func (r *Runner[T]) doRun(wrap *wrapper[T]) {
|
|||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
logx.Error(fmt.Sprintf("failed to run job: %v", err))
|
logx.Error(fmt.Sprintf("failed to run job: %v", err))
|
||||||
|
time.Sleep(time.Millisecond * 10)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
wrap.job.SetStatus(JobRunning, nil)
|
||||||
r.runJob(r.context, wrap.job)
|
if err := r.updateJob(r.context, wrap.job); err != nil {
|
||||||
|
err = fmt.Errorf("任务状态保存失败: %w", err)
|
||||||
|
wrap.job.SetStatus(JobFailed, err)
|
||||||
|
_ = r.updateJob(r.context, wrap.job)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
runErr := r.runJob(r.context, wrap.job)
|
||||||
|
if runErr != nil {
|
||||||
|
wrap.job.SetStatus(JobFailed, runErr)
|
||||||
|
} else {
|
||||||
|
wrap.job.SetStatus(JobSuccess, nil)
|
||||||
|
}
|
||||||
|
if err := r.updateJob(r.context, wrap.job); err != nil {
|
||||||
|
if runErr != nil {
|
||||||
|
err = fmt.Errorf("任务状态保存失败: %w, %w", err, runErr)
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("任务状态保存失败: %w", err)
|
||||||
|
}
|
||||||
|
wrap.job.SetStatus(JobFailed, err)
|
||||||
|
_ = r.updateJob(r.context, wrap.job)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runner[T]) afterRun(wrap *wrapper[T]) {
|
func (r *Runner[T]) afterRun(wrap *wrapper[T]) {
|
||||||
@@ -249,12 +285,19 @@ func (r *Runner[T]) pickRunnableJob() (*wrapper[T], bool) {
|
|||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
defer r.mutex.Unlock()
|
defer r.mutex.Unlock()
|
||||||
|
|
||||||
|
var disabled []JobKey
|
||||||
iter := r.running.Iterator()
|
iter := r.running.Iterator()
|
||||||
var runnable *wrapper[T]
|
var runnable *wrapper[T]
|
||||||
ok := r.waiting.Any(func(key interface{}, value interface{}) bool {
|
ok := r.waiting.Any(func(key interface{}, value interface{}) bool {
|
||||||
wrap := value.(*wrapper[T])
|
wrap := value.(*wrapper[T])
|
||||||
iter.Begin()
|
iter.Begin()
|
||||||
if r.runnableJob(wrap.job, iter.Next) {
|
able, err := r.runnableJob(wrap.job, iter.Next)
|
||||||
|
if err != nil {
|
||||||
|
wrap.job.SetEnabled(false, err.Error())
|
||||||
|
r.updateJob(r.context, wrap.job)
|
||||||
|
disabled = append(disabled, key.(JobKey))
|
||||||
|
}
|
||||||
|
if able {
|
||||||
if r.running.Full() {
|
if r.running.Full() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -269,6 +312,10 @@ func (r *Runner[T]) pickRunnableJob() (*wrapper[T], bool) {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
for _, key := range disabled {
|
||||||
|
r.waiting.Remove(key)
|
||||||
|
delete(r.all, key)
|
||||||
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
@@ -304,24 +351,23 @@ func (r *Runner[T]) Add(ctx context.Context, job T) error {
|
|||||||
return r.schedule(ctx, deadline, job)
|
return r.schedule(ctx, deadline, job)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runner[T]) UpdateOrAdd(ctx context.Context, job T) error {
|
func (r *Runner[T]) Update(ctx context.Context, job T) error {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
defer r.mutex.Unlock()
|
defer r.mutex.Unlock()
|
||||||
|
|
||||||
wrap, ok := r.all[job.GetKey()]
|
wrap, ok := r.all[job.GetKey()]
|
||||||
if ok {
|
if !ok {
|
||||||
wrap.job.Update(job)
|
return ErrJobNotFound
|
||||||
switch wrap.status {
|
}
|
||||||
case JobDelaying:
|
wrap.job.Update(job)
|
||||||
r.delayQueue.Remove(ctx, wrap.key)
|
switch wrap.status {
|
||||||
delete(r.all, wrap.key)
|
case JobDelaying:
|
||||||
case JobWaiting:
|
r.delayQueue.Remove(ctx, wrap.key)
|
||||||
r.waiting.Remove(wrap.key)
|
case JobWaiting:
|
||||||
delete(r.all, wrap.key)
|
r.waiting.Remove(wrap.key)
|
||||||
case JobRunning:
|
case JobRunning:
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
}
|
|
||||||
}
|
}
|
||||||
deadline, err := r.doScheduleJob(job, false)
|
deadline, err := r.doScheduleJob(job, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -360,7 +406,7 @@ func (r *Runner[T]) Remove(ctx context.Context, key JobKey) error {
|
|||||||
|
|
||||||
wrap, ok := r.all[key]
|
wrap, ok := r.all[key]
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrJobNotFound
|
return nil
|
||||||
}
|
}
|
||||||
switch wrap.status {
|
switch wrap.status {
|
||||||
case JobDelaying:
|
case JobDelaying:
|
||||||
@@ -372,6 +418,7 @@ func (r *Runner[T]) Remove(ctx context.Context, key JobKey) error {
|
|||||||
case JobRunning:
|
case JobRunning:
|
||||||
// 统一标记为 removed, 待任务执行完成后再删除
|
// 统一标记为 removed, 待任务执行完成后再删除
|
||||||
wrap.removed = true
|
wrap.removed = true
|
||||||
|
return ErrJobRunning
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -29,14 +29,19 @@ func (t *testJob) GetKey() JobKey {
|
|||||||
return t.Key
|
return t.Key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *testJob) SetStatus(status JobStatus, err error) {}
|
||||||
|
|
||||||
|
func (t *testJob) SetEnabled(enabled bool, desc string) {}
|
||||||
|
|
||||||
func TestRunner_Close(t *testing.T) {
|
func TestRunner_Close(t *testing.T) {
|
||||||
signal := make(chan struct{}, 1)
|
signal := make(chan struct{}, 1)
|
||||||
waiting := sync.WaitGroup{}
|
waiting := sync.WaitGroup{}
|
||||||
waiting.Add(1)
|
waiting.Add(1)
|
||||||
runner := NewRunner[*testJob](1, func(ctx context.Context, job *testJob) {
|
runner := NewRunner[*testJob](1, func(ctx context.Context, job *testJob) error {
|
||||||
waiting.Done()
|
waiting.Done()
|
||||||
timex.SleepWithContext(ctx, time.Hour)
|
timex.SleepWithContext(ctx, time.Hour)
|
||||||
signal <- struct{}{}
|
signal <- struct{}{}
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
go func() {
|
go func() {
|
||||||
job := &testJob{
|
job := &testJob{
|
||||||
@@ -78,8 +83,9 @@ func TestRunner_AddJob(t *testing.T) {
|
|||||||
want: ErrJobExist,
|
want: ErrJobExist,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
runner := NewRunner[*testJob](1, func(ctx context.Context, job *testJob) {
|
runner := NewRunner[*testJob](1, func(ctx context.Context, job *testJob) error {
|
||||||
timex.SleepWithContext(ctx, time.Hour)
|
timex.SleepWithContext(ctx, time.Hour)
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
defer runner.Close()
|
defer runner.Close()
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
@@ -99,10 +105,11 @@ func TestJob_UpdateStatus(t *testing.T) {
|
|||||||
running
|
running
|
||||||
finished
|
finished
|
||||||
)
|
)
|
||||||
runner := NewRunner[*testJob](1, func(ctx context.Context, job *testJob) {
|
runner := NewRunner[*testJob](1, func(ctx context.Context, job *testJob) error {
|
||||||
job.status = running
|
job.status = running
|
||||||
timex.SleepWithContext(ctx, d*2)
|
timex.SleepWithContext(ctx, d*2)
|
||||||
job.status = finished
|
job.status = finished
|
||||||
|
return nil
|
||||||
})
|
})
|
||||||
first := newTestJob("first")
|
first := newTestJob("first")
|
||||||
second := newTestJob("second")
|
second := newTestJob("second")
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
INSERT INTO `t_sys_resource` (`id`, `pid`, `ui_path`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `is_deleted`, `delete_time`)
|
INSERT INTO `t_sys_resource` (`id`, `pid`, `ui_path`, `type`, `status`, `name`, `code`, `weight`, `meta`, `creator_id`, `creator`, `modifier_id`, `modifier`, `create_time`, `update_time`, `is_deleted`, `delete_time`)
|
||||||
VALUES (161, 49, 'dbms23ax/xleaiec2/3NUXQFIO/', 2, 1, '数据库备份', 'db:backup', 1705973876, 'null', 1, 'admin', 1, 'admin', '2024-01-23 09:37:56', '2024-01-23 09:37:56', 0, NULL),
|
VALUES (161, 49, 'dbms23ax/xleaiec2/3NUXQFIO/', 2, 1, '数据库备份', 'db:backup', 1705973876, 'null', 1, 'admin', 1, 'admin', '2024-01-23 09:37:56', '2024-01-23 09:37:56', 0, NULL),
|
||||||
(160, 49, 'dbms23ax/xleaiec2/ghErkTdb/', 2, 1, '数据库恢复', 'db:restore', 1705973909, 'null', 1, 'admin', 1, 'admin', '2024-01-23 09:38:29', '2024-01-23 09:38:29', 0, NULL);
|
(160, 49, 'dbms23ax/xleaiec2/ghErkTdb/', 2, 1, '数据库恢复', 'db:restore', 1705973909, 'null', 1, 'admin', 1, 'admin', '2024-01-23 09:38:29', '2024-01-23 09:38:29', 0, NULL);
|
||||||
|
|
||||||
|
ALTER TABLE `mayfly-go`.`t_db_backup`
|
||||||
|
ADD COLUMN `enabled_desc` varchar(64) NULL COMMENT '任务启用描述' AFTER `enabled`;
|
||||||
|
|
||||||
|
ALTER TABLE `mayfly-go`.`t_db_restore`
|
||||||
|
ADD COLUMN `enabled_desc` varchar(64) NULL COMMENT '任务启用描述' AFTER `enabled`;
|
||||||
|
|
||||||
|
ALTER TABLE `mayfly-go`.`t_db_backup_history`
|
||||||
|
ADD COLUMN `restoring` int(1) NOT NULL DEFAULT(0) COMMENT '备份历史恢复标识',
|
||||||
|
ADD COLUMN `deleting` int(1) NOT NULL DEFAULT(0) COMMENT '备份历史删除标识';
|
||||||
|
|||||||
Reference in New Issue
Block a user