Hangfire JobFlow

This commit is contained in:
Sedat ÖZTÜRK 2026-05-15 22:42:33 +03:00
parent 48acf4211d
commit 9afa9edb98
11 changed files with 530 additions and 101 deletions

View file

@ -0,0 +1,65 @@
using System;
using System.Threading.Tasks;
using Hangfire.SqlServer;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Sozsoft.Platform.Data;
using Volo.Abp.DependencyInjection;
using static Sozsoft.Settings.SettingsConsts;
namespace Sozsoft.Platform.DbMigrator;
/// <summary>
/// DbMigrator çalışırken HangFire şemasını oluşturur.
/// EF Core migration'larından sonra (EntityFrameworkCorePlatformDbSchemaMigrator),
/// SqlDataSeeder'dan önce çalışarak HangFire tablolarının (HangFire.Job vb.) mevcut
/// olmasını sağlar. Bu sayede SqlData klasöründeki HangFire view'ları ilk çalışmada oluşturulabilir.
/// [ExposeServices] ABP'nin naming convention'ından bağımsız olarak IPlatformDbSchemaMigrator
/// listesine dahil edilmesini sağlar.
/// </summary>
[ExposeServices(typeof(IPlatformDbSchemaMigrator))]
public class HangfireDbSchemaMigrator : IPlatformDbSchemaMigrator, ITransientDependency
{
private readonly IConfiguration _configuration;
private readonly ILogger<HangfireDbSchemaMigrator> _logger;
public HangfireDbSchemaMigrator(
IConfiguration configuration,
ILogger<HangfireDbSchemaMigrator> logger)
{
_configuration = configuration;
_logger = logger;
}
public async Task MigrateAsync()
{
var connectionString = _configuration.GetConnectionString(DefaultDatabaseProvider);
if (string.IsNullOrWhiteSpace(connectionString))
{
_logger.LogWarning("HangFire schema migration skipped: connection string '{Key}' not found.", DefaultDatabaseProvider);
return;
}
try
{
await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
// HangFire tablolarını oluştur (idempotent — mevcutsa atlar).
SqlServerObjectsInstaller.Install(connection, "HangFire", enableHeavyMigrations: true);
_logger.LogInformation("HangFire schema initialized successfully.");
}
catch (SqlException sqlEx) when (sqlEx.Number == 4060)
{
// Veritabanı henüz oluşturulmamış (ilk migration dışında tenant DB senaryosu)
_logger.LogWarning("HangFire schema skipped: database does not exist yet (error 4060).");
}
catch (Exception ex)
{
_logger.LogError(ex, "HangFire schema initialization FAILED.");
throw;
}
}
}

View file

