Windows窗体应用 + SQL Server 自动清理功能方案:按数量与按日期双模式

Windows窗体应用 + SQL Server 自动清理功能方案:按数量与按日期双模式

一、项目架构设计

1. 技术栈

  • 前端:Windows Forms (.NET Framework/Core)
  • 数据库:SQL Server
  • ORM:Entity Framework 或 Dapper(推荐Dapper性能更好)

2. 数据库表结构设计

sql 复制代码
-- 主数据表(示例)
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)

csharp 复制代码
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}");
            }
        }
    }
}

2. 配置管理窗体 (ConfigForm.cs)

csharp 复制代码
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. 核心服务类

csharp 复制代码
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. 数据模型类

csharp 复制代码
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 存储过程实现

sql 复制代码
-- 获取待执行的清理任务
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 复制代码
<?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. 优化建议

  • 为清理表添加合适的索引
  • 分批次删除大表数据避免锁表
  • 在低峰期执行清理操作
  • 定期重建索引维护性能

六、安全注意事项

  1. 数据库连接字符串加密存储
  2. 实现操作日志审计功能
  3. 限制应用数据库账户权限
  4. 防止SQL注入攻击
  5. 配置访问控制列表
相关推荐
阿蒙Amon2 小时前
C#每日面试题-索引器和迭代器的区别
开发语言·windows·c#
观远数据2 小时前
在线数据分析网站有哪些?7款自助平台选型指南
大数据·数据库·数据分析
不想写bug呀2 小时前
Redis集群介绍
数据库·redis·缓存
派大鑫wink2 小时前
【Day47】MyBatis 进阶:动态 SQL、关联查询(一对一 / 一对多)
数据库·sql·mybatis
biter00882 小时前
Ubuntu 上搜狗输入法突然“消失 / 只能英文”的排查与修复教程
linux·数据库·ubuntu
何以不说话2 小时前
MyCat实现 MySQL 读写分离
数据库·mysql
齐 飞2 小时前
SQL server使用MybatisPlus查询SQL加上WITH (NOLOCK)
数据库·mysql·sqlserver
_F_y2 小时前
MySQL表的增删查改
android·数据库·mysql
yangSnowy2 小时前
Redis数据类型
数据库·redis·wpf