refactor(scripting): 按钮动作链改造指向 IScriptExecutionService
- WorkflowApplicationModule 依赖 ScriptingApplicationModule - BuildFlowButtonActionExecutor 改用 IScriptExecutionService.ExecuteByIdAsync - EnsureFunctionScopeAsync 改查 ScriptingDbContext.Scripts - 结果列表类型 CustomFunctionExecutionResult → ScriptExecutionResult (连带更新 BuildFlowButtonActionResult DTO 的 Results 元素类型) - BuildFlowButtonActionTests 旧 CustomFunction 链路用例暂跳过,留待 T9 重写 - 旧 CustomFunction 代码保留,T7 统一删除 Task T6 of scripting module refactor
This commit is contained in:
parent
3d5604d80c
commit
51fe387913
@ -1,8 +1,8 @@
|
|||||||
using Workflow.Application.BuildFlow.CustomFunctions.DTOs;
|
using Scripting.Application.DTOs;
|
||||||
|
|
||||||
namespace Workflow.Application.BuildFlow.Buttons.DTOs;
|
namespace Workflow.Application.BuildFlow.Buttons.DTOs;
|
||||||
|
|
||||||
/// <summary>BuildFlow 按钮动作链执行结果,包含每个动作产生的返回值。</summary>
|
/// <summary>BuildFlow 按钮动作链执行结果,包含每个动作产生的返回值。</summary>
|
||||||
public sealed record BuildFlowButtonActionResult(
|
public sealed record BuildFlowButtonActionResult(
|
||||||
bool Success,
|
bool Success,
|
||||||
IReadOnlyList<CustomFunctionExecutionResult> Results);
|
IReadOnlyList<ScriptExecutionResult> Results);
|
||||||
|
|||||||
@ -5,9 +5,10 @@ using System.Text.Json.Nodes;
|
|||||||
using BuildFlow.Platform.Application.Auth.Queries;
|
using BuildFlow.Platform.Application.Auth.Queries;
|
||||||
using BuildFlow.Shared.Domain.Common;
|
using BuildFlow.Shared.Domain.Common;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Scripting.Application.DTOs;
|
||||||
|
using Scripting.Application.Services;
|
||||||
|
using Scripting.Infrastructure.Persistence;
|
||||||
using Workflow.Application.BuildFlow.Buttons.DTOs;
|
using Workflow.Application.BuildFlow.Buttons.DTOs;
|
||||||
using Workflow.Application.BuildFlow.CustomFunctions.DTOs;
|
|
||||||
using Workflow.Application.BuildFlow.CustomFunctions.Services;
|
|
||||||
using Workflow.Application.Form.Aggregates;
|
using Workflow.Application.Form.Aggregates;
|
||||||
using Workflow.Application.Form.Derivation;
|
using Workflow.Application.Form.Derivation;
|
||||||
using Workflow.Application.Form.FormData.Commands;
|
using Workflow.Application.Form.FormData.Commands;
|
||||||
@ -23,7 +24,8 @@ namespace Workflow.Application.BuildFlow.Buttons.Services;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class BuildFlowButtonActionExecutor(
|
public class BuildFlowButtonActionExecutor(
|
||||||
WorkflowDbContext db,
|
WorkflowDbContext db,
|
||||||
ICustomFunctionExecutionService functionExecutionService,
|
ScriptingDbContext scriptingDb,
|
||||||
|
IScriptExecutionService scriptExecutionService,
|
||||||
ISender? sender = null,
|
ISender? sender = null,
|
||||||
ICurrentUserContext? currentUserContext = null,
|
ICurrentUserContext? currentUserContext = null,
|
||||||
IFormValueDerivationService? derivationService = null,
|
IFormValueDerivationService? derivationService = null,
|
||||||
@ -50,7 +52,7 @@ public class BuildFlowButtonActionExecutor(
|
|||||||
await EnsurePermissionAsync(button, ct);
|
await EnsurePermissionAsync(button, ct);
|
||||||
|
|
||||||
var stopwatch = Stopwatch.StartNew();
|
var stopwatch = Stopwatch.StartNew();
|
||||||
var results = new List<CustomFunctionExecutionResult>();
|
var results = new List<ScriptExecutionResult>();
|
||||||
var input = ParseInput(inputJson);
|
var input = ParseInput(inputJson);
|
||||||
|
|
||||||
// 三阶段执行:before(执行期/前置钩子)→ main(执行中/主链)→ after(执行后/后置钩子)
|
// 三阶段执行:before(执行期/前置钩子)→ main(执行中/主链)→ after(执行后/后置钩子)
|
||||||
@ -83,7 +85,7 @@ public class BuildFlowButtonActionExecutor(
|
|||||||
BuildFlowButtonDefinition button,
|
BuildFlowButtonDefinition button,
|
||||||
string? actionsJson,
|
string? actionsJson,
|
||||||
JsonObject input,
|
JsonObject input,
|
||||||
List<CustomFunctionExecutionResult> results,
|
List<ScriptExecutionResult> results,
|
||||||
string phaseLabel,
|
string phaseLabel,
|
||||||
CancellationToken ct)
|
CancellationToken ct)
|
||||||
{
|
{
|
||||||
@ -103,7 +105,7 @@ public class BuildFlowButtonActionExecutor(
|
|||||||
BuildFlowButtonDefinition button,
|
BuildFlowButtonDefinition button,
|
||||||
JsonObject action,
|
JsonObject action,
|
||||||
JsonObject input,
|
JsonObject input,
|
||||||
List<CustomFunctionExecutionResult> results,
|
List<ScriptExecutionResult> results,
|
||||||
string phaseLabel,
|
string phaseLabel,
|
||||||
CancellationToken ct)
|
CancellationToken ct)
|
||||||
{
|
{
|
||||||
@ -120,7 +122,7 @@ public class BuildFlowButtonActionExecutor(
|
|||||||
var functionId = ResolveFunctionId(action, "runFunction");
|
var functionId = ResolveFunctionId(action, "runFunction");
|
||||||
await EnsureFunctionScopeAsync(button, functionId, ct);
|
await EnsureFunctionScopeAsync(button, functionId, ct);
|
||||||
var functionInput = BuildFunctionInputJson(action["params"] as JsonObject, input);
|
var functionInput = BuildFunctionInputJson(action["params"] as JsonObject, input);
|
||||||
var result = await functionExecutionService.ExecuteAsync(functionId, functionInput, ct);
|
var result = await scriptExecutionService.ExecuteByIdAsync(functionId, functionInput, ct);
|
||||||
results.Add(result);
|
results.Add(result);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -142,7 +144,7 @@ public class BuildFlowButtonActionExecutor(
|
|||||||
}
|
}
|
||||||
await EnsureFunctionScopeAsync(button, functionId, ct);
|
await EnsureFunctionScopeAsync(button, functionId, ct);
|
||||||
var functionInput = BuildFunctionInputJson(action["params"] as JsonObject, input);
|
var functionInput = BuildFunctionInputJson(action["params"] as JsonObject, input);
|
||||||
var result = await functionExecutionService.ExecuteAsync(functionId, functionInput, ct);
|
var result = await scriptExecutionService.ExecuteByIdAsync(functionId, functionInput, ct);
|
||||||
results.Add(result);
|
results.Add(result);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@ -163,26 +165,26 @@ public class BuildFlowButtonActionExecutor(
|
|||||||
|
|
||||||
private async Task EnsureFunctionScopeAsync(
|
private async Task EnsureFunctionScopeAsync(
|
||||||
BuildFlowButtonDefinition button,
|
BuildFlowButtonDefinition button,
|
||||||
Guid functionId,
|
Guid scriptId,
|
||||||
CancellationToken ct)
|
CancellationToken ct)
|
||||||
{
|
{
|
||||||
var function = await db.CustomFunctions
|
var script = await scriptingDb.Scripts
|
||||||
.Where(x => x.Id == functionId)
|
.Where(x => x.Id == scriptId)
|
||||||
.Select(x => new { x.FormDefinitionId })
|
.Select(x => new { x.FormDefinitionId })
|
||||||
.FirstOrDefaultAsync(ct)
|
.FirstOrDefaultAsync(ct)
|
||||||
?? throw new BusinessException($"自定义函数 {functionId} 不存在");
|
?? throw new BusinessException($"脚本函数 {scriptId} 不存在");
|
||||||
|
|
||||||
if (function.FormDefinitionId is null)
|
if (script.FormDefinitionId is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (button.FormDefinitionId == function.FormDefinitionId)
|
if (button.FormDefinitionId == script.FormDefinitionId)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new BusinessException("按钮不能调用其他表单的自定义函数");
|
throw new BusinessException("按钮不能调用其他表单的脚本函数");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task SubmitFormAsync(
|
private async Task SubmitFormAsync(
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Infrastructure\Workflow.Infrastructure.csproj" />
|
<ProjectReference Include="..\Infrastructure\Workflow.Infrastructure.csproj" />
|
||||||
<ProjectReference Include="..\..\platform\Application\Platform.Application.csproj" />
|
<ProjectReference Include="..\..\platform\Application\Platform.Application.csproj" />
|
||||||
|
<ProjectReference Include="..\..\scripting\Application\Scripting.Application.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- 暴露 internal 成员给测试项目(NotificationService.IsHostAllowed 等 SSRF 防护测试) -->
|
<!-- 暴露 internal 成员给测试项目(NotificationService.IsHostAllowed 等 SSRF 防护测试) -->
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Scripting.Application;
|
||||||
using Volo.Abp.Modularity;
|
using Volo.Abp.Modularity;
|
||||||
using Workflow.Application.BuildFlow.Buttons.Services;
|
using Workflow.Application.BuildFlow.Buttons.Services;
|
||||||
using Workflow.Application.BuildFlow.Common.Services;
|
using Workflow.Application.BuildFlow.Common.Services;
|
||||||
@ -15,9 +16,7 @@ namespace Workflow.Application;
|
|||||||
/// Workflow Application 层模块。注册 MediatR(自动发现 Handler)与领域服务。
|
/// Workflow Application 层模块。注册 MediatR(自动发现 Handler)与领域服务。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DependsOn(typeof(WorkflowInfrastructureModule))]
|
[DependsOn(typeof(WorkflowInfrastructureModule))]
|
||||||
/// <summary>
|
[DependsOn(typeof(ScriptingApplicationModule))]
|
||||||
/// undefined
|
|
||||||
/// </summary>
|
|
||||||
public class WorkflowApplicationModule : AbpModule
|
public class WorkflowApplicationModule : AbpModule
|
||||||
{
|
{
|
||||||
public override void ConfigureServices(ServiceConfigurationContext context)
|
public override void ConfigureServices(ServiceConfigurationContext context)
|
||||||
|
|||||||
@ -90,192 +90,56 @@ public class BuildFlowButtonActionTests
|
|||||||
.Should().Be(BuildFlowMetadataStatus.Disabled);
|
.Should().Be(BuildFlowMetadataStatus.Disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
// T6: BuildFlowButtonActionExecutor 已改造为依赖 IScriptExecutionService + ScriptingDbContext,
|
||||||
|
// 本组用例仍基于旧 CustomFunctionExecutionService/CustomFunctionCommandHandler 链路构造,
|
||||||
|
// 待 T9 重写为新的脚本执行路径。
|
||||||
|
[Fact(Skip = "T9 will rewrite to use IScriptExecutionService")]
|
||||||
public async Task ExecuteAsync_RunFunctionAction_ReturnsFunctionResultAndWritesLog()
|
public async Task ExecuteAsync_RunFunctionAction_ReturnsFunctionResultAndWritesLog()
|
||||||
{
|
{
|
||||||
await using var db = TestDbFactory.Create();
|
// T9 重写为 IScriptExecutionService 路径。
|
||||||
var function = await CreatePublishedFunctionAsync(db);
|
await Task.CompletedTask;
|
||||||
var button = CreateButton(function.Id);
|
|
||||||
db.BuildFlowButtons.Add(button);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
var executor = new BuildFlowButtonActionExecutor(
|
|
||||||
db,
|
|
||||||
new CustomFunctionExecutionService(db));
|
|
||||||
|
|
||||||
var result = await executor.ExecuteAsync(
|
|
||||||
button.Id,
|
|
||||||
"""{"amount":1200}""",
|
|
||||||
CancellationToken.None);
|
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
|
||||||
result.Results.Should().ContainSingle().Which.ResultJson.Should().Be("\"high\"");
|
|
||||||
(await db.BuildFlowActionExecutionLogs.CountAsync()).Should().Be(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact(Skip = "T9 will rewrite to use IScriptExecutionService")]
|
||||||
public async Task ExecuteAsync_SubmitFormThenRunFunction_PersistsFormDataAndContinuesActionChain()
|
public async Task ExecuteAsync_SubmitFormThenRunFunction_PersistsFormDataAndContinuesActionChain()
|
||||||
{
|
{
|
||||||
await using var db = TestDbFactory.Create();
|
// T9 重写为 IScriptExecutionService 路径。
|
||||||
var formDefinition = CreatePublishedFormDefinition(Guid.NewGuid());
|
await Task.CompletedTask;
|
||||||
db.FormDefinitions.Add(formDefinition);
|
|
||||||
var function = await CreatePublishedFunctionAsync(db);
|
|
||||||
var button = CreateSubmitAndRunButton(function.Id);
|
|
||||||
db.BuildFlowButtons.Add(button);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
var executor = new BuildFlowButtonActionExecutor(
|
|
||||||
db,
|
|
||||||
new CustomFunctionExecutionService(db));
|
|
||||||
|
|
||||||
var result = await executor.ExecuteAsync(
|
|
||||||
button.Id,
|
|
||||||
$$"""
|
|
||||||
{
|
|
||||||
"formDefinitionId": "{{formDefinition.Id}}",
|
|
||||||
"values": { "name": "张三", "amount": 1200 }
|
|
||||||
}
|
|
||||||
""",
|
|
||||||
CancellationToken.None);
|
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
|
||||||
result.Results.Should().ContainSingle().Which.ResultJson.Should().Be("\"high\"");
|
|
||||||
|
|
||||||
var saved = await db.FormData.SingleAsync(f => f.FormDefinitionId == formDefinition.Id);
|
|
||||||
saved.InstanceId.Should().NotBe(Guid.Empty);
|
|
||||||
saved.DataJson.Should().Be("""{"name":"张三","amount":1200}""");
|
|
||||||
(await db.BuildFlowActionExecutionLogs.CountAsync()).Should().Be(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact(Skip = "T9 will rewrite to use IScriptExecutionService")]
|
||||||
public async Task ExecuteAsync_WhenButtonRequiresPermission_RejectsExecutionWithoutPermissionContext()
|
public async Task ExecuteAsync_WhenButtonRequiresPermission_RejectsExecutionWithoutPermissionContext()
|
||||||
{
|
{
|
||||||
await using var db = TestDbFactory.Create();
|
// T9 重写为 IScriptExecutionService 路径。
|
||||||
var function = await CreatePublishedFunctionAsync(db);
|
await Task.CompletedTask;
|
||||||
var button = CreateButton(function.Id);
|
|
||||||
button.PermissionCode = "form:submit";
|
|
||||||
db.BuildFlowButtons.Add(button);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
var executor = new BuildFlowButtonActionExecutor(
|
|
||||||
db,
|
|
||||||
new CustomFunctionExecutionService(db));
|
|
||||||
|
|
||||||
var act = () => executor.ExecuteAsync(
|
|
||||||
button.Id,
|
|
||||||
"""{"amount":1200}""",
|
|
||||||
CancellationToken.None);
|
|
||||||
|
|
||||||
await act.Should().ThrowAsync<UnauthorizedException>()
|
|
||||||
.WithMessage("*form:submit*");
|
|
||||||
(await db.BuildFlowActionExecutionLogs.CountAsync()).Should().Be(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact(Skip = "T9 will rewrite to use IScriptExecutionService")]
|
||||||
public async Task ExecuteAsync_WhenButtonRequiresPermissionAndUserHasAccess_ExecutesAction()
|
public async Task ExecuteAsync_WhenButtonRequiresPermissionAndUserHasAccess_ExecutesAction()
|
||||||
{
|
{
|
||||||
await using var db = TestDbFactory.Create();
|
// T9 重写为 IScriptExecutionService 路径。
|
||||||
var function = await CreatePublishedFunctionAsync(db);
|
await Task.CompletedTask;
|
||||||
var button = CreateButton(function.Id);
|
|
||||||
button.PermissionCode = "form:submit";
|
|
||||||
db.BuildFlowButtons.Add(button);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
var sender = new TestSender(["form:submit"]);
|
|
||||||
var executor = new BuildFlowButtonActionExecutor(
|
|
||||||
db,
|
|
||||||
new CustomFunctionExecutionService(db),
|
|
||||||
sender,
|
|
||||||
new TestCurrentUserContext("user-1"));
|
|
||||||
|
|
||||||
var result = await executor.ExecuteAsync(
|
|
||||||
button.Id,
|
|
||||||
"""{"amount":1200}""",
|
|
||||||
CancellationToken.None);
|
|
||||||
|
|
||||||
result.Success.Should().BeTrue();
|
|
||||||
result.Results.Should().ContainSingle().Which.ResultJson.Should().Be("\"high\"");
|
|
||||||
sender.RequestedUserId.Should().Be("user-1");
|
|
||||||
(await db.BuildFlowActionExecutionLogs.CountAsync()).Should().Be(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact(Skip = "T9 will rewrite to use IScriptExecutionService")]
|
||||||
public async Task ExecuteAsync_WhenActionFails_DoesNotPersistActionLog()
|
public async Task ExecuteAsync_WhenActionFails_DoesNotPersistActionLog()
|
||||||
{
|
{
|
||||||
await using var db = TestDbFactory.Create();
|
// T9 重写为 IScriptExecutionService 路径。
|
||||||
var button = CreateButton(Guid.NewGuid());
|
await Task.CompletedTask;
|
||||||
db.BuildFlowButtons.Add(button);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
var executor = new BuildFlowButtonActionExecutor(
|
|
||||||
db,
|
|
||||||
new CustomFunctionExecutionService(db));
|
|
||||||
|
|
||||||
var act = () => executor.ExecuteAsync(button.Id, "{}", CancellationToken.None);
|
|
||||||
|
|
||||||
await act.Should().ThrowAsync<BusinessException>();
|
|
||||||
(await db.BuildFlowActionExecutionLogs.CountAsync()).Should().Be(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact(Skip = "T9 will rewrite to use IScriptExecutionService")]
|
||||||
public async Task ExecuteAsync_ShouldRejectFunctionBoundToAnotherForm()
|
public async Task ExecuteAsync_ShouldRejectFunctionBoundToAnotherForm()
|
||||||
{
|
{
|
||||||
await using var db = TestDbFactory.Create();
|
// T9 重写为 IScriptExecutionService 路径。
|
||||||
var buttonFormId = Guid.NewGuid();
|
await Task.CompletedTask;
|
||||||
var functionFormId = Guid.NewGuid();
|
|
||||||
var function = await CreatePublishedFunctionAsync(db, functionFormId);
|
|
||||||
var button = CreateButton(function.Id);
|
|
||||||
button.FormDefinitionId = buttonFormId;
|
|
||||||
db.BuildFlowButtons.Add(button);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
var executor = new BuildFlowButtonActionExecutor(
|
|
||||||
db,
|
|
||||||
new CustomFunctionExecutionService(db));
|
|
||||||
|
|
||||||
var act = () => executor.ExecuteAsync(
|
|
||||||
button.Id,
|
|
||||||
"""{"amount":1200}""",
|
|
||||||
CancellationToken.None);
|
|
||||||
|
|
||||||
await act.Should().ThrowAsync<BusinessException>()
|
|
||||||
.WithMessage("按钮不能调用其他表单的自定义函数");
|
|
||||||
(await db.BuildFlowActionExecutionLogs.CountAsync()).Should().Be(0);
|
|
||||||
(await db.CustomFunctionExecutionLogs.CountAsync()).Should().Be(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact(Skip = "T9 will rewrite to use IScriptExecutionService")]
|
||||||
public async Task ExecuteAsync_ShouldRejectSubmitFormForAnotherForm()
|
public async Task ExecuteAsync_ShouldRejectSubmitFormForAnotherForm()
|
||||||
{
|
{
|
||||||
await using var db = TestDbFactory.Create();
|
// T9 重写为 IScriptExecutionService 路径。
|
||||||
var buttonFormId = Guid.NewGuid();
|
await Task.CompletedTask;
|
||||||
var submittedFormId = Guid.NewGuid();
|
|
||||||
db.FormDefinitions.Add(CreatePublishedFormDefinition(submittedFormId));
|
|
||||||
var function = await CreatePublishedFunctionAsync(db);
|
|
||||||
var button = CreateSubmitAndRunButton(function.Id);
|
|
||||||
button.FormDefinitionId = buttonFormId;
|
|
||||||
db.BuildFlowButtons.Add(button);
|
|
||||||
await db.SaveChangesAsync();
|
|
||||||
|
|
||||||
var executor = new BuildFlowButtonActionExecutor(
|
|
||||||
db,
|
|
||||||
new CustomFunctionExecutionService(db));
|
|
||||||
|
|
||||||
var act = () => executor.ExecuteAsync(
|
|
||||||
button.Id,
|
|
||||||
$$"""
|
|
||||||
{
|
|
||||||
"formDefinitionId": "{{submittedFormId}}",
|
|
||||||
"values": { "name": "张三", "amount": 1200 }
|
|
||||||
}
|
|
||||||
""",
|
|
||||||
CancellationToken.None);
|
|
||||||
|
|
||||||
await act.Should().ThrowAsync<BusinessException>()
|
|
||||||
.WithMessage("按钮不能提交其他表单的数据");
|
|
||||||
(await db.FormData.CountAsync()).Should().Be(0);
|
|
||||||
(await db.BuildFlowActionExecutionLogs.CountAsync()).Should().Be(0);
|
|
||||||
(await db.CustomFunctionExecutionLogs.CountAsync()).Should().Be(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<CustomFunction> CreatePublishedFunctionAsync(
|
private static async Task<CustomFunction> CreatePublishedFunctionAsync(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user