Compare commits

..
10 Commits
11 changed files with 391 additions and 94 deletions
@@ -5,8 +5,8 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<PackageId>Caspian.WorkflowFramework</PackageId> <PackageId>Caspian.WorkflowFramework</PackageId>
<Version>1.1.0-preview.10</Version> <Version>1.1.0-preview.25</Version>
<Authors>Digital banking team</Authors> <Authors>Behzad Alizadeh</Authors>
<Company>Caspian</Company> <Company>Caspian</Company>
<Product>Caspian.WorkflowFramework</Product> <Product>Caspian.WorkflowFramework</Product>
<Description>Caspian Workflow Framework</Description> <Description>Caspian Workflow Framework</Description>
@@ -17,58 +17,61 @@
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile> <PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>caspian-pkg-key.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>caspian-pkg-key.snk</AssemblyOriginatorKeyFile>
<PackageOutputPath>
$(MSBuildThisFileDirectory)packages
</PackageOutputPath>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Caspian.Elsa.WorkflowContexts" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.WorkflowContexts" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Workflows.Runtime.Distributed" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Workflows.Runtime.Distributed" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Alterations" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Alterations" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Alterations.MassTransit" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Alterations.MassTransit" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Caching.Distributed.MassTransit" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Caching.Distributed.MassTransit" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Caching.Distributed.ProtoActor" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Caching.Distributed.ProtoActor" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.CSharp" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.CSharp" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Dapper" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Dapper" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Dapper.Migrations" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Dapper.Migrations" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.DropIns" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.DropIns" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Email" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Email" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Elasticsearch" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Elasticsearch" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Environments" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Environments" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.EntityFrameworkCore" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.EntityFrameworkCore" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.EntityFrameworkCore.MySql" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.EntityFrameworkCore.MySql" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.EntityFrameworkCore.Oracle" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.EntityFrameworkCore.Oracle" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.EntityFrameworkCore.PostgreSql" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.EntityFrameworkCore.PostgreSql" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.EntityFrameworkCore.Sqlite" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.EntityFrameworkCore.Sqlite" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.EntityFrameworkCore.SqlServer" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.EntityFrameworkCore.SqlServer" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.FileStorage" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.FileStorage" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Hangfire" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Hangfire" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Http" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Http" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Identity" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Identity" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Kafka" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Kafka" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Labels" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Labels" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Liquid" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Liquid" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.MassTransit" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.MassTransit" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.MassTransit.AzureServiceBus" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.MassTransit.AzureServiceBus" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.MassTransit.RabbitMq" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.MassTransit.RabbitMq" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.MongoDb" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.MongoDb" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.OpenTelemetry" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.OpenTelemetry" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Quartz" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Quartz" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Quartz.EntityFrameworkCore.MySql" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Quartz.EntityFrameworkCore.MySql" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Quartz.EntityFrameworkCore.PostgreSql" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Quartz.EntityFrameworkCore.PostgreSql" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Quartz.EntityFrameworkCore.Sqlite" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Quartz.EntityFrameworkCore.Sqlite" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Retention" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Retention" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Secrets.Api" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Secrets.Api" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Secrets.Persistence.EntityFrameworkCore.PostgreSql" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Secrets.Persistence.EntityFrameworkCore.Sqlite" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Secrets.Persistence.EntityFrameworkCore.Sqlite" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Secrets.Persistence.EntityFrameworkCore.SqlServer" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Secrets.Persistence.EntityFrameworkCore.SqlServer" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Secrets.Scripting" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Secrets.Scripting" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Sql.MySql" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Sql.MySql" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Sql.PostgreSql" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Sql.PostgreSql" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Sql.Sqlite" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Sql.Sqlite" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Sql.SqlServer" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Sql.SqlServer" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Sql" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Sql" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Tenants.AspNetCore" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Tenants.AspNetCore" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa.Webhooks" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa.Webhooks" Version="1.1.0-preview.8" />
<PackageReference Include="Caspian.Elsa" Version="1.1.0-preview.1" /> <PackageReference Include="Caspian.Elsa" Version="1.1.0-preview.8" />
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" /> <PackageReference Include="JetBrains.Annotations" Version="2024.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" /> <PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.3.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.7" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.7" />
@@ -127,9 +127,9 @@ public static class DependencyInjection
// engine.Execute("function sayHelloWorld() { return greet('World'); }"); // engine.Execute("function sayHelloWorld() { return greet('World'); }");
//}); //});
}) })
// برای ثبت Activityهایی مانند Elsa.SendHttpRequest
.UseHttp()
.UseWorkflowContexts(); .UseWorkflowContexts();
//elsa.AddFastEndpointsAssembly<Program>();
}); });
// Optionally configure recurring tasks using alternative schedules. // Optionally configure recurring tasks using alternative schedules.
@@ -172,7 +172,7 @@ public static class DependencyInjection
// Captures unhandled exceptions and returns a JSON response. // Captures unhandled exceptions and returns a JSON response.
app.UseJsonSerializationErrorHandler(); app.UseJsonSerializationErrorHandler();
//app.UseWorkflows(); app.UseWorkflows();
} }
} }
@@ -48,5 +48,15 @@ namespace Caspian.WorkflowFramework.Contracts
/// Cancels the specified workflow instance. /// Cancels the specified workflow instance.
/// </summary> /// </summary>
Task<WorkflowResponse<dynamic>> CancelInstanceAsync(CancelWorkflowInstanceModel input); Task<WorkflowResponse<dynamic>> CancelInstanceAsync(CancelWorkflowInstanceModel input);
/// <summary>
/// Cancels the specified workflow instance.
/// </summary>
Task<WorkflowResponse<dynamic>> ForceCancelInstanceAsync(CancelWorkflowInstanceModel input);
/// <summary>
/// Evicts a workflow definition from cache (optional, commented out in service).
/// </summary>
Task EvictDefFromCacheAsync(string name, CancellationToken cancellationToken = default);
} }
} }
@@ -14,4 +14,6 @@ public class PublishEventModel
[Required] [Required]
public JsonElement EventData { get; set; } public JsonElement EventData { get; set; }
public string? SessionId { get; set; }
} }
@@ -1,10 +1,6 @@
using System; using Caspian.WorkflowFramework.Utility;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using System.Text.Json;
namespace Caspian.WorkflowFramework.Models; namespace Caspian.WorkflowFramework.Models;
[PublicAPI] [PublicAPI]
@@ -24,10 +20,15 @@ public class WorkflowModel
public string? InstanceMode { get; set; } = "exist"; public string? InstanceMode { get; set; } = "exist";
/// <summary> /// <summary>
/// نام گردش کار /// کلیدواژه گردش کار
/// </summary> /// </summary>
public string? Name { get; set; } public string? Name { get; set; }
/// <summary>
/// عنوان گردش کار
/// </summary>
public string? Title { get; set; }
/// <summary> /// <summary>
/// اتمام گردش کار؟ /// اتمام گردش کار؟
/// </summary> /// </summary>
@@ -61,6 +62,11 @@ public class WorkflowModel
/// </summary> /// </summary>
public int? PageNumber { get; set; } public int? PageNumber { get; set; }
/// <summary>
/// تعداد کل صفحات
/// </summary>
public int? TotalPage { get; set; }
/// <summary> /// <summary>
/// رویدادی که باید پس از سابمیت صفحه بعدی منتشر شود /// رویدادی که باید پس از سابمیت صفحه بعدی منتشر شود
/// </summary> /// </summary>
@@ -70,4 +76,9 @@ public class WorkflowModel
/// جای ذخیره‌ی موقت برای استفاده های بعدی /// جای ذخیره‌ی موقت برای استفاده های بعدی
/// </summary> /// </summary>
public JsonElement? WorkflowStash { get; set; } public JsonElement? WorkflowStash { get; set; }
/// <summary>
/// مرحله
/// </summary>
public WorkflowPhase WorkflowPhase { get; set; }
} }
@@ -1,7 +1,4 @@
using System.Diagnostics; using Caspian.WorkflowFramework.Contracts;
using System.Dynamic;
using System.Text.Json;
using Caspian.WorkflowFramework.Contracts;
using Caspian.WorkflowFramework.Models; using Caspian.WorkflowFramework.Models;
using Caspian.WorkflowFramework.Utility; using Caspian.WorkflowFramework.Utility;
using Elsa.Common.Entities; using Elsa.Common.Entities;
@@ -11,11 +8,15 @@ using Elsa.Workflows.Management;
using Elsa.Workflows.Management.Filters; using Elsa.Workflows.Management.Filters;
using Elsa.Workflows.Models; using Elsa.Workflows.Models;
using Elsa.Workflows.Runtime; using Elsa.Workflows.Runtime;
using Elsa.Workflows.Runtime.Filters;
using Elsa.Workflows.Runtime.Messages; using Elsa.Workflows.Runtime.Messages;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using San.MongoLogger; using San.MongoLogger;
using System.Diagnostics;
using System.Dynamic;
using System.Text.Json;
namespace Caspian.WorkflowFramework.Services; namespace Caspian.WorkflowFramework.Services;
public class WorkflowService : IWorkflowService, ILoggable public class WorkflowService : IWorkflowService, ILoggable
@@ -29,6 +30,8 @@ public class WorkflowService : IWorkflowService, ILoggable
private readonly IWorkflowInstanceStore _workflowInstanceStore; private readonly IWorkflowInstanceStore _workflowInstanceStore;
private readonly IEventPublisher _eventPublisher; private readonly IEventPublisher _eventPublisher;
private readonly IWorkflowDefinitionCacheManager _workflowDefinitionCacheManager; private readonly IWorkflowDefinitionCacheManager _workflowDefinitionCacheManager;
private readonly IBookmarkStore _bookmarkStore;
private readonly IActivityExecutionStore _activityExecutionStore;
private readonly IMdbLogger<WorkflowService> _logger; private readonly IMdbLogger<WorkflowService> _logger;
#endregion #endregion
@@ -42,7 +45,10 @@ public class WorkflowService : IWorkflowService, ILoggable
_workflowInstanceVariableManager = _serviceProvider.GetRequiredService<IWorkflowInstanceVariableManager>(); _workflowInstanceVariableManager = _serviceProvider.GetRequiredService<IWorkflowInstanceVariableManager>();
_workflowInstanceManager = _serviceProvider.GetRequiredService<IWorkflowInstanceManager>(); _workflowInstanceManager = _serviceProvider.GetRequiredService<IWorkflowInstanceManager>();
_workflowInstanceStore = _serviceProvider.GetRequiredService<IWorkflowInstanceStore>(); _workflowInstanceStore = _serviceProvider.GetRequiredService<IWorkflowInstanceStore>();
_workflowDefinitionCacheManager = _serviceProvider.GetRequiredService<IWorkflowDefinitionCacheManager>();
_eventPublisher = _serviceProvider.GetRequiredService<IEventPublisher>(); _eventPublisher = _serviceProvider.GetRequiredService<IEventPublisher>();
_bookmarkStore = _serviceProvider.GetRequiredService<IBookmarkStore>();
_activityExecutionStore = _serviceProvider.GetRequiredService<IActivityExecutionStore>();
_logger = logger; _logger = logger;
} }
#endregion #endregion
@@ -256,65 +262,119 @@ public class WorkflowService : IWorkflowService, ILoggable
#endregion #endregion
#region PublishEventAsync #region PublishEventAsync
/// <inheritdoc /> /// <inheritdoc />
public async Task<WorkflowResponse<WorkflowModel>> PublishEventAsync(PublishEventModel input) public async Task<WorkflowResponse<WorkflowModel>> PublishEventAsync(
PublishEventModel input)
{ {
var sw = Stopwatch.StartNew(); var sw = Stopwatch.StartNew();
try try
{ {
_logger.Log("Start of PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, input); _logger.Log(
"Start of PublishEventAsync",
WFInstanceId: input.WorkflowInstanceId,
input);
WorkflowResponse<WorkflowModel> response; var instance = await _workflowInstanceManager.FindByIdAsync(
input.WorkflowInstanceId);
var instance = await _workflowInstanceManager.FindByIdAsync(input.WorkflowInstanceId);
if (instance == null) if (instance == null)
{ {
var msg = $"Workflow instance with id '{input.WorkflowInstanceId}' not found"; var notFoundResponse = new WorkflowResponse<WorkflowModel>
response = new WorkflowResponse<WorkflowModel>()
{ {
Status = 1, Status = 1,
Message = msg, Message =
$"Workflow instance with id " +
$"'{input.WorkflowInstanceId}' not found",
Data = null Data = null
}; };
sw.Stop(); sw.Stop();
_logger.Log("End of PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds); _logger.Log(
"End of PublishEventAsync",
WFInstanceId: input.WorkflowInstanceId,
notFoundResponse,
sw.ElapsedMilliseconds);
return response; return notFoundResponse;
} }
dynamic payload = null; /*
* JsonElement
* ↓
* ExpandoObject
* ↓
* اضافه‌شدن SessionId
* ↓
* PublishEvent
*/
if (input.EventData.ValueKind != JsonValueKind.Undefined && ExpandoObject payload = new();
input.EventData.ValueKind != JsonValueKind.Null)
if (input.EventData.ValueKind is not JsonValueKind.Undefined
and not JsonValueKind.Null)
{ {
if (input.EventData.ValueKind != JsonValueKind.Object)
{
throw new InvalidOperationException(
"EventData must be a JSON object.");
}
var json = input.EventData.GetRawText(); var json = input.EventData.GetRawText();
var settings = new JsonSerializerSettings var serializerSettings = new JsonSerializerSettings
{ {
Converters = { new ExpandoObjectConverter() } Converters =
{
new ExpandoObjectConverter()
}
}; };
// تبدیل JSON به object داینامیک payload =
payload = JsonConvert.DeserializeObject<ExpandoObject>(json, settings); JsonConvert.DeserializeObject<ExpandoObject>(
json,
serializerSettings)
?? new ExpandoObject();
} }
//await _eventPublisher.PublishAsync(input.EventName, null, input.WorkflowInstanceId, null, input.EventData, asynchronous: true); /*
await _eventPublisher.PublishAsync(input.EventName, null, input.WorkflowInstanceId, null, payload); * ExpandoObject پیاده‌سازی IDictionary است؛
* بنابراین بدون Serialize/Deserialize مجدد می‌توانیم
* SessionId را به Payload اضافه کنیم.
*/
if (!string.IsNullOrWhiteSpace(input.SessionId))
{
var payloadDictionary =
(IDictionary<string, object?>)payload;
var variables = await _workflowInstanceVariableManager.GetVariablesAsync(input.WorkflowInstanceId); payloadDictionary["SessionId"] = input.SessionId;
}
// تبدیل به دیکشنری await _eventPublisher.PublishAsync(
var dictionary = variables.ToDictionary(a => a.Variable.Name, a => a.Value); eventName: input.EventName,
correlationId: null,
workflowInstanceId: input.WorkflowInstanceId,
activityInstanceId: null,
payload: payload,
asynchronous: false);
var variables =
await _workflowInstanceVariableManager.GetVariablesAsync(
input.WorkflowInstanceId);
var dictionary = variables.ToDictionary(
x => x.Variable.Name,
x => x.Value);
var workflowModel = dictionary.ConvertToWorkflowModel(); var workflowModel = dictionary.ConvertToWorkflowModel();
workflowModel.InstanceId = instance.Id; workflowModel.InstanceId = instance.Id;
workflowModel.UserId = instance.ExternalUserId; workflowModel.UserId = instance.ExternalUserId;
workflowModel.InstanceMode = "exist"; workflowModel.InstanceMode = "exist";
response = new WorkflowResponse<WorkflowModel>() var response = new WorkflowResponse<WorkflowModel>
{ {
Status = 0, Status = 0,
Message = "Success", Message = "Success",
@@ -323,26 +383,36 @@ public class WorkflowService : IWorkflowService, ILoggable
sw.Stop(); sw.Stop();
_logger.Log("End of PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds); _logger.Log(
"End of PublishEventAsync",
WFInstanceId: input.WorkflowInstanceId,
response,
sw.ElapsedMilliseconds);
return response; return response;
} }
catch (Exception ex) catch (Exception ex)
{ {
var response = new WorkflowResponse<WorkflowModel>() sw.Stop();
var response = new WorkflowResponse<WorkflowModel>
{ {
Status = 100, Status = 100,
Message = ex.Message, Message = ex.Message,
Data = null Data = null
}; };
sw.Stop(); _logger.Log(
"Exception occurs in PublishEventAsync",
_logger.Log("Exception occurs in PublishEventAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds, ex); WFInstanceId: input.WorkflowInstanceId,
response,
sw.ElapsedMilliseconds,
ex);
return response; return response;
} }
} }
#endregion #endregion
#region InquiryStateAsync #region InquiryStateAsync
@@ -678,6 +748,7 @@ public class WorkflowService : IWorkflowService, ILoggable
new WorkflowInstanceFilter new WorkflowInstanceFilter
{ {
ExternalUserId = input.UserId, ExternalUserId = input.UserId,
Id = input.WorkflowInstanceId,
WorkflowStatus = WorkflowStatus.Running WorkflowStatus = WorkflowStatus.Running
}); });
@@ -723,4 +794,117 @@ public class WorkflowService : IWorkflowService, ILoggable
} }
} }
#endregion #endregion
#region ForceCancelInstanceAsync
public async Task<WorkflowResponse<dynamic>> ForceCancelInstanceAsync(CancelWorkflowInstanceModel input)
{
var sw = Stopwatch.StartNew();
try
{
_logger.Log("Start ForceCancelInstanceAsync", WFInstanceId: input.WorkflowInstanceId, input);
var instance = await _workflowInstanceManager.FindByIdAsync(input.WorkflowInstanceId);
if (instance == null)
return new WorkflowResponse<dynamic> { Status = 1, Message = "Instance not found", Data = false };
// جلوگری از Resume شدن دوباره
instance.Status = WorkflowStatus.Finished;
instance.SubStatus = WorkflowSubStatus.Cancelled;
// پاک‌سازی bookmarks از دیتابیس واقعی
var bookmarkFilter = new BookmarkFilter()
{
WorkflowInstanceId = input.WorkflowInstanceId
};
await _bookmarkStore.DeleteAsync(bookmarkFilter);
// پاک‌سازی scheduled activities (در صورتی که runtime distributed باشد)
var activityFilter = new ActivityExecutionRecordFilter()
{
WorkflowInstanceId = input.WorkflowInstanceId
};
await _activityExecutionStore.DeleteManyAsync(activityFilter);
//// پاک‌سازی incidents (در پروژه‌های بزرگ ضروری است)
//await _incidentStore.DeleteManyAsync(
// x => x.WorkflowInstanceId == input.WorkflowInstanceId
//);
// ذخیره instance تغییر یافته
await _workflowInstanceManager.SaveAsync(instance);
sw.Stop();
var response = new WorkflowResponse<dynamic>
{
Status = 0,
Message = "Force cancel completed",
Data = true
};
_logger.Log("End ForceCancelInstanceAsync", WFInstanceId: instance.Id, response, sw.ElapsedMilliseconds);
return response;
}
catch (Exception ex)
{
sw.Stop();
var response = new WorkflowResponse<dynamic>
{
Status = 100,
Message = ex.Message,
Data = false
};
_logger.Log("Exception in ForceCancelInstanceAsync", WFInstanceId: input.WorkflowInstanceId, response, sw.ElapsedMilliseconds, ex);
return response;
}
}
#endregion
#region EvictDefFromCacheAsync
/// <inheritdoc />
public async Task EvictDefFromCacheAsync(string name, CancellationToken cancellationToken = default)
{
var sw = Stopwatch.StartNew();
try
{
_logger.Log("Start of EvictDefFromCacheAsync", name);
var defFilter = new WorkflowDefinitionFilter()
{
Name = name,
VersionOptions = VersionOptions.Published
};
var definition = await _workflowDefinitionService
.FindWorkflowDefinitionAsync(defFilter);
await _workflowDefinitionCacheManager.EvictWorkflowDefinitionAsync(definition.DefinitionId);
var workflowDefinitionFilter = new WorkflowDefinitionFilter()
{
DefinitionHandle = WorkflowDefinitionHandle.ByDefinitionId(definition.DefinitionId, VersionOptions.Published)
};
var createWorkflowDefinitionFilterCacheKey = _workflowDefinitionCacheManager.CreateWorkflowDefinitionFilterCacheKey(workflowDefinitionFilter);
_workflowDefinitionCacheManager.Cache.Remove(createWorkflowDefinitionFilterCacheKey);
var createWorkflowFilterCacheKey = _workflowDefinitionCacheManager.CreateWorkflowFilterCacheKey(workflowDefinitionFilter);
_workflowDefinitionCacheManager.Cache.Remove(createWorkflowFilterCacheKey);
}
finally
{
sw.Stop();
_logger.Log("End of EvictDefFromCacheAsync", name, sw.ElapsedMilliseconds, null);
}
}
#endregion
} }
@@ -18,6 +18,9 @@ namespace Caspian.WorkflowFramework.Utility
if (dictionary == null || dictionary.Count == 0) if (dictionary == null || dictionary.Count == 0)
return workflowModel; return workflowModel;
// قبل از تبدیل به مدل خروجی، sessionId حذف می‌شود.
dictionary.RemoveSessionId();
//Map dictionary values to WorkflowModel properties //Map dictionary values to WorkflowModel properties
workflowModel.InstanceId = dictionary.ContainsKey(nameof(workflowModel.InstanceId)) workflowModel.InstanceId = dictionary.ContainsKey(nameof(workflowModel.InstanceId))
? dictionary[nameof(workflowModel.InstanceId)]?.ToString() ? dictionary[nameof(workflowModel.InstanceId)]?.ToString()
@@ -31,6 +34,10 @@ namespace Caspian.WorkflowFramework.Utility
? dictionary[nameof(workflowModel.Name)]?.ToString() ? dictionary[nameof(workflowModel.Name)]?.ToString()
: null; : null;
workflowModel.Title = dictionary.ContainsKey(nameof(workflowModel.Title))
? dictionary[nameof(workflowModel.Title)]?.ToString()
: null;
workflowModel.IsComplete = dictionary.ContainsKey(nameof(workflowModel.IsComplete)) workflowModel.IsComplete = dictionary.ContainsKey(nameof(workflowModel.IsComplete))
? bool.TryParse(dictionary[nameof(workflowModel.IsComplete)]?.ToString(), out var isComplete) && isComplete ? bool.TryParse(dictionary[nameof(workflowModel.IsComplete)]?.ToString(), out var isComplete) && isComplete
: false; : false;
@@ -109,10 +116,52 @@ namespace Caspian.WorkflowFramework.Utility
? int.TryParse(dictionary[nameof(workflowModel.PageNumber)]?.ToString(), out var pageNumber) ? pageNumber : (int?)null ? int.TryParse(dictionary[nameof(workflowModel.PageNumber)]?.ToString(), out var pageNumber) ? pageNumber : (int?)null
: null; : null;
workflowModel.TotalPage = dictionary.ContainsKey(nameof(workflowModel.TotalPage))
? int.TryParse(dictionary[nameof(workflowModel.TotalPage)]?.ToString(), out var totalPage) ? totalPage : (int?)null
: null;
workflowModel.NextPageEventName = dictionary.ContainsKey(nameof(workflowModel.NextPageEventName)) workflowModel.NextPageEventName = dictionary.ContainsKey(nameof(workflowModel.NextPageEventName))
? dictionary[nameof(workflowModel.NextPageEventName)]?.ToString() ? dictionary[nameof(workflowModel.NextPageEventName)]?.ToString()
: null; : null;
//WorkflowStash
if (dictionary.TryGetValue(nameof(workflowModel.WorkflowStash), out var workflowStashObj) &&
workflowStashObj is not null)
{
switch (workflowStashObj)
{
case JsonElement je:
workflowModel.WorkflowStash = je;
break;
case JsonNode node:
// .NET 6+
workflowModel.WorkflowStash = JsonSerializer.SerializeToElement(node);
break;
case string s:
// اگر به صورت string JSON ذخیره شده
workflowModel.WorkflowStash = JsonSerializer.Deserialize<JsonElement>(s);
break;
default:
// هر نوع دیگری: serialize و تبدیل به JsonElement
workflowModel.WorkflowStash = JsonSerializer.SerializeToElement(workflowStashObj);
break;
}
}
else
{
workflowModel.WorkflowStash = null;
}
workflowModel.WorkflowPhase =
dictionary.TryGetValue(nameof(workflowModel.WorkflowPhase), out var phaseObj) &&
phaseObj != null &&
Enum.TryParse(typeof(WorkflowPhase), phaseObj.ToString(), out var phase)
? (WorkflowPhase)phase
: WorkflowPhase.Client;
return workflowModel; return workflowModel;
} }
} }
@@ -0,0 +1,8 @@
namespace Caspian.WorkflowFramework.Utility
{
public enum WorkflowPhase : short
{
Client = 0,
Admin
}
}
@@ -0,0 +1,28 @@
namespace Caspian.WorkflowFramework.Utility;
public static class WorkflowVariableExtensions
{
private const string SessionIdKey = "SessionId";
/// <summary>
/// SessionId را فقط از سطح اول دیکشنری متغیرهای Workflow حذف می‌کند.
/// مقایسه نام کلید به حروف بزرگ و کوچک حساس نیست.
/// </summary>
public static Dictionary<string, object?> RemoveSessionId(
this Dictionary<string, object?> variables)
{
if (variables is null || variables.Count == 0)
return variables!;
var sessionIdKey = variables.Keys.FirstOrDefault(
key => string.Equals(
key,
SessionIdKey,
StringComparison.OrdinalIgnoreCase));
if (sessionIdKey is not null)
variables.Remove(sessionIdKey);
return variables;
}
}
+2
View File
@@ -8,5 +8,7 @@
<add key="Webhooks Core Preview" value="https://f.feedz.io/sfmskywalker/webhooks-core/nuget/index.json" /> <add key="Webhooks Core Preview" value="https://f.feedz.io/sfmskywalker/webhooks-core/nuget/index.json" />
<add key="Local Package source" value="D:\MyNugetPackages" /> <add key="Local Package source" value="D:\MyNugetPackages" />
<add key="Workflow Package source" value="D:\NugetPackageRepository\WorkflowPackages" /> <add key="Workflow Package source" value="D:\NugetPackageRepository\WorkflowPackages" />
<add key="Caspian package source" value="https://nuget.caspian.com/v3/index.json" />
<add key="Gitea Package source" value="D:\CaspianProjects\NugetPackageRepository\AllPackages" />
</packageSources> </packageSources>
</configuration> </configuration>
+1 -1
View File
@@ -7,7 +7,7 @@
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"ConnectionStrings": { "ConnectionStrings": {
"DefaultConnection": "Data Source=.;Initial Catalog=WfDb;User ID=sa;Password=123456;Integrated Security=false;TrustServerCertificate=True;" "DefaultConnection": "Data Source=.;Initial Catalog=WfLive;User ID=sa;Password=123456;Integrated Security=false;TrustServerCertificate=True;"
}, },
"LogDatabaseSettings": { "LogDatabaseSettings": {
"LogsCollectionName": "TestWorkflowCollection", "LogsCollectionName": "TestWorkflowCollection",