@ -1830,6 +1830,20 @@
"en": "Background Workers",
"tr": "Arkaplan İşleri"
},
{
"resourceName": "Platform",
"key": "App.BackgroundWorkers.Definitions",
"en": "Background Worker Definitions",
"tr": "Arkaplan İşleri Tanımları"
},
{
"resourceName": "Platform",
"key": "App.BackgroundWorkers.JobFlow",
"en": "Background Worker Job Flow",
"tr": "Arkaplan İşleri İş Akışı"
},
{
"resourceName": "Platform",
"key": "App.Notifications",
@ -14492,6 +14506,18 @@
"en": "Id",
"tr": "Kimlik"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.JobName",
"en": "Job Name",
"tr": "İş Adı"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.ExecutionDate",
"en": "Execution Date",
"tr": "İşlem Tarihi"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.SessionId",
@ -16202,6 +16228,24 @@
"en": "Status",
"tr": "Durum"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.FailureReason",
"en": "Failure Reason",
"tr": "Hata Nedeni"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.ExceptionType",
"en": "Exception Type",
"tr": "Hata Türü"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.ExceptionDetails",
"en": "Exception Details",
"tr": "Hata Detayları"
},
{
"resourceName": "Platform",
"key": "App.Listform.ListformField.StatusId",

View file

@ -4720,7 +4720,7 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
#endregion
#region Background Worker
listFormName = AppCodes.BackgroundWorkers;
listFormName = AppCodes.BackgroundWorkers.Definitions;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
{
var listForm = await _listFormRepository.InsertAsync(
@ -4957,6 +4957,181 @@ public class ListFormSeeder_Saas : IDataSeedContributor, ITransientDependency
}
#endregion
#region Background Worker Job Flow
listFormName = AppCodes.BackgroundWorkers.JobFlow;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))
{
var listForm = await _listFormRepository.InsertAsync(
new ListForm()
{
ListFormType = ListFormTypeEnum.List,
PageSize = 10,
ExportJson = DefaultExportJson,
IsSubForm = false,
ShowNote = true,
LayoutJson = DefaultLayoutJson(),
CultureName = LanguageCodes.En,
ListFormCode = listFormName,
Name = listFormName,
Title = listFormName,
DataSourceCode = SeedConsts.DataSources.DefaultCode,
IsTenant = false,
IsBranch = false,
IsOrganizationUnit = false,
Description = listFormName,
SelectCommandType = SelectCommandTypeEnum.View,
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.BackgroundWorker_JobFlow)),
KeyFieldName = "Id",
KeyFieldDbSourceType = DbType.Guid,
SortMode = GridOptions.SortModeSingle,
FilterRowJson = DefaultFilterRowJson,
HeaderFilterJson = DefaultHeaderFilterJson,
SearchPanelJson = DefaultSearchPanelJson,
GroupPanelJson = DefaultGroupPanelJson,
SelectionJson = DefaultSelectionSingleJson,
ColumnOptionJson = DefaultColumnOptionJson(),
PermissionJson = DefaultPermissionJson(listFormName),
PagerOptionJson = DefaultPagerOptionJson
}
);
#region Background Worker Job Flow Fields
await _listFormFieldRepository.InsertManyAsync([
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Guid,
FieldName = "Id",
CaptionName = "App.Listform.ListformField.Id",
Width = 0,
ListOrderNo = 1,
Visible = false,
IsActive = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "JobName",
CaptionName = "App.Listform.ListformField.JobName",
Width = 0,
ListOrderNo = 2,
Visible = true,
IsActive = true,
SortIndex = 1,
SortDirection = GridColumnOptions.SortOrderAsc,
AllowSearch = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.DateTime,
FieldName = "ExecutionDate",
CaptionName = "App.Listform.ListformField.ExecutionDate",
Width = 0,
ListOrderNo = 3,
Visible = true,
IsActive = true,
AllowSearch = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.Int32,
FieldName = "Duration",
CaptionName = "App.Listform.ListformField.Duration",
Width = 0,
ListOrderNo = 4,
Visible = true,
IsActive = true,
ValidationRuleJson = DefaultValidationRuleRequiredJson,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "Status",
CaptionName = "App.Listform.ListformField.Status",
Width = 0,
ListOrderNo = 5,
Visible = true,
IsActive = true,
AllowSearch = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "FailureReason",
CaptionName = "App.Listform.ListformField.FailureReason",
Width = 0,
ListOrderNo = 6,
Visible = true,
IsActive = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "ExceptionType",
CaptionName = "App.Listform.ListformField.ExceptionType",
Width = 0,
ListOrderNo = 7,
Visible = true,
IsActive = true,
AllowSearch = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
new() {
ListFormCode = listForm.ListFormCode,
CultureName = LanguageCodes.En,
SourceDbType = DbType.String,
FieldName = "ExceptionDetails",
CaptionName = "App.Listform.ListformField.ExceptionDetails",
Width = 0,
ListOrderNo = 8,
Visible = true,
IsActive = true,
AllowSearch = true,
ColumnCustomizationJson = DefaultColumnCustomizationJson,
PermissionJson = DefaultFieldPermissionJson(listForm.Name),
PivotSettingsJson = DefaultPivotSettingsJson
},
]);
#endregion
}
#endregion
#region Route
listFormName = AppCodes.Menus.Routes;
if (!await _listFormRepository.AnyAsync(a => a.ListFormCode == listFormName))

View file

