BackupWorker Job

This commit is contained in:
Sedat Öztürk 2026-04-30 01:40:32 +03:00
parent b9cc68ff41
commit 2037cfd6d7
11 changed files with 123 additions and 7 deletions

View file

@ -1066,6 +1066,14 @@
"workerType": "NotificationWorker", "workerType": "NotificationWorker",
"isActive": true, "isActive": true,
"dataSourceCode": "Default" "dataSourceCode": "Default"
},
{
"name": "Backup Databases",
"cron": "0 1 * * *",
"workerType": "BackupWorker",
"isActive": true,
"dataSourceCode": "Default",
"beforeSp": "Adm_T_DatabaseBackup"
} }
], ],
"ContactTitles": [ "ContactTitles": [

View file

@ -4749,9 +4749,9 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
new EditingFormItemDto { Order = 1, DataField = "Name", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxTextBox }, new EditingFormItemDto { Order = 1, DataField = "Name", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxTextBox },
new EditingFormItemDto { Order = 2, DataField = "Cron", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxTextBox }, new EditingFormItemDto { Order = 2, DataField = "Cron", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxTextBox },
new EditingFormItemDto { Order = 3, DataField = "WorkerType", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton }, new EditingFormItemDto { Order = 3, DataField = "WorkerType", ColSpan = 1, IsRequired = true, EditorType2=EditorTypes.dxSelectBox, EditorOptions=EditorOptionValues.ShowClearButton },
new EditingFormItemDto { Order = 4, DataField = "BeforeSp", ColSpan = 1, EditorType2=EditorTypes.dxTextBox }, new EditingFormItemDto { Order = 4, DataField = "IsActive", ColSpan = 1, EditorType2=EditorTypes.dxCheckBox },
new EditingFormItemDto { Order = 5, DataField = "AfterSp", ColSpan = 1, EditorType2=EditorTypes.dxTextBox }, new EditingFormItemDto { Order = 5, DataField = "BeforeSp", ColSpan = 1, EditorType2=EditorTypes.dxTextBox },
new EditingFormItemDto { Order = 6, DataField = "IsActive", ColSpan = 1, EditorType2=EditorTypes.dxCheckBox }, new EditingFormItemDto { Order = 6, DataField = "AfterSp", ColSpan = 1, EditorType2=EditorTypes.dxTextBox },
] ]
}, },
new() { Order=2,ColCount=2,ColSpan=1,ItemType="group",Items= [ new() { Order=2,ColCount=2,ColSpan=1,ItemType="group",Items= [
@ -4841,6 +4841,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
new() { Key=2,Name="SqlWorker" }, new() { Key=2,Name="SqlWorker" },
new() { Key=3,Name="NotificationWorker" }, new() { Key=3,Name="NotificationWorker" },
new() { Key=4,Name="SessionCleanupWorker" }, new() { Key=4,Name="SessionCleanupWorker" },
new() { Key=5,Name="BackupWorker" },
}), }),
}), }),
ValidationRuleJson = DefaultValidationRuleRequiredJson, ValidationRuleJson = DefaultValidationRuleRequiredJson,

View file

@ -6,5 +6,6 @@ public enum WorkerTypeEnum
SqlWorker = 2, SqlWorker = 2,
NotificationWorker = 3, NotificationWorker = 3,
SessionCleanupWorker = 4, SessionCleanupWorker = 4,
BackupWorker = 5,
} }

View file

