using Microsoft.Extensions.Hosting; using SqlSugar; using System; using System.IO; using System.Threading; using System.Threading.Tasks; using WIDESEAWCS_Core.DB; using WIDESEAWCS_Core.Seed; using WIDESEAWCS_Model.Models; namespace WIDESEAWCS_Core.LogHelper { /// /// 日志数据清理服务 /// public class LogCleanupService : IHostedService, IDisposable { private Timer _cleanupTimer; private bool _isDisposed = false; private readonly object _lockObject = new object(); /// /// 数据保留天数(默认90天) /// public int RetentionDays { get; set; } = 90; /// /// 清理间隔(小时,默认24小时) /// public int CleanupIntervalHours { get; set; } = 24; /// /// 清理执行时间(小时,0-23,默认凌晨2点) /// public int CleanupHour { get; set; } = 2; /// /// 是否启用归档 /// public bool EnableArchive { get; set; } = false; /// /// 是否启用自动清理 /// public bool EnableAutoCleanup { get; set; } = true; /// /// 日志文件保留天数 /// public int LogRetentionDays { get; set; } = 30; /// /// 日志文件路径 /// private string LogFilePath => Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs", "LogCleanup", $"Cleanup_{DateTime.Now:yyyyMMdd}.log"); public Task StartAsync(CancellationToken cancellationToken) { if (EnableAutoCleanup) { // 确保日志目录存在 EnsureLogDirectoryExists(); // 计算到下一个清理时间的间隔 var now = DateTime.Now; var nextCleanupTime = DateTime.Today.AddHours(CleanupHour); if (now > nextCleanupTime) { nextCleanupTime = nextCleanupTime.AddDays(1); } var timeToFirstCleanup = nextCleanupTime - now; // 设置定时器,在指定时间执行清理 _cleanupTimer = new Timer(CleanupCallback, null, timeToFirstCleanup, TimeSpan.FromHours(CleanupIntervalHours)); WriteLog($"日志清理服务已启动,首次清理时间: {nextCleanupTime:yyyy-MM-dd HH:mm:ss}"); } return Task.CompletedTask; } /// /// 确保日志目录存在 /// private void EnsureLogDirectoryExists() { try { string logDirectory = Path.GetDirectoryName(LogFilePath); if (!Directory.Exists(logDirectory)) { Directory.CreateDirectory(logDirectory); } } catch (Exception ex) { Console.WriteLine($"创建日志目录失败: {ex.Message}"); } } /// /// 写入日志到文件 /// private void WriteLog(string message, bool isError = false) { try { lock (_lockObject) { EnsureLogDirectoryExists(); string logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {(isError ? "[ERROR]" : "[INFO]")} {message}"; // 写入文件 File.AppendAllText(LogFilePath, logMessage + Environment.NewLine); // 同时输出到控制台(可选,便于调试) Console.WriteLine(logMessage); // 日志文件过大时自动清理(保留最近30天的日志文件) AutoCleanupLogFiles(); } } catch (Exception ex) { // 如果文件日志失败,至少输出到控制台 Console.WriteLine($"写入日志文件失败: {ex.Message}"); Console.WriteLine($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}"); } } /// /// 自动清理过期的日志文件(保留30天) /// private void AutoCleanupLogFiles() { try { string logDirectory = Path.GetDirectoryName(LogFilePath); if (Directory.Exists(logDirectory)) { var files = Directory.GetFiles(logDirectory, "Cleanup_*.log"); DateTime cutoffDate = DateTime.Now.AddDays(-30); foreach (var file in files) { try { // 从文件名中提取日期 string fileName = Path.GetFileNameWithoutExtension(file); if (fileName.StartsWith("Cleanup_")) { string dateStr = fileName.Replace("Cleanup_", ""); if (DateTime.TryParseExact(dateStr, "yyyyMMdd", null, System.Globalization.DateTimeStyles.None, out DateTime fileDate)) { if (fileDate < cutoffDate) { File.Delete(file); WriteLog($"删除过期日志文件: {Path.GetFileName(file)}"); } } } } catch (Exception ex) { Console.WriteLine($"删除过期日志文件失败 {file}: {ex.Message}"); } } } } catch (Exception ex) { Console.WriteLine($"清理日志文件失败: {ex.Message}"); } } private void CleanupCallback(object state) { Task.Run(() => CleanupAsync()); } /// /// 执行清理 /// public async Task CleanupAsync() { try { WriteLog($"开始清理 {RetentionDays} 天前的日志数据"); using (SqlSugarClient sugarClient = new SqlSugarClient(new ConnectionConfig() { ConnectionString = DBContext.GetMainConnectionDb().Connection, IsAutoCloseConnection = true, DbType = DBContext.DbType, })) { DateTime cutoffDate = DateTime.Now.AddDays(-RetentionDays); WriteLog($"截止日期: {cutoffDate:yyyy-MM-dd HH:mm:ss}"); if (EnableArchive) { // 先归档再删除 await ArchiveDataAsync(sugarClient, cutoffDate); } // 删除历史数据 int deletedCount = await sugarClient.Deleteable() .Where(x => x.CreateDate < cutoffDate) .ExecuteCommandAsync(); WriteLog($"清理完成,共删除 {deletedCount} 条记录"); // 可选:记录清理日志到数据库 await LogCleanupResult(sugarClient, deletedCount, cutoffDate); } } catch (Exception ex) { WriteLog($"清理失败: {ex.Message}", true); WriteLog($"详细错误: {ex.ToString()}", true); } } private async Task ArchiveDataAsync(SqlSugarClient sugarClient, DateTime cutoffDate) { try { string archiveTableName = $"Dt_InterfaceLog_Archive_{DateTime.Now:yyyyMM}"; WriteLog($"开始归档数据到表: {archiveTableName}"); // 创建归档表 string createTableSql = $@" IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='{archiveTableName}' AND xtype='U') BEGIN SELECT * INTO {archiveTableName} FROM Dt_InterfaceLog WHERE 1=0 ALTER TABLE {archiveTableName} ADD ArchiveDate datetime DEFAULT GETDATE() END"; await sugarClient.Ado.ExecuteCommandAsync(createTableSql); // 归档数据 string archiveSql = $@" INSERT INTO {archiveTableName} (Id, ApiCode, ApiName, ApiAddress, PushFrequency, PushState, Requestor, RequestParameters, RequestParametersHash, Recipient, ResponseParameters, ResponseParametersHash, Remark, CreateTime, ArchiveDate) SELECT Id, ApiCode, ApiName, ApiAddress, PushFrequency, PushState, Requestor, RequestParameters, RequestParametersHash, Recipient, ResponseParameters, ResponseParametersHash, Remark, CreateTime, GETDATE() FROM Dt_InterfaceLog WHERE CreateTime < @CutoffDate"; int archivedCount = await sugarClient.Ado.ExecuteCommandAsync(archiveSql, new { CutoffDate = cutoffDate }); WriteLog($"归档完成,共归档 {archivedCount} 条记录到 {archiveTableName}"); } catch (Exception ex) { WriteLog($"归档失败: {ex.Message}", true); } } private async Task LogCleanupResult(SqlSugarClient sugarClient, int deletedCount, DateTime cutoffDate) { try { // 创建清理日志表(如果不存在) string createLogTableSql = @" IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='LogCleanupHistory' AND xtype='U') BEGIN CREATE TABLE LogCleanupHistory ( Id INT IDENTITY(1,1) PRIMARY KEY, TableName NVARCHAR(100), DeletedCount INT, CutoffDate DATETIME, CleanupTime DATETIME DEFAULT GETDATE() ) END"; await sugarClient.Ado.ExecuteCommandAsync(createLogTableSql); // 记录清理日志 string insertLogSql = @" INSERT INTO LogCleanupHistory (TableName, DeletedCount, CutoffDate, CleanupTime) VALUES ('Dt_InterfaceLog', @DeletedCount, @CutoffDate, GETDATE())"; await sugarClient.Ado.ExecuteCommandAsync(insertLogSql, new { DeletedCount = deletedCount, CutoffDate = cutoffDate }); WriteLog($"清理记录已写入数据库日志表"); } catch (Exception ex) { WriteLog($"写入数据库日志失败: {ex.Message}", true); } } /// /// 手动执行清理 /// public async Task ManualCleanup(int days = 90) { try { WriteLog($"手动清理开始,删除超过 {days} 天的数据"); using (SqlSugarClient sugarClient = new SqlSugarClient(new ConnectionConfig() { ConnectionString = DBContext.GetMainConnectionDb().Connection, IsAutoCloseConnection = true, DbType = DBContext.DbType, })) { DateTime cutoffDate = DateTime.Now.AddDays(-days); int deletedCount = await sugarClient.Deleteable() .Where(x => x.CreateDate < cutoffDate) .ExecuteCommandAsync(); WriteLog($"手动清理完成,删除 {deletedCount} 条记录"); return deletedCount; } } catch (Exception ex) { WriteLog($"手动清理失败: {ex.Message}", true); return 0; } } /// /// 获取清理统计信息 /// public async Task GetStatistics() { try { using (SqlSugarClient sugarClient = new SqlSugarClient(new ConnectionConfig() { ConnectionString = DBContext.GetMainConnectionDb().Connection, IsAutoCloseConnection = true, DbType = DBContext.DbType, })) { var totalCount = await sugarClient.Queryable().CountAsync(); var oldestLog = await sugarClient.Queryable() .OrderBy(x => x.CreateDate) .FirstAsync(); var newestLog = await sugarClient.Queryable() .OrderBy(x => x.CreateDate, OrderByType.Desc) .FirstAsync(); return new CleanupStatistics { TotalCount = totalCount, OldestLogTime = oldestLog?.CreateDate, NewestLogTime = newestLog?.CreateDate, RetentionDays = RetentionDays, LastCleanupTime = GetLastCleanupTime(), LogFilePath = LogFilePath }; } } catch (Exception ex) { WriteLog($"获取统计信息失败: {ex.Message}", true); return null; } } private DateTime? GetLastCleanupTime() { try { string logDirectory = Path.GetDirectoryName(LogFilePath); if (Directory.Exists(logDirectory)) { var latestLogFile = Directory.GetFiles(logDirectory, "Cleanup_*.log") .OrderByDescending(f => f) .FirstOrDefault(); if (latestLogFile != null && File.Exists(latestLogFile)) { return File.GetLastWriteTime(latestLogFile); } } } catch (Exception ex) { Console.WriteLine($"获取上次清理时间失败: {ex.Message}"); } return null; } public Task StopAsync(CancellationToken cancellationToken) { _cleanupTimer?.Dispose(); WriteLog("日志清理服务已停止"); return Task.CompletedTask; } public void Dispose() { if (!_isDisposed) { _cleanupTimer?.Dispose(); _isDisposed = true; } } } /// /// 清理统计信息 /// public class CleanupStatistics { public int TotalCount { get; set; } public DateTime? OldestLogTime { get; set; } public DateTime? NewestLogTime { get; set; } public int RetentionDays { get; set; } public DateTime? LastCleanupTime { get; set; } public string LogFilePath { get; set; } } }