@ -152,27 +152,21 @@
"path": "/admin/menuManager",
"componentPath": "@/views/menu/MenuManager",
"routeType": "protected",
"authority": [
"App.Menus.Manager"
]
"authority": ["App.Menus.Manager"]
},
{
"key": "admin.listFormManagement.wizard",
"path": "/admin/listform/wizard",
"componentPath": "@/views/admin/listForm/wizard/Wizard",
"routeType": "protected",
"authority": [
"App.Listforms.Wizard"
]
"authority": ["App.Listforms.Wizard"]
},
{
"key": "admin.listFormManagement.wizardManager",
"path": "/admin/listform/wizardManager",
"componentPath": "@/views/admin/listForm/wizard/WizardFileManager",
"routeType": "protected",
"authority": [
"App.Listforms.Wizard"
]
"authority": ["App.Listforms.Wizard"]
},
{
"key": "admin.listFormManagement.edit",
@ -186,18 +180,14 @@
"path": "/admin/forumManagement",
"componentPath": "@/views/forum/Management",
"routeType": "protected",
"authority": [
"App.ForumManagement"
]
"authority": ["App.ForumManagement"]
},
{
"key": "admin.ai",
"path": "/admin/ai",
"componentPath": "@/views/ai/Assistant",
"routeType": "protected",
"authority": [
"App.Definitions.AiBot.Asistant"
]
"authority": ["App.Definitions.AiBot.Asistant"]
},
{
"key": "admin.profile.general",
@ -239,45 +229,35 @@
"path": "/admin/settings",
"componentPath": "@/views/settings/Settings",
"routeType": "protected",
"authority": [
"App.Setting"
]
"authority": ["App.Setting"]
},
{
"key": "admin.identity.user.detail",
"path": "/admin/users/detail/:userId",
"componentPath": "@/views/admin/user-management/Details",
"routeType": "protected",
"authority": [
"AbpIdentity.Users.Update"
]
"authority": ["AbpIdentity.Users.Update"]
},
{
"key": "admin.identity.ous",
"path": "/admin/ous",
"componentPath": "@/views/admin/organization-unit/OrganizationUnits",
"routeType": "protected",
"authority": [
"Abp.Identity.OrganizationUnits"
]
"authority": ["Abp.Identity.OrganizationUnits"]
},
{
"key": "admin.hr.organization",
"path": "/admin/organization",
"componentPath": "@/views/admin/hr/OrgChart",
"routeType": "protected",
"authority": [
"App.Definitions.Department"
]
"authority": ["App.Definitions.Department"]
},
{
"key": "admin.forum",
"path": "/admin/forum",
"componentPath": "@/views/forum/Forum",
"routeType": "protected",
"authority": [
"App.ForumManagement.Publish"
]
"authority": ["App.ForumManagement.Publish"]
},
{
"key": "admin.list",
@ -319,90 +299,70 @@
"path": "/admin/sqlQueryManager",
"componentPath": "@/views/developerKit/SqlQueryManager",
"routeType": "protected",
"authority": [
"App.SqlQueryManager"
]
"authority": ["App.SqlQueryManager"]
},
{
"key": "admin.developerkit.endpoints",
"path": "/admin/developerkit/endpoints",
"componentPath": "@/views/developerKit/CrudEndpointManager",
"routeType": "protected",
"authority": [
"App.DeveloperKit.CrudEndpoints"
]
"authority": ["App.DeveloperKit.CrudEndpoints"]
},
{
"key": "admin.developerkit.dynamic-services",
"path": "/admin/developerkit/dynamic-services",
"componentPath": "@/views/developerKit/DynamicServiceManager",
"routeType": "protected",
"authority": [
"App.DeveloperKit.DynamicServices"
]
"authority": ["App.DeveloperKit.DynamicServices"]
},
{
"key": "admin.developerkit.dynamic-services.new",
"path": "/admin/developerkit/dynamic-services/new",
"componentPath": "@/views/developerKit/DynamicServiceEditor",
"routeType": "protected",
"authority": [
"App.DeveloperKit.DynamicServices"
]
"authority": ["App.DeveloperKit.DynamicServices"]
},
{
"key": "admin.developerkit.dynamic-services.edit",
"path": "/admin/developerkit/dynamic-services/edit/:id",
"componentPath": "@/views/developerKit/DynamicServiceEditor",
"routeType": "protected",
"authority": [
"App.DeveloperKit.DynamicServices"
]
"authority": ["App.DeveloperKit.DynamicServices"]
},
{
"key": "admin.developerkit.components",
"path": "/admin/developerkit/components",
"componentPath": "@/views/developerKit/ComponentManagerPage",
"routeType": "protected",
"authority": [
"App.DeveloperKit.Components"
]
"authority": ["App.DeveloperKit.Components"]
},
{
"key": "admin.developerkit.components.new",
"path": "/admin/developerkit/components/new",
"componentPath": "@/views/developerKit/ComponentEditorPage",
"routeType": "protected",
"authority": [
"App.DeveloperKit.Components"
]
"authority": ["App.DeveloperKit.Components"]
},
{
"key": "admin.developerkit.components.view",
"path": "/admin/developerkit/components/view/:id",
"componentPath": "@/views/developerKit/ComponentEditorPage",
"routeType": "protected",
"authority": [
"App.DeveloperKit.Components"
]
"authority": ["App.DeveloperKit.Components"]
},
{
"key": "admin.developerkit.components.edit",
"path": "/admin/developerkit/components/edit/:id",
"componentPath": "@/views/developerKit/ComponentCodeLayout",
"routeType": "protected",
"authority": [
"App.DeveloperKit.Components"
]
"authority": ["App.DeveloperKit.Components"]
},
{
"key": "admin.fileManagement",
"path": "/admin/files",
"componentPath": "@/views/admin/files/FileManager",
"routeType": "protected",
"authority": [
"App.Files"
]
"authority": ["App.Files"]
},
{
"key": "admin.devexpressReportView",
@ -423,63 +383,49 @@
"path": "/admin/public/home/designer",
"componentPath": "@/views/public/Home",
"routeType": "protected",
"authority": [
"App.Home"
]
"authority": ["App.Home"]
},
{
"key": "aboutDesigner",
"path": "/admin/public/about/designer",
"componentPath": "@/views/public/About",
"routeType": "protected",
"authority": [
"App.About"
]
"authority": ["App.About"]
},
{
"key": "servicesDesigner",
"path": "/admin/public/services/designer",
"componentPath": "@/views/public/Services",
"routeType": "protected",
"authority": [
"App.Services"
]
"authority": ["App.Services"]
},
{
"key": "contactDesigner",
"path": "/admin/public/contact/designer",
"componentPath": "@/views/public/Contact",
"routeType": "protected",
"authority": [
"App.Contact"
]
"authority": ["App.Contact"]
},
{
"key": "admin.videoroom.dashboard",
"path": "/admin/videoroom/dashboard",
"componentPath": "@/views/admin/videoroom/Dashboard",
"routeType": "protected",
"authority": [
"App.Videoroom.Dashboard"
]
"authority": ["App.Videoroom.Dashboard"]
},
{
"key": "admin.videoroom.list",
"path": "/admin/videoroom/list",
"componentPath": "@/views/admin/videoroom/RoomList",
"routeType": "protected",
"authority": [
"App.Videoroom.List"
]
"authority": ["App.Videoroom.List"]
},
{
"key": "admin.videoroom.roomdetail",
"path": "/admin/videoroom/room/:id",
"componentPath": "@/views/admin/videoroom/RoomDetail",
"routeType": "protected",
"authority": [
"App.Videoroom.RoomDetail"
]
"authority": ["App.Videoroom.RoomDetail"]
}
],
"MenuGroups": [
@ -732,14 +678,35 @@
"RequiredPermissionName": "App.Notifications.Notification",
"IsDisabled": false
},
{
"ParentCode": "App.Saas",
"Code": "App.BackgroundWorkers",
"DisplayName": "App.BackgroundWorkers",
"Order": 11,
"Url": "/admin/list/App.BackgroundWorkers",
"Icon": "FcScatterPlot",
"RequiredPermissionName": "App.BackgroundWorkers",
"Url": null,
"Icon": "FcSynchronize",
"RequiredPermissionName": null,
"IsDisabled": false
},
{
"ParentCode": "App.BackgroundWorkers",
"Code": "App.BackgroundWorkers.Definitions",
"DisplayName": "App.BackgroundWorkers.Definitions",
"Order": 1,
"Url": "/admin/list/App.BackgroundWorkers.Definitions",
"Icon": "FcDataConfiguration",
"RequiredPermissionName": "App.BackgroundWorkers.Definitions",
"IsDisabled": false
},
{
"ParentCode": "App.BackgroundWorkers",
"Code": "App.BackgroundWorkers.JobFlow",
"DisplayName": "App.BackgroundWorkers.JobFlow",
"Order": 2,
"Url": "/admin/list/App.BackgroundWorkers.JobFlow",
"Icon": "FcFlowChart",
"RequiredPermissionName": "App.BackgroundWorkers.JobFlow",
"IsDisabled": false
},
{

View file

@ -1364,17 +1364,17 @@
},
{
"GroupName": "App.Saas",
"Name": "App.BackgroundWorkers",
"Name": "App.BackgroundWorkers.Definitions",
"ParentName": null,
"DisplayName": "App.BackgroundWorkers",
"DisplayName": "App.BackgroundWorkers.Definitions",
"IsEnabled": true,
"MultiTenancySide": 2,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Saas",
"Name": "App.BackgroundWorkers.Create",
"ParentName": "App.BackgroundWorkers",
"Name": "App.BackgroundWorkers.Definitions.Create",
"ParentName": "App.BackgroundWorkers.Definitions",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 2,
@ -1382,8 +1382,8 @@
},
{
"GroupName": "App.Saas",
"Name": "App.BackgroundWorkers.Delete",
"ParentName": "App.BackgroundWorkers",
"Name": "App.BackgroundWorkers.Definitions.Delete",
"ParentName": "App.BackgroundWorkers.Definitions",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 2,
@ -1391,8 +1391,8 @@
},
{
"GroupName": "App.Saas",
"Name": "App.BackgroundWorkers.Export",
"ParentName": "App.BackgroundWorkers",
"Name": "App.BackgroundWorkers.Definitions.Export",
"ParentName": "App.BackgroundWorkers.Definitions",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 2,
@ -1400,8 +1400,8 @@
},
{
"GroupName": "App.Saas",
"Name": "App.BackgroundWorkers.Import",
"ParentName": "App.BackgroundWorkers",
"Name": "App.BackgroundWorkers.Definitions.Import",
"ParentName": "App.BackgroundWorkers.Definitions",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 2,
@ -1409,13 +1409,69 @@
},
{
"GroupName": "App.Saas",
"Name": "App.BackgroundWorkers.Update",
"ParentName": "App.BackgroundWorkers",
"Name": "App.BackgroundWorkers.Definitions.Update",
"ParentName": "App.BackgroundWorkers.Definitions",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 2,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Saas",
"Name": "App.BackgroundWorkers.JobFlow",
"ParentName": null,
"DisplayName": "App.BackgroundWorkers.JobFlow",
"IsEnabled": true,
"MultiTenancySide": 2,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Saas",
"Name": "App.BackgroundWorkers.JobFlow.Create",
"ParentName": "App.BackgroundWorkers.JobFlow",
"DisplayName": "Create",
"IsEnabled": true,
"MultiTenancySide": 2,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Saas",
"Name": "App.BackgroundWorkers.JobFlow.Delete",
"ParentName": "App.BackgroundWorkers.JobFlow",
"DisplayName": "Delete",
"IsEnabled": true,
"MultiTenancySide": 2,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Saas",
"Name": "App.BackgroundWorkers.JobFlow.Export",
"ParentName": "App.BackgroundWorkers.JobFlow",
"DisplayName": "Export",
"IsEnabled": true,
"MultiTenancySide": 2,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Saas",
"Name": "App.BackgroundWorkers.JobFlow.Import",
"ParentName": "App.BackgroundWorkers.JobFlow",
"DisplayName": "Import",
"IsEnabled": true,
"MultiTenancySide": 2,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Saas",
"Name": "App.BackgroundWorkers.JobFlow.Update",
"ParentName": "App.BackgroundWorkers.JobFlow",
"DisplayName": "Update",
"IsEnabled": true,
"MultiTenancySide": 2,
"MenuGroup": "Erp|Kurs"
},
{
"GroupName": "App.Saas",
"Name": "App.Home",

View file

@ -0,0 +1,107 @@
CREATE OR ALTER VIEW [dbo].[Sas_H_BackgroundWorker_JobFlow] AS
SELECT
J.Id,
ISNULL(P.RecurringJobId, N'OneTimeJob') AS [JobName],
COALESCE(PRC.ProcessingAt, ENQ.EnqueuedAt, J.CreatedAt) AS [ExecutionDate],
CASE
WHEN SUC.DurationMs IS NOT NULL
THEN SUC.DurationMs
WHEN PRC.ProcessingAt IS NOT NULL AND SUC.SucceededAt IS NOT NULL
THEN DATEDIFF(MILLISECOND, PRC.ProcessingAt, SUC.SucceededAt)
WHEN PRC.ProcessingAt IS NOT NULL AND FLD.FailedAt IS NOT NULL
THEN DATEDIFF(MILLISECOND, PRC.ProcessingAt, FLD.FailedAt)
ELSE NULL
END AS [Duration],
J.StateName AS [Status],
CASE
WHEN J.StateName = 'Failed'
THEN COALESCE(
FLD.ExceptionMessage,
FLD.FailedReason,
FLD.ExceptionType,
N'Unknown error'
)
ELSE NULL
END AS [FailureReason],
FLD.ExceptionType AS [ExceptionType],
FLD.ExceptionDetails AS [ExceptionDetails]
FROM HangFire.Job J
LEFT JOIN HangFire.JobQueue JQ
ON JQ.JobId = J.Id
OUTER APPLY
(
SELECT
MAX(CASE
WHEN JP.Name = 'RecurringJobId'
THEN REPLACE(JP.Value, '"', '')
END) AS RecurringJobId
FROM HangFire.JobParameter JP
WHERE JP.JobId = J.Id
) P
OUTER APPLY
(
SELECT TOP 1
S.CreatedAt AS EnqueuedAt,
S.Reason AS EnqueuedReason,
JSON_VALUE(S.Data, '$.Queue') AS QueueName
FROM HangFire.State S
WHERE S.JobId = J.Id
AND S.Name = 'Enqueued'
ORDER BY S.CreatedAt DESC
) ENQ
OUTER APPLY
(
SELECT TOP 1
S.CreatedAt AS ProcessingAt,
S.Reason AS ProcessingReason,
JSON_VALUE(S.Data, '$.ServerId') AS ServerName,
JSON_VALUE(S.Data, '$.WorkerId') AS WorkerId
FROM HangFire.State S
WHERE S.JobId = J.Id
AND S.Name = 'Processing'
ORDER BY S.CreatedAt DESC
) PRC
OUTER APPLY
(
SELECT TOP 1
S.CreatedAt AS SucceededAt,
S.Reason AS SucceededReason,
TRY_CONVERT(BIGINT, JSON_VALUE(S.Data, '$.PerformanceDuration')) AS DurationMs
FROM HangFire.State S
WHERE S.JobId = J.Id
AND S.Name = 'Succeeded'
ORDER BY S.CreatedAt DESC
) SUC
OUTER APPLY
(
SELECT TOP 1
S.CreatedAt AS FailedAt,
S.Reason AS FailedReason,
JSON_VALUE(S.Data, '$.ExceptionType') AS ExceptionType,
JSON_VALUE(S.Data, '$.ExceptionMessage') AS ExceptionMessage,
JSON_VALUE(S.Data, '$.ExceptionDetails') AS ExceptionDetails
FROM HangFire.State S
WHERE S.JobId = J.Id
AND S.Name = 'Failed'
ORDER BY S.CreatedAt DESC
) FLD
WHERE JSON_VALUE(J.InvocationData, '$.Method') <> 'DoWorkAsync'
GO

View file

@ -96,6 +96,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Hangfire.SqlServer" Version="1.8.14" />
<PackageReference Include="Volo.Abp.Autofac" Version="9.0.2" />
<PackageReference Include="Volo.Abp.Caching.StackExchangeRedis" Version="9.0.2" />
<ProjectReference Include="..\Sozsoft.Platform.Application.Contracts\Sozsoft.Platform.Application.Contracts.csproj" />

View file

@ -13,6 +13,7 @@ public enum TableNameEnum
BackgroundWorker_MailQueue,
BackgroundWorker_MailQueueEvents,
BackgroundWorker_MailQueueTableFormat,
BackgroundWorker_JobFlow,
LogEntry,
AiBot,
Branch,

View file

@ -432,7 +432,13 @@ public static class PlatformConsts
public const string Notification = Default + ".Notification";
}
public const string BackgroundWorkers = Prefix.App + ".BackgroundWorkers";
public static class BackgroundWorkers
{
public const string Default = Prefix.App + ".BackgroundWorkers";
public const string Definitions = Default + ".Definitions";
public const string JobFlow = Default + ".JobFlow";
}
public static class Menus
{

View file

@ -42,6 +42,7 @@ public static class TableNameResolver
{ nameof(TableNameEnum.NotificationRule), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.NotificationType), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.BackgroundWorker), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.BackgroundWorker_JobFlow), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.BackgroundWorker_MailQueue), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.BackgroundWorker_MailQueueEvents), (TablePrefix.PlatformByName, MenuPrefix.Saas) },
{ nameof(TableNameEnum.BackgroundWorker_MailQueueTableFormat), (TablePrefix.PlatformByName, MenuPrefix.Saas) },

View file

@ -404,7 +404,13 @@ public static class SeedConsts
public const string Notification = Default + ".Notification";
}
public const string BackgroundWorkers = Prefix.App + ".BackgroundWorkers";
public static class BackgroundWorkers
{
public const string Default = Prefix.App + ".BackgroundWorkers";
public const string Definitions = Default + ".Definitions";
public const string JobFlow = Default + ".JobFlow";
}
//Web Site
public const string Home = Prefix.App + ".Home";