基于C#实现的定时数据库备份工具
更新时间:2026年06月11日 08:45:06 作者:ytttr873
这篇文章主要介绍了如何C#实现的定时数据库备份工具,支持 SQL Server、MySQL、PostgreSQL、Oracle 等多种数据库,包含定时任务 、邮件通知、备份清理等功能,需要的朋友可以参考下
C# 定时数据库备份解决方案 支持 SQL Server、MySQL、PostgreSQL、Oracle 等多种数据库,包含定时任务、邮件通知、备份清理等功能。
一、项目结构
DatabaseBackupTool/ ├── Program.cs # 程序入口 ├── MainForm.cs # 主窗体 ├── BackupService.cs # 备份服务核心 ├── BackupScheduler.cs # 定时调度器 ├── DatabaseBackup.cs # 数据库备份基类 ├── SqlServerBackup.cs # SQL Server备份 ├── MySqlBackup.cs # MySQL备份 ├── PostgreSqlBackup.cs # PostgreSQL备份 ├── OracleBackup.cs # Oracle备份 ├── EmailNotifier.cs # 邮件通知 ├── LogManager.cs # 日志管理 ├── ConfigManager.cs # 配置管理 └── DatabaseBackupTool.csproj
二、核心源码实现
2.1 项目文件 (DatabaseBackupTool.csproj)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
</PropertyGroup>
<ItemGroup>
<!-- 数据库驱动 -->
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.1" />
<PackageReference Include="MySql.Data" Version="8.2.0" />
<PackageReference Include="Npgsql" Version="7.0.4" />
<PackageReference Include="Oracle.ManagedDataAccess.Core" Version="3.21.110" />
<!-- Quartz.NET 定时任务 -->
<PackageReference Include="Quartz" Version="3.7.0" />
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.7.0" />
<!-- 其他工具 -->
<PackageReference Include="MailKit" Version="4.3.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>2.2 程序入口 (Program.cs)
using System;
using System.Windows.Forms;
namespace DatabaseBackupTool
{
internal static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// 初始化日志
LogManager.Initialize();
LogManager.Info("数据库备份工具启动");
Application.Run(new MainForm());
}
}
}
2.3 主窗体 (MainForm.cs)
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
namespace DatabaseBackupTool
{
public partial class MainForm : Form
{
private BackupService backupService = new BackupService();
private BackupScheduler scheduler = new BackupScheduler();
private List<BackupTask> backupTasks = new List<BackupTask>();
public MainForm()
{
InitializeComponent();
LoadBackupTasks();
UpdateTaskList();
}
private void LoadBackupTasks()
{
backupTasks = ConfigManager.LoadBackupTasks();
}
private void UpdateTaskList()
{
dgvTasks.Rows.Clear();
foreach (var task in backupTasks)
{
dgvTasks.Rows.Add(
task.Name,
task.DatabaseType,
task.Server,
task.DatabaseName,
task.ScheduleType,
task.LastBackupTime.ToString("yyyy-MM-dd HH:mm"),
task.NextBackupTime.ToString("yyyy-MM-dd HH:mm"),
task.IsEnabled ? "启用" : "禁用"
);
}
}
private void btnAddTask_Click(object sender, EventArgs e)
{
BackupTaskForm taskForm = new BackupTaskForm();
if (taskForm.ShowDialog() == DialogResult.OK)
{
var newTask = taskForm.BackupTask;
backupTasks.Add(newTask);
ConfigManager.SaveBackupTasks(backupTasks);
UpdateTaskList();
if (newTask.IsEnabled)
{
scheduler.ScheduleTask(newTask);
}
LogManager.Info($"添加备份任务: {newTask.Name}");
}
}
private void btnEditTask_Click(object sender, EventArgs e)
{
if (dgvTasks.SelectedRows.Count == 0)
{
MessageBox.Show("请选择要编辑的任务!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
int index = dgvTasks.SelectedRows[0].Index;
var task = backupTasks[index];
BackupTaskForm taskForm = new BackupTaskForm(task);
if (taskForm.ShowDialog() == DialogResult.OK)
{
backupTasks[index] = taskForm.BackupTask;
ConfigManager.SaveBackupTasks(backupTasks);
UpdateTaskList();
// 重新调度任务
scheduler.RescheduleTask(backupTasks[index]);
LogManager.Info($"更新备份任务: {task.Name}");
}
}
private void btnDeleteTask_Click(object sender, EventArgs e)
{
if (dgvTasks.SelectedRows.Count == 0)
{
MessageBox.Show("请选择要删除的任务!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
int index = dgvTasks.SelectedRows[0].Index;
var task = backupTasks[index];
if (MessageBox.Show($"确定要删除任务 '{task.Name}' 吗?", "确认", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
backupTasks.RemoveAt(index);
ConfigManager.SaveBackupTasks(backupTasks);
UpdateTaskList();
scheduler.UnscheduleTask(task.Id);
LogManager.Info($"删除备份任务: {task.Name}");
}
}
private void btnManualBackup_Click(object sender, EventArgs e)
{
if (dgvTasks.SelectedRows.Count == 0)
{
MessageBox.Show("请选择要执行的任务!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
int index = dgvTasks.SelectedRows[0].Index;
var task = backupTasks[index];
btnManualBackup.Enabled = false;
lblStatus.Text = $"正在执行备份: {task.Name}";
backupService.ExecuteBackup(task, (success, message) =>
{
btnManualBackup.Enabled = true;
lblStatus.Text = message;
if (success)
{
LogManager.Info($"手动备份成功: {task.Name}");
MessageBox.Show("备份执行成功!", "完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
LogManager.Error($"手动备份失败: {task.Name} - {message}");
MessageBox.Show($"备份执行失败: {message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
UpdateTaskList();
});
}
private void btnTestConnection_Click(object sender, EventArgs e)
{
if (dgvTasks.SelectedRows.Count == 0)
{
MessageBox.Show("请选择要测试的任务!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
int index = dgvTasks.SelectedRows[0].Index;
var task = backupTasks[index];
lblStatus.Text = $"正在测试连接: {task.Name}";
try
{
bool success = backupService.TestConnection(task);
if (success)
{
lblStatus.Text = "连接测试成功";
MessageBox.Show("数据库连接测试成功!", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
else
{
lblStatus.Text = "连接测试失败";
MessageBox.Show("数据库连接测试失败!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
catch (Exception ex)
{
lblStatus.Text = $"连接测试异常: {ex.Message}";
MessageBox.Show($"连接测试异常: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void btnViewLogs_Click(object sender, EventArgs e)
{
LogViewerForm logForm = new LogViewerForm();
logForm.ShowDialog();
}
private void btnCleanup_Click(object sender, EventArgs e)
{
CleanupForm cleanupForm = new CleanupForm(backupTasks);
cleanupForm.ShowDialog();
}
private void timerStatus_Tick(object sender, EventArgs e)
{
// 更新任务状态
foreach (var task in backupTasks)
{
task.NextBackupTime = scheduler.GetNextFireTime(task);
}
UpdateTaskList();
}
#region Windows Form Designer generated code
private System.ComponentModel.IContainer components = null;
private MenuStrip menuStrip1;
private ToolStripMenuItem 文件ToolStripMenuItem;
private ToolStripMenuItem 退出ToolStripMenuItem;
private ToolStripMenuItem 工具ToolStripMenuItem;
private ToolStripMenuItem 清理旧备份ToolStripMenuItem;
private ToolStripMenuItem 查看日志ToolStripMenuItem;
private ToolStripMenuItem 帮助ToolStripMenuItem;
private ToolStripMenuItem 关于ToolStripMenuItem;
private GroupBox groupBox1;
private Button btnAddTask;
private Button btnEditTask;
private Button btnDeleteTask;
private Button btnManualBackup;
private Button btnTestConnection;
private DataGridView dgvTasks;
private GroupBox groupBox2;
private Button btnViewLogs;
private Button btnCleanup;
private Label lblStatus;
private Timer timerStatus;
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.menuStrip1 = new System.Windows.Forms.MenuStrip();
this.文件ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.退出ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.工具ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.清理旧备份ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.查看日志ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.帮助ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.关于ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.btnTestConnection = new System.Windows.Forms.Button();
this.btnManualBackup = new System.Windows.Forms.Button();
this.btnDeleteTask = new System.Windows.Forms.Button();
this.btnEditTask = new System.Windows.Forms.Button();
this.btnAddTask = new System.Windows.Forms.Button();
this.dgvTasks = new System.Windows.Forms.DataGridView();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.btnCleanup = new System.Windows.Forms.Button();
this.btnViewLogs = new System.Windows.Forms.Button();
this.lblStatus = new System.Windows.Forms.Label();
this.timerStatus = new System.Windows.Forms.Timer(this.components);
this.menuStrip1.SuspendLayout();
this.groupBox1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.dgvTasks)).BeginInit();
this.groupBox2.SuspendLayout();
this.SuspendLayout();
// menuStrip1
this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.文件ToolStripMenuItem,
this.工具ToolStripMenuItem,
this.帮助ToolStripMenuItem});
this.menuStrip1.Location = new System.Drawing.Point(0, 0);
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.Size = new System.Drawing.Size(800, 25);
this.menuStrip1.TabIndex = 0;
this.menuStrip1.Text = "menuStrip1";
// 文件ToolStripMenuItem
this.文件ToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.退出ToolStripMenuItem});
this.文件ToolStripMenuItem.Name = "文件ToolStripMenuItem";
this.文件ToolStripMenuItem.Size = new System.Drawing.Size(44, 21);
this.文件ToolStripMenuItem.Text = "文件";
// 退出ToolStripMenuItem
this.退出ToolStripMenuItem.Name = "退出ToolStripMenuItem";
this.退出ToolStripMenuItem.Size = new System.Drawing.Size(93, 22);
this.退出ToolStripMenuItem.Text = "退出";
this.退出ToolStripMenuItem.Click += new System.EventHandler(this.退出ToolStripMenuItem_Click);
// 工具ToolStripMenuItem
this.工具ToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.清理旧备份ToolStripMenuItem,
this.查看日志ToolStripMenuItem});
this.工具ToolStripMenuItem.Name = "工具ToolStripMenuItem";
this.工具ToolStripMenuItem.Size = new System.Drawing.Size(44, 21);
this.工具ToolStripMenuItem.Text = "工具";
// 清理旧备份ToolStripMenuItem
this.清理旧备份ToolStripMenuItem.Name = "清理旧备份ToolStripMenuItem";
this.清理旧备份ToolStripMenuItem.Size = new System.Drawing.Size(152, 22);
this.清理旧备份ToolStripMenuItem.Text = "清理旧备份";
this.清理旧备份ToolStripMenuItem.Click += new System.EventHandler(this.btnCleanup_Click);
// 查看日志ToolStripMenuItem
this.查看日志ToolStripMenuItem.Name = "查看日志ToolStripMenuItem";
this.查看日志ToolStripMenuItem.Size = new System.Drawing.Size(152, 22);
this.查看日志ToolStripMenuItem.Text = "查看日志";
this.查看日志ToolStripMenuItem.Click += new System.EventHandler(this.btnViewLogs_Click);
// 帮助ToolStripMenuItem
this.帮助ToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.关于ToolStripMenuItem});
this.帮助ToolStripMenuItem.Name = "帮助ToolStripMenuItem";
this.帮助ToolStripMenuItem.Size = new System.Drawing.Size(44, 21);
this.帮助ToolStripMenuItem.Text = "帮助";
// 关于ToolStripMenuItem
this.关于ToolStripMenuItem.Name = "关于ToolStripMenuItem";
this.关于ToolStripMenuItem.Size = new System.Drawing.Size(107, 22);
this.关于ToolStripMenuItem.Text = "关于";
this.关于ToolStripMenuItem.Click += new System.EventHandler(this.关于ToolStripMenuItem_Click);
// groupBox1
this.groupBox1.Controls.Add(this.btnTestConnection);
this.groupBox1.Controls.Add(this.btnManualBackup);
this.groupBox1.Controls.Add(this.btnDeleteTask);
this.groupBox1.Controls.Add(this.btnEditTask);
this.groupBox1.Controls.Add(this.btnAddTask);
this.groupBox1.Location = new System.Drawing.Point(12, 30);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(200, 150);
this.groupBox1.TabIndex = 1;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "任务管理";
// btnTestConnection
this.btnTestConnection.Location = new System.Drawing.Point(20, 115);
this.btnTestConnection.Name = "btnTestConnection";
this.btnTestConnection.Size = new System.Drawing.Size(160, 25);
this.btnTestConnection.TabIndex = 4;
this.btnTestConnection.Text = "测试连接";
this.btnTestConnection.UseVisualStyleBackColor = true;
this.btnTestConnection.Click += new System.EventHandler(this.btnTestConnection_Click);
// btnManualBackup
this.btnManualBackup.Location = new System.Drawing.Point(20, 85);
this.btnManualBackup.Name = "btnManualBackup";
this.btnManualBackup.Size = new System.Drawing.Size(160, 25);
this.btnManualBackup.TabIndex = 3;
this.btnManualBackup.Text = "立即备份";
this.btnManualBackup.UseVisualStyleBackColor = true;
this.btnManualBackup.Click += new System.EventHandler(this.btnManualBackup_Click);
// btnDeleteTask
this.btnDeleteTask.Location = new System.Drawing.Point(20, 55);
this.btnDeleteTask.Name = "btnDeleteTask";
this.btnDeleteTask.Size = new System.Drawing.Size(160, 25);
this.btnDeleteTask.TabIndex = 2;
this.btnDeleteTask.Text = "删除任务";
this.btnDeleteTask.UseVisualStyleBackColor = true;
this.btnDeleteTask.Click += new System.EventHandler(this.btnDeleteTask_Click);
// btnEditTask
this.btnEditTask.Location = new System.Drawing.Point(20, 30);
this.btnEditTask.Name = "btnEditTask";
this.btnEditTask.Size = new System.Drawing.Size(160, 25);
this.btnEditTask.TabIndex = 1;
this.btnEditTask.Text = "编辑任务";
this.btnEditTask.UseVisualStyleBackColor = true;
this.btnEditTask.Click += new System.EventHandler(this.btnEditTask_Click);
// btnAddTask
this.btnAddTask.Location = new System.Drawing.Point(20, 5);
this.btnAddTask.Name = "btnAddTask";
this.btnAddTask.Size = new System.Drawing.Size(160, 25);
this.btnAddTask.TabIndex = 0;
this.btnAddTask.Text = "添加任务";
this.btnAddTask.UseVisualStyleBackColor = true;
this.btnAddTask.Click += new System.EventHandler(this.btnAddTask_Click);
// dgvTasks
this.dgvTasks.AllowUserToAddRows = false;
this.dgvTasks.AllowUserToDeleteRows = false;
this.dgvTasks.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dgvTasks.Location = new System.Drawing.Point(220, 30);
this.dgvTasks.Name = "dgvTasks";
this.dgvTasks.ReadOnly = true;
this.dgvTasks.RowHeadersWidth = 51;
this.dgvTasks.RowTemplate.Height = 23;
this.dgvTasks.Size = new System.Drawing.Size(568, 300);
this.dgvTasks.TabIndex = 2;
this.dgvTasks.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[]
{
new DataGridViewTextBoxColumn { HeaderText = "任务名称", Name = "Name", Width = 120 },
new DataGridViewTextBoxColumn { HeaderText = "数据库类型", Name = "DatabaseType", Width = 80 },
new DataGridViewTextBoxColumn { HeaderText = "服务器", Name = "Server", Width = 100 },
new DataGridViewTextBoxColumn { HeaderText = "数据库", Name = "DatabaseName", Width = 100 },
new DataGridViewTextBoxColumn { HeaderText = "计划", Name = "ScheduleType", Width = 80 },
new DataGridViewTextBoxColumn { HeaderText = "上次备份", Name = "LastBackupTime", Width = 120 },
new DataGridViewTextBoxColumn { HeaderText = "下次备份", Name = "NextBackupTime", Width = 120 },
new DataGridViewTextBoxColumn { HeaderText = "状态", Name = "Status", Width = 60 }
});
// groupBox2
this.groupBox2.Controls.Add(this.btnCleanup);
this.groupBox2.Controls.Add(this.btnViewLogs);
this.groupBox2.Location = new System.Drawing.Point(12, 190);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(200, 140);
this.groupBox2.TabIndex = 3;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "系统工具";
// btnCleanup
this.btnCleanup.Location = new System.Drawing.Point(20, 55);
this.btnCleanup.Name = "btnCleanup";
this.btnCleanup.Size = new System.Drawing.Size(160, 25);
this.btnCleanup.TabIndex = 1;
this.btnCleanup.Text = "清理旧备份";
this.btnCleanup.UseVisualStyleBackColor = true;
this.btnCleanup.Click += new System.EventHandler(this.btnCleanup_Click);
// btnViewLogs
this.btnViewLogs.Location = new System.Drawing.Point(20, 25);
this.btnViewLogs.Name = "btnViewLogs";
this.btnViewLogs.Size = new System.Drawing.Size(160, 25);
this.btnViewLogs.TabIndex = 0;
this.btnViewLogs.Text = "查看日志";
this.btnViewLogs.UseVisualStyleBackColor = true;
this.btnViewLogs.Click += new System.EventHandler(this.btnViewLogs_Click);
// lblStatus
this.lblStatus.AutoSize = true;
this.lblStatus.Location = new System.Drawing.Point(220, 335);
this.lblStatus.Name = "lblStatus";
this.lblStatus.Size = new System.Drawing.Size(44, 13);
this.lblStatus.TabIndex = 4;
this.lblStatus.Text = "就绪...";
// timerStatus
this.timerStatus.Enabled = true;
this.timerStatus.Interval = 60000; // 每分钟更新一次
this.timerStatus.Tick += new System.EventHandler(this.timerStatus_Tick);
// MainForm
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 360);
this.Controls.Add(this.lblStatus);
this.Controls.Add(this.groupBox2);
this.Controls.Add(this.dgvTasks);
this.Controls.Add(this.groupBox1);
this.Controls.Add(this.menuStrip1);
this.MainMenuStrip = this.menuStrip1;
this.Name = "MainForm";
this.Text = "数据库定时备份工具";
this.menuStrip1.ResumeLayout(false);
this.menuStrip1.PerformLayout();
this.groupBox1.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.dgvTasks)).EndInit();
this.groupBox2.ResumeLayout(false);
this.ResumeLayout(false);
this.PerformLayout();
}
private void 退出ToolStripMenuItem_Click(object sender, EventArgs e)
{
Application.Exit();
}
private void 关于ToolStripMenuItem_Click(object sender, EventArgs e)
{
MessageBox.Show("数据库定时备份工具 v1.0\n\n支持SQL Server、MySQL、PostgreSQL、Oracle等数据库\n© 2024 版权所有", "关于", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
#endregion
}
}
2.4 备份任务表单 (BackupTaskForm.cs)
using System;
using System.Windows.Forms;
namespace DatabaseBackupTool
{
public partial class BackupTaskForm : Form
{
public BackupTask BackupTask { get; private set; }
public BackupTaskForm(BackupTask existingTask = null)
{
InitializeComponent();
if (existingTask != null)
{
BackupTask = existingTask;
LoadTaskData();
}
else
{
BackupTask = new BackupTask();
BackupTask.IsEnabled = true;
BackupTask.ScheduleType = ScheduleType.Daily;
BackupTask.BackupTime = new TimeSpan(2, 0, 0); // 默认凌晨2点
BackupTask.RetentionDays = 30; // 默认保留30天
}
}
private void LoadTaskData()
{
txtName.Text = BackupTask.Name;
cmbDatabaseType.SelectedItem = BackupTask.DatabaseType;
txtServer.Text = BackupTask.Server;
txtPort.Text = BackupTask.Port.ToString();
txtDatabase.Text = BackupTask.DatabaseName;
txtUsername.Text = BackupTask.Username;
txtPassword.Text = BackupTask.Password;
txtBackupPath.Text = BackupTask.BackupPath;
cmbScheduleType.SelectedItem = BackupTask.ScheduleType.ToString();
dtpBackupTime.Value = DateTime.Today.Add(BackupTask.BackupTime);
numRetentionDays.Value = BackupTask.RetentionDays;
chkEnabled.Checked = BackupTask.IsEnabled;
txtEmailRecipient.Text = BackupTask.EmailRecipient;
chkSendEmail.Checked = BackupTask.SendEmailNotification;
}
private void btnOK_Click(object sender, EventArgs e)
{
if (ValidateInputs())
{
SaveTaskData();
this.DialogResult = DialogResult.OK;
this.Close();
}
}
private bool ValidateInputs()
{
if (string.IsNullOrWhiteSpace(txtName.Text))
{
MessageBox.Show("请输入任务名称!", "验证", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtName.Focus();
return false;
}
if (string.IsNullOrWhiteSpace(txtServer.Text))
{
MessageBox.Show("请输入数据库服务器地址!", "验证", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtServer.Focus();
return false;
}
if (string.IsNullOrWhiteSpace(txtDatabase.Text))
{
MessageBox.Show("请输入数据库名称!", "验证", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtDatabase.Focus();
return false;
}
if (string.IsNullOrWhiteSpace(txtBackupPath.Text))
{
MessageBox.Show("请输入备份路径!", "验证", MessageBoxButtons.OK, MessageBoxIcon.Warning);
txtBackupPath.Focus();
return false;
}
return true;
}
private void SaveTaskData()
{
BackupTask.Name = txtName.Text;
BackupTask.DatabaseType = cmbDatabaseType.SelectedItem.ToString();
BackupTask.Server = txtServer.Text;
BackupTask.Port = int.Parse(txtPort.Text);
BackupTask.DatabaseName = txtDatabase.Text;
BackupTask.Username = txtUsername.Text;
BackupTask.Password = txtPassword.Text;
BackupTask.BackupPath = txtBackupPath.Text;
BackupTask.ScheduleType = (ScheduleType)Enum.Parse(typeof(ScheduleType), cmbScheduleType.SelectedItem.ToString());
BackupTask.BackupTime = dtpBackupTime.Value.TimeOfDay;
BackupTask.RetentionDays = (int)numRetentionDays.Value;
BackupTask.IsEnabled = chkEnabled.Checked;
BackupTask.EmailRecipient = txtEmailRecipient.Text;
BackupTask.SendEmailNotification = chkSendEmail.Checked;
}
#region Windows Form Designer generated code
private System.ComponentModel.IContainer components = null;
private TextBox txtName;
private Label label1;
private ComboBox cmbDatabaseType;
private Label label2;
private TextBox txtServer;
private Label label3;
private TextBox txtPort;
private Label label4;
private TextBox txtDatabase;
private Label label5;
private TextBox txtUsername;
private Label label6;
private TextBox txtPassword;
private Label label7;
private TextBox txtBackupPath;
private Button btnBrowsePath;
private Label label8;
private ComboBox cmbScheduleType;
private Label label9;
private DateTimePicker dtpBackupTime;
private Label label10;
private NumericUpDown numRetentionDays;
private CheckBox chkEnabled;
private GroupBox groupBox1;
private TextBox txtEmailRecipient;
private Label label11;
private CheckBox chkSendEmail;
private Button btnOK;
private Button btnCancel;
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.txtName = new System.Windows.Forms.TextBox();
this.label1 = new System.Windows.Forms.Label();
this.cmbDatabaseType = new System.Windows.Forms.ComboBox();
this.label2 = new System.Windows.Forms.Label();
this.txtServer = new System.Windows.Forms.TextBox();
this.label3 = new System.Windows.Forms.Label();
this.txtPort = new System.Windows.Forms.TextBox();
this.label4 = new System.Windows.Forms.Label();
this.txtDatabase = new System.Windows.Forms.TextBox();
this.label5 = new System.Windows.Forms.Label();
this.txtUsername = new System.Windows.Forms.TextBox();
this.label6 = new System.Windows.Forms.Label();
this.txtPassword = new System.Windows.Forms.TextBox();
this.label7 = new System.Windows.Forms.Label();
this.txtBackupPath = new System.Windows.Forms.TextBox();
this.btnBrowsePath = new System.Windows.Forms.Button();
this.label8 = new System.Windows.Forms.Label();
this.cmbScheduleType = new System.Windows.Forms.ComboBox();
this.label9 = new System.Windows.Forms.Label();
this.dtpBackupTime = new System.Windows.Forms.DateTimePicker();
this.label10 = new System.Windows.Forms.Label();
this.numRetentionDays = new System.Windows.Forms.NumericUpDown();
this.chkEnabled = new System.Windows.Forms.CheckBox();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.chkSendEmail = new System.Windows.Forms.CheckBox();
this.txtEmailRecipient = new System.Windows.Forms.TextBox();
this.label11 = new System.Windows.Forms.Label();
this.btnOK = new System.Windows.Forms.Button();
this.btnCancel = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.numRetentionDays)).BeginInit();
this.groupBox1.SuspendLayout();
this.SuspendLayout();
// txtName
this.txtName.Location = new System.Drawing.Point(100, 20);
this.txtName.Name = "txtName";
this.txtName.Size = new System.Drawing.Size(250, 23);
this.txtName.TabIndex = 0;
// label1
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(20, 23);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(56, 13);
this.label1.TabIndex = 1;
this.label1.Text = "任务名称:";
// cmbDatabaseType
this.cmbDatabaseType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbDatabaseType.FormattingEnabled = true;
this.cmbDatabaseType.Items.AddRange(new object[] { "SQL Server", "MySQL", "PostgreSQL", "Oracle" });
this.cmbDatabaseType.Location = new System.Drawing.Point(100, 50);
this.cmbDatabaseType.Name = "cmbDatabaseType";
this.cmbDatabaseType.Size = new System.Drawing.Size(250, 23);
this.cmbDatabaseType.TabIndex = 2;
this.cmbDatabaseType.SelectedIndex = 0;
// label2
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(20, 53);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(56, 13);
this.label2.TabIndex = 3;
this.label2.Text = "数据库类型:";
// txtServer
this.txtServer.Location = new System.Drawing.Point(100, 80);
this.txtServer.Name = "txtServer";
this.txtServer.Size = new System.Drawing.Size(180, 23);
this.txtServer.TabIndex = 3;
this.txtServer.Text = "localhost";
// label3
this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(20, 83);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(56, 13);
this.label3.TabIndex = 4;
this.label3.Text = "服务器地址:";
// txtPort
this.txtPort.Location = new System.Drawing.Point(290, 80);
this.txtPort.Name = "txtPort";
this.txtPort.Size = new System.Drawing.Size(60, 23);
this.txtPort.TabIndex = 4;
this.txtPort.Text = "1433";
// label4
this.label4.AutoSize = true;
this.label4.Location = new System.Drawing.Point(290, 63);
this.label4.Name = "label4";
this.label4.Size = new System.Drawing.Size(32, 13);
this.label4.TabIndex = 5;
this.label4.Text = "端口:";
// txtDatabase
this.txtDatabase.Location = new System.Drawing.Point(100, 110);
this.txtDatabase.Name = "txtDatabase";
this.txtDatabase.Size = new System.Drawing.Size(250, 23);
this.txtDatabase.TabIndex = 5;
// label5
this.label5.AutoSize = true;
this.label5.Location = new System.Drawing.Point(20, 113);
this.label5.Name = "label5";
this.label5.Size = new System.Drawing.Size(56, 13);
this.label5.TabIndex = 6;
this.label5.Text = "数据库名称:";
// txtUsername
this.txtUsername.Location = new System.Drawing.Point(100, 140);
this.txtUsername.Name = "txtUsername";
this.txtUsername.Size = new System.Drawing.Size(250, 23);
this.txtUsername.TabIndex = 6;
// label6
this.label6.AutoSize = true;
this.label6.Location = new System.Drawing.Point(20, 143);
this.label6.Name = "label6";
this.label6.Size = new System.Drawing.Size(56, 13);
this.label6.TabIndex = 7;
this.label6.Text = "用户名:";
// txtPassword
this.txtPassword.Location = new System.Drawing.Point(100, 170);
this.txtPassword.Name = "txtPassword";
this.txtPassword.PasswordChar = '*';
this.txtPassword.Size = new System.Drawing.Size(250, 23);
this.txtPassword.TabIndex = 7;
// label7
this.label7.AutoSize = true;
this.label7.Location = new System.Drawing.Point(20, 173);
this.label7.Name = "label7";
this.label7.Size = new System.Drawing.Size(56, 13);
this.label7.TabIndex = 8;
this.label7.Text = "密码:";
// txtBackupPath
this.txtBackupPath.Location = new System.Drawing.Point(100, 200);
this.txtBackupPath.Name = "txtBackupPath";
this.txtBackupPath.Size = new System.Drawing.Size(250, 23);
this.txtBackupPath.TabIndex = 8;
// btnBrowsePath
this.btnBrowsePath.Location = new System.Drawing.Point(355, 198);
this.btnBrowsePath.Name = "btnBrowsePath";
this.btnBrowsePath.Size = new System.Drawing.Size(25, 23);
this.btnBrowsePath.TabIndex = 9;
this.btnBrowsePath.Text = "...";
this.btnBrowsePath.UseVisualStyleBackColor = true;
this.btnBrowsePath.Click += new System.EventHandler(this.btnBrowsePath_Click);
// label8
this.label8.AutoSize = true;
this.label8.Location = new System.Drawing.Point(20, 203);
this.label8.Name = "label8";
this.label8.Size = new System.Drawing.Size(56, 13);
this.label8.TabIndex = 9;
this.label8.Text = "备份路径:";
// cmbScheduleType
this.cmbScheduleType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbScheduleType.FormattingEnabled = true;
this.cmbScheduleType.Items.AddRange(new object[] { "Daily", "Weekly", "Monthly" });
this.cmbScheduleType.Location = new System.Drawing.Point(100, 230);
this.cmbScheduleType.Name = "cmbScheduleType";
this.cmbScheduleType.Size = new System.Drawing.Size(120, 23);
this.cmbScheduleType.TabIndex = 10;
this.cmbScheduleType.SelectedIndex = 0;
// label9
this.label9.AutoSize = true;
this.label9.Location = new System.Drawing.Point(20, 233);
this.label9.Name = "label9";
this.label9.Size = new System.Drawing.Size(56, 13);
this.label9.TabIndex = 10;
this.label9.Text = "备份计划:";
// dtpBackupTime
this.dtpBackupTime.Format = System.Windows.Forms.DateTimePickerFormat.Time;
this.dtpBackupTime.Location = new System.Drawing.Point(230, 230);
this.dtpBackupTime.Name = "dtpBackupTime";
this.dtpBackupTime.Size = new System.Drawing.Size(120, 23);
this.dtpBackupTime.TabIndex = 11;
this.dtpBackupTime.Value = new System.DateTime(2024, 1, 1, 2, 0, 0, 0);
// label10
this.label10.AutoSize = true;
this.label10.Location = new System.Drawing.Point(230, 213);
this.label10.Name = "label10";
this.label10.Size = new System.Drawing.Size(56, 13);
this.label10.TabIndex = 11;
this.label10.Text = "备份时间:";
// numRetentionDays
this.numRetentionDays.Location = new System.Drawing.Point(100, 260);
this.numRetentionDays.Maximum = new decimal(new int[] { 365, 0, 0, 0 });
this.numRetentionDays.Minimum = new decimal(new int[] { 1, 0, 0, 0 });
this.numRetentionDays.Name = "numRetentionDays";
this.numRetentionDays.Size = new System.Drawing.Size(120, 23);
this.numRetentionDays.TabIndex = 12;
this.numRetentionDays.Value = new decimal(new int[] { 30, 0, 0, 0 });
// label11
this.label11.AutoSize = true;
this.label11.Location = new System.Drawing.Point(20, 263);
this.label11.Name = "label11";
this.label11.Size = new System.Drawing.Size(56, 13);
this.label11.TabIndex = 12;
this.label11.Text = "保留天数:";
// chkEnabled
this.chkEnabled.AutoSize = true;
this.chkEnabled.Location = new System.Drawing.Point(230, 262);
this.chkEnabled.Name = "chkEnabled";
this.chkEnabled.Size = new System.Drawing.Size(48, 17);
this.chkEnabled.TabIndex = 13;
this.chkEnabled.Text = "启用";
this.chkEnabled.UseVisualStyleBackColor = true;
this.chkEnabled.Checked = true;
// groupBox1
this.groupBox1.Controls.Add(this.chkSendEmail);
this.groupBox1.Controls.Add(this.txtEmailRecipient);
this.groupBox1.Controls.Add(this.label11);
this.groupBox1.Location = new System.Drawing.Point(20, 290);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(360, 80);
this.groupBox1.TabIndex = 14;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "邮件通知";
// chkSendEmail
this.chkSendEmail.AutoSize = true;
this.chkSendEmail.Location = new System.Drawing.Point(20, 50);
this.chkSendEmail.Name = "chkSendEmail";
this.chkSendEmail.Size = new System.Drawing.Size(96, 17);
this.chkSendEmail.TabIndex = 1;
this.chkSendEmail.Text = "发送邮件通知";
this.chkSendEmail.UseVisualStyleBackColor = true;
// txtEmailRecipient
this.txtEmailRecipient.Location = new System.Drawing.Point(20, 20);
this.txtEmailRecipient.Name = "txtEmailRecipient";
this.txtEmailRecipient.Size = new System.Drawing.Size(320, 23);
this.txtEmailRecipient.TabIndex = 0;
// label12
this.label12.AutoSize = true;
this.label12.Location = new System.Drawing.Point(20, 3);
this.label12.Name = "label12";
this.label12.Size = new System.Drawing.Size(56, 13);
this.label12.TabIndex = 0;
this.label12.Text = "收件人邮箱:";
// btnOK
this.btnOK.Location = new System.Drawing.Point(200, 380);
this.btnOK.Name = "btnOK";
this.btnOK.Size = new System.Drawing.Size(80, 30);
this.btnOK.TabIndex = 15;
this.btnOK.Text = "确定";
this.btnOK.UseVisualStyleBackColor = true;
this.btnOK.Click += new System.EventHandler(this.btnOK_Click);
// btnCancel
this.btnCancel.Location = new System.Drawing.Point(290, 380);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(80, 30);
this.btnCancel.TabIndex = 16;
this.btnCancel.Text = "取消";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
// BackupTaskForm
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(384, 422);
this.Controls.Add(this.btnCancel);
this.Controls.Add(this.btnOK);
this.Controls.Add(this.groupBox1);
this.Controls.Add(this.chkEnabled);
this.Controls.Add(this.numRetentionDays);
this.Controls.Add(this.dtpBackupTime);
this.Controls.Add(this.cmbScheduleType);
this.Controls.Add(this.txtBackupPath);
this.Controls.Add(this.btnBrowsePath);
this.Controls.Add(this.txtPassword);
this.Controls.Add(this.txtUsername);
this.Controls.Add(this.txtDatabase);
this.Controls.Add(this.txtPort);
this.Controls.Add(this.txtServer);
this.Controls.Add(this.cmbDatabaseType);
this.Controls.Add(this.txtName);
this.Controls.Add(this.label10);
this.Controls.Add(this.label9);
this.Controls.Add(this.label8);
this.Controls.Add(this.label7);
this.Controls.Add(this.label6);
this.Controls.Add(this.label5);
this.Controls.Add(this.label4);
this.Controls.Add(this.label3);
this.Controls.Add(this.label2);
this.Controls.Add(this.label1);
this.Name = "BackupTaskForm";
this.Text = "备份任务配置";
((System.ComponentModel.ISupportInitialize)(this.numRetentionDays)).EndInit();
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
private void btnBrowsePath_Click(object sender, EventArgs e)
{
FolderBrowserDialog folderDialog = new FolderBrowserDialog();
folderDialog.Description = "选择备份文件保存路径";
if (folderDialog.ShowDialog() == DialogResult.OK)
{
txtBackupPath.Text = folderDialog.SelectedPath;
}
}
private void btnCancel_Click(object sender, EventArgs e)
{
this.DialogResult = DialogResult.Cancel;
this.Close();
}
#endregion
}
}
2.5 备份任务模型 (BackupTask.cs)
using System;
namespace DatabaseBackupTool
{
/// <summary>
/// 备份任务模型
/// </summary>
public class BackupTask
{
public Guid Id { get; set; } = Guid.NewGuid();
public string Name { get; set; } = "";
public string DatabaseType { get; set; } = "SQL Server";
public string Server { get; set; } = "localhost";
public int Port { get; set; } = 1433;
public string DatabaseName { get; set; } = "";
public string Username { get; set; } = "";
public string Password { get; set; } = "";
public string BackupPath { get; set; } = "";
public ScheduleType ScheduleType { get; set; } = ScheduleType.Daily;
public TimeSpan BackupTime { get; set; } = new TimeSpan(2, 0, 0);
public int RetentionDays { get; set; } = 30;
public bool IsEnabled { get; set; } = true;
public string EmailRecipient { get; set; } = "";
public bool SendEmailNotification { get; set; } = false;
public DateTime LastBackupTime { get; set; } = DateTime.MinValue;
public DateTime NextBackupTime { get; set; } = DateTime.MinValue;
public DateTime CreatedTime { get; set; } = DateTime.Now;
public DateTime ModifiedTime { get; set; } = DateTime.Now;
}
/// <summary>
/// 备份计划类型
/// </summary>
public enum ScheduleType
{
Daily, // 每天
Weekly, // 每周
Monthly // 每月
}
/// <summary>
/// 备份结果
/// </summary>
public class BackupResult
{
public bool Success { get; set; }
public string Message { get; set; } = "";
public string BackupFilePath { get; set; } = "";
public long BackupSize { get; set; } = 0;
public TimeSpan Duration { get; set; } = TimeSpan.Zero;
public DateTime StartTime { get; set; } = DateTime.Now;
public DateTime EndTime { get; set; } = DateTime.Now;
}
}
2.6 备份服务核心 (BackupService.cs)
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
namespace DatabaseBackupTool
{
/// <summary>
/// 备份服务核心
/// </summary>
public class BackupService
{
private DatabaseBackup databaseBackup;
/// <summary>
/// 执行备份
/// </summary>
public void ExecuteBackup(BackupTask task, Action<bool, string> callback)
{
Task.Run(() =>
{
try
{
LogManager.Info($"开始执行备份任务: {task.Name}");
// 创建备份目录
if (!Directory.Exists(task.BackupPath))
{
Directory.CreateDirectory(task.BackupPath);
}
// 获取数据库备份实例
databaseBackup = GetDatabaseBackup(task.DatabaseType);
// 执行备份
var result = databaseBackup.Backup(task);
// 更新任务状态
task.LastBackupTime = DateTime.Now;
task.NextBackupTime = CalculateNextBackupTime(task);
task.ModifiedTime = DateTime.Now;
// 发送邮件通知
if (task.SendEmailNotification && !string.IsNullOrEmpty(task.EmailRecipient))
{
SendEmailNotification(task, result);
}
// 清理旧备份
CleanupOldBackups(task);
LogManager.Info($"备份任务完成: {task.Name} - {(result.Success ? "成功" : "失败")}");
callback(result.Success, result.Message);
}
catch (Exception ex)
{
LogManager.Error($"备份任务异常: {task.Name} - {ex.Message}");
callback(false, $"备份异常: {ex.Message}");
}
});
}
/// <summary>
/// 测试数据库连接
/// </summary>
public bool TestConnection(BackupTask task)
{
try
{
databaseBackup = GetDatabaseBackup(task.DatabaseType);
return databaseBackup.TestConnection(task);
}
catch (Exception ex)
{
LogManager.Error($"连接测试失败: {task.Name} - {ex.Message}");
return false;
}
}
/// <summary>
/// 获取数据库备份实例
/// </summary>
private DatabaseBackup GetDatabaseBackup(string databaseType)
{
return databaseType switch
{
"SQL Server" => new SqlServerBackup(),
"MySQL" => new MySqlBackup(),
"PostgreSQL" => new PostgreSqlBackup(),
"Oracle" => new OracleBackup(),
_ => throw new NotSupportedException($"不支持的数据库类型: {databaseType}")
};
}
/// <summary>
/// 计算下次备份时间
/// </summary>
private DateTime CalculateNextBackupTime(BackupTask task)
{
DateTime now = DateTime.Now;
DateTime todayBackupTime = now.Date.Add(task.BackupTime);
return task.ScheduleType switch
{
ScheduleType.Daily => todayBackupTime <= now ? todayBackupTime.AddDays(1) : todayBackupTime,
ScheduleType.Weekly => CalculateNextWeeklyBackup(now, task.BackupTime),
ScheduleType.Monthly => CalculateNextMonthlyBackup(now, task.BackupTime),
_ => todayBackupTime.AddDays(1)
};
}
private DateTime CalculateNextWeeklyBackup(DateTime now, TimeSpan backupTime)
{
DateTime nextBackup = now.Date.Add(backupTime);
while (nextBackup <= now || nextBackup.DayOfWeek != DayOfWeek.Sunday)
{
nextBackup = nextBackup.AddDays(1);
}
return nextBackup;
}
private DateTime CalculateNextMonthlyBackup(DateTime now, TimeSpan backupTime)
{
DateTime nextBackup = new DateTime(now.Year, now.Month, 1).Add(backupTime);
if (nextBackup <= now)
{
nextBackup = nextBackup.AddMonths(1);
}
return nextBackup;
}
/// <summary>
/// 清理旧备份
/// </summary>
private void CleanupOldBackups(BackupTask task)
{
try
{
if (task.RetentionDays <= 0) return;
string[] backupFiles = Directory.GetFiles(task.BackupPath, $"{task.DatabaseName}_*.bak");
DateTime cutoffDate = DateTime.Now.AddDays(-task.RetentionDays);
foreach (string file in backupFiles)
{
FileInfo fileInfo = new FileInfo(file);
if (fileInfo.CreationTime < cutoffDate)
{
File.Delete(file);
LogManager.Info($"删除旧备份文件: {file}");
}
}
}
catch (Exception ex)
{
LogManager.Error($"清理旧备份失败: {task.Name} - {ex.Message}");
}
}
/// <summary>
/// 发送邮件通知
/// </summary>
private void SendEmailNotification(BackupTask task, BackupResult result)
{
try
{
EmailNotifier notifier = new EmailNotifier();
string subject = $"数据库备份通知 - {task.Name}";
string body = $@"
备份任务: {task.Name}<br/>
数据库: {task.DatabaseName}<br/>
服务器: {task.Server}<br/>
结果: {(result.Success ? "成功" : "失败")}<br/>
消息: {result.Message}<br/>
备份文件: {result.BackupFilePath}<br/>
文件大小: {result.BackupSize / 1024.0 / 1024.0:F2} MB<br/>
耗时: {result.Duration.TotalMinutes:F2} 分钟<br/>
开始时间: {result.StartTime:yyyy-MM-dd HH:mm:ss}<br/>
结束时间: {result.EndTime:yyyy-MM-dd HH:mm:ss}
";
notifier.SendEmail(task.EmailRecipient, subject, body);
LogManager.Info($"邮件通知已发送到: {task.EmailRecipient}");
}
catch (Exception ex)
{
LogManager.Error($"发送邮件通知失败: {task.Name} - {ex.Message}");
}
}
}
}
2.7 数据库备份基类 (DatabaseBackup.cs)
using System;
using System.Diagnostics;
using System.IO;
namespace DatabaseBackupTool
{
/// <summary>
/// 数据库备份基类
/// </summary>
public abstract class DatabaseBackup
{
/// <summary>
/// 执行备份
/// </summary>
public abstract BackupResult Backup(BackupTask task);
/// <summary>
/// 测试连接
/// </summary>
public abstract bool TestConnection(BackupTask task);
/// <summary>
/// 生成备份文件名
/// </summary>
protected string GenerateBackupFileName(string databaseName)
{
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
return $"{databaseName}_{timestamp}.bak";
}
/// <summary>
/// 执行命令行工具
/// </summary>
protected BackupResult ExecuteCommand(string command, string arguments, string workingDirectory = "")
{
var result = new BackupResult();
var stopwatch = Stopwatch.StartNew();
try
{
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = command,
Arguments = arguments,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
WorkingDirectory = workingDirectory
};
using Process process = Process.Start(psi);
if (process == null)
{
result.Success = false;
result.Message = "无法启动进程";
return result;
}
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
process.WaitForExit();
stopwatch.Stop();
result.Duration = stopwatch.Elapsed;
result.EndTime = DateTime.Now;
if (process.ExitCode == 0)
{
result.Success = true;
result.Message = "备份成功";
result.BackupFilePath = output.Trim();
}
else
{
result.Success = false;
result.Message = $"备份失败: {error}";
}
}
catch (Exception ex)
{
stopwatch.Stop();
result.Success = false;
result.Message = $"执行命令异常: {ex.Message}";
result.Duration = stopwatch.Elapsed;
}
return result;
}
/// <summary>
/// 获取文件大小
/// </summary>
protected long GetFileSize(string filePath)
{
if (File.Exists(filePath))
{
FileInfo fileInfo = new FileInfo(filePath);
return fileInfo.Length;
}
return 0;
}
}
}
2.8 SQL Server 备份实现 (SqlServerBackup.cs)
using System;
using System.Data;
using Microsoft.Data.SqlClient;
namespace DatabaseBackupTool
{
/// <summary>
/// SQL Server 备份实现
/// </summary>
public class SqlServerBackup : DatabaseBackup
{
public override BackupResult Backup(BackupTask task)
{
var result = new BackupResult { StartTime = DateTime.Now };
try
{
string backupFileName = GenerateBackupFileName(task.DatabaseName);
string backupFilePath = Path.Combine(task.BackupPath, backupFileName);
string connectionString = BuildConnectionString(task);
using var connection = new SqlConnection(connectionString);
connection.Open();
// 构建备份SQL
string backupSql = $@"
BACKUP DATABASE [{task.DatabaseName}]
TO DISK = @BackupPath
WITH FORMAT, INIT, NAME = @BackupName, SKIP, NOREWIND, NOUNLOAD, STATS = 10
";
using var command = new SqlCommand(backupSql, connection);
command.Parameters.AddWithValue("@BackupPath", backupFilePath);
command.Parameters.AddWithValue("@BackupName", $"{task.DatabaseName} Full Backup");
command.CommandTimeout = 3600; // 1小时超时
command.ExecuteNonQuery();
result.Success = true;
result.Message = "SQL Server 备份成功";
result.BackupFilePath = backupFilePath;
result.BackupSize = GetFileSize(backupFilePath);
result.EndTime = DateTime.Now;
LogManager.Info($"SQL Server 备份完成: {backupFilePath}");
}
catch (Exception ex)
{
result.Success = false;
result.Message = $"SQL Server 备份失败: {ex.Message}";
result.EndTime = DateTime.Now;
LogManager.Error($"SQL Server 备份失败: {ex.Message}");
}
return result;
}
public override bool TestConnection(BackupTask task)
{
try
{
string connectionString = BuildConnectionString(task);
using var connection = new SqlConnection(connectionString);
connection.Open();
return true;
}
catch (Exception ex)
{
LogManager.Error($"SQL Server 连接测试失败: {ex.Message}");
return false;
}
}
private string BuildConnectionString(BackupTask task)
{
if (string.IsNullOrEmpty(task.Username))
{
return $"Server={task.Server},{task.Port};Database=master;Integrated Security=True;TrustServerCertificate=True;";
}
else
{
return $"Server={task.Server},{task.Port};Database=master;User ID={task.Username};Password={task.Password};TrustServerCertificate=True;";
}
}
}
}
2.9 MySQL 备份实现 (MySqlBackup.cs)
using System;
using System.Diagnostics;
using System.IO;
namespace DatabaseBackupTool
{
/// <summary>
/// MySQL 备份实现
/// </summary>
public class MySqlBackup : DatabaseBackup
{
public override BackupResult Backup(BackupTask task)
{
var result = new BackupResult { StartTime = DateTime.Now };
try
{
string backupFileName = GenerateBackupFileName(task.DatabaseName);
string backupFilePath = Path.Combine(task.BackupPath, backupFileName);
// 构建 mysqldump 命令
string arguments = $"-h{task.Server} -P{task.Port} -u{task.Username} -p{task.Password} " +
$"--single-transaction --routines --triggers --events " +
$"--databases {task.DatabaseName} > \"{backupFilePath}\"";
// 执行 mysqldump
result = ExecuteCommand("mysqldump", arguments);
result.StartTime = DateTime.Now;
if (result.Success)
{
result.BackupFilePath = backupFilePath;
result.BackupSize = GetFileSize(backupFilePath);
LogManager.Info($"MySQL 备份完成: {backupFilePath}");
}
else
{
LogManager.Error($"MySQL 备份失败: {result.Message}");
}
}
catch (Exception ex)
{
result.Success = false;
result.Message = $"MySQL 备份异常: {ex.Message}";
LogManager.Error($"MySQL 备份异常: {ex.Message}");
}
result.EndTime = DateTime.Now;
return result;
}
public override bool TestConnection(BackupTask task)
{
try
{
using var connection = new MySql.Data.MySqlClient.MySqlConnection(BuildConnectionString(task));
connection.Open();
return true;
}
catch (Exception ex)
{
LogManager.Error($"MySQL 连接测试失败: {ex.Message}");
return false;
}
}
private string BuildConnectionString(BackupTask task)
{
return $"server={task.Server};port={task.Port};database={task.DatabaseName};uid={task.Username};pwd={task.Password};";
}
}
}
2.10 定时调度器 (BackupScheduler.cs)
using System;
using System.Collections.Generic;
using Quartz;
using Quartz.Impl;
namespace DatabaseBackupTool
{
/// <summary>
/// 备份定时调度器
/// </summary>
public class BackupScheduler
{
private IScheduler scheduler;
private Dictionary<Guid, JobKey> jobKeys = new Dictionary<Guid, JobKey>();
public async void Initialize()
{
try
{
// 创建调度器工厂
StdSchedulerFactory factory = new StdSchedulerFactory();
scheduler = await factory.GetScheduler();
await scheduler.Start();
LogManager.Info("定时调度器启动成功");
}
catch (Exception ex)
{
LogManager.Error($"定时调度器启动失败: {ex.Message}");
}
}
public async void ScheduleTask(BackupTask task)
{
if (!task.IsEnabled) return;
try
{
// 创建作业
IJobDetail job = JobBuilder.Create<BackupJob>()
.WithIdentity($"Job_{task.Id}", "BackupJobs")
.UsingJobData("TaskId", task.Id.ToString())
.Build();
// 创建触发器
ITrigger trigger = CreateTrigger(task);
// 调度作业
await scheduler.ScheduleJob(job, trigger);
jobKeys[task.Id] = job.Key;
LogManager.Info($"任务已调度: {task.Name}");
}
catch (Exception ex)
{
LogManager.Error($"调度任务失败: {task.Name} - {ex.Message}");
}
}
public async void RescheduleTask(BackupTask task)
{
// 先取消原有调度
UnscheduleTask(task.Id);
// 重新调度
if (task.IsEnabled)
{
ScheduleTask(task);
}
}
public async void UnscheduleTask(Guid taskId)
{
if (jobKeys.ContainsKey(taskId))
{
await scheduler.DeleteJob(jobKeys[taskId]);
jobKeys.Remove(taskId);
LogManager.Info($"任务已取消调度: {taskId}");
}
}
private ITrigger CreateTrigger(BackupTask task)
{
string cronExpression = GetCronExpression(task);
return TriggerBuilder.Create()
.WithIdentity($"Trigger_{task.Id}", "BackupTriggers")
.WithCronSchedule(cronExpression)
.Build();
}
private string GetCronExpression(BackupTask task)
{
int hour = task.BackupTime.Hours;
int minute = task.BackupTime.Minutes;
int second = task.BackupTime.Seconds;
return task.ScheduleType switch
{
ScheduleType.Daily => $"{second} {minute} {hour} * * ?",
ScheduleType.Weekly => $"{second} {minute} {hour} ? * SUN",
ScheduleType.Monthly => $"{second} {minute} {hour} 1 * ?",
_ => $"{second} {minute} {hour} * * ?"
};
}
public DateTime GetNextFireTime(BackupTask task)
{
if (!task.IsEnabled) return DateTime.MinValue;
string cronExpression = GetCronExpression(task);
var cron = new Quartz.CronExpression(cronExpression);
return cron.GetNextValidTimeAfter(DateTime.Now) ?? DateTime.MinValue;
}
public async void Shutdown()
{
if (scheduler != null)
{
await scheduler.Shutdown();
LogManager.Info("定时调度器已关闭");
}
}
}
/// <summary>
/// 备份作业
/// </summary>
public class BackupJob : IJob
{
public async Task Execute(IJobExecutionContext context)
{
var jobData = context.JobDetail.JobDataMap;
string taskIdStr = jobData.GetString("TaskId");
if (Guid.TryParse(taskIdStr, out Guid taskId))
{
// 从配置中获取任务
var tasks = ConfigManager.LoadBackupTasks();
var task = tasks.Find(t => t.Id == taskId);
if (task != null && task.IsEnabled)
{
var backupService = new BackupService();
backupService.ExecuteBackup(task, (success, message) =>
{
LogManager.Info($"定时备份完成: {task.Name} - {message}");
});
}
}
await Task.CompletedTask;
}
}
}
2.11 邮件通知 (EmailNotifier.cs)
using System;
using MailKit.Net.Smtp;
using MimeKit;
using MimeKit.Text;
namespace DatabaseBackupTool
{
/// <summary>
/// 邮件通知
/// </summary>
public class EmailNotifier
{
private readonly string smtpServer = "smtp.gmail.com"; // 配置您的SMTP服务器
private readonly int smtpPort = 587;
private readonly string smtpUsername = "your-email@gmail.com"; // 配置您的邮箱
private readonly string smtpPassword = "your-password"; // 配置您的密码
public void SendEmail(string recipient, string subject, string body)
{
try
{
var message = new MimeMessage();
message.From.Add(new MailboxAddress("数据库备份系统", smtpUsername));
message.To.Add(new MailboxAddress("", recipient));
message.Subject = subject;
message.Body = new TextPart(TextFormat.Html) { Text = body };
using var client = new SmtpClient();
client.Connect(smtpServer, smtpPort, MailKit.Security.SecureSocketOptions.StartTls);
client.Authenticate(smtpUsername, smtpPassword);
client.Send(message);
client.Disconnect(true);
}
catch (Exception ex)
{
LogManager.Error($"发送邮件失败: {ex.Message}");
throw;
}
}
}
}
2.12 日志管理 (LogManager.cs)
using System;
using System.IO;
namespace DatabaseBackupTool
{
/// <summary>
/// 日志管理
/// </summary>
public static class LogManager
{
private static string logFilePath = "logs/backup.log";
private static object lockObject = new object();
public static void Initialize()
{
string logDir = Path.GetDirectoryName(logFilePath);
if (!string.IsNullOrEmpty(logDir) && !Directory.Exists(logDir))
{
Directory.CreateDirectory(logDir);
}
}
public static void Info(string message)
{
WriteLog("INFO", message);
}
public static void Warning(string message)
{
WriteLog("WARNING", message);
}
public static void Error(string message)
{
WriteLog("ERROR", message);
}
private static void WriteLog(string level, string message)
{
try
{
string logMessage = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{level}] {message}";
lock (lockObject)
{
File.AppendAllText(logFilePath, logMessage + Environment.NewLine);
}
}
catch
{
// 忽略日志写入错误
}
}
public static string[] ReadLogs(int count = 100)
{
try
{
if (File.Exists(logFilePath))
{
var lines = File.ReadAllLines(logFilePath);
if (lines.Length > count)
{
return lines[^count..];
}
return lines;
}
}
catch
{
// 忽略错误
}
return Array.Empty<string>();
}
}
}
2.13 配置管理 (ConfigManager.cs)
using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
namespace DatabaseBackupTool
{
/// <summary>
/// 配置管理
/// </summary>
public static class ConfigManager
{
private static string configFilePath = "config/backup_tasks.json";
public static List<BackupTask> LoadBackupTasks()
{
try
{
if (File.Exists(configFilePath))
{
string json = File.ReadAllText(configFilePath);
return JsonConvert.DeserializeObject<List<BackupTask>>(json) ?? new List<BackupTask>();
}
}
catch (Exception ex)
{
LogManager.Error($"加载配置失败: {ex.Message}");
}
return new List<BackupTask>();
}
public static void SaveBackupTasks(List<BackupTask> tasks)
{
try
{
string configDir = Path.GetDirectoryName(configFilePath);
if (!string.IsNullOrEmpty(configDir) && !Directory.Exists(configDir))
{
Directory.CreateDirectory(configDir);
}
string json = JsonConvert.SerializeObject(tasks, Formatting.Indented);
File.WriteAllText(configFilePath, json);
LogManager.Info("备份任务配置已保存");
}
catch (Exception ex)
{
LogManager.Error($"保存配置失败: {ex.Message}");
}
}
}
}
2.14 日志查看器 (LogViewerForm.cs)
using System;
using System.Windows.Forms;
namespace DatabaseBackupTool
{
public partial class LogViewerForm : Form
{
public LogViewerForm()
{
InitializeComponent();
LoadLogs();
}
private void LoadLogs()
{
string[] logs = LogManager.ReadLogs(200);
txtLogs.Lines = logs;
txtLogs.SelectionStart = txtLogs.Text.Length;
txtLogs.ScrollToCaret();
}
#region Windows Form Designer generated code
private System.ComponentModel.IContainer components = null;
private TextBox txtLogs;
private Button btnRefresh;
private Button btnClear;
private Button btnClose;
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.txtLogs = new System.Windows.Forms.TextBox();
this.btnRefresh = new System.Windows.Forms.Button();
this.btnClear = new System.Windows.Forms.Button();
this.btnClose = new System.Windows.Forms.Button();
this.SuspendLayout();
// txtLogs
this.txtLogs.Location = new System.Drawing.Point(12, 12);
this.txtLogs.Multiline = true;
this.txtLogs.Name = "txtLogs";
this.txtLogs.ReadOnly = true;
this.txtLogs.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.txtLogs.Size = new System.Drawing.Size(660, 400);
this.txtLogs.TabIndex = 0;
// btnRefresh
this.btnRefresh.Location = new System.Drawing.Point(12, 422);
this.btnRefresh.Name = "btnRefresh";
this.btnRefresh.Size = new System.Drawing.Size(80, 30);
this.btnRefresh.TabIndex = 1;
this.btnRefresh.Text = "刷新";
this.btnRefresh.UseVisualStyleBackColor = true;
this.btnRefresh.Click += new System.EventHandler(this.btnRefresh_Click);
// btnClear
this.btnClear.Location = new System.Drawing.Point(100, 422);
this.btnClear.Name = "btnClear";
this.btnClear.Size = new System.Drawing.Size(80, 30);
this.btnClear.TabIndex = 2;
this.btnClear.Text = "清空日志";
this.btnClear.UseVisualStyleBackColor = true;
this.btnClear.Click += new System.EventHandler(this.btnClear_Click);
// btnClose
this.btnClose.Location = new System.Drawing.Point(592, 422);
this.btnClose.Name = "btnClose";
this.btnClose.Size = new System.Drawing.Size(80, 30);
this.btnClose.TabIndex = 3;
this.btnClose.Text = "关闭";
this.btnClose.UseVisualStyleBackColor = true;
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
// LogViewerForm
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(684, 464);
this.Controls.Add(this.btnClose);
this.Controls.Add(this.btnClear);
this.Controls.Add(this.btnRefresh);
this.Controls.Add(this.txtLogs);
this.Name = "LogViewerForm";
this.Text = "日志查看器";
this.ResumeLayout(false);
this.PerformLayout();
}
private void btnRefresh_Click(object sender, EventArgs e)
{
LoadLogs();
}
private void btnClear_Click(object sender, EventArgs e)
{
if (MessageBox.Show("确定要清空所有日志吗?", "确认", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
try
{
File.WriteAllText("logs/backup.log", "");
txtLogs.Clear();
}
catch (Exception ex)
{
MessageBox.Show($"清空日志失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private void btnClose_Click(object sender, EventArgs e)
{
this.Close();
}
#endregion
}
}
2.15 清理工具 (CleanupForm.cs)
using System;
using System.IO;
using System.Windows.Forms;
namespace DatabaseBackupTool
{
public partial class CleanupForm : Form
{
private List<BackupTask> backupTasks;
public CleanupForm(List<BackupTask> tasks)
{
InitializeComponent();
backupTasks = tasks;
LoadTasks();
}
private void LoadTasks()
{
cmbTasks.Items.Clear();
foreach (var task in backupTasks)
{
cmbTasks.Items.Add(task.Name);
}
if (cmbTasks.Items.Count > 0)
{
cmbTasks.SelectedIndex = 0;
}
}
private void btnCleanup_Click(object sender, EventArgs e)
{
if (cmbTasks.SelectedIndex < 0)
{
MessageBox.Show("请选择要清理的任务!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
int retentionDays = (int)numRetentionDays.Value;
if (retentionDays <= 0)
{
MessageBox.Show("保留天数必须大于0!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var task = backupTasks[cmbTasks.SelectedIndex];
string backupPath = task.BackupPath;
if (!Directory.Exists(backupPath))
{
MessageBox.Show("备份路径不存在!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
if (MessageBox.Show($"确定要清理 {task.Name} 中超过 {retentionDays} 天的备份文件吗?", "确认", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
try
{
int deletedCount = CleanupOldBackups(backupPath, retentionDays, task.DatabaseName);
MessageBox.Show($"清理完成!共删除 {deletedCount} 个备份文件。", "完成", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show($"清理失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private int CleanupOldBackups(string backupPath, int retentionDays, string databaseName)
{
int deletedCount = 0;
DateTime cutoffDate = DateTime.Now.AddDays(-retentionDays);
string[] backupFiles = Directory.GetFiles(backupPath, $"{databaseName}_*.bak");
foreach (string file in backupFiles)
{
FileInfo fileInfo = new FileInfo(file);
if (fileInfo.CreationTime < cutoffDate)
{
File.Delete(file);
deletedCount++;
LogManager.Info($"删除旧备份文件: {file}");
}
}
return deletedCount;
}
#region Windows Form Designer generated code
private System.ComponentModel.IContainer components = null;
private ComboBox cmbTasks;
private Label label1;
private NumericUpDown numRetentionDays;
private Label label2;
private Button btnCleanup;
private Button btnClose;
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.cmbTasks = new System.Windows.Forms.ComboBox();
this.label1 = new System.Windows.Forms.Label();
this.numRetentionDays = new System.Windows.Forms.NumericUpDown();
this.label2 = new System.Windows.Forms.Label();
this.btnCleanup = new System.Windows.Forms.Button();
this.btnClose = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.numRetentionDays)).BeginInit();
this.SuspendLayout();
// cmbTasks
this.cmbTasks.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbTasks.FormattingEnabled = true;
this.cmbTasks.Location = new System.Drawing.Point(100, 20);
this.cmbTasks.Name = "cmbTasks";
this.cmbTasks.Size = new System.Drawing.Size(250, 23);
this.cmbTasks.TabIndex = 0;
// label1
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(20, 23);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(56, 13);
this.label1.TabIndex = 1;
this.label1.Text = "选择任务:";
// numRetentionDays
this.numRetentionDays.Location = new System.Drawing.Point(100, 50);
this.numRetentionDays.Maximum = new decimal(new int[] { 365, 0, 0, 0 });
this.numRetentionDays.Minimum = new decimal(new int[] { 1, 0, 0, 0 });
this.numRetentionDays.Name = "numRetentionDays";
this.numRetentionDays.Size = new System.Drawing.Size(120, 23);
this.numRetentionDays.TabIndex = 2;
this.numRetentionDays.Value = new decimal(new int[] { 30, 0, 0, 0 });
// label2
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(20, 53);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(56, 13);
this.label2.TabIndex = 3;
this.label2.Text = "保留天数:";
// btnCleanup
this.btnCleanup.Location = new System.Drawing.Point(100, 90);
this.btnCleanup.Name = "btnCleanup";
this.btnCleanup.Size = new System.Drawing.Size(100, 30);
this.btnCleanup.TabIndex = 4;
this.btnCleanup.Text = "开始清理";
this.btnCleanup.UseVisualStyleBackColor = true;
this.btnCleanup.Click += new System.EventHandler(this.btnCleanup_Click);
// btnClose
this.btnClose.Location = new System.Drawing.Point(210, 90);
this.btnClose.Name = "btnClose";
this.btnClose.Size = new System.Drawing.Size(100, 30);
this.btnClose.TabIndex = 5;
this.btnClose.Text = "关闭";
this.btnClose.UseVisualStyleBackColor = true;
this.btnClose.Click += new System.EventHandler(this.btnClose_Click);
// CleanupForm
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(370, 130);
this.Controls.Add(this.btnClose);
this.Controls.Add(this.btnCleanup);
this.Controls.Add(this.numRetentionDays);
this.Controls.Add(this.label2);
this.Controls.Add(this.cmbTasks);
this.Controls.Add(this.label1);
this.Name = "CleanupForm";
this.Text = "清理旧备份";
((System.ComponentModel.ISupportInitialize)(this.numRetentionDays)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
private void btnClose_Click(object sender, EventArgs e)
{
this.Close();
}
#endregion
}
}
三、功能特点
多数据库支持:SQL Server、MySQL、PostgreSQL、Oracle
定时备份:支持每天、每周、每月定时执行
邮件通知:备份完成后自动发送邮件通知
备份清理:自动清理超过保留天数的旧备份
日志记录:详细记录所有操作和错误信息
任务管理:添加、编辑、删除、启用/禁用备份任务
手动备份:支持随时手动执行备份
连接测试:测试数据库连接是否正常
配置持久化:备份任务配置自动保存
四、使用说明
4.1 快速开始
- 运行程序,点击"添加任务"
- 填写数据库连接信息
- 设置备份路径和计划
- 点击"确定"保存任务
- 任务将按计划自动执行
4.2 配置邮件通知
- 在任务配置中勾选"发送邮件通知"
- 填写收件人邮箱
- 修改
EmailNotifier.cs中的 SMTP 服务器配置
4.3 手动执行备份
- 在主界面选择要执行的任务
- 点击"立即备份"按钮
- 查看备份结果
4.4 注意事项
- 确保数据库服务正常运行
- 确保备份路径有写入权限
- MySQL 需要安装 mysqldump 工具
- 建议使用专用备份账户,权限最小化
以上就是基于C#实现的定时数据库备份工具的详细内容,更多关于C#定时数据库备份工具的资料请关注脚本之家其它相关文章!


最新评论