Merge pull request 'feat:新增数据库备份(mysql),自动备份,自动清理备份文件的功能。' (#121) from skywolf627/Admin.NET.Pro:main into main

Reviewed-on: http://101.43.53.74:3000/Admin.NET/Admin.NET.Pro/pulls/121
This commit is contained in:
zuohuaijun 2024-09-03 22:02:49 +08:00
commit 683ebe16a5
7 changed files with 349 additions and 0 deletions

View File

@ -29,6 +29,7 @@
<PackageReference Include="Magicodes.IE.Pdf" Version="2.7.5.1" />
<PackageReference Include="Magicodes.IE.Word" Version="2.7.5.1" />
<PackageReference Include="MailKit" Version="4.7.1.1" />
<PackageReference Include="MySqlBackup.NET.MySqlConnector" Version="2.3.8" />
<PackageReference Include="NewLife.Redis" Version="5.7.2024.801" />
<PackageReference Include="Novell.Directory.Ldap.NETStandard" Version="3.6.0" />
<PackageReference Include="QRCoder" Version="1.6.0" />

View File

@ -0,0 +1,49 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Furion.TimeCrontab;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Admin.NET.Core;
[JobDetail("job_DbBackupJob", Description = "备份数据库", GroupName = "default", Concurrent = false)]
[Cron("0 1 * * *", CronStringFormat.Default, TriggerId = "trigger_DbBackupJob", Description = "备份数据库", RunOnStart = false)]
public class DbBackupJob : IJob
{
private readonly IServiceScopeFactory _scopeFactory;
public DbBackupJob(IServiceScopeFactory scopeFactory, ILoggerFactory loggerFactory)
{
_scopeFactory = scopeFactory;
}
public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)
{
string msg = $"【{DateTime.Now}】开始备份数据库";
var originColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg);
Console.ForegroundColor = originColor;
using var serviceScope = _scopeFactory.CreateScope();
var dbBackupService = serviceScope.ServiceProvider.GetRequiredService<DbBackupService>();
//清理7天前的备份文件
dbBackupService.DeleteExpiredDbFile(7);
//开始备份
await dbBackupService.Backup();
msg = $"【{DateTime.Now}】数据库备份完成";
originColor = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(msg);
Console.ForegroundColor = originColor;
}
}

View File