@ -18,6 +18,8 @@ using Volo.Abp.ExceptionHandling;
using Volo.Abp.ObjectMapping; using Volo.Abp.ObjectMapping;
using Volo.Abp.Settings; using Volo.Abp.Settings;
using Volo.Abp.Users; using Volo.Abp.Users;
using Microsoft.Extensions.Configuration;
using System.IO;
namespace Sozsoft.Platform.BackgroundWorkers; namespace Sozsoft.Platform.BackgroundWorkers;
@ -28,6 +30,7 @@ public interface IPlatformBackgroundWorker : ITransientDependency
public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgroundWorker public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgroundWorker
{ {
private readonly IConfiguration Configuration;
protected IAbpDistributedLock DistributedLock { get; } protected IAbpDistributedLock DistributedLock { get; }
protected ISpRepository SpRepository { get; } protected ISpRepository SpRepository { get; }
public IDynamicDataManager DynamicDataManager { get; } public IDynamicDataManager DynamicDataManager { get; }
@ -41,11 +44,13 @@ public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgrou
ICurrentUser currentUser, ICurrentUser currentUser,
IObjectMapper objectMapper, IObjectMapper objectMapper,
IDynamicDataManager dynamicDataManager, IDynamicDataManager dynamicDataManager,
IConfiguration configuration,
IRepository<Entities.BackgroundWorker> repository) : base(settingProvider, localizer, currentUser, objectMapper) IRepository<Entities.BackgroundWorker> repository) : base(settingProvider, localizer, currentUser, objectMapper)
{ {
DistributedLock = distributedLock; DistributedLock = distributedLock;
SpRepository = spRepository; SpRepository = spRepository;
DynamicDataManager = dynamicDataManager; DynamicDataManager = dynamicDataManager;
Configuration = configuration;
Repository = repository; Repository = repository;
} }
@ -55,6 +60,8 @@ public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgrou
var LogPrefix = $"{Clock.Now:s}_{Worker.Name}: {{0}}"; var LogPrefix = $"{Clock.Now:s}_{Worker.Name}: {{0}}";
var DistributedLockName = Worker.Name; var DistributedLockName = Worker.Name;
var backupPath = "";
var backupDeleteAfterDays = Configuration.GetValue<int>("App:BackupDeleteAfterDays", 3);
//using (var scope = ServiceScopeFactory.CreateScope()) //using (var scope = ServiceScopeFactory.CreateScope())
@ -70,6 +77,16 @@ public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgrou
try try
{ {
// Backup Worker için yedeklerin saklanacağı klasörün oluşturulması
if (Worker.WorkerType == WorkerTypeEnum.BackupWorker)
{
backupPath = Path.Combine(Configuration["App:CdnPath"], "host", "backup");
if (!Directory.Exists(backupPath))
{
Directory.CreateDirectory(backupPath);
}
}
// Call BeforeSp // Call BeforeSp
if (!Worker.BeforeSp.IsNullOrWhiteSpace()) if (!Worker.BeforeSp.IsNullOrWhiteSpace())
{ {
@ -87,7 +104,7 @@ public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgrou
worker.Options = JsonSerializer.Deserialize<MailQueueWorkerOptions>(Worker.Options); worker.Options = JsonSerializer.Deserialize<MailQueueWorkerOptions>(Worker.Options);
await worker.StartAsync(cancellationToken); await worker.StartAsync(cancellationToken);
} }
else if (Worker.WorkerType == WorkerTypeEnum.SqlWorker) else if (Worker.WorkerType == WorkerTypeEnum.SqlWorker || Worker.WorkerType == WorkerTypeEnum.BackupWorker)
{ {
await LazyServiceProvider.GetRequiredService<SqlWorker>().StartAsync(cancellationToken); await LazyServiceProvider.GetRequiredService<SqlWorker>().StartAsync(cancellationToken);
} }
@ -107,6 +124,20 @@ public class PlatformBackgroundWorker : PlatformDomainService, IPlatformBackgrou
var result = await SpRepository.CallSpAsync(Worker.AfterSp, Worker.DataSourceCode, null); var result = await SpRepository.CallSpAsync(Worker.AfterSp, Worker.DataSourceCode, null);
Logger.LogInformation(LogPrefix, $"AfterSp çalıştırıldı. Sonuç: {result}"); Logger.LogInformation(LogPrefix, $"AfterSp çalıştırıldı. Sonuç: {result}");
} }
// Backup Worker için yedekleme işlemi ve eski yedeklerin silinmesi
if (Worker.WorkerType == WorkerTypeEnum.BackupWorker)
{
var files = Directory.GetFiles(backupPath, "*.bak");
foreach (var file in files)
{
if (File.GetLastWriteTime(file) < DateTime.Now.AddDays(backupDeleteAfterDays))
{
File.Delete(file);
}
}
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -30,3 +30,23 @@ BEGIN
ALTER TABLE [dbo].[Adm_T_Behavior] ADD DEFAULT (CONVERT([bit],(0))) FOR [IsDeleted] ALTER TABLE [dbo].[Adm_T_Behavior] ADD DEFAULT (CONVERT([bit],(0))) FOR [IsDeleted]
END END
GO GO
CREATE OR ALTER PROCEDURE [dbo].[Adm_T_DatabaseBackup]
AS
BEGIN
SET NOCOUNT ON;
DECLARE @SQL NVARCHAR(MAX) = N'';
SELECT @SQL = @SQL + N'
BACKUP DATABASE ' + QUOTENAME(name) + N'
TO DISK = N''/var/opt/mssql/backup/'
+ name + N'_' + CONVERT(VARCHAR(8), GETDATE(), 112) + N'.bak''
WITH INIT;'
FROM sys.databases
WHERE name NOT IN ('master','model','msdb','tempdb')
AND state_desc = 'ONLINE';
EXEC sp_executesql @SQL;
END
GO

View file

@ -55,15 +55,67 @@ public class SqlTablesSeeder : IDataSeedContributor, ITransientDependency
try try
{ {
var (action, objectName, objectType) = ExtractSqlInfo(sql);
if (!string.IsNullOrEmpty(objectName))
{
_logger.LogInformation(
"Executing SQL: {Action} {ObjectType} {ObjectName}",
action,
objectType,
objectName);
}
else
{
_logger.LogInformation("Executing SQL batch (object not detected)");
}
await dbContext.Database.ExecuteSqlRawAsync(sql); await dbContext.Database.ExecuteSqlRawAsync(sql);
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogWarning("SQL batch skipped (may already exist): {Message}", ex.Message); _logger.LogError(ex, "SQL batch failed: {Sql}", sql);
throw;
} }
} }
_logger.LogInformation("Database script seeding completed successfully."); _logger.LogInformation("Database script seeding completed successfully.");
} }
private (string Action, string? ObjectName, string? ObjectType) ExtractSqlInfo(string sql)
{
var patterns = new[]
{
(@"CREATE\s+TABLE\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "TABLE"),
(@"ALTER\s+TABLE\s+(\[?\w+\]?\.?\[?\w+\]?)", "ALTER", "TABLE"),
(@"CREATE\s+VIEW\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "VIEW"),
(@"CREATE\s+PROCEDURE\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "PROCEDURE"),
(@"ALTER\s+PROCEDURE\s+(\[?\w+\]?\.?\[?\w+\]?)", "ALTER", "PROCEDURE"),
(@"CREATE\s+OR\s+ALTER\s+PROCEDURE\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE/ALTER", "PROCEDURE"),
(@"CREATE\s+FUNCTION\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "FUNCTION"),
(@"CREATE\s+INDEX\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "INDEX"),
(@"CREATE\s+TRIGGER\s+(\[?\w+\]?\.?\[?\w+\]?)", "CREATE", "TRIGGER"),
(@"DROP\s+TABLE\s+(\[?\w+\]?\.?\[?\w+\]?)", "DROP", "TABLE"),
(@"DROP\s+VIEW\s+(\[?\w+\]?\.?\[?\w+\]?)", "DROP", "VIEW"),
(@"DROP\s+PROCEDURE\s+(\[?\w+\]?\.?\[?\w+\]?)", "DROP", "PROCEDURE")
};
foreach (var (pattern, action, type) in patterns)
{
var match = Regex.Match(sql, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
return (action, match.Groups[1].Value, type);
}
}
return ("UNKNOWN", null, null);
}
} }

View file

@ -21,6 +21,7 @@
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<None Remove="Seeds\SqlTables.sql" />
<Content Include="Seeds\SqlTables.sql"> <Content Include="Seeds\SqlTables.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>

View file

@ -6,7 +6,8 @@
"RedirectAllowedUrls": "https://sozsoft.com,https://sozsoft.com/authentication/callback", "RedirectAllowedUrls": "https://sozsoft.com,https://sozsoft.com/authentication/callback",
"AttachmentsPath": "/etc/api/mail-queue/attachments", "AttachmentsPath": "/etc/api/mail-queue/attachments",
"CdnPath": "/etc/api/cdn", "CdnPath": "/etc/api/cdn",
"BaseDomain": "sozsoft.com" "BaseDomain": "sozsoft.com",
"BackupDeleteAfterDays": 3
}, },
"ConnectionStrings": { "ConnectionStrings": {
"SqlServer": "Server=sql;Database=Sozsoft;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;", "SqlServer": "Server=sql;Database=Sozsoft;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -46,3 +46,4 @@ services:
- 1433:1433 - 1433:1433
volumes: volumes:
- mssql:/var/opt/mssql - mssql:/var/opt/mssql
- /etc/api/cdn/host/backup:/var/opt/mssql/backup