//using Microsoft.Data.SqlClient; //using NPOI.HSSF.Record; //using SkiaSharp; //using System; //using System.Collections.Generic; //using System.Linq; //using System.Linq.Expressions; //using System.Text; //using System.Threading.Tasks; //using WIDESEAWCS_Core.BaseServices; //using WIDESEAWCS_ISystemRepository; //using WIDESEAWCS_ISystemServices; //using WIDESEAWCS_Model.Models; //namespace WIDESEAWCS_SystemServices //{ // public class Sys_LogService : ServiceBase, ISys_LogService // { // private static DateTime? _lastExecutionDate = null; // private readonly object _lock = new object(); // private const string DbName = "WIDESEA_HF"; // private const long MaxMdfSizeBytes = 8L * 1024 * 1024 * 1024; // 8GB // private const int TargetShrinkSizeMB = 4500; // 4.5GB // public Sys_LogService(ISys_LogRepository BaseDal) : base(BaseDal) // { // } // public void DeleteOldLogs() // { // // 使用锁确保线程安全 // lock (_lock) // { // var today = DateTime.Today; // // 检查今天是否已经执行过 // if (_lastExecutionDate.HasValue && _lastExecutionDate.Value.Date == today) // { // return; // } // try // { // // 计算3个月前的日期 // var threeMonthsAgo = DateTime.Now.AddMonths(-3); // // 计算3天前的日期 // var sevenDaysAgo = DateTime.Now.AddDays(-3); // // 特定URL // var specificUrl = "http://11.2.30.141:10870/interfaces/api/amr/robotQuery"; // // 构建删除条件:3个月前的日志 或 (3天前且URL为特定地址的日志) // var result = BaseDal.Db.Deleteable() // .Where(log => log.EndDate < threeMonthsAgo || // (log.EndDate < sevenDaysAgo && log.Url == specificUrl)) // .ExecuteCommand(); // // 更新最后执行日期为今天 // _lastExecutionDate = today; // } // catch (Exception ex) // { // Console.WriteLine($"执行日志清理时发生错误:{ex.Message}"); // // 发生错误时不更新最后执行日期,以便重试 // } // } // } // // 15天执行一次星期天2点 // public void CheckAndManageFileSize() // { // try // { // // --检查数据库文件空间使用情况 // //SELECT // // name AS[文件逻辑名], // // physical_name AS[物理路径], // // size/ 128.0 AS[当前大小MB], // // FILEPROPERTY(name, 'SpaceUsed') / 128.0 AS[已用空间MB], // // size / 128.0 - FILEPROPERTY(name, 'SpaceUsed') / 128.0 AS[可用空间MB], // // growth / 128 AS[增长量MB], // // CASE is_percent_growth // // WHEN 1 THEN CAST(growth AS VARCHAR) +'%' // // ELSE CAST(growth/ 128 AS VARCHAR) +'MB' // // END AS[增长方式] // //FROM sys.database_files; // using (var connection = new SqlConnection("Data Source=.;Initial Catalog=WIDESEA_HF;User ID=sa;Password=123456;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False")) // { // connection.Open(); // // 检查数据库文件大小 // var sizeCheckSql = @"SELECT name AS [文件逻辑名], size/128.0 AS [当前大小MB], FILEPROPERTY(name, 'SpaceUsed')/128.0 AS [已用空间MB] FROM sys.database_files WHERE name = @dbName"; // using (var command = new SqlCommand(sizeCheckSql, connection)) // { // command.Parameters.AddWithValue("@dbName", DbName); // using (var reader = command.ExecuteReader()) // { // if (reader.Read()) // { // double currentSizeMB = Convert.ToDouble(reader["当前大小MB"]); // double usedSpaceMB = Convert.ToDouble(reader["已用空间MB"]); // if (currentSizeMB > MaxMdfSizeBytes / (1024 * 1024)) // { // // 执行收缩数据库 // }else // { // // 整理碎片>30% // } // } // } // } // } // } // catch (Exception ex) // { // throw new Exception($"文件监控失败: {ex.Message}"); // } // } // } //} using Microsoft.Data.SqlClient; using NPOI.HSSF.Record; using SkiaSharp; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Text; using System.Threading.Tasks; using WIDESEAWCS_Core.BaseServices; using WIDESEAWCS_ISystemRepository; using WIDESEAWCS_ISystemServices; using WIDESEAWCS_Model.Models; namespace WIDESEAWCS_SystemServices { public class Sys_LogService : ServiceBase, ISys_LogService { private static DateTime? _lastExecutionDate = null; private static DateTime? _lastFileSizeCheckDate = null; private static DateTime? _lastBackupDate = null; private readonly object _lock = new object(); private readonly object _fileSizeLock = new object(); private readonly object _backupLock = new object(); private const string DbName = "WIDESEA_HF"; private const string ConnectionString = "Data Source=11.2.30.112;Initial Catalog=WIDESEA_HF;User ID=kuka;Password=kuka;Integrated Security=False;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False"; private const long MaxMdfSizeBytes = 8L * 1024 * 1024 * 1024; // 8GB private const int TargetShrinkSizeMB = 4500; // 4.5GB private const double FragmentationThreshold = 30.0; // 30%碎片率阈值 private const int MaintenanceIntervalDays = 15; // 15天执行一次 private const int BackupRetentionDays = 30; // 备份保留30天 private const string BackupBasePath = @"D:\DatabaseBackups"; // 备份文件存储目录 private const bool UseBackupCompression = true; // 是否使用备份压缩 private const bool VerifyBackup = true; // 是否验证备份 private const int MaxBackupRetryCount = 3; // 最大重试次数 private const int BackupTimeoutSeconds = 7200; // 备份超时时间(秒) public Sys_LogService(ISys_LogRepository BaseDal) : base(BaseDal) { // 确保备份目录存在 EnsureBackupDirectory(); } private void EnsureBackupDirectory() { try { if (!Directory.Exists(BackupBasePath)) { Directory.CreateDirectory(BackupBasePath); } } catch (Exception ex) { Console.WriteLine($"创建备份目录失败: {ex.Message}"); throw; } } public void DeleteOldLogs() { // 使用锁确保线程安全 lock (_lock) { var today = DateTime.Today; // 检查今天是否已经执行过 if (_lastExecutionDate.HasValue && _lastExecutionDate.Value.Date == today) { return; } try { // 计算3个月前的日期 var threeMonthsAgo = DateTime.Now.AddMonths(-3); // 计算3天前的日期 var threeDaysAgo = DateTime.Now.AddDays(-3); // 特定URL var specificUrl = "http://11.2.30.141:10870/interfaces/api/amr/robotQuery"; var anonUrl = "http://11.2.30.111/anon/api/kuka/drivestatus"; // 构建删除条件:3个月前的日志 或 (3天前且URL为特定地址的日志) var result = BaseDal.Db.Deleteable() .Where(log => log.EndDate < threeMonthsAgo || (log.EndDate < threeDaysAgo && (log.Url == specificUrl || log.Url == anonUrl))) .ExecuteCommand(); // 更新最后执行日期为今天 _lastExecutionDate = today; } catch (Exception ex) { Console.WriteLine($"执行日志清理时发生错误:{ex.Message}"); throw new Exception($"日志清理时发生错误: {ex.Message}", ex); // 发生错误时不更新最后执行日期,以便重试 } } } // 15天执行一次星期天2点 public void CheckAndManageFileSize() { // 检查是否符合执行条件:星期天、2点左右、距离上次执行至少15天 //if (!ShouldExecuteMaintenance()) //{ // return; //} try { using (var connection = new SqlConnection(ConnectionString)) { connection.Open(); // 1. 备份数据库 BackupDatabase(); // 2. 检查数据库文件大小 CheckDatabaseFileSize(connection); // 3. 检查并处理索引碎片 CheckAndHandleIndexFragmentation(connection); // 4. 更新统计信息 UpdateStatistics(connection); // 5. 清理旧备份 CleanupOldBackups(); // 更新最后检查日期 lock (_fileSizeLock) { _lastFileSizeCheckDate = DateTime.Now; } } } catch (Exception ex) { Console.WriteLine($"文件监控失败: {ex.Message}"); throw new Exception($"文件监控失败: {ex.Message}", ex); } } public void BackupDatabase() { lock (_backupLock) { try { var today = DateTime.Today; // 检查今天是否已经备份过 if (_lastBackupDate.HasValue && _lastBackupDate.Value.Date == today) { return; } var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss"); var backupFileName = $"{DbName}_Full_{timestamp}.bak"; var backupFilePath = Path.Combine(BackupBasePath, backupFileName); using (var connection = new SqlConnection(ConnectionString)) { connection.Open(); // 获取数据库文件信息,计算备份文件大小 var dbSizeQuery = @" SELECT SUM(size/128.0) AS TotalSizeMB, SUM(FILEPROPERTY(name, 'SpaceUsed')/128.0) AS UsedSizeMB, COUNT(*) AS FileCount FROM sys.database_files WHERE type IN (0, 1)"; // 数据文件和日志文件 double totalSizeMB = 0; double usedSizeMB = 0; int fileCount = 0; using (var cmd = new SqlCommand(dbSizeQuery, connection)) using (var reader = cmd.ExecuteReader()) { if (reader.Read()) { totalSizeMB = Convert.ToDouble(reader["TotalSizeMB"]); usedSizeMB = Convert.ToDouble(reader["UsedSizeMB"]); fileCount = Convert.ToInt32(reader["FileCount"]); } } // 检查磁盘空间是否足够 if (!CheckDiskSpace(backupFilePath, totalSizeMB)) { throw new Exception("磁盘空间不足,无法执行备份"); } // 构建备份SQL语句 var backupSql = BuildBackupSql(backupFilePath); // 执行备份(带重试机制) ExecuteBackupWithRetry(connection, backupSql, backupFilePath); // 验证备份文件 if (VerifyBackup) { VerifyBackupFile(connection, backupFilePath); } // 更新最后备份日期 _lastBackupDate = DateTime.Now; } } catch (Exception ex) { Console.WriteLine($"数据库备份失败: {ex.Message}"); throw new Exception($"数据库备份失败: {ex.Message}", ex); } } } private bool CheckDiskSpace(string backupFilePath, double estimatedBackupSizeMB) { try { var driveInfo = new DriveInfo(Path.GetPathRoot(backupFilePath)); var freeSpaceMB = driveInfo.AvailableFreeSpace / (1024 * 1024); // 需要至少1.5倍的空间来确保备份顺利进行 var requiredSpaceMB = estimatedBackupSizeMB * 1.5; if (freeSpaceMB < requiredSpaceMB) { return false; } return true; } catch (Exception ex) { Console.WriteLine($"检查磁盘空间时出错: {ex.Message}"); return false; } } private string BuildBackupSql(string backupFilePath) { var compressionOption = UseBackupCompression ? "COMPRESSION" : "NO_COMPRESSION"; var verifyOption = VerifyBackup ? ", CHECKSUM" : ""; return $@" BACKUP DATABASE [{DbName}] TO DISK = '{backupFilePath.Replace("'", "''")}' WITH {compressionOption} {verifyOption} , STATS = 5 , DESCRIPTION = 'Full database backup created by automated maintenance on {DateTime.Now:yyyy-MM-dd HH:mm:ss}' , MAXTRANSFERSIZE = 4194304 , BLOCKSIZE = 65536 , BUFFERCOUNT = 10"; } private void ExecuteBackupWithRetry(SqlConnection connection, string backupSql, string backupFilePath) { int retryCount = 0; while (retryCount < MaxBackupRetryCount) { try { using (var cmd = new SqlCommand(backupSql, connection)) { cmd.CommandTimeout = BackupTimeoutSeconds; var rowsAffected = cmd.ExecuteNonQuery(); return; // 成功则退出 } } catch (Exception ex) { retryCount++; if (retryCount >= MaxBackupRetryCount) { // 删除可能损坏的备份文件 try { if (File.Exists(backupFilePath)) { File.Delete(backupFilePath); } } catch { } throw new Exception($"备份失败,已达到最大重试次数({MaxBackupRetryCount}次): {ex.Message}", ex); } Console.WriteLine($"备份失败,{MaxBackupRetryCount - retryCount}次重试机会: {ex.Message}"); Thread.Sleep(5000); // 等待5秒后重试 } } } private void VerifyBackupFile(SqlConnection connection, string backupFilePath) { try { var verifySql = $@" RESTORE VERIFYONLY FROM DISK = '{backupFilePath.Replace("'", "''")}' WITH CHECKSUM"; using (var cmd = new SqlCommand(verifySql, connection)) { cmd.CommandTimeout = 300; // 5分钟超时 cmd.ExecuteNonQuery(); } } catch (Exception ex) { // 验证失败时删除备份文件 try { if (File.Exists(backupFilePath)) { File.Delete(backupFilePath); } } catch { } throw new Exception($"备份文件验证失败: {ex.Message}", ex); } } private void CleanupOldBackups() { try { if (!Directory.Exists(BackupBasePath)) { Console.WriteLine("备份目录不存在,跳过清理"); return; } var cutoffDate = DateTime.Now.AddDays(-BackupRetentionDays); var backupFiles = Directory.GetFiles(BackupBasePath, $"{DbName}_Full_*.bak"); int deletedCount = 0; foreach (var file in backupFiles) { try { var fileInfo = new FileInfo(file); if (fileInfo.CreationTime < cutoffDate) { fileInfo.Delete(); deletedCount++; } } catch (Exception ex) { Console.WriteLine($"删除备份文件 {file} 时出错: {ex.Message}"); // 继续处理其他文件 } } } catch (Exception ex) { Console.WriteLine($"清理旧备份时出错: {ex.Message}"); // 不抛出异常,避免影响主流程 } } private bool ShouldExecuteMaintenance() { lock (_fileSizeLock) { var now = DateTime.Now; // 检查今天是否是星期天 if (now.DayOfWeek != DayOfWeek.Sunday) { return false; } // 检查时间是否是1点左右(允许1:00-1:59) if (now.Hour != 1) { return false; } // 检查是否已经执行过 if (_lastFileSizeCheckDate.HasValue) { // 检查距离上次执行是否满足15天间隔 var daysSinceLastCheck = (now - _lastFileSizeCheckDate.Value).TotalDays; if (daysSinceLastCheck < MaintenanceIntervalDays) { return false; } } return true; } } private void CheckDatabaseFileSize(SqlConnection connection) { try { var sizeCheckSql = @" SELECT name AS [文件逻辑名], physical_name AS [物理路径], size/128.0 AS [当前大小MB], FILEPROPERTY(name, 'SpaceUsed')/128.0 AS [已用空间MB], size/128.0 - FILEPROPERTY(name, 'SpaceUsed')/128.0 AS [可用空间MB], max_size/128.0 AS [最大大小MB], growth/128.0 AS [增长量MB], CASE is_percent_growth WHEN 1 THEN '百分比增长' ELSE '固定大小增长' END AS [增长方式] FROM sys.database_files WHERE type = 0"; // 0表示数据文件,1表示日志文件 using (var command = new SqlCommand(sizeCheckSql, connection)) { using (var reader = command.ExecuteReader()) { while (reader.Read()) { string logicalName = reader["文件逻辑名"].ToString(); double currentSizeMB = Convert.ToDouble(reader["当前大小MB"]); double usedSpaceMB = Convert.ToDouble(reader["已用空间MB"]); double freeSpaceMB = Convert.ToDouble(reader["可用空间MB"]); // 检查是否需要收缩 if (currentSizeMB * 1024 * 1024 > MaxMdfSizeBytes) { Console.WriteLine("数据库文件超过8GB,开始收缩..."); ShrinkDatabaseFile(connection, logicalName); } } } } } catch (Exception ex) { Console.WriteLine($"检查数据库文件大小时出错: {ex.Message}"); throw; } } private void ShrinkDatabaseFile(SqlConnection connection, string logicalName) { try { // 收缩数据库文件 string shrinkSql = $"DBCC SHRINKFILE (N'{logicalName}', {TargetShrinkSizeMB})"; using (var command = new SqlCommand(shrinkSql, connection)) { int rowsAffected = command.ExecuteNonQuery(); Console.WriteLine($"数据库文件收缩完成,目标大小: {TargetShrinkSizeMB} MB"); } // 重新组织索引以避免收缩导致的碎片 ReorganizeAllIndexes(connection); } catch (Exception ex) { Console.WriteLine($"收缩数据库文件时出错: {ex.Message}"); throw; } } private void CheckAndHandleIndexFragmentation(SqlConnection connection) { try { // 查询所有表的索引碎片率 string fragmentationSql = @" SELECT OBJECT_NAME(ips.object_id) AS TableName, i.name AS IndexName, ips.index_type_desc AS IndexType, ips.avg_fragmentation_in_percent AS FragmentationPercent, ips.page_count AS PageCount FROM sys.dm_db_index_physical_stats(DB_ID(), NULL, NULL, NULL, 'LIMITED') ips INNER JOIN sys.indexes i ON ips.object_id = i.object_id AND ips.index_id = i.index_id WHERE ips.avg_fragmentation_in_percent > 0 AND ips.index_id > 0 -- 排除堆 AND ips.page_count > 100 -- 只处理超过100页的索引 ORDER BY ips.avg_fragmentation_in_percent DESC"; using (var command = new SqlCommand(fragmentationSql, connection)) using (var reader = command.ExecuteReader()) { var indexesToReorganize = new List<(string TableName, string IndexName, double Fragmentation)>(); while (reader.Read()) { string tableName = reader["TableName"].ToString(); string indexName = reader["IndexName"].ToString(); double fragmentation = Convert.ToDouble(reader["FragmentationPercent"]); long pageCount = Convert.ToInt64(reader["PageCount"]); Console.WriteLine($"表: {tableName}, 索引: {indexName}, 碎片率: {fragmentation:F2}%, 页数: {pageCount}"); if (fragmentation > FragmentationThreshold) { indexesToReorganize.Add((tableName, indexName, fragmentation)); } } reader.Close(); // 关闭reader以执行后续命令 // 处理需要重新组织的索引 if (indexesToReorganize.Any()) { foreach (var index in indexesToReorganize) { ReorganizeIndex(connection, index.TableName, index.IndexName); } } } } catch (Exception ex) { Console.WriteLine($"检查索引碎片时出错: {ex.Message}"); throw; } } private void ReorganizeIndex(SqlConnection connection, string tableName, string indexName) { try { string reorganizeSql = $"ALTER INDEX [{indexName}] ON [{tableName}] REORGANIZE"; using (var command = new SqlCommand(reorganizeSql, connection)) { command.CommandTimeout = 600; // 设置10分钟超时 command.ExecuteNonQuery(); } } catch (Exception ex) { Console.WriteLine($"重新组织索引 {tableName}.{indexName} 时出错: {ex.Message}"); // 继续处理其他索引 } } private void ReorganizeAllIndexes(SqlConnection connection) { try { string reorganizeAllSql = @" DECLARE @TableName NVARCHAR(128) DECLARE @IndexName NVARCHAR(128) DECLARE @Sql NVARCHAR(MAX) DECLARE IndexCursor CURSOR FOR SELECT OBJECT_NAME(i.object_id) AS TableName, i.name AS IndexName FROM sys.indexes i WHERE i.index_id > 0 -- 排除堆 AND OBJECT_NAME(i.object_id) IS NOT NULL OPEN IndexCursor FETCH NEXT FROM IndexCursor INTO @TableName, @IndexName WHILE @@FETCH_STATUS = 0 BEGIN SET @Sql = 'ALTER INDEX [' + @IndexName + '] ON [' + @TableName + '] REORGANIZE' BEGIN TRY EXEC sp_executesql @Sql PRINT '已重新组织索引: ' + @TableName + '.' + @IndexName END TRY BEGIN CATCH PRINT '重新组织索引 ' + @TableName + '.' + @IndexName + ' 时出错: ' + ERROR_MESSAGE() END CATCH FETCH NEXT FROM IndexCursor INTO @TableName, @IndexName END CLOSE IndexCursor DEALLOCATE IndexCursor"; using (var command = new SqlCommand(reorganizeAllSql, connection)) { command.CommandTimeout = 1800; // 设置30分钟超时 command.ExecuteNonQuery(); } } catch (Exception ex) { Console.WriteLine($"重新组织所有索引时出错: {ex.Message}"); throw; } } private void UpdateStatistics(SqlConnection connection) { try { // 更新所有表的统计信息 string updateStatsSql = "EXEC sp_updatestats"; using (var command = new SqlCommand(updateStatsSql, connection)) { command.CommandTimeout = 600; // 设置10分钟超时 command.ExecuteNonQuery(); } } catch (Exception ex) { Console.WriteLine($"更新统计信息时出错: {ex.Message}"); throw; } } // 提供一个方法供任务调度器调用 public void ScheduleDatabaseMaintenance() { try { // 1. 删除旧日志 DeleteOldLogs(); // 检查是否符合执行条件 if (!ShouldExecuteMaintenance()) { return; } // 2. 检查和管理文件大小 CheckAndManageFileSize(); } catch (Exception ex) { Console.WriteLine($"执行数据库维护任务时出错: {ex.Message}"); throw; } } } }