@ -175,6 +175,11 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
new SysMenu{ Id=1310000000445, Pid=1310000000441, Title="获取支付订单详情(微信接口)", Permission="sysWechatPay/payInfoFromWechat", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
new SysMenu{ Id=1310000000446, Pid=1310000000441, Title="退款申请", Permission="sysWechatPay/refundDomestic", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
new SysMenu{ Id=1310000000447, Pid=1310000000301, Title="数据库备份", Path="/platform/dbBackup", Name="dbBackup", Component="/system/dbBackup/index", Icon="ele-Coin", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=240 },
new SysMenu{ Id=1310000000448, Pid=1310000000447, Title="查询", Permission="dbBackup/page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
new SysMenu{ Id=1310000000449, Pid=1310000000447, Title="删除", Permission="dbBackup/delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
new SysMenu{ Id=1310000000450, Pid=1310000000447, Title="增加", Permission="dbBackup/add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
new SysMenu{ Id=1310000000501, Pid=0, Title="日志管理", Path="/log", Name="log", Component="Layout", Icon="ele-DocumentCopy", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=12000 },
new SysMenu{ Id=1310000000511, Pid=1310000000501, Title="访问日志", Path="/log/logvis", Name="sysLogVis", Component="/system/log/logvis/index", Icon="ele-Document", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
new SysMenu{ Id=1310000000512, Pid=1310000000511, Title="查询", Permission="sysLogVis/page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },

View File

@ -0,0 +1,123 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Admin.NET.Core.Service;
public class DbBackupService : IDynamicApiController, ITransient
{
private readonly SqlSugarRepository<SysUser> _sysUserRep;
private readonly string backupDir;
public DbBackupService(SqlSugarRepository<SysUser> sysUserRep)
{
_sysUserRep = sysUserRep;
backupDir = Path.Combine(App.WebHostEnvironment.WebRootPath, "DbBackup");
}
/// <summary>
/// 备份数据库
/// </summary>
/// <returns></returns>
[HttpPost]
[ApiDescriptionSettings(Name = "Add")]
[DisplayName("备份数据库")]
public async Task AddBackup()
{
await Backup();
}
/// <summary>
/// 备份数据库 用于job调用
/// </summary>
/// <returns></returns>
[ApiDescriptionSettings(false)]
public async Task Backup()
{
await Task.Run(() =>
{
var options = App.GetOptions<DbConnectionOptions>();
foreach (var option in options.ConnectionConfigs)
{
var configId = option.ConfigId == null || string.IsNullOrWhiteSpace(option.ConfigId.ToString())
? SqlSugarConst.MainConfigId
: option.ConfigId.ToString();
if (option?.DbType == SqlSugar.DbType.MySql) //备份mysql 其他数据库自行扩展
{
var path = Path.Combine(backupDir, $"{configId}-{DateTime.Now:yyyyMMddHHmmss}.sql");
_sysUserRep.Context.DbMaintenance.BackupDataBase(_sysUserRep.Context.Ado.Connection.Database, path);
}
}
});
}
/// <summary>
/// 删除过期备份文件
/// </summary>
/// <param name="day">过期天数</param>
[ApiDescriptionSettings(false)]
public void DeleteExpiredDbFile(int day = 7)
{
var list = Directory.GetFiles(backupDir);
foreach (var item in list)
{
var info = new FileInfo(item);
if (info.CreationTime.AddDays(day) < DateTime.Now)
{
try
{
File.Delete(item);
}
catch (Exception)
{
continue;
}
}
}
}
/// <summary>
/// 获取备份列表
/// </summary>
/// <returns></returns>
[HttpPost]
[ApiDescriptionSettings(Name = "Page")]
[DisplayName("获取备份列表")]
public List<BackupDto> GetBackupList()
{
var list = Directory.GetFiles(backupDir);
var result = new List<BackupDto>();
foreach (var item in list)
{
var info = new FileInfo(item);
result.Add(new BackupDto
{
FileName = info.Name,
Size = info.Length,
CreateTime = info.CreationTime
});
}
return result;
}
/// <summary>
/// 删除备份
/// </summary>
/// <param name="fileName"></param>
[HttpPost]
[ApiDescriptionSettings(Name = "Delete")]
[DisplayName("删除备份")]
public void DeleteBackup([FromQuery] string fileName)
{
var path = Path.Combine(backupDir, fileName);
File.Delete(path);
}
}

View File

@ -0,0 +1,20 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Admin.NET.Core.Service;
public class BackupDto
{
public long Size { get; set; }
public string FileName { get; set; }
public DateTime CreateTime { get; set; }
}

View File

@ -17,3 +17,25 @@ export const getAllDictList = () =>
url: `${Api.AllDictList}`,
method: 'get',
});
// 获取所有备份文件
export const getBackupList = () =>
request({
url: `/api/dbBackup/page`,
method: 'post',
});
// 新增备份文件
export const addBackup = () =>
request({
url: `/api/dbBackup/add`,
method: 'post',
});
// 删除备份文件
export const deleteBackup = (fileName: String) =>
request({
url: `/api/dbBackup/delete`,
method: 'post',
params: { fileName },
});

View File

@ -0,0 +1,129 @@
<template>
<div>
<el-card class="full-table" shadow="hover" style="margin-top: 5px">
<vxe-grid ref="xGrid" class="xGrid-style" v-bind="options" v-on="gridEvents">
<template #toolbar_buttons>
<el-button
type="primary"
icon="ele-Plus"
@click="handleAdd"
v-auth="'dbBackup/add'"
:loading="loading"
>
新增
</el-button>
</template>
<template #toolbar_tools> </template>
<template #row_createTime="{ row }">
<el-tooltip :content="row.createTime" placement="top">
<span>{{ formatPast(row.createTime) }}</span>
</el-tooltip>
</template>
<template #row_size="{ row }">
<span>{{ (row.size / 1024 / 1024).toFixed(2) }} MB</span>
</template>
<template #row_buttons="{ row }">
<el-tooltip content="删除" placement="top">
<el-button
icon="ele-Delete"
text
type="danger"
v-auth="'dbBackup/delete'"
@click="handleDelete(row)"
>
</el-button>
</el-tooltip>
</template>
</vxe-grid>
</el-card>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { ElMessageBox, ElMessage } from "element-plus";
import { VxeGridInstance, VxeGridListeners } from "vxe-table";
import { useVxeTable } from "/@/hooks/useVxeTableOptionsHook";
import { formatPast } from "/@/utils/formatTime";
import { getBackupList, addBackup, deleteBackup } from "/@/api/system/admin";
const xGrid = ref<VxeGridInstance>();
const loading = ref(false);
//
const options = useVxeTable(
{
id: "dbBackup",
name: "备份信息",
columns: [
// { type: 'checkbox', width: 40, fixed: 'left' },
{ type: "seq", title: "序号", width: 60, fixed: "left" },
{
field: "fileName",
title: "文件名称",
minWidth: 180,
showOverflow: "tooltip",
treeNode: true,
align: "left",
headerAlign: "center",
},
{
field: "size",
title: "文件大小",
width: 100,
showOverflow: "tooltip",
slots: { default: "row_size" },
},
{
field: "createTime",
title: "备份时间",
width: 140,
showOverflow: "tooltip",
slots: { default: "row_createTime" },
},
{
title: "操作",
fixed: "right",
width: 100,
showOverflow: true,
slots: { default: "row_buttons" },
},
],
},
// vxeGrid()vxe-table
{
stripe: false,
//
checkboxConfig: { range: false },
//
proxyConfig: { autoLoad: true, ajax: { query: () => handleQueryApi() } },
//
pagerConfig: { enabled: false },
//
toolbarConfig: { export: false },
//
treeConfig: { expandAll: false },
}
);
// api
const handleQueryApi = async () => {
return getBackupList();
};
const handleAdd = async () => {
loading.value = true;
await addBackup();
loading.value = false;
await xGrid.value?.commitProxy("reload");
};
const handleDelete = async (row: any) => {
loading.value = true;
await deleteBackup(row.fileName);
loading.value = false;
await xGrid.value?.commitProxy("reload");
};
</script>
<style scoped></style>