Windows窗体应用 + SQL Server 自动清理功能方案:按数量与按日期双模式
一、项目架构设计
1. 技术栈
- 前端:Windows Forms (.NET Framework/Core)
- 数据库:SQL Server
- ORM:Entity Framework 或 Dapper(推荐Dapper性能更好)
2. 数据库表结构设计
-- 主数据表(示例)
CREATE TABLE LogData
(
Id BIGINT PRIMARY KEY IDENTITY(1,1),
Content NVARCHAR(MAX),
LogLevel INT,
CreateTime DATETIME DEFAULT GETDATE(),
Category NVARCHAR(100)
);
-- 清理配置表
CREATE TABLE CleanupConfig
(
Id INT PRIMARY KEY IDENTITY(1,1),
ConfigName NVARCHAR(100) NOT NULL,
TableName NVARCHAR(100) NOT NULL,
Mode INT NOT NULL, -- 1:按数量 2:按日期
MaxRows INT NULL, -- 按数量模式时使用
KeepDays INT NULL, -- 按日期模式时使用
Enabled BIT DEFAULT 1,
LastCleanTime DATETIME NULL,
ScheduleType INT DEFAULT 1, -- 1:每日 2:每周 3:每月
NextCleanTime DATETIME NULL,
CreatedTime DATETIME DEFAULT GETDATE()
);
-- 清理历史记录表
CREATE TABLE CleanupHistory
(
Id BIGINT PRIMARY KEY IDENTITY(1,1),
ConfigId INT NOT NULL,
TableName NVARCHAR(100),
CleanMode INT,
DeletedRows INT,
StartTime DATETIME,
EndTime DATETIME,
DurationMs INT,
Status INT, -- 1:成功 2:失败
ErrorMessage NVARCHAR(MAX),
FOREIGN KEY (ConfigId) REFERENCES CleanupConfig(Id)
);
二、Windows窗体应用实现
1. 主窗体设计 (MainForm.cs)
using System;
using System.Windows.Forms;
using System.Data;
using System.Threading.Tasks;
namespace AutoCleanupTool
{
public partial class MainForm : Form
{
private CleanupService _cleanupService;
private ConfigService _configService;
public MainForm()
{
InitializeComponent();
_cleanupService = new CleanupService();
_configService = new ConfigService();
}
private async void MainForm_Load(object sender, EventArgs e)
{
await LoadConfigs();
timerAutoClean.Interval = 60000; // 每分钟检查一次
timerAutoClean.Start();
}
private async Task LoadConfigs()
{
try
{
var configs = await _configService.GetAllConfigsAsync();
dgvConfigs.DataSource = configs;
}
catch (Exception ex)
{
MessageBox.Show($"加载配置失败: {ex.Message}");
}
}
// 添加配置按钮事件
private void btnAddConfig_Click(object sender, EventArgs e)
{
var configForm = new ConfigForm();
if (configForm.ShowDialog() == DialogResult.OK)
{
LoadConfigs();
}
}
// 编辑配置
private void btnEditConfig_Click(object sender, EventArgs e)
{
if (dgvConfigs.CurrentRow?.DataBoundItem is CleanupConfig config)
{
var configForm = new ConfigForm(config);
if (configForm.ShowDialog() == DialogResult.OK)
{
LoadConfigs();
}
}
}
// 立即执行清理
private async void btnExecuteNow_Click(object sender, EventArgs e)
{
if (dgvConfigs.CurrentRow?.DataBoundItem is CleanupConfig config)
{
try
{
btnExecuteNow.Enabled = false;
var result = await _cleanupService.ExecuteCleanupAsync(config);
MessageBox.Show($"清理完成,删除 {result.DeletedRows} 条记录",
"提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
await LoadCleanupHistory();
}
catch (Exception ex)
{
MessageBox.Show($"清理失败: {ex.Message}", "错误",
MessageBoxButtons.OK, MessageBoxIcon.Error);
}
finally
{
btnExecuteNow.Enabled = true;
}
}
}
// 定时器触发自动清理
private async void timerAutoClean_Tick(object sender, EventArgs e)
{
await CheckAndExecuteCleanup();
}
private async Task CheckAndExecuteCleanup()
{
try
{
var dueConfigs = await _configService.GetDueConfigsAsync();
foreach (var config in dueConfigs)
{
await ExecuteAutoCleanup(config);
}
}
catch (Exception ex)
{
LogError($"自动清理检查失败: {ex.Message}");
}
}
private async Task ExecuteAutoCleanup(CleanupConfig config)
{
try
{
var result = await _cleanupService.ExecuteCleanupAsync(config);
await _configService.UpdateLastCleanTimeAsync(config.Id);
LogInfo($"自动清理完成 - {config.ConfigName}: 删除 {result.DeletedRows} 条记录");
}
catch (Exception ex)
{
LogError($"自动清理失败 - {config.ConfigName}: {ex.Message}");
}
}
}
}
public partial class ConfigForm : Form
{
private CleanupConfig _config;
public ConfigForm(CleanupConfig config = null)
{
InitializeComponent();
_config = config ?? new CleanupConfig();
InitializeControls();
}
private void InitializeControls()
{
// 绑定数据源
cmbTableName.DataSource = GetTableList();
cmbMode.DataSource = new List<KeyValuePair<int, string>>
{
new KeyValuePair<int, string>(1, "按数量清理"),
new KeyValuePair<int, string>(2, "按日期清理")
};
cmbMode.DisplayMember = "Value";
cmbMode.ValueMember = "Key";
cmbScheduleType.DataSource = new List<KeyValuePair<int, string>>
{
new KeyValuePair<int, string>(1, "每日"),
new KeyValuePair<int, string>(2, "每周"),
new KeyValuePair<int, string>(3, "每月")
};
cmbScheduleType.DisplayMember = "Value";
cmbScheduleType.ValueMember = "Key";
if (_config.Id > 0)
{
txtConfigName.Text = _config.ConfigName;
cmbTableName.SelectedValue = _config.TableName;
cmbMode.SelectedValue = _config.Mode;
numMaxRows.Value = _config.MaxRows ?? 0;
numKeepDays.Value = _config.KeepDays ?? 0;
chkEnabled.Checked = _config.Enabled;
cmbScheduleType.SelectedValue = _config.ScheduleType;
}
UpdateModeControls();
}
private void cmbMode_SelectedIndexChanged(object sender, EventArgs e)
{
UpdateModeControls();
}
private void UpdateModeControls()
{
bool isCountMode = cmbMode.SelectedValue?.ToString() == "1";
lblMaxRows.Visible = isCountMode;
numMaxRows.Visible = isCountMode;
lblKeepDays.Visible = !isCountMode;
numKeepDays.Visible = !isCountMode;
}
private async void btnSave_Click(object sender, EventArgs e)
{
if (!ValidateInput()) return;
try
{
_config.ConfigName = txtConfigName.Text;
_config.TableName = cmbTableName.SelectedValue.ToString();
_config.Mode = (int)cmbMode.SelectedValue;
_config.MaxRows = _config.Mode == 1 ? (int)numMaxRows.Value : (int?)null;
_config.KeepDays = _config.Mode == 2 ? (int)numKeepDays.Value : (int?)null;
_config.Enabled = chkEnabled.Checked;
_config.ScheduleType = (int)cmbScheduleType.SelectedValue;
_config.NextCleanTime = CalculateNextCleanTime();
await SaveConfigAsync();
DialogResult = DialogResult.OK;
Close();
}
catch (Exception ex)
{
MessageBox.Show($"保存失败: {ex.Message}");
}
}
private DateTime CalculateNextCleanTime()
{
var now = DateTime.Now;
switch (_config.ScheduleType)
{
case 1: // 每日
return now.AddDays(1).Date.AddHours(2); // 第二天凌晨2点
case 2: // 每周
return now.AddDays(7).Date.AddHours(3); // 下一周凌晨3点
case 3: // 每月
return new DateTime(now.Year, now.Month, 1).AddMonths(1).AddHours(4); // 下月1号4点
default:
return now.AddDays(1);
}
}
}
3. 核心服务类
public class CleanupService
{
private readonly string _connectionString;
public CleanupService()
{
_connectionString = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
}
public async Task<CleanupResult> ExecuteCleanupAsync(CleanupConfig config)
{
var result = new CleanupResult();
var startTime = DateTime.Now;
try
{
using (var connection = new SqlConnection(_connectionString))
{
await connection.OpenAsync();
// 开始事务
using (var transaction = connection.BeginTransaction())
{
try
{
string deleteSql = BuildDeleteSql(config);
var parameters = BuildParameters(config);
// 执行删除
var deletedRows = await connection.ExecuteAsync(
deleteSql, parameters, transaction);
// 记录历史
await LogCleanupHistory(connection, transaction, config,
deletedRows, startTime, DateTime.Now);
transaction.Commit();
result.Success = true;
result.DeletedRows = deletedRows;
}
catch
{
transaction.Rollback();
throw;
}
}
}
}
catch (Exception ex)
{
result.Success = false;
result.ErrorMessage = ex.Message;
throw;
}
return result;
}
private string BuildDeleteSql(CleanupConfig config)
{
switch (config.Mode)
{
case 1: // 按数量清理
return $@"
WITH CTE AS (
SELECT TOP (SELECT COUNT(*) FROM {config.TableName} - @MaxRows) *
FROM {config.TableName}
ORDER BY Id ASC
)
DELETE FROM CTE";
case 2: // 按日期清理
return $@"
DELETE FROM {config.TableName}
WHERE CreateTime < DATEADD(DAY, -@KeepDays, GETDATE())";
default:
throw new ArgumentException("无效的清理模式");
}
}
private object BuildParameters(CleanupConfig config)
{
return config.Mode == 1
? new { config.MaxRows }
: new { config.KeepDays };
}
}
4. 数据模型类
public class CleanupConfig
{
public int Id { get; set; }
public string ConfigName { get; set; }
public string TableName { get; set; }
public int Mode { get; set; }
public int? MaxRows { get; set; }
public int? KeepDays { get; set; }
public bool Enabled { get; set; }
public DateTime? LastCleanTime { get; set; }
public int ScheduleType { get; set; }
public DateTime? NextCleanTime { get; set; }
public DateTime CreatedTime { get; set; }
}
public class CleanupResult
{
public bool Success { get; set; }
public int DeletedRows { get; set; }
public string ErrorMessage { get; set; }
}
三、SQL Server 存储过程实现
-- 获取待执行的清理任务
CREATE PROCEDURE sp_GetDueCleanupConfigs
AS
BEGIN
SELECT * FROM CleanupConfig
WHERE Enabled = 1
AND (NextCleanTime IS NULL OR NextCleanTime <= GETDATE())
AND (LastCleanTime IS NULL OR DATEDIFF(HOUR, LastCleanTime, GETDATE()) >= 1)
END
-- 清理历史数据存储过程
CREATE PROCEDURE sp_CleanupByCount
@TableName NVARCHAR(100),
@MaxRows INT,
@DeletedRows INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @TotalRows INT
DECLARE @Sql NVARCHAR(MAX)
-- 获取总行数
SET @Sql = N'SELECT @TotalRows = COUNT(*) FROM ' + QUOTENAME(@TableName)
EXEC sp_executesql @Sql, N'@TotalRows INT OUTPUT', @TotalRows OUTPUT
-- 计算需要删除的行数
DECLARE @RowsToDelete INT = @TotalRows - @MaxRows
IF @RowsToDelete > 0
BEGIN
-- 动态SQL删除最旧的数据
SET @Sql = N'
WITH CTE AS (
SELECT TOP (@RowsToDelete) *
FROM ' + QUOTENAME(@TableName) + '
ORDER BY Id ASC
)
DELETE FROM CTE'
EXEC sp_executesql @Sql, N'@RowsToDelete INT', @RowsToDelete
SET @DeletedRows = @RowsToDelete
END
ELSE
BEGIN
SET @DeletedRows = 0
END
END
-- 按日期清理
CREATE PROCEDURE sp_CleanupByDate
@TableName NVARCHAR(100),
@KeepDays INT,
@DeletedRows INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE @Sql NVARCHAR(MAX)
DECLARE @CutoffDate DATETIME = DATEADD(DAY, -@KeepDays, GETDATE())
-- 构建删除SQL
SET @Sql = N'
DELETE FROM ' + QUOTENAME(@TableName) + '
WHERE CreateTime < @CutoffDate;
SELECT @DeletedRows = @@ROWCOUNT'
EXEC sp_executesql @Sql,
N'@CutoffDate DATETIME, @DeletedRows INT OUTPUT',
@CutoffDate, @DeletedRows OUTPUT
END
四、配置文件和依赖项
App.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add name="DefaultConnection"
connectionString="Server=.;Database=AutoCleanupDB;Integrated Security=True;"
providerName="System.Data.SqlClient" />
</connectionStrings>
<appSettings>
<add key="CleanupCheckInterval" value="60000" />
<add key="MaxRetryCount" value="3" />
<add key="EnableLogging" value="true" />
</appSettings>
</configuration>
五、部署和监控建议
1. 部署方案
- 将应用安装为Windows服务
- 使用任务计划程序定期运行
- 配置应用自动启动
2. 监控建议
- 实现邮件通知功能
- 添加清理失败告警
- 定期检查清理历史记录
- 监控数据库空间使用情况
3. 优化建议
- 为清理表添加合适的索引
- 分批次删除大表数据避免锁表
- 在低峰期执行清理操作
- 定期重建索引维护性能
六、安全注意事项
- 数据库连接字符串加密存储
- 实现操作日志审计功能
- 限制应用数据库账户权限
- 防止SQL注入攻击
- 配置访问控